runner.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. from __future__ import print_function # PY2
  2. import __main__
  3. import argparse
  4. import sys
  5. import traceback
  6. import tokenize
  7. from ctypes import pythonapi, POINTER, c_long, cast
  8. from types import CodeType as Code
  9. from . import console, enable, disable
  10. from .info import PY2
  11. inspect_flag = cast(pythonapi.Py_InspectFlag, POINTER(c_long)).contents
  12. def set_inspect_flag(value):
  13. inspect_flag.value = int(value)
  14. CODE_FIELDS = ["argcount", "kwonlyargcount", "nlocals", "stacksize",
  15. "flags", "code", "consts", "names", "varnames", "filename",
  16. "name", "firstlineno", "lnotab", "freevars", "cellvars"]
  17. if PY2:
  18. CODE_FIELDS.remove("kwonlyargcount")
  19. def update_code(codeobj, **kwargs):
  20. def field_values():
  21. for field in CODE_FIELDS:
  22. original_value = getattr(codeobj, "co_{}".format(field))
  23. value = kwargs.get(field, original_value)
  24. yield value
  25. return Code(*field_values())
  26. def update_code_recursively(codeobj, **kwargs):
  27. updated = {}
  28. def update(codeobj, **kwargs):
  29. result = updated.get(codeobj, None)
  30. if result is not None:
  31. return result
  32. if any(isinstance(c, Code) for c in codeobj.co_consts):
  33. consts = tuple(update(c, **kwargs) if isinstance(c, Code) else c
  34. for c in codeobj.co_consts)
  35. else:
  36. consts = codeobj.co_consts
  37. result = update_code(codeobj, consts=consts, **kwargs)
  38. updated[codeobj] = result
  39. return result
  40. return update(codeobj, **kwargs)
  41. def get_code(path):
  42. if PY2:
  43. from .tokenize_open import read_source_lines
  44. source = u"".join(read_source_lines(path))
  45. else:
  46. with tokenize.open(path) as f: # opens with detected source encoding
  47. source = f.read()
  48. try:
  49. code = compile(source, path, "exec", dont_inherit=True)
  50. except UnicodeEncodeError:
  51. code = compile(source, "<encoding error>", "exec", dont_inherit=True)
  52. if PY2:
  53. path = path.encode("utf-8")
  54. code = update_code_recursively(code, filename=path)
  55. # so code constains correct filename (even if it contains Unicode)
  56. # and tracebacks show contents of code lines
  57. return code
  58. def print_exception_without_first_line(etype, value, tb, limit=None, file=None, chain=True):
  59. if file is None:
  60. file = sys.stderr
  61. lines = iter(traceback.TracebackException(
  62. type(value), value, tb, limit=limit).format(chain=chain))
  63. next(lines)
  64. for line in lines:
  65. print(line, file=file, end="")
  66. def run_script(args):
  67. sys.argv = [args.script] + args.script_arguments
  68. path = args.script
  69. __main__.__file__ = path
  70. try:
  71. code = get_code(path)
  72. except Exception as e:
  73. traceback.print_exception(e.__class__, e, None, file=sys.stderr)
  74. else:
  75. try:
  76. exec(code, __main__.__dict__)
  77. except BaseException as e:
  78. if not sys.flags.inspect and isinstance(e, SystemExit):
  79. raise
  80. elif PY2: # Python 2 produces tracebacks in mixed encoding (!)
  81. etype, e, tb = sys.exc_info()
  82. for line in traceback.format_exception(etype, e, tb.tb_next):
  83. line = line.decode("utf-8", "replace")
  84. try:
  85. sys.stderr.write(line)
  86. except UnicodeEncodeError:
  87. line = line.encode(sys.stderr.encoding, "backslashreplace")
  88. sys.stderr.write(line)
  89. sys.stderr.flush() # is this needed?
  90. else: # PY3
  91. traceback.print_exception(e.__class__, e, e.__traceback__.tb_next, file=sys.stderr)
  92. def run_init(args):
  93. if args.init == "enable":
  94. enable()
  95. elif args.init == "disable":
  96. disable()
  97. elif args.init == "module":
  98. __import__(args.module)
  99. elif args.init == "none":
  100. pass
  101. else:
  102. raise ValueError("unknown runner init mode {}".format(repr(args.init)))
  103. def run_with_custom_repl(args):
  104. run_init(args)
  105. if args.script:
  106. run_script(args)
  107. if sys.flags.interactive or not args.script:
  108. if sys.flags.interactive and not args.script:
  109. console.print_banner()
  110. try:
  111. console.enable()
  112. finally:
  113. set_inspect_flag(0)
  114. def run_with_standard_repl(args):
  115. run_init(args)
  116. if args.script:
  117. run_script(args)
  118. if sys.flags.interactive and not args.script:
  119. console.print_banner()
  120. def run_arguments():
  121. parser = argparse.ArgumentParser(description="Runs a script after customizable initialization. By default, win_unicode_console is enabled.")
  122. init_group = parser.add_mutually_exclusive_group()
  123. init_group.add_argument(
  124. "-e", "--init-enable", dest="init", action="store_const", const="enable",
  125. help="enable win_unicode_console on init (default)")
  126. init_group.add_argument(
  127. "-d", "--init-disable", dest="init", action="store_const", const="disable",
  128. help="disable win_unicode_console on init")
  129. init_group.add_argument(
  130. "-m", "--init-module", dest="module",
  131. help="import the given module on init")
  132. init_group.add_argument(
  133. "-n", "--no-init", dest="init", action="store_const", const="none",
  134. help="do nothing special on init")
  135. parser.set_defaults(init="enable")
  136. repl_group = parser.add_mutually_exclusive_group()
  137. repl_group.add_argument(
  138. "-s", "--standard-repl", dest="use_repl", action="store_false",
  139. help="use the standard Python REPL (default)")
  140. repl_group.add_argument(
  141. "-c", "--custom-repl", dest="use_repl", action="store_true",
  142. help="use win_unicode_console.console REPL")
  143. parser.set_defaults(use_repl=False)
  144. parser.add_argument("script", nargs="?")
  145. parser.add_argument("script_arguments", nargs=argparse.REMAINDER, metavar="script-arguments")
  146. try:
  147. args = parser.parse_args(sys.argv[1:])
  148. except SystemExit:
  149. set_inspect_flag(0) # don't go interactive after printing help
  150. raise
  151. if args.module:
  152. args.init = "module"
  153. if args.use_repl:
  154. run_with_custom_repl(args)
  155. else:
  156. run_with_standard_repl(args)