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

183 lines, 118 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 with_eol=not arg.t)
136 except pyos.ReadError as e:
137 self.errfmt.PrintMessage("mapfile: read() error: %s" %
138 posix.strerror(e.err_num))
139 return 1
140 if len(line) == 0:
141 break
142 lines.append(line)
143
144 state.BuiltinSetArray(self.mem, var_name, lines)
145 return 0
146
147
148class Cat(vm._Builtin):
149 """Internal implementation detail for $(< file).
150
151 Maybe expose this as 'builtin cat' ?
152 """
153
154 def __init__(self):
155 # type: () -> None
156 """Empty constructor for mycpp."""
157 vm._Builtin.__init__(self)
158
159 def Run(self, cmd_val):
160 # type: (cmd_value.Argv) -> int
161 chunks = [] # type: List[str]
162 while True:
163 n, err_num = pyos.Read(0, 4096, chunks)
164
165 if n < 0:
166 if err_num == EINTR:
167 pass # retry
168 else:
169 # Like the top level IOError handler
170 e_die_status(2,
171 'osh I/O error: %s' % posix.strerror(err_num))
172 # TODO: Maybe just return 1?
173
174 elif n == 0: # EOF
175 break
176
177 else:
178 # Stream it to stdout
179 assert len(chunks) == 1
180 mylib.Stdout().write(chunks[0])
181 chunks.pop()
182
183 return 0