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 lexer
|
12 | from frontend import match
|
13 | from frontend import typed_args
|
14 | from core import optview
|
15 | from core import pyos
|
16 | from core import state
|
17 | from core import vm
|
18 | from mycpp import mylib
|
19 | from mycpp.mylib import log
|
20 | from osh import word_compile
|
21 |
|
22 | import posix_ as posix
|
23 |
|
24 | from typing import List, Dict, TYPE_CHECKING
|
25 | if 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 |
|
33 | class 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 |
|
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 | 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 |
|
153 | class 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
|