text: # Copyright 2017 Stefan Richthofer
  # 
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  # 
  #     http://www.apache.org/licenses/LICENSE-2.0
  # 
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  # Created on 25.10.2016
  
  """
  Creates Python 2.7-style stubfiles from Python 3.5-style stubfiles.
  Conversion process needs Python 3.5, but resulting files can be
  distributed for use with Python 2.7.
  Only works if no Python 3.5 specific code was used to construct
  type variables and such.
  
  So far the conversion is somewhat naive and a hand-crafted
  Python 2.7. stub-file might be required for advanced use-cases.
  Such a file should be stored like the usual stub-file, but
  using the suffix 'pyi2'. If such a file exists, pytypes will take
  it as override of the pyi-file when running on Python 2.7.
  """
  
  import sys
  import imp
  import inspect
  import os
  import datetime
  
  sys.path.append('/data/workspace/linux/pytypes')
  
  try:
      import pkg_resources
  except ImportError:
      pass
  
  from mixt.vendor import pytypes
  from mixt.vendor.pytypes import util, typelogger, type_util
  
  try:
      from backports.typing import Any, TypeVar
  except ImportError:
      from typing import Any, TypeVar
  
  
  py3 = False
  force = False
  silent = False
  indent = '    '
  stub_open_mode = "U"
  stub_descr = (".pyi", stub_open_mode, imp.PY_SOURCE)
  _implicit_globals = set()
  # This behavior changed with Python 3.6:
  _tpvar_is_class = inspect.isclass(TypeVar('_test'))
  
  
  def _print(line):
      if not silent:
          print(line)
  
  
  def _typestring(_types, argspecs, slf_or_clsm=False, assumed_globals=None):
      if _types[0] is Any:
          argstr = '...'
      else:
          args = type_util._preprocess_typecheck(_types[0], argspecs, slf_or_clsm)
          argstr = typelogger._prepare_arg_types_str(args, argspecs, slf_or_clsm,
                  assumed_globals=assumed_globals, update_assumed_globals=True,
                  implicit_globals=_implicit_globals)
      retstr = type_util.type_str(_types[1], assumed_globals, True, _implicit_globals)
      res = (argstr+' -> '+retstr)
      res = res.replace('NoneType', 'None')
      return res
  
  
  def _typecomment(_types, argspec, slf_or_clsm=False, assumed_globals=None):
      return '# type: '+_typestring(_types, argspec, slf_or_clsm, assumed_globals)
  
  
  def typecomment(func, argspec=None, slf_or_clsm=False, assumed_globals=None):
      if argspec is None:
          argspec = util.getargspecs(func)
      return _typecomment(type_util.get_types(func), argspec, slf_or_clsm, assumed_globals)
  
  
  def signature(func):
      argstr = ', '.join(util.getargnames(util.getargspecs(func), True))
      return 'def '+func.__name__+'('+argstr+'):'
  
  
  def annotated_signature(func, argspec=None, slf_or_clsm=False, assumed_globals=None):
      # inspect.formatargspec would only work in Python 3 with annotations
      if argspec is None:
          argspec = util.getargspecs(func)
      _types = type_util.get_types(func)
      sig_lst = []
      tp_lst = typelogger._prepare_arg_types_list(_types[0], argspec, slf_or_clsm, sig_lst)
      for i in range(len(sig_lst)):
          sig_lst[i] += ': '+type_util.type_str(tp_lst[i], assumed_globals, True, _implicit_globals)
      if slf_or_clsm:
          sig_lst.insert(0, argspec.args[0])
      res_tp = type_util.type_str(_types[1], assumed_globals, True, _implicit_globals)
      res = ''.join(('(', ', '.join(sig_lst), ') -> ', res_tp))
      return ''.join(('def ', func.__name__, res.replace('NoneType', 'None'), ':'))
  
  
  def _write_func(func, lines, inc=0, decorators=None, slf_or_clsm=False, assumed_globals=None):
      if type_util.has_type_hints(func):
          if not decorators is None:
              for dec in decorators:
                  lines.append(inc*indent+'@'+dec)
          if py3:
              lines.append(inc*indent+ \
                      annotated_signature(func, slf_or_clsm=slf_or_clsm,
                      assumed_globals=assumed_globals)+' ...')
          else:
              lines.append(inc*indent+signature(func))
              lines.append((inc+1)*indent+typecomment(func, slf_or_clsm=slf_or_clsm,
                      assumed_globals=assumed_globals))
              lines.append((inc+1)*indent+'pass')
  
  
  def _write_property(prop, lines, inc=0, decorators=None, assumed_globals=None):
      if not decorators is None:
          for dec in decorators:
              lines.append(inc*indent+'@'+dec)
      if not prop.fget is None:
          _write_func(prop.fget, lines, inc, ['property'], True, assumed_globals)
      if not prop.fset is None:
          if not prop.fget is None:
              lines.append('')
          _write_func(prop.fset, lines, inc, ['%s.setter'%prop.fget.__name__], True,
                  assumed_globals)
  
  
  def _write_TypeVar(tpv, lines, inc=0):
      args = [tpv.__name__]
      if not tpv.__bound__ is None:
          args.append('bound='+type_util.type_str(tpv.__bound__))
      if tpv.__covariant__:
          args.append('covariant=True')
      if tpv.__contravariant__:
          args.append('contravariant=True')
      lines.append("%s%s = TypeVar('%s')"%(inc*indent, tpv.__name__, ', '.join(args)))
  
  
  def _write_class(clss, lines, inc = 0, assumed_globals=None, implicit_globals=None,
              assumed_typevars=None):
      _print("write class: "+str(clss))
      anyElement = False
      lines.append(inc*indent+typelogger._signature_class(clss,
              assumed_globals, implicit_globals, assumed_typevars))
      mb = inspect.getmembers(clss, lambda t: inspect.isclass(t) or type_util._check_as_func(t))
      # todo: Care for overload-decorator
      for elem in mb:
          if elem[0] in clss.__dict__:
              el = clss.__dict__[elem[0]]
              if inspect.isfunction(el):
                  lines.append('')
                  _write_func(el, lines, inc+1, slf_or_clsm=True,
                          assumed_globals=assumed_globals)
                  anyElement = True
              elif inspect.isclass(el):
                  lines.append('')
                  if isinstance(el, TypeVar):
                      _write_TypeVar(el, lines, inc+1)
                  else:
                      _write_class(el, lines, inc+1, assumed_globals, implicit_globals,
                              assumed_typevars)
                  anyElement = True
              elif inspect.ismethoddescriptor(el) and type(el) is staticmethod:
                  lines.append('')
                  _write_func(el.__func__, lines, inc+1, ['staticmethod'],
                          assumed_globals=assumed_globals)
                  anyElement = True
              elif isinstance(el, property):
                  lines.append('')
                  _write_property(el, lines, inc+1, assumed_globals=assumed_globals)
                  anyElement = True
  
      # classmethods are not obtained via inspect.getmembers.
      # We have to look into __dict__ for that.
      for key in clss.__dict__:
          attr = getattr(clss, key)
          if util.is_classmethod(attr):
              lines.append('')
              _write_func(attr, lines, inc+1, ['classmethod'], True, assumed_globals)
              anyElement = True
  
      if not anyElement:
          lines.append((inc+1)*indent+'pass')
  
  
  def _func_get_line(func):
      try:
          return func.__code__.co_firstlineno
      except:
          pass
      if isinstance(func, property):
          return func.fget.__code__.co_firstlineno if not func.fget is None \
                  else func.fset.__code__.co_firstlineno
      else:
          return func.__func__.__code__.co_firstlineno
  
  
  def _class_get_line(clss):
      return inspect.findsource(clss)[1]
  
  
  def convert(in_file, out_file = None):
      global _implicit_globals
      _print('in_file: '+in_file)
      assert(os.path.isfile(in_file))
      checksum = util._md5(in_file)
      if out_file is None:
          if in_file.endswith('py'):
              if py3:
                  out_file = in_file+'i'
              else:
                  out_file = in_file+'i2'
          elif in_file.endswith('pyi'):
              if py3:
                  out_file = in_file
              else:
                  out_file = in_file+'2'
          else:
              out_file = in_file+'2'
          if os.path.exists(out_file) and not force:
              _print("File already exists: "+str(out_file))
              _print("Run with --force to force overwriting.")
              sys.exit(os.EX_CANTCREAT)
      _print('out_file: '+out_file)
  
      with open(in_file, stub_open_mode) as module_file:
          module_name = os.path.basename(in_file)
          stub_module = imp.load_module(
                  module_name, module_file, in_file, stub_descr)
          if module_name in sys.modules:
              _implicit_globals.add(sys.modules[module_name])
          module_basename = module_name.rsplit('.')[0]
          if module_basename in sys.modules:
              _implicit_globals.add(sys.modules[module_basename])
  
      funcs = [func[1] for func in inspect.getmembers(stub_module, inspect.isfunction)]
      cls = [cl[1] for cl in inspect.getmembers(stub_module, inspect.isclass)]
      tpvs = set()
      i = 0
      if _tpvar_is_class:
          while i < len(cls):
              if isinstance(cls[i], TypeVar):
                  tpvs.add(cls[i])
                  del cls[i]
              else:
                  i += 1
      else:
          # Python 3.6
          for tpv in inspect.getmembers(stub_module, lambda t: isinstance(t, TypeVar)):
              tpvs.add(tpv[1])
  
      funcs.sort(key=_func_get_line)
      cls.sort(key=_class_get_line)
  
      directory = os.path.dirname(out_file)
      if directory == '':
          directory = '.'
      if not os.path.exists(directory):
          os.makedirs(directory)
      assumed_glbls = set()
      assumed_typevars = set()
      nw = datetime.datetime.now()
      with open(out_file, 'w') as out_file_handle:
          try:
              version = pkg_resources.get_distribution('pytypes').version
          except:
              version = pytypes._version
          lines = ['"""',
                  'Python 2.7-compliant stubfile of ',
                  in_file,
                  'MD5-Checksum: '+checksum,
                  '\nThis file was generated by pytypes.stubfile_2_converter v'+version,
                  'at '+nw.isoformat(),
                  'running on '+util._python_version_string()+'.\n',
                  'WARNING:',
                  'If you edit this file, be aware that it was automatically generated.',
                  'Save your customized version to a distinct place;',
                  'this file might be overwritten without notice.',
                  '"""',
                  '',
                  ''] # import section later goes here; don't forget to track in imp_index
          imp_index = 13
          any_tpv = False
          for tpv in tpvs:
              if util.search_class_module(tpv) is stub_module:
                  if not any_tpv:
                      lines.append('')
                  _write_TypeVar(tpv, lines)
                  any_tpv = True
  
          for func in funcs:
              lines.append('')
              _write_func(func, lines, assumed_globals=assumed_glbls)
  
          for cl in cls:
              if sys.modules[cl.__module__] in _implicit_globals:
                  lines.append('\n')
                  _write_class(cl, lines, assumed_globals=assumed_glbls,
                          implicit_globals=_implicit_globals,
                          assumed_typevars=assumed_typevars)
  
          for el in tpvs:
              assumed_typevars.add(el)
  
          imp_lines = typelogger._make_import_section(assumed_glbls, assumed_typevars,
                  _implicit_globals)
          lines.insert(imp_index, '\n'.join(imp_lines))
          for i in range(len(lines)):
              _print(lines[i])
              lines[i] = lines[i]+'\n'
          lines.append('\n')
          out_file_handle.writelines(lines)
  
  
  def err_no_in_file():
      print("Error: No in_file given! Use -h for help.")
      sys.exit(os.EX_USAGE)
  
  
  def print_usage():
      print("stubfile_2_converter usage:")
      print("python3 -m pytypes.stubfile_2_converter [options/flags] [in_file]")
      print("Supported options/flags:")
      print(" -o [out_file] : custom output-file")
      print(" -s            : silent mode")
      print("--py3          : write Python 3 stubfile")
      print(" -t            : indent with tabs")
      print(" -h            : usage")
  
  
  def main():
      global silent, py3, in_file, out_file, indent, force
  
      if '-h' in sys.argv:
          print_usage()
          sys.exit(0)
      in_file = sys.argv[-1]
      if len(sys.argv) < 2 or in_file.startswith('-'):
          err_no_in_file()
      out_file = None
      if '-s' in sys.argv:
          silent = True
      if '--py3' in sys.argv:
          py3 = True
      if '--force' in sys.argv:
          force = True
      if '-t' in sys.argv:
          indent = '\t'
      try:
          index_o = sys.argv.index('-o')
          if index_o == len(sys.argv)-2:
              err_no_in_file()
          out_file = sys.argv[index_o+1]
      except ValueError:
          pass
      convert(in_file, out_file)
  
  
  if __name__ == '__main__':
      main()
  