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

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