forked from oils-for-unix/oils
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoil.py
executable file
·312 lines (248 loc) · 7.84 KB
/
oil.py
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
#!/usr/bin/env python2
# Copyright 2016 Andy Chu. All rights reserved.
# 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
"""
oil.py - A busybox-like binary for OSH and Oil.
Based on argv[0], it acts like a few different programs.
- true, false
- readlink
Note: could also expose some other binaries for a smaller POSIX system?
- test / '['
- printf, echo
- cat
- seq
- 'time' -- has some different flags
"""
from __future__ import print_function
import posix_ as posix
import sys
from typing import List
# Needed for oil.ovm app bundle build, since there is an functino-local import
# to break a circular build dep in frontend/consts.py.
from _devbuild.gen import id_kind
_ = id_kind
from _devbuild.gen.option_asdl import option_i
from _devbuild.gen.syntax_asdl import source
from core import alloc
from core import error
from core import main_loop
from core import shell
from core import optview
from core import pure
from core import pyutil
from core.pyutil import stderr_line
from core import state
from core import ui
from core.pyerror import log
from frontend import args
from frontend import reader
from frontend import parse_lib
from osh import builtin_misc
from pylib import os_path
from tea import tea_main
from tools import deps
from tools import osh2oil
from tools import readlink
try:
import line_input
except ImportError:
line_input = None
# TODO: Hook up to completion.
SUBCOMMANDS = [
'translate', 'arena', 'spans', 'format', 'deps', 'undefined-vars',
'parse-glob', 'parse-printf',
]
def OshCommandMain(argv):
"""Run an 'oshc' tool.
'osh' is short for "osh compiler" or "osh command".
TODO:
- oshc --help
oshc deps
--path: the $PATH to use to find executables. What about libraries?
NOTE: we're leaving out su -c, find, xargs, etc.? Those should generally
run functions using the $0 pattern.
--chained-command sudo
"""
try:
action = argv[0]
except IndexError:
raise error.Usage('Missing required subcommand.')
if action not in SUBCOMMANDS:
raise error.Usage('Invalid subcommand %r.' % action)
if action == 'parse-glob':
# Pretty-print the AST produced by osh/glob_.py
print('TODO:parse-glob')
return 0
if action == 'parse-printf':
# Pretty-print the AST produced by osh/builtin_printf.py
print('TODO:parse-printf')
return 0
arena = alloc.Arena()
try:
script_name = argv[1]
arena.PushSource(source.MainFile(script_name))
except IndexError:
arena.PushSource(source.Stdin())
f = sys.stdin
else:
try:
f = open(script_name)
except IOError as e:
stderr_line("oshc: Couldn't open %r: %s", script_name,
posix.strerror(e.errno))
return 2
aliases = {} # Dummy value; not respecting aliases!
loader = pyutil.GetResourceLoader()
oil_grammar = pyutil.LoadOilGrammar(loader)
opt0_array = state.InitOpts()
no_stack = None # type: List[bool] # for mycpp
opt_stacks = [no_stack] * option_i.ARRAY_SIZE # type: List[List[bool]]
parse_opts = optview.Parse(opt0_array, opt_stacks)
# parse `` and a[x+1]=bar differently
parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar)
parse_ctx.Init_OnePassParse(True)
line_reader = reader.FileLineReader(f, arena)
c_parser = parse_ctx.MakeOshParser(line_reader)
try:
node = main_loop.ParseWholeFile(c_parser)
except error.Parse as e:
ui.PrettyPrintError(e, arena)
return 2
assert node is not None
f.close()
# Columns for list-*
# path line name
# where name is the binary path, variable name, or library path.
# bin-deps and lib-deps can be used to make an app bundle.
# Maybe I should list them together? 'deps' can show 4 columns?
#
# path, line, type, name
#
# --pretty can show the LST location.
# stderr: show how we're following imports?
if action == 'translate':
osh2oil.PrintAsOil(arena, node)
elif action == 'arena': # for debugging
osh2oil.PrintArena(arena)
elif action == 'spans': # for debugging
osh2oil.PrintSpans(arena)
elif action == 'format':
# TODO: autoformat code
raise NotImplementedError(action)
elif action == 'deps':
deps.Deps(node)
elif action == 'undefined-vars': # could be environment variables
raise NotImplementedError()
else:
raise AssertionError # Checked above
return 0
# TODO: Hook up these applets and all valid applets to completion
# APPLETS = ['osh', 'osh', 'oil', 'readlink']
def AppBundleMain(argv):
# type: (List[str]) -> int
# NOTE: This has a side effect of deleting _OVM_* from the environment!
loader = pyutil.GetResourceLoader()
b = os_path.basename(argv[0])
main_name, ext = os_path.splitext(b)
arg_r = args.Reader(argv)
if main_name == 'oil' and ext: # oil.py or oil.ovm
arg_r.Next()
first_arg = arg_r.Peek()
if first_arg is None:
raise error.Usage('Missing required applet name.')
if first_arg in ('-h', '--help'):
errfmt = None # not needed here
help_builtin = builtin_misc.Help(loader, errfmt)
help_builtin.Run(pure.MakeBuiltinArgv(['bundle-usage']))
sys.exit(0)
if first_arg in ('-V', '--version'):
pyutil.ShowAppVersion('Oil', loader)
sys.exit(0)
main_name = first_arg
login_shell = False
if main_name.startswith('-'):
login_shell = True
main_name = main_name[1:]
if main_name in ('osh', 'sh'):
# TODO:
# - Initialize a different shell if line_input isn't present
status = shell.Main('osh', arg_r, posix.environ, login_shell,
loader, line_input)
return status
elif main_name == 'osh-pure':
# TODO: pure.Main()
pass
elif main_name == 'oshc':
arg_r.Next()
main_argv = arg_r.Rest()
try:
return OshCommandMain(main_argv)
except error.Usage as e:
stderr_line('oshc usage error: %s', e.msg)
return 2
elif main_name == 'oil':
return shell.Main('oil', arg_r, posix.environ, login_shell,
loader, line_input)
elif main_name == 'tea':
arg_r.Next()
return tea_main.Main(arg_r)
# For testing latency
elif main_name == 'true':
return 0
elif main_name == 'false':
return 1
elif main_name == 'readlink':
main_argv = arg_r.Rest()
return readlink.main(main_argv)
else:
raise error.Usage('Invalid applet name %r.' % main_name)
def main(argv):
# type: (List[str]) -> int
try:
return AppBundleMain(argv)
except error.Usage as e:
#builtin.Help(['oil-usage'], util.GetResourceLoader())
log('oil: %s', e.msg)
return 2
except RuntimeError as e:
if 0:
import traceback
traceback.print_exc()
# NOTE: The Python interpreter can cause this, e.g. on stack overflow.
log('FATAL: %r', e)
return 1
except KeyboardInterrupt:
print()
return 130 # 128 + 2
except (IOError, OSError) as e:
if 0:
import traceback
traceback.print_exc()
# test this with prlimit --nproc=1 --pid=$$
stderr_line('osh I/O error: %s', posix.strerror(e.errno))
return 2 # dash gives status 2
# Called from Python-2.7.13/Modules/main.c.
def _cpython_main_hook():
sys.exit(main(sys.argv))
if __name__ == '__main__':
pyann_out = posix.environ.get('PYANN_OUT')
if pyann_out:
from pyannotate_runtime import collect_types
collect_types.init_types_collection()
with collect_types.collect():
status = main(sys.argv)
collect_types.dump_stats(pyann_out)
sys.exit(status)
elif posix.environ.get('RESOLVE') == '1':
from opy import resolve
resolve.Walk(dict(sys.modules))
elif posix.environ.get('CALLGRAPH') == '1':
# NOTE: This could end up as opy.InferTypes(), opy.GenerateCode(), etc.
from opy import callgraph
callgraph.Walk(main, sys.modules)
else:
sys.exit(main(sys.argv))