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

184 lines, 120 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 processing!
83 if p is None:
84 backslash_c = True
85 break
86
87 parts.append(p)
88
89 new_argv.append(''.join(parts))
90 if backslash_c: # no more args either
91 break
92
93 # Replace it
94 argv = new_argv
95
96 #log('echo argv %s', argv)
97 for i, a in enumerate(argv):
98 if i != 0:
99 self.f.write(' ') # arg separator
100 self.f.write(a)
101
102 if not arg.n and not backslash_c:
103 self.f.write('\n')
104
105 return 0
106
107
108class MapFile(vm._Builtin):
109 """Mapfile / readarray."""
110
111 def __init__(self, mem, errfmt, cmd_ev):
112 # type: (state.Mem, ui.ErrorFormatter, cmd_eval.CommandEvaluator) -> None
113 self.mem = mem
114 self.errfmt = errfmt
115 self.cmd_ev = cmd_ev
116
117 def Run(self, cmd_val):
118 # type: (cmd_value.Argv) -> int
119 attrs, arg_r = flag_util.ParseCmdVal('mapfile', cmd_val)
120 arg = arg_types.mapfile(attrs.attrs)
121
122 var_name, _ = arg_r.Peek2()
123 if var_name is None:
124 var_name = 'MAPFILE'
125 else:
126 if var_name.startswith(':'):
127 var_name = var_name[1:]
128
129 lines = [] # type: List[str]
130 while True:
131 # bash uses this slow algorithm; YSH could provide read --all-lines
132 try:
133 line = read_osh.ReadLineSlowly(self.cmd_ev)
134 except pyos.ReadError as e:
135 self.errfmt.PrintMessage("mapfile: read() error: %s" %
136 posix.strerror(e.err_num))
137 return 1
138 if len(line) == 0:
139 break
140 # note: at least on Linux, bash doesn't strip \r\n
141 if arg.t and line.endswith('\n'):
142 line = line[:-1]
143 lines.append(line)
144
145 state.BuiltinSetArray(self.mem, var_name, lines)
146 return 0
147
148
149class Cat(vm._Builtin):
150 """Internal implementation detail for $(< file).
151
152 Maybe expose this as 'builtin cat' ?
153 """
154
155 def __init__(self):
156 # type: () -> None
157 """Empty constructor for mycpp."""
158 vm._Builtin.__init__(self)
159
160 def Run(self, cmd_val):
161 # type: (cmd_value.Argv) -> int
162 chunks = [] # type: List[str]
163 while True:
164 n, err_num = pyos.Read(0, 4096, chunks)
165
166 if n < 0:
167 if err_num == EINTR:
168 pass # retry
169 else:
170 # Like the top level IOError handler
171 e_die_status(2,
172 'osh I/O error: %s' % posix.strerror(err_num))
173 # TODO: Maybe just return 1?
174
175 elif n == 0: # EOF
176 break
177
178 else:
179 # Stream it to stdout
180 assert len(chunks) == 1
181 mylib.Stdout().write(chunks[0])
182 chunks.pop()
183
184 return 0