1 | from __future__ import print_function
|
2 |
|
3 | from errno import EINTR
|
4 |
|
5 | from _devbuild.gen import arg_types
|
6 | from _devbuild.gen.id_kind_asdl import Id
|
7 | from _devbuild.gen.value_asdl import (value, value_t)
|
8 | from builtin import read_osh
|
9 | from core.error import e_die_status
|
10 | from frontend import flag_util
|
11 | from frontend import match
|
12 | from frontend import typed_args
|
13 | from core import optview
|
14 | from core import pyos
|
15 | from core import state
|
16 | from core import vm
|
17 | from mycpp import mylib
|
18 | from mycpp.mylib import log
|
19 | from osh import word_compile
|
20 |
|
21 | import posix_ as posix
|
22 |
|
23 | from typing import List, Dict, TYPE_CHECKING
|
24 | if 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 |
|
32 | class 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 |
|
112 | class 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 |
|
150 | class 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
|