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

181 lines, 117 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
126 lines = [] # type: List[str]
127 while True:
128 # bash uses this slow algorithm; YSH could provide read --all-lines
129 try:
130 line = read_osh.ReadLineSlowly(self.cmd_ev)
131 except pyos.ReadError as e:
132 self.errfmt.PrintMessage("mapfile: read() error: %s" %
133 posix.strerror(e.err_num))
134 return 1
135 if len(line) == 0:
136 break
137 # note: at least on Linux, bash doesn't strip \r\n
138 if arg.t and line.endswith('\n'):
139 line = line[:-1]
140 lines.append(line)
141
142 state.BuiltinSetArray(self.mem, var_name, lines)
143 return 0
144
145
146class Cat(vm._Builtin):
147 """Internal implementation detail for $(< file).
148
149 Maybe expose this as 'builtin cat' ?
150 """
151
152 def __init__(self):
153 # type: () -> None
154 """Empty constructor for mycpp."""
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