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

185 lines, 119 significant
1from __future__ import print_function
2
3from errno import EINTR
4
5from _devbuild.gen import arg_types
6from _devbuild.gen.id_kind_asdl import Id
7from _devbuild.gen.value_asdl import (value, value_t)
8from builtin import read_osh
9from core.error import e_die_status
10from frontend import flag_util
11from frontend import match
12from frontend import typed_args
13from core import optview
14from core import pyos
15from core import state
16from core import vm
17from mycpp import mylib
18from mycpp.mylib import log
19from osh import word_compile
20
21import posix_ as posix
22
23from typing import List, Dict, TYPE_CHECKING
24if TYPE_CHECKING:
25 from _devbuild.gen.runtime_asdl import cmd_value
26 from core import ui
27 from osh import cmd_eval
28
29_ = log
30
31
32class Echo(vm._Builtin):
33 """echo builtin.
34
35 shopt -s simple_echo disables -e and -n.
36 """
37
38 def __init__(self, exec_opts):
39 # type: (optview.Exec) -> None
40 self.exec_opts = exec_opts
41 self.f = mylib.Stdout()
42
43 # Reuse this constant instance
44 self.simple_flag = None # type: arg_types.echo
45
46 def _SimpleFlag(self):
47 # type: () -> arg_types.echo
48 """For arg.e and arg.n without parsing."""
49 if self.simple_flag is None:
50 attrs = {} # type: Dict[str, value_t]
51 attrs['e'] = value.Bool(False)
52 attrs['n'] = value.Bool(False)
53 self.simple_flag = arg_types.echo(attrs)
54 return self.simple_flag
55
56 def Run(self, cmd_val):
57 # type: (cmd_value.Argv) -> int
58 argv = cmd_val.argv[1:]
59
60 if self.exec_opts.simple_echo():
61 typed_args.DoesNotAccept(cmd_val.typed_args) # Disallow echo (42)
62 arg = self._SimpleFlag() # Avoid parsing -e -n
63 else:
64 attrs, arg_r = flag_util.ParseLikeEcho('echo', cmd_val)
65 arg = arg_types.echo(attrs.attrs)
66 argv = arg_r.Rest()
67
68 backslash_c = False # \c terminates input
69
70 if arg.e:
71 new_argv = [] # type: List[str]
72 for a in argv:
73 parts = [] # type: List[str]
74 lex = match.EchoLexer(a)
75 while not backslash_c:
76 id_, s = lex.Next()
77 if id_ == Id.Eol_Tok: # Note: This is really a NUL terminator
78 break
79
80 p = word_compile.EvalCStringToken(id_, s)
81
82 # Unusual behavior: '\c' prints what is there and aborts
83 # processing!
84 if p is None:
85 backslash_c = True
86 break
87
88 parts.append(p)
89
90 new_argv.append(''.join(parts))
91 if backslash_c: # no more args either
92 break
93
94 # Replace it
95 argv = new_argv
96
97 buf = mylib.BufWriter()
98
99 #log('echo argv %s', argv)
100 for i, a in enumerate(argv):
101 if i != 0:
102 buf.write(' ') # arg separator
103 buf.write(a)
104
105 if not arg.n and not backslash_c:
106 buf.write('\n')
107
108 self.f.write(buf.getvalue())
109 return 0
110
111
112class MapFile(vm._Builtin):
113 """Mapfile / readarray."""
114
115 def __init__(self, mem, errfmt, cmd_ev):
116 # type: (state.Mem, ui.ErrorFormatter, cmd_eval.CommandEvaluator) -> None
117 self.mem = mem
118 self.errfmt = errfmt
119 self.cmd_ev = cmd_ev
120
121 def Run(self, cmd_val):
122 # type: (cmd_value.Argv) -> int
123 attrs, arg_r = flag_util.ParseCmdVal('mapfile', cmd_val)
124 arg = arg_types.mapfile(attrs.attrs)
125
126 var_name, _ = arg_r.Peek2()
127 if var_name is None:
128 var_name = 'MAPFILE'
129
130 lines = [] # type: List[str]
131 while True:
132 # bash uses this slow algorithm; YSH could provide read --all-lines
133 try:
134 line, _ = read_osh.ReadLineSlowly(self.cmd_ev)
135 except pyos.ReadError as e:
136 self.errfmt.PrintMessage("mapfile: read() error: %s" %
137 posix.strerror(e.err_num))
138 return 1
139 if len(line) == 0:
140 break
141 # note: at least on Linux, bash doesn't strip \r\n
142 if arg.t and line.endswith('\n'):
143 line = line[:-1]
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 Maybe expose this as 'builtin cat' ?
154 """
155
156 def __init__(self):
157 # type: () -> None
158 """Empty constructor for mycpp."""
159 vm._Builtin.__init__(self)
160
161 def Run(self, cmd_val):
162 # type: (cmd_value.Argv) -> int
163 chunks = [] # type: List[str]
164 while True:
165 n, err_num = pyos.Read(0, 4096, chunks)
166
167 if n < 0:
168 if err_num == EINTR:
169 pass # retry
170 else:
171 # Like the top level IOError handler
172 e_die_status(2,
173 'osh I/O error: %s' % posix.strerror(err_num))
174 # TODO: Maybe just return 1?
175
176 elif n == 0: # EOF
177 break
178
179 else:
180 # Stream it to stdout
181 assert len(chunks) == 1
182 mylib.Stdout().write(chunks[0])
183 chunks.pop()
184
185 return 0