aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/deprecated/python/win-unicode-console/win_unicode_console/runner.py
blob: f3c04e685ebdc17b1ae16eb25e9e63a924fb277d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
from __future__ import print_function # PY2

import __main__
import argparse
import sys
import traceback
import tokenize
from ctypes import pythonapi, POINTER, c_long, cast
from types import CodeType as Code

from . import console, enable, disable
from .info import PY2


inspect_flag = cast(pythonapi.Py_InspectFlag, POINTER(c_long)).contents

def set_inspect_flag(value):
	inspect_flag.value = int(value)


CODE_FIELDS = ["argcount", "kwonlyargcount", "nlocals", "stacksize", 
		"flags", "code", "consts", "names", "varnames", "filename", 
		"name", 	"firstlineno", "lnotab", "freevars", "cellvars"]
if PY2:
	CODE_FIELDS.remove("kwonlyargcount")

def update_code(codeobj, **kwargs):
	def field_values():
		for field in CODE_FIELDS:
			original_value = getattr(codeobj, "co_{}".format(field))
			value = kwargs.get(field, original_value)
			yield value
	
	return Code(*field_values())

def update_code_recursively(codeobj, **kwargs):
	updated = {}
	
	def update(codeobj, **kwargs):
		result = updated.get(codeobj, None)
		if result is not None:
			return result
		
		if any(isinstance(c, Code) for c in codeobj.co_consts):
			consts = tuple(update(c, **kwargs) if isinstance(c, Code) else c
				for c in codeobj.co_consts)
		else:
			consts = codeobj.co_consts
		
		result = update_code(codeobj, consts=consts, **kwargs)
		updated[codeobj] = result
		return result
	
	return update(codeobj, **kwargs)


def get_code(path):
	if PY2:
		from .tokenize_open import read_source_lines
		source = u"".join(read_source_lines(path))
	else:
		with tokenize.open(path) as f: # opens with detected source encoding
			source = f.read()
	
	try:
		code = compile(source, path, "exec", dont_inherit=True)
	except UnicodeEncodeError:
		code = compile(source, "<encoding error>", "exec", dont_inherit=True)
		if PY2:
			path = path.encode("utf-8")
		code = update_code_recursively(code, filename=path)
			# so code constains correct filename (even if it contains Unicode)
			# and tracebacks show contents of code lines
	
	return code


def print_exception_without_first_line(etype, value, tb, limit=None, file=None, chain=True):
	if file is None:
		file = sys.stderr
	
	lines = iter(traceback.TracebackException(
		type(value), value, tb, limit=limit).format(chain=chain))
	
	next(lines)
	for line in lines:
		print(line, file=file, end="")


def run_script(args):
	sys.argv = [args.script] + args.script_arguments
	path = args.script
	__main__.__file__ = path
	
	try:
		code = get_code(path)
	except Exception as e:
		traceback.print_exception(e.__class__, e, None, file=sys.stderr)
	else:
		try:
			exec(code, __main__.__dict__)
		except BaseException as e:
			if not sys.flags.inspect and isinstance(e, SystemExit):
				raise
				
			elif PY2: # Python 2 produces tracebacks in mixed encoding (!)
				etype, e, tb = sys.exc_info()
				for line in traceback.format_exception(etype, e, tb.tb_next):
					line = line.decode("utf-8", "replace")
					try:
						sys.stderr.write(line)
					except UnicodeEncodeError:
						line = line.encode(sys.stderr.encoding, "backslashreplace")
						sys.stderr.write(line)
					
					sys.stderr.flush() # is this needed?
				
			else: # PY3
				traceback.print_exception(e.__class__, e, e.__traceback__.tb_next, file=sys.stderr)

def run_init(args):
	if args.init == "enable":
		enable()
	elif args.init == "disable":
		disable()
	elif args.init == "module":
		__import__(args.module)
	elif args.init == "none":
		pass
	else:
		raise ValueError("unknown runner init mode {}".format(repr(args.init)))

def run_with_custom_repl(args):
	run_init(args)
	
	if args.script:
		run_script(args)
	
	if sys.flags.interactive or not args.script:
		if sys.flags.interactive and not args.script:
			console.print_banner()
		try:
			console.enable()
		finally:
			set_inspect_flag(0)

def run_with_standard_repl(args):
	run_init(args)
	
	if args.script:
		run_script(args)
	
	if sys.flags.interactive and not args.script:
		console.print_banner()

def run_arguments():
	parser = argparse.ArgumentParser(description="Runs a script after customizable initialization. By default, win_unicode_console is enabled.")
	
	init_group = parser.add_mutually_exclusive_group()
	init_group.add_argument(
		"-e", "--init-enable", dest="init", action="store_const", const="enable", 
		help="enable win_unicode_console on init (default)")
	init_group.add_argument(
		"-d", "--init-disable", dest="init", action="store_const", const="disable", 
		help="disable win_unicode_console on init")
	init_group.add_argument(
		"-m", "--init-module", dest="module", 
		help="import the given module on init")
	init_group.add_argument(
		"-n", "--no-init", dest="init", action="store_const", const="none", 
		help="do nothing special on init")
	parser.set_defaults(init="enable")
	
	repl_group = parser.add_mutually_exclusive_group()
	repl_group.add_argument(
		"-s", "--standard-repl", dest="use_repl", action="store_false", 
		help="use the standard Python REPL (default)")
	repl_group.add_argument(
		"-c", "--custom-repl", dest="use_repl", action="store_true", 
		help="use win_unicode_console.console REPL")
	parser.set_defaults(use_repl=False)
	
	parser.add_argument("script", nargs="?")
	parser.add_argument("script_arguments", nargs=argparse.REMAINDER, metavar="script-arguments")
	
	try:
		args = parser.parse_args(sys.argv[1:])
	except SystemExit:
		set_inspect_flag(0)	# don't go interactive after printing help
		raise
	
	if args.module:
		args.init = "module"
	
	if args.use_repl:
		run_with_custom_repl(args)
	else:
		run_with_standard_repl(args)