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

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