-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy pathodooly_run.py
More file actions
executable file
·182 lines (150 loc) · 5.67 KB
/
odooly_run.py
File metadata and controls
executable file
·182 lines (150 loc) · 5.67 KB
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
#!/usr/bin/env python
"""Connect to an Odoo server."""
# Colorize command-line output
import builtins
import keyword
import io
import os
import re
import signal
import token as T
import tokenize
import unicodedata
import odooly
BUILTINS = {name for name in dir(builtins) if name[:1] != '_'}
T_STRING = {getattr(T, tk) for tk in dir(T) if 'STRING' in tk}
T_MIDDLE = {getattr(T, tk) for tk in dir(T) if 'STRING_MIDDLE' in tk}
KEYWORDS = {*keyword.kwlist}
SOFTKEYW = {*getattr(keyword, 'softkwlist', ())}
J_SPECIAL = {'false', 'null', 'true'}
CONSTANTS = {'False', 'None', 'True'}
DEF_CLASS = {'def', 'class'}
CSI = '\33[' # Control Sequence Introducer, same as ESC+[ or \x1b[
class THEME: # Default theme in Python 3.14+
keyword = f'{CSI}1;34m'
builtin = f'{CSI}36m'
comment = f'{CSI}31m'
string = f'{CSI}32m'
number = f'{CSI}33m'
op = '' # No color
definition = f'{CSI}1m'
reset = f'{CSI}m'
def gen_colors(value, theme, keywords=KEYWORDS):
"""Generate color tuples (start, end, color)."""
sio = io.StringIO(value)
line_offsets = [..., 0] + [sio.tell() for __ in sio]
sio.seek(0)
bracket_level = is_def_name = 0
yield None, 0, theme.reset
try:
for token in tokenize.generate_tokens(sio.readline):
if token.start == token.end:
continue
color, end_offset = None, 0
if token.type in T_STRING:
if token.type in T_MIDDLE and token.string[-1:] in '{}':
end_offset += 1
color = theme.string
elif token.type == T.COMMENT:
color = theme.comment
elif token.type == T.NUMBER:
color = theme.number
elif token.type == T.OP:
if token.string in '([{':
bracket_level += 1
elif token.string in ')]}':
bracket_level -= 1
color = theme.op
elif token.type == T.NAME:
if token.string in keywords:
is_def_name = token.string in DEF_CLASS
color = theme.keyword
elif keywords is not KEYWORDS:
continue
elif is_def_name:
is_def_name = False
color = theme.definition
elif token.string in SOFTKEYW and not bracket_level:
color = theme.keyword # soft_keyword
elif token.string in BUILTINS:
color = theme.builtin
if color is not None:
start = line_offsets[token.start[0]] + token.start[1]
end = line_offsets[token.end[0]] + token.end[1] + end_offset
yield start, end, color
except (SyntaxError, tokenize.TokenError):
pass
yield None, None, theme.reset
def _apply_colors(value, theme, keywords):
output = ''
if value:
__, end, c_reset = next(colors := gen_colors(value, theme=theme, keywords=keywords))
while (last_pos := end) is not None:
start, end, color = next(colors)
if start is None:
output += _escape(value[last_pos:])
else:
output += _escape(value[last_pos:start]) + color + _escape(value[start:end]) + c_reset
return output
def _escape(value):
if value.isascii():
return value
result = ''
for char in value:
if char > '\x7f' and unicodedata.category(char)[:1] == 'C':
char = rf'\u{ord(char):04x}'
result += char
return result
def color_python(value):
"""Colorize Python syntax."""
return _apply_colors(value, THEME(), KEYWORDS)
def color_repr(value):
"""Colorize string representation."""
return _apply_colors(value, THEME(), CONSTANTS)
def color_json(value):
"""Colorize JSON representation."""
return _apply_colors(value, THEME(), J_SPECIAL)
def patch_colors(module):
"""Set functions to color output."""
global THEME
try: # Python >= 3.14
from _pyrepl.utils import BUILTINS, THEME
BUILTINS |= {'Client', 'client', 'env', 'clear'}
except ImportError:
pass
module.color_repr = color_repr
decolor = odooly.partial(re.compile('(\233|\33\\[)[;0-9]*m').sub, '')
if module.color_py is not str: # Python 3.14+
return {'color_repr': color_repr, 'decolor': decolor}
theme = THEME()
module.color_py = color_python
module.color_bold = f'{theme.definition}{{}}{theme.reset}'.format
module.color_comment = f'{theme.comment}{{}}{theme.reset}'.format
return {'color_py': color_python, 'color_repr': color_repr, 'decolor': decolor}
def handle_resize(signum, frame):
term = os.get_terminal_size()
if not odooly.Client._is_interactive() or not term.columns:
return
client = odooly.Client._globals.get('client')
if client and client.verbose:
client.verbose = term.columns
odooly.PP_FORMAT['width'] = odooly.MAXCOL[1] = term.columns
def install_signal_handler():
signal.signal(signal.SIGWINCH, handle_resize)
signal.raise_signal(signal.SIGWINCH)
def main():
args = odooly.get_parser().parse_args()
if args.config:
odooly.Client._config_file = odooly.Path.cwd() / args.config
if args.list_env:
print('Available settings: ' + ' '.join(odooly.read_config()))
return
global_vars = odooly.Client._set_interactive()
if odooly.color_py is not str or (os.getenv('FORCE_COLOR') and not os.getenv('NO_COLOR')):
global_vars.update(patch_colors(odooly))
install_signal_handler()
print(odooly.color_repr(odooly.USAGE))
odooly.connect_client(args)
odooly._interact(global_vars)
if __name__ == '__main__':
main()