OILS / builtin / method_io.py View on Github | oilshell.org

215 lines, 113 significant
1"""Methods on IO type"""
2from __future__ import print_function
3
4from _devbuild.gen.value_asdl import value, value_e, value_t
5
6from core import error
7from core import num
8from core import state
9from core import vm
10from frontend import typed_args
11from mycpp.mylib import log, NewDict
12from osh import prompt
13
14from typing import Dict, List, cast, TYPE_CHECKING
15if TYPE_CHECKING:
16 from osh import cmd_eval
17 from _devbuild.gen.runtime_asdl import Cell
18
19_ = log
20
21EVAL_NULL = 1
22EVAL_DICT = 2
23
24
25def _PrintFrame(prefix, frame):
26 # type: (str, Dict[str, Cell]) -> None
27 print('%s %s' % (prefix, ' '.join(frame.keys())))
28
29 rear = frame.get('__rear__')
30 if rear:
31 rear_val = rear.val
32 if rear_val.tag() == value_e.Frame:
33 r = cast(value.Frame, rear_val)
34 _PrintFrame('--> ' + prefix, r.frame)
35
36
37class EvalInFrame(vm._Callable):
38 """
39 For making "inline procs"
40 """
41
42 def __init__(self, mem, cmd_ev):
43 # type: (state.Mem, cmd_eval.CommandEvaluator) -> None
44 self.mem = mem
45 self.cmd_ev = cmd_ev
46
47 def Call(self, rd):
48 # type: (typed_args.Reader) -> value_t
49 frag = rd.PosCommandFrag()
50 bound = rd.PosFrame()
51
52 # TODO: EvalCommandFrag()
53
54 return value.Null
55
56
57class Eval(vm._Callable):
58 """
59 These are similar:
60
61 var cmd = ^(echo hi)
62 call io->eval(cmd)
63
64 Also give the top namespace
65
66 call io->evalToDict(cmd)
67
68 The CALLER must handle errors.
69 """
70
71 def __init__(self, mem, cmd_ev, which):
72 # type: (state.Mem, cmd_eval.CommandEvaluator, int) -> None
73 self.mem = mem
74 self.cmd_ev = cmd_ev
75 self.which = which
76
77 def Call(self, rd):
78 # type: (typed_args.Reader) -> value_t
79 unused = rd.PosValue()
80 bound = rd.PosCommand()
81
82 captured_frame = bound.captured_frame
83 cmd = typed_args.GetCommandFrag(bound)
84
85 #log('CAPTURED %r', captured_frame)
86
87 dollar0 = rd.NamedStr("dollar0", None)
88 pos_args_raw = rd.NamedList("pos_args", None)
89 vars_ = rd.NamedDict("vars", None)
90 rd.Done()
91
92 pos_args = None # type: List[str]
93 if pos_args_raw is not None:
94 pos_args = []
95 for arg in pos_args_raw:
96 if arg.tag() != value_e.Str:
97 raise error.TypeErr(
98 arg, "Expected pos_args to be a List of Strs",
99 rd.LeftParenToken())
100
101 pos_args.append(cast(value.Str, arg).s)
102
103 if self.which == EVAL_NULL:
104 # _PrintFrame('[captured]', captured_frame)
105 with state.ctx_FrontFrame(self.mem, captured_frame, None):
106 # _PrintFrame('[new]', self.cmd_ev.mem.var_stack[-1])
107 with state.ctx_Eval(self.mem, dollar0, pos_args, vars_):
108 unused_status = self.cmd_ev.EvalCommandFrag(cmd)
109 return value.Null
110
111 elif self.which == EVAL_DICT:
112 # TODO: dollar0, pos_args, vars_ not supported
113 # Does ctx_FrontFrame has different scoping rules? For "vars"?
114
115 bindings = NewDict() # type: Dict[str, value_t]
116 with state.ctx_FrontFrame(self.mem, captured_frame, bindings):
117 unused_status = self.cmd_ev.EvalCommandFrag(cmd)
118 return value.Dict(bindings)
119
120 else:
121 raise AssertionError()
122
123
124class CaptureStdout(vm._Callable):
125
126 def __init__(self, mem, shell_ex):
127 # type: (state.Mem, vm._Executor) -> None
128 self.mem = mem
129 self.shell_ex = shell_ex
130
131 def Call(self, rd):
132 # type: (typed_args.Reader) -> value_t
133
134 unused = rd.PosValue()
135 cmd = rd.PosCommand()
136 rd.Done() # no more args
137
138 frag = typed_args.GetCommandFrag(cmd)
139 with state.ctx_FrontFrame(self.mem, cmd.captured_frame, None):
140 status, stdout_str = self.shell_ex.CaptureStdout(frag)
141 if status != 0:
142 # Note that $() raises error.ErrExit with the status.
143 # But I think that results in a more confusing error message, so we
144 # "wrap" the errors.
145 properties = {
146 'status': num.ToBig(status)
147 } # type: Dict[str, value_t]
148 raise error.Structured(
149 4, 'captureStdout(): command failed with status %d' % status,
150 rd.LeftParenToken(), properties)
151
152 return value.Str(stdout_str)
153
154
155class PromptVal(vm._Callable):
156 """
157 _io->promptVal('$') is like \$
158 It expands to $ or # when root
159 """
160
161 def __init__(self, prompt_ev):
162 # type: (prompt.Evaluator) -> None
163 self.prompt_ev = prompt_ev
164
165 def Call(self, rd):
166 # type: (typed_args.Reader) -> value_t
167
168 # "self" param is guaranteed to succeed
169 unused = rd.PosValue()
170 what = rd.PosStr()
171 rd.Done() # no more args
172
173 # Bug fix: protect against crash later in PromptVal()
174 if len(what) != 1:
175 raise error.Expr(
176 'promptVal() expected a single char, got %r' % what,
177 rd.LeftParenToken())
178
179 return value.Str(self.prompt_ev.PromptVal(what))
180
181
182# TODO: Implement these
183
184
185class Time(vm._Callable):
186
187 def __init__(self):
188 # type: () -> None
189 pass
190
191 def Call(self, rd):
192 # type: (typed_args.Reader) -> value_t
193 return value.Null
194
195
196class Strftime(vm._Callable):
197
198 def __init__(self):
199 # type: () -> None
200 pass
201
202 def Call(self, rd):
203 # type: (typed_args.Reader) -> value_t
204 return value.Null
205
206
207class Glob(vm._Callable):
208
209 def __init__(self):
210 # type: () -> None
211 pass
212
213 def Call(self, rd):
214 # type: (typed_args.Reader) -> value_t
215 return value.Null