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

290 lines, 175 significant
1#!/usr/bin/env python2
2"""
3builtin/io_ysh.py - YSH builtins that perform I/O
4"""
5from __future__ import print_function
6
7from _devbuild.gen import arg_types
8from _devbuild.gen.runtime_asdl import cmd_value
9from _devbuild.gen.syntax_asdl import command_e, BraceGroup, loc
10from _devbuild.gen.value_asdl import value, value_e, value_t
11from asdl import format as fmt
12from core import error
13from core.error import e_usage
14from core import state
15from display import ui
16from core import vm
17from data_lang import j8
18from frontend import flag_util
19from frontend import match
20from frontend import typed_args
21from mycpp import mylib
22from mycpp.mylib import tagswitch, log
23
24from typing import TYPE_CHECKING, cast
25if TYPE_CHECKING:
26 from core.alloc import Arena
27 from osh import cmd_eval
28 from ysh import expr_eval
29
30_ = log
31
32
33class _Builtin(vm._Builtin):
34
35 def __init__(self, mem, errfmt):
36 # type: (state.Mem, ui.ErrorFormatter) -> None
37 self.mem = mem
38 self.errfmt = errfmt
39
40
41class Pp(_Builtin):
42 """Given a list of variable names, print their values.
43
44 'pp cell a' is a lot easier to type than 'argv.py "${a[@]}"'.
45 """
46
47 def __init__(
48 self,
49 expr_ev, # type: expr_eval.ExprEvaluator
50 mem, # type: state.Mem
51 errfmt, # type: ui.ErrorFormatter
52 procs, # type: state.Procs
53 arena, # type: Arena
54 ):
55 # type: (...) -> None
56 _Builtin.__init__(self, mem, errfmt)
57 self.expr_ev = expr_ev
58 self.procs = procs
59 self.arena = arena
60 self.stdout_ = mylib.Stdout()
61
62 def _PrettyPrint(self, cmd_val):
63 # type: (cmd_value.Argv) -> int
64 rd = typed_args.ReaderForProc(cmd_val)
65 val = rd.PosValue()
66 rd.Done()
67
68 blame_tok = rd.LeftParenToken()
69
70 # It might be nice to add a string too, like
71 # pp 'my annotation' (actual)
72 # But the var name should meaningful in most cases
73
74 UP_val = val
75 result = None # type: value_t
76 with tagswitch(val) as case:
77 if case(value_e.Expr): # Destructured assert [true === f()]
78 val = cast(value.Expr, UP_val)
79
80 # In this case, we could get the unevaluated code string and
81 # print it. Although quoting the line seems enough.
82 result = self.expr_ev.EvalExpr(val.e, blame_tok)
83 else:
84 result = val
85
86 # Show it with location
87 excerpt, prefix = ui.CodeExcerptAndPrefix(blame_tok)
88 self.stdout_.write(excerpt)
89 ui.PrettyPrintValue(prefix, result, self.stdout_)
90
91 return 0
92
93 def Run(self, cmd_val):
94 # type: (cmd_value.Argv) -> int
95 arg, arg_r = flag_util.ParseCmdVal('pp',
96 cmd_val,
97 accept_typed_args=True)
98
99 action, action_loc = arg_r.Peek2()
100
101 # pp (x) prints in the same way that '= x' does
102 # TODO: We also need pp [x], which shows the expression
103 if action is None:
104 return self._PrettyPrint(cmd_val)
105
106 arg_r.Next()
107
108 # Actions that print unstable formats start with '.'
109 if action == 'cell':
110 argv, locs = arg_r.Rest2()
111
112 status = 0
113 for i, name in enumerate(argv):
114 if name.startswith(':'):
115 name = name[1:]
116
117 if not match.IsValidVarName(name):
118 raise error.Usage('got invalid variable name %r' % name,
119 locs[i])
120
121 cell = self.mem.GetCell(name)
122 if cell is None:
123 self.errfmt.Print_("Couldn't find a variable named %r" %
124 name,
125 blame_loc=locs[i])
126 status = 1
127 else:
128 self.stdout_.write('%s = ' % name)
129 pretty_f = fmt.DetectConsoleOutput(self.stdout_)
130 fmt.PrintTree(cell.PrettyTree(), pretty_f)
131 self.stdout_.write('\n')
132
133 elif action == 'asdl':
134 # TODO: could be pp asdl (x, y, z)
135 rd = typed_args.ReaderForProc(cmd_val)
136 val = rd.PosValue()
137 rd.Done()
138
139 tree = val.PrettyTree()
140 #tree = val.AbbreviatedTree() # I used this to test cycle detection
141
142 # TODO: ASDL should print the IDs. And then they will be
143 # line-wrapped.
144 # The IDs should also be used to detect cycles, and omit values
145 # already printed.
146 #id_str = vm.ValueIdString(val)
147 #f.write(' <%s%s>\n' % (ysh_type, id_str))
148
149 pretty_f = fmt.DetectConsoleOutput(self.stdout_)
150 fmt.PrintTree(tree, pretty_f)
151 self.stdout_.write('\n')
152
153 status = 0
154
155 elif action == 'line':
156 # Print format for unit tests
157
158 # TODO: could be pp line (x, y, z)
159 rd = typed_args.ReaderForProc(cmd_val)
160 val = rd.PosValue()
161 rd.Done()
162
163 if ui.TypeNotPrinted(val):
164 ysh_type = ui.ValType(val)
165 self.stdout_.write('(%s) ' % ysh_type)
166
167 j8.PrintLine(val, self.stdout_)
168
169 status = 0
170
171 elif action == 'gc-stats':
172 print('TODO')
173 status = 0
174
175 elif action == 'proc':
176 names, locs = arg_r.Rest2()
177 if len(names):
178 for i, name in enumerate(names):
179 node = self.procs.Get(name)
180 if node is None:
181 self.errfmt.Print_('Invalid proc %r' % name,
182 blame_loc=locs[i])
183 return 1
184 else:
185 names = self.procs.GetNames()
186
187 # TSV8 header
188 print('proc_name\tdoc_comment')
189 for name in names:
190 proc = self.procs.Get(name) # must exist
191 #log('Proc %s', proc)
192 body = proc.body
193
194 # TODO: not just command.ShFunction, but command.Proc!
195 doc = ''
196 if body.tag() == command_e.BraceGroup:
197 bgroup = cast(BraceGroup, body)
198 if bgroup.doc_token:
199 token = bgroup.doc_token
200 # 1 to remove leading space
201 doc = token.line.content[token.col + 1:token.col +
202 token.length]
203
204 # Note: these should be attributes on value.Proc
205 buf = mylib.BufWriter()
206 j8.EncodeString(name, buf, unquoted_ok=True)
207 buf.write('\t')
208 j8.EncodeString(doc, buf, unquoted_ok=True)
209 print(buf.getvalue())
210
211 status = 0
212
213 else:
214 e_usage('got invalid action %r' % action, action_loc)
215
216 return status
217
218
219class Write(_Builtin):
220 """
221 write -- @strs
222 write --sep ' ' --end '' -- @strs
223 write -n -- @
224 write --j8 -- @strs # argv serialization
225 write --j8 --sep $'\t' -- @strs # this is like TSV8
226 """
227
228 def __init__(self, mem, errfmt):
229 # type: (state.Mem, ui.ErrorFormatter) -> None
230 _Builtin.__init__(self, mem, errfmt)
231 self.stdout_ = mylib.Stdout()
232
233 def Run(self, cmd_val):
234 # type: (cmd_value.Argv) -> int
235 attrs, arg_r = flag_util.ParseCmdVal('write', cmd_val)
236 arg = arg_types.write(attrs.attrs)
237 #print(arg)
238
239 i = 0
240 while not arg_r.AtEnd():
241 if i != 0:
242 self.stdout_.write(arg.sep)
243 s = arg_r.Peek()
244
245 if arg.json:
246 s = j8.MaybeEncodeJsonString(s)
247
248 elif arg.j8:
249 s = j8.MaybeEncodeString(s)
250
251 self.stdout_.write(s)
252
253 arg_r.Next()
254 i += 1
255
256 if arg.n:
257 pass
258 elif len(arg.end):
259 self.stdout_.write(arg.end)
260
261 return 0
262
263
264class Fopen(vm._Builtin):
265 """fopen does nothing but run a block.
266
267 It's used solely for its redirects.
268 fopen >out.txt { echo hi }
269
270 It's a subset of eval
271 eval >out.txt { echo hi }
272 """
273
274 def __init__(self, mem, cmd_ev):
275 # type: (state.Mem, cmd_eval.CommandEvaluator) -> None
276 self.mem = mem
277 self.cmd_ev = cmd_ev # To run blocks
278
279 def Run(self, cmd_val):
280 # type: (cmd_value.Argv) -> int
281 _, arg_r = flag_util.ParseCmdVal('fopen',
282 cmd_val,
283 accept_typed_args=True)
284
285 cmd = typed_args.OptionalBlock(cmd_val)
286 if not cmd:
287 raise error.Usage('expected a block', loc.Missing)
288
289 unused = self.cmd_ev.EvalCommand(cmd)
290 return 0