| 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
 |