1 #!/usr/bin/env python2
2 """
3 builtin_oil.py - Oil builtins.
4
5 See rfc/0024-oil-builtins.md for notes.
6
7 env: Should be in builtin_compat.py?
8
9 It's sort of like xargs too.
10 """
11 from __future__ import print_function
12
13 from _devbuild.gen import arg_types
14 from _devbuild.gen.runtime_asdl import (value, value_e, value_t, Proc,
15 cmd_value)
16 from _devbuild.gen.syntax_asdl import command_e, BraceGroup
17 from core import error
18 from core.error import e_usage
19 from core import state
20 from core import vm
21 from frontend import flag_spec
22 from frontend import match
23 from mycpp import mylib
24 from mycpp.mylib import log, tagswitch, Stdout
25 from data_lang import qsn
26 from data_lang import j8
27
28 from typing import TYPE_CHECKING, cast, Dict, List
29 if TYPE_CHECKING:
30 from core.alloc import Arena
31 from core.ui import ErrorFormatter
32
33 _ = log
34
35
36 class _Builtin(vm._Builtin):
37
38 def __init__(self, mem, errfmt):
39 # type: (state.Mem, ErrorFormatter) -> None
40 self.mem = mem
41 self.errfmt = errfmt
42
43
44 class Pp(_Builtin):
45 """Given a list of variable names, print their values.
46
47 'pp cell a' is a lot easier to type than 'argv.py "${a[@]}"'.
48 """
49
50 def __init__(self, mem, errfmt, procs, arena):
51 # type: (state.Mem, ErrorFormatter, Dict[str, Proc], Arena) -> None
52 _Builtin.__init__(self, mem, errfmt)
53 self.procs = procs
54 self.arena = arena
55 self.stdout_ = Stdout()
56
57 def Run(self, cmd_val):
58 # type: (cmd_value.Argv) -> int
59 arg, arg_r = flag_spec.ParseCmdVal('pp', cmd_val)
60
61 action, action_loc = arg_r.ReadRequired2(
62 'expected an action (proc, cell, etc.)')
63
64 # Actions that print unstable formats start with '.'
65 if action == 'cell':
66 argv, locs = arg_r.Rest2()
67
68 status = 0
69 for i, name in enumerate(argv):
70 if name.startswith(':'):
71 name = name[1:]
72
73 if not match.IsValidVarName(name):
74 raise error.Usage('got invalid variable name %r' % name,
75 locs[i])
76
77 cell = self.mem.GetCell(name)
78 if cell is None:
79 self.errfmt.Print_("Couldn't find a variable named %r" %
80 name,
81 blame_loc=locs[i])
82 status = 1
83 else:
84 self.stdout_.write('%s = ' % name)
85 if mylib.PYTHON:
86 cell.PrettyPrint() # may be color
87
88 self.stdout_.write('\n')
89
90 elif action == 'proc':
91 names, locs = arg_r.Rest2()
92 if len(names):
93 for i, name in enumerate(names):
94 node = self.procs.get(name)
95 if node is None:
96 self.errfmt.Print_('Invalid proc %r' % name,
97 blame_loc=locs[i])
98 return 1
99 else:
100 names = sorted(self.procs)
101
102 # QTSV header
103 print('proc_name\tdoc_comment')
104 for name in names:
105 proc = self.procs[name] # must exist
106 #log('Proc %s', proc)
107 body = proc.body
108
109 # TODO: not just command.ShFunction, but command.Proc!
110 doc = ''
111 if body.tag() == command_e.BraceGroup:
112 bgroup = cast(BraceGroup, body)
113 if bgroup.doc_token:
114 token = bgroup.doc_token
115 # 1 to remove leading space
116 doc = token.line.content[token.col + 1:token.col +
117 token.length]
118
119 # No limits on proc names
120 print('%s\t%s' %
121 (qsn.maybe_encode(name), qsn.maybe_encode(doc)))
122
123 status = 0
124
125 else:
126 e_usage('got invalid action %r' % action, action_loc)
127
128 return status
129
130
131 class Append(_Builtin):
132 """Push args onto an array.
133
134 Note: this could also be in builtins_pure.py?
135 """
136
137 def __init__(self, mem, errfmt):
138 # type: (state.Mem, ErrorFormatter) -> None
139 _Builtin.__init__(self, mem, errfmt)
140
141 def Run(self, cmd_val):
142 # type: (cmd_value.Argv) -> int
143 arg, arg_r = flag_spec.ParseCmdVal('append', cmd_val)
144
145 var_name, var_loc = arg_r.ReadRequired2('requires a variable name')
146
147 if var_name.startswith(':'): # optional : sigil
148 var_name = var_name[1:]
149
150 if not match.IsValidVarName(var_name):
151 raise error.Usage('got invalid variable name %r' % var_name,
152 var_loc)
153
154 val = self.mem.GetValue(var_name)
155
156 # TODO: Get rid of value.BashArray
157 ok = False
158 UP_val = val
159 with tagswitch(val) as case:
160 if case(value_e.BashArray):
161 val = cast(value.BashArray, UP_val)
162 val.strs.extend(arg_r.Rest())
163 ok = True
164 elif case(value_e.List):
165 val = cast(value.List, UP_val)
166 typed = [value.Str(s)
167 for s in arg_r.Rest()] # type: List[value_t]
168 val.items.extend(typed)
169 ok = True
170
171 if not ok:
172 # consider exit code 3 like error.TypeErrVerbose?
173 self.errfmt.Print_("%r isn't a List" % var_name, blame_loc=var_loc)
174 return 1
175
176 return 0
177
178
179 class Write(_Builtin):
180 """
181 write -- @strs
182 write --sep ' ' --end '' -- @strs
183 write -n -- @
184 write --qsn -- @strs # argv serialization
185 write --qsn --sep $'\t' -- @strs # this is like QTSV
186 """
187
188 def __init__(self, mem, errfmt):
189 # type: (state.Mem, ErrorFormatter) -> None
190 _Builtin.__init__(self, mem, errfmt)
191 self.stdout_ = Stdout()
192 self.printer = j8.Printer(0)
193
194 def Run(self, cmd_val):
195 # type: (cmd_value.Argv) -> int
196 attrs, arg_r = flag_spec.ParseCmdVal('write', cmd_val)
197 arg = arg_types.write(attrs.attrs)
198 #print(arg)
199
200 if arg.unicode == 'raw':
201 bit8_display = qsn.BIT8_UTF8
202 elif arg.unicode == 'u':
203 bit8_display = qsn.BIT8_U_ESCAPE
204 elif arg.unicode == 'x':
205 bit8_display = qsn.BIT8_X_ESCAPE
206 else:
207 raise AssertionError()
208
209 i = 0
210 while not arg_r.AtEnd():
211 if i != 0:
212 self.stdout_.write(arg.sep)
213 s = arg_r.Peek()
214
215 if arg.j8:
216 buf = mylib.BufWriter()
217 self.printer.Print(value.Str(s), buf, -1)
218 s = buf.getvalue()
219
220 elif arg.qsn:
221 s = qsn.maybe_encode(s, bit8_display)
222
223 self.stdout_.write(s)
224
225 arg_r.Next()
226 i += 1
227
228 if arg.n:
229 pass
230 elif len(arg.end):
231 self.stdout_.write(arg.end)
232
233 return 0