OILS / builtin / io_osh.py View on Github | oils.pub

215 lines, 140 significant
1from __future__ import print_function
2
3from errno import EINTR
4import time as time_
5
6from _devbuild.gen import arg_types
7from _devbuild.gen.id_kind_asdl import Id
8from _devbuild.gen.value_asdl import (value, value_t)
9from builtin import read_osh
10from core import error
11from core.error import e_die_status
12from core import optview
13from core import pyos
14from core import state
15from core import vm
16from frontend import flag_util
17from frontend import match
18from frontend import typed_args
19from mycpp import mylib
20from mycpp.mylib import log
21from osh import word_compile
22
23import posix_ as posix
24
25from typing import List, Dict, TYPE_CHECKING
26if TYPE_CHECKING:
27 from _devbuild.gen.runtime_asdl import cmd_value
28 from display import ui
29 from osh import cmd_eval
30
31_ = log
32
33
34class Echo(vm._Builtin):
35 """echo builtin.
36
37 shopt -s simple_echo disables -e and -n.
38 """
39
40 def __init__(self, exec_opts):
41 # type: (optview.Exec) -> None
42 self.exec_opts = exec_opts
43 self.f = mylib.Stdout()
44
45 # Reuse this constant instance
46 self.simple_flag = None # type: arg_types.echo
47
48 def _SimpleFlag(self):
49 # type: () -> arg_types.echo
50 """For arg.e and arg.n without parsing."""
51 if self.simple_flag is None:
52 attrs = {} # type: Dict[str, value_t]
53 attrs['e'] = value.Bool(False)
54 attrs['n'] = value.Bool(False)
55 self.simple_flag = arg_types.echo(attrs)
56 return self.simple_flag
57
58 def Run(self, cmd_val):
59 # type: (cmd_value.Argv) -> int
60 argv = cmd_val.argv[1:]
61
62 if self.exec_opts.simple_echo():
63 typed_args.DoesNotAccept(cmd_val.proc_args) # Disallow echo (42)
64 arg = self._SimpleFlag() # Avoid parsing -e -n
65 else:
66 attrs, arg_r = flag_util.ParseLikeEcho('echo', cmd_val)
67 arg = arg_types.echo(attrs.attrs)
68 argv = arg_r.Rest()
69
70 backslash_c = False # \c terminates input
71
72 if arg.e:
73 new_argv = [] # type: List[str]
74 for a in argv:
75 parts = [] # type: List[str]
76 lex = match.EchoLexer(a)
77 while not backslash_c:
78 id_, s = lex.Next()
79 if id_ == Id.Eol_Tok: # Note: This is really a NUL terminator
80 break
81
82 p = word_compile.EvalCStringToken(id_, s)
83
84 # Unusual behavior: '\c' prints what is there and aborts
85 # processing!
86 if p is None:
87 backslash_c = True
88 break
89
90 parts.append(p)
91
92 new_argv.append(''.join(parts))
93 if backslash_c: # no more args either
94 break
95
96 # Replace it
97 argv = new_argv
98
99 buf = mylib.BufWriter()
100
101 #log('echo argv %s', argv)
102 for i, a in enumerate(argv):
103 if i != 0:
104 buf.write(' ') # arg separator
105 buf.write(a)
106
107 if not arg.n and not backslash_c:
108 buf.write('\n')
109
110 self.f.write(buf.getvalue())
111 return 0
112
113
114class MapFile(vm._Builtin):
115 """Mapfile / readarray."""
116
117 def __init__(self, mem, errfmt, cmd_ev):
118 # type: (state.Mem, ui.ErrorFormatter, cmd_eval.CommandEvaluator) -> None
119 self.mem = mem
120 self.errfmt = errfmt
121 self.cmd_ev = cmd_ev
122
123 def Run(self, cmd_val):
124 # type: (cmd_value.Argv) -> int
125 attrs, arg_r = flag_util.ParseCmdVal('mapfile', cmd_val)
126 arg = arg_types.mapfile(attrs.attrs)
127
128 var_name, _ = arg_r.Peek2()
129 if var_name is None:
130 var_name = 'MAPFILE'
131
132 lines = [] # type: List[str]
133 while True:
134 # bash uses this slow algorithm; YSH could provide read --all-lines
135 try:
136 line, eof = read_osh.ReadLineSlowly(self.cmd_ev,
137 with_eol=not arg.t)
138 except pyos.ReadError as e:
139 self.errfmt.PrintMessage("mapfile: read() error: %s" %
140 posix.strerror(e.err_num))
141 return 1
142 if eof:
143 break
144 lines.append(line)
145
146 state.BuiltinSetArray(self.mem, var_name, lines)
147 return 0
148
149
150class Cat(vm._Builtin):
151 """Internal implementation detail for $(< file)."""
152
153 def __init__(self):
154 # type: () -> None
155 vm._Builtin.__init__(self)
156
157 def Run(self, cmd_val):
158 # type: (cmd_value.Argv) -> int
159 chunks = [] # type: List[str]
160 while True:
161 n, err_num = pyos.Read(0, 4096, chunks)
162
163 if n < 0:
164 if err_num == EINTR:
165 pass # retry
166 else:
167 # Like the top level IOError handler
168 e_die_status(2,
169 'osh I/O error: %s' % posix.strerror(err_num))
170 # TODO: Maybe just return 1?
171
172 elif n == 0: # EOF
173 break
174
175 else:
176 # Stream it to stdout
177 assert len(chunks) == 1
178 mylib.Stdout().write(chunks[0])
179 chunks.pop()
180
181 return 0
182
183
184class Sleep(vm._Builtin):
185 """Mimics some of the external sleep."""
186
187 def __init__(self):
188 # type: () -> None
189 vm._Builtin.__init__(self)
190
191 def Run(self, cmd_val):
192 # type: (cmd_value.Argv) -> int
193 _, arg_r = flag_util.ParseCmdVal('sleep', cmd_val)
194
195 # Only supports integral seconds
196 # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sleep.html
197
198 duration, duration_loc = arg_r.Peek2()
199 if duration is None:
200 raise error.Usage('expected a number of seconds',
201 cmd_val.arg_locs[0])
202 arg_r.Next()
203 arg_r.Done()
204
205 msg = 'got invalid number of seconds %r' % duration
206 try:
207 seconds = float(duration)
208 except ValueError:
209 raise error.Usage(msg, duration_loc)
210
211 if seconds < 0:
212 raise error.Usage(msg, duration_loc)
213
214 time_.sleep(seconds)
215 return 0