| 1 | from __future__ import print_function
 | 
| 2 | 
 | 
| 3 | from _devbuild.gen.option_asdl import option_i
 | 
| 4 | from _devbuild.gen.runtime_asdl import cmd_value, CommandStatus
 | 
| 5 | from _devbuild.gen.syntax_asdl import loc
 | 
| 6 | from _devbuild.gen.value_asdl import value, value_e
 | 
| 7 | from core import error
 | 
| 8 | from core.error import e_die_status, e_usage
 | 
| 9 | from core import executor
 | 
| 10 | from core import num
 | 
| 11 | from core import state
 | 
| 12 | from core import vm
 | 
| 13 | from frontend import flag_util
 | 
| 14 | from frontend import typed_args
 | 
| 15 | from mycpp import mops
 | 
| 16 | from mycpp.mylib import tagswitch, log
 | 
| 17 | 
 | 
| 18 | _ = log
 | 
| 19 | 
 | 
| 20 | from typing import Any, cast, TYPE_CHECKING
 | 
| 21 | if TYPE_CHECKING:
 | 
| 22 |     from core import ui
 | 
| 23 |     from osh import cmd_eval
 | 
| 24 | 
 | 
| 25 | 
 | 
| 26 | class ctx_Try(object):
 | 
| 27 | 
 | 
| 28 |     def __init__(self, mutable_opts):
 | 
| 29 |         # type: (state.MutableOpts) -> None
 | 
| 30 | 
 | 
| 31 |         mutable_opts.Push(option_i.errexit, True)
 | 
| 32 |         self.mutable_opts = mutable_opts
 | 
| 33 | 
 | 
| 34 |     def __enter__(self):
 | 
| 35 |         # type: () -> None
 | 
| 36 |         pass
 | 
| 37 | 
 | 
| 38 |     def __exit__(self, type, value, traceback):
 | 
| 39 |         # type: (Any, Any, Any) -> None
 | 
| 40 |         self.mutable_opts.Pop(option_i.errexit)
 | 
| 41 | 
 | 
| 42 | 
 | 
| 43 | class Try(vm._Builtin):
 | 
| 44 |     """Allows explicit handling of errors.
 | 
| 45 | 
 | 
| 46 |     Takes command argv, or a block:
 | 
| 47 | 
 | 
| 48 |     try ls /bad
 | 
| 49 | 
 | 
| 50 |     try {
 | 
| 51 |       var x = 1 / 0
 | 
| 52 | 
 | 
| 53 |       ls | wc -l
 | 
| 54 | 
 | 
| 55 |       diff <(sort left.txt) <(sort right.txt)
 | 
| 56 |     }
 | 
| 57 | 
 | 
| 58 |     TODO:
 | 
| 59 |     - Set _error_str (e.UserErrorString())
 | 
| 60 |     - Set _error_location
 | 
| 61 |     - These could be used by a 'raise' builtin?  Or 'reraise'
 | 
| 62 | 
 | 
| 63 |     try {
 | 
| 64 |       foo
 | 
| 65 |     }
 | 
| 66 |     if (_status !== 0) {
 | 
| 67 |       echo 'hello'
 | 
| 68 |       raise  # reads _status, _error_str, and _error_location ?
 | 
| 69 |     }
 | 
| 70 |     """
 | 
| 71 | 
 | 
| 72 |     def __init__(
 | 
| 73 |             self,
 | 
| 74 |             mutable_opts,  # type: state.MutableOpts
 | 
| 75 |             mem,  # type: state.Mem
 | 
| 76 |             cmd_ev,  # type: cmd_eval.CommandEvaluator
 | 
| 77 |             shell_ex,  # type: vm._Executor
 | 
| 78 |             errfmt,  # type: ui.ErrorFormatter
 | 
| 79 |     ):
 | 
| 80 |         # type: (...) -> None
 | 
| 81 |         self.mutable_opts = mutable_opts
 | 
| 82 |         self.mem = mem
 | 
| 83 |         self.shell_ex = shell_ex
 | 
| 84 |         self.cmd_ev = cmd_ev
 | 
| 85 |         self.errfmt = errfmt
 | 
| 86 | 
 | 
| 87 |     def Run(self, cmd_val):
 | 
| 88 |         # type: (cmd_value.Argv) -> int
 | 
| 89 |         _, arg_r = flag_util.ParseCmdVal('try_',
 | 
| 90 |                                          cmd_val,
 | 
| 91 |                                          accept_typed_args=True)
 | 
| 92 | 
 | 
| 93 |         rd = typed_args.ReaderForProc(cmd_val)
 | 
| 94 |         cmd = rd.RequiredBlock()
 | 
| 95 |         rd.Done()
 | 
| 96 | 
 | 
| 97 |         error_dict = None  # type: value.Dict
 | 
| 98 | 
 | 
| 99 |         status = 0  # success by default
 | 
| 100 |         try:
 | 
| 101 |             with ctx_Try(self.mutable_opts):
 | 
| 102 |                 unused = self.cmd_ev.EvalCommand(cmd)
 | 
| 103 |         except error.Expr as e:
 | 
| 104 |             status = e.ExitStatus()
 | 
| 105 |         except error.ErrExit as e:
 | 
| 106 |             status = e.ExitStatus()
 | 
| 107 | 
 | 
| 108 |         except error.Structured as e:
 | 
| 109 |             #log('*** STRUC %s', e)
 | 
| 110 |             status = e.ExitStatus()
 | 
| 111 |             error_dict = e.ToDict()
 | 
| 112 | 
 | 
| 113 |         if error_dict is None:
 | 
| 114 |             error_dict = value.Dict({'code': num.ToBig(status)})
 | 
| 115 | 
 | 
| 116 |         # Always set _error
 | 
| 117 |         self.mem.SetTryError(error_dict)
 | 
| 118 | 
 | 
| 119 |         # TODO: remove _status in favor of _error.code.  This is marked in
 | 
| 120 |         # spec/TODO-deprecate
 | 
| 121 |         self.mem.SetTryStatus(status)
 | 
| 122 |         return 0
 | 
| 123 | 
 | 
| 124 | 
 | 
| 125 | class Failed(vm._Builtin):
 | 
| 126 | 
 | 
| 127 |     def __init__(self, mem):
 | 
| 128 |         # type: (state.Mem) -> None
 | 
| 129 |         self.mem = mem
 | 
| 130 | 
 | 
| 131 |     def Run(self, cmd_val):
 | 
| 132 |         # type: (cmd_value.Argv) -> int
 | 
| 133 |         _, arg_r = flag_util.ParseCmdVal('failed', cmd_val)
 | 
| 134 | 
 | 
| 135 |         # No args
 | 
| 136 |         arg_r.Done()
 | 
| 137 | 
 | 
| 138 |         # Should we have
 | 
| 139 |         #   failed (_error) ?
 | 
| 140 | 
 | 
| 141 |         err = self.mem.TryError()
 | 
| 142 |         code = err.d.get('code')
 | 
| 143 |         if code is None:
 | 
| 144 |             # No error
 | 
| 145 |             return 1
 | 
| 146 | 
 | 
| 147 |         UP_code = code
 | 
| 148 |         with tagswitch(code) as case:
 | 
| 149 |             if case(value_e.Int):
 | 
| 150 |                 code = cast(value.Int, UP_code)
 | 
| 151 |                 # return 0 if and only if it failed
 | 
| 152 |                 return 1 if mops.Equal(code.i, mops.ZERO) else 0
 | 
| 153 |             else:
 | 
| 154 |                 # This should never happen because the interpreter controls the
 | 
| 155 |                 # contents of TryError()
 | 
| 156 |                 raise AssertionError()
 | 
| 157 | 
 | 
| 158 | 
 | 
| 159 | class Error(vm._Builtin):
 | 
| 160 | 
 | 
| 161 |     def __init__(self):
 | 
| 162 |         # type: () -> None
 | 
| 163 |         pass
 | 
| 164 | 
 | 
| 165 |     def Run(self, cmd_val):
 | 
| 166 |         # type: (cmd_value.Argv) -> int
 | 
| 167 |         _, arg_r = flag_util.ParseCmdVal('error',
 | 
| 168 |                                          cmd_val,
 | 
| 169 |                                          accept_typed_args=True)
 | 
| 170 | 
 | 
| 171 |         message = arg_r.Peek()
 | 
| 172 |         if message is None:
 | 
| 173 |             raise error.Usage('expected a message to display',
 | 
| 174 |                               cmd_val.arg_locs[0])
 | 
| 175 | 
 | 
| 176 |         rd = typed_args.ReaderForProc(cmd_val)
 | 
| 177 |         # Status 10 is distinct from what the Oils interpreter itself uses.  We
 | 
| 178 |         # use status 3 for expressions and 4 for encode/decode, and 10 "leaves
 | 
| 179 |         # room" for others.
 | 
| 180 |         # The user is of course free to choose status 1.
 | 
| 181 |         status = mops.BigTruncate(rd.NamedInt('code', 10))
 | 
| 182 | 
 | 
| 183 |         # attach rest of named args to _error Dict
 | 
| 184 |         properties = rd.RestNamed()
 | 
| 185 |         rd.Done()
 | 
| 186 | 
 | 
| 187 |         if status == 0:
 | 
| 188 |             raise error.Usage('status must be a non-zero integer',
 | 
| 189 |                               cmd_val.arg_locs[0])
 | 
| 190 | 
 | 
| 191 |         raise error.Structured(status, message, cmd_val.arg_locs[0],
 | 
| 192 |                                properties)
 | 
| 193 | 
 | 
| 194 | 
 | 
| 195 | class BoolStatus(vm._Builtin):
 | 
| 196 | 
 | 
| 197 |     def __init__(self, shell_ex, errfmt):
 | 
| 198 |         # type: (vm._Executor, ui.ErrorFormatter) -> None
 | 
| 199 |         self.shell_ex = shell_ex
 | 
| 200 |         self.errfmt = errfmt
 | 
| 201 | 
 | 
| 202 |     def Run(self, cmd_val):
 | 
| 203 |         # type: (cmd_value.Argv) -> int
 | 
| 204 | 
 | 
| 205 |         _, arg_r = flag_util.ParseCmdVal('boolstatus', cmd_val)
 | 
| 206 | 
 | 
| 207 |         if arg_r.Peek() is None:
 | 
| 208 |             e_usage('expected a command to run', loc.Missing)
 | 
| 209 | 
 | 
| 210 |         argv, locs = arg_r.Rest2()
 | 
| 211 |         cmd_val2 = cmd_value.Argv(argv, locs, cmd_val.typed_args,
 | 
| 212 |                                   cmd_val.pos_args, cmd_val.named_args,
 | 
| 213 |                                   cmd_val.block_arg)
 | 
| 214 | 
 | 
| 215 |         cmd_st = CommandStatus.CreateNull(alloc_lists=True)
 | 
| 216 |         status = self.shell_ex.RunSimpleCommand(cmd_val2, cmd_st,
 | 
| 217 |                                                 executor.DO_FORK)
 | 
| 218 | 
 | 
| 219 |         if status not in (0, 1):
 | 
| 220 |             e_die_status(status,
 | 
| 221 |                          'boolstatus expected status 0 or 1, got %d' % status,
 | 
| 222 |                          locs[0])
 | 
| 223 | 
 | 
| 224 |         return status
 |