def Export(argv, mem): arg, i = EXPORT_SPEC.Parse(argv) if arg.n: for name in argv[i:]: m = match.IsValidVarName(name) if not m: raise args.UsageError('export: Invalid variable name %r' % name) # NOTE: bash doesn't care if it wasn't found. mem.ClearFlag(name, var_flags_e.Exported, scope_e.Dynamic) else: for arg in argv[i:]: parts = arg.split('=', 1) if len(parts) == 1: name = parts[0] val = None # Creates an empty variable else: name, s = parts val = value.Str(s) m = match.IsValidVarName(name) if not m: raise args.UsageError('export: Invalid variable name %r' % name) #log('%s %s', name, val) mem.SetVar(lvalue.LhsName(name), val, (var_flags_e.Exported, ), scope_e.Dynamic) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # NOTE: If first char is a colon, error reporting is different. Alpine # might not use that? spec_str = arg_r.ReadRequired('requires an argspec') var_name, var_spid = arg_r.ReadRequired2( 'requires the name of a variable to set') spec = self.spec_cache.get(spec_str) if spec is None: spec = _ParseOptSpec(spec_str) self.spec_cache[spec_str] = spec user_argv = self.mem.GetArgv() if arg_r.AtEnd() else arg_r.Rest() #util.log('user_argv %s', user_argv) status, flag_char = _GetOpts(spec, user_argv, self.my_state, self.errfmt) if match.IsValidVarName(var_name): state.SetStringDynamic(self.mem, var_name, flag_char) else: # NOTE: The builtin has PARTIALLY set state. This happens in all shells # except mksh. raise error.Usage('got invalid variable name %r' % var_name, span_id=var_spid) return status
def _MakeAssignment(parse_ctx, assign_kw, suffix_words): """Create an command.Assignment node from a keyword and a list of words. NOTE: We don't allow dynamic assignments like: local $1 This can be replaced with eval 'local $1' """ # First parse flags, e.g. -r -x -a -A. None of the flags have arguments. flags = [] n = len(suffix_words) i = 1 while i < n: w = suffix_words[i] ok, static_val, quoted = word.StaticEval(w) if not ok or quoted: break # can't statically evaluate if static_val.startswith('-'): flags.append(static_val) else: break # not a flag, rest are args i += 1 # Now parse bindings or variable names pairs = [] while i < n: w = suffix_words[i] # declare x[y]=1 is valid left_token, close_token, part_offset = word.DetectAssignment(w) if left_token: pair = _MakeAssignPair(parse_ctx, (left_token, close_token, part_offset, w)) else: # In aboriginal in variables/sources: export_if_blank does export "$1". # We should allow that. # Parse this differently then? # dynamic-export? It sets global # variables. ok, static_val, quoted = word.StaticEval(w) if not ok or quoted: p_die("Variable names must be unquoted constants", word=w) # No value is equivalent to '' if not match.IsValidVarName(static_val): p_die('Invalid variable name %r', static_val, word=w) lhs = lhs_expr.LhsName(static_val) lhs.spids.append(word.LeftMostSpanForWord(w)) pair = syntax_asdl.assign_pair(lhs, assign_op_e.Equal, None) left_spid = word.LeftMostSpanForWord(w) pair.spids.append(left_spid) pairs.append(pair) i += 1 node = command.Assignment(assign_kw, flags, pairs) return node
def _UnsetVar(self, name, spid): if not match.IsValidVarName(name): raise args.UsageError( 'got invalid variable name %r' % name, span_id=spid) ok, found = self.mem.Unset(lvalue.Named(name), scope_e.Dynamic) if not ok: self.errfmt.Print("Can't unset readonly variable %r", name, span_id=spid) return ok, found
def _ParseForEachLoop(self): node = command.ForEach() node.do_arg_iter = False ok, iter_name, quoted = word.StaticEval(self.cur_word) if not ok or quoted: p_die("Loop variable name should be a constant", word=self.cur_word) if not match.IsValidVarName(iter_name): p_die("Invalid loop variable name", word=self.cur_word) node.iter_name = iter_name self._Next() # skip past name self._NewlineOk() in_spid = const.NO_INTEGER semi_spid = const.NO_INTEGER self._Peek() if self.c_id == Id.KW_In: self._Next() # skip in in_spid = word.LeftMostSpanForWord(self.cur_word) + 1 iter_words, semi_spid = self.ParseForWords() assert iter_words is not None words2 = braces.BraceDetectAll(iter_words) words3 = word.TildeDetectAll(words2) node.iter_words = words3 elif self.c_id == Id.Op_Semi: node.do_arg_iter = True # implicit for loop self._Next() elif self.c_id == Id.KW_Do: node.do_arg_iter = True # implicit for loop # do not advance else: # for foo BAD p_die('Unexpected word after for loop variable', word=self.cur_word) node.spids.extend((in_spid, semi_spid)) body_node = self.ParseDoGroup() assert body_node is not None node.body = body_node return node
def __call__(self, arg_vec): status = 0 for i in xrange(1, len(arg_vec.strs)): name = arg_vec.strs[i] if not match.IsValidVarName(name): raise args.UsageError('got invalid variable name %r' % name, span_id=arg_vec.spids[i]) cell = self.mem.GetCell(name) if cell is None: print('%r is not defined' % name) status = 1 else: sys.stdout.write('%s = ' % name) cell.PrettyPrint() # may be color sys.stdout.write('\n') return status
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # NOTE: If first char is a colon, error reporting is different. Alpine # might not use that? spec_str = arg_r.ReadRequired('requires an argspec') var_name, var_spid = arg_r.ReadRequired2( 'requires the name of a variable to set') try: spec = self.spec_cache[spec_str] except KeyError: spec = _ParseOptSpec(spec_str) self.spec_cache[spec_str] = spec # These errors are fatal errors, not like the builtin exiting with code 1. # Because the invariants of the shell have been violated! v = self.mem.GetVar('OPTIND') if v.tag != value_e.Str: e_die('OPTIND should be a string, got %r', v) try: optind = int(v.s) except ValueError: e_die("OPTIND doesn't look like an integer, got %r", v.s) user_argv = arg_r.Rest() or self.mem.GetArgv() #util.log('user_argv %s', user_argv) status, opt_char, optarg, optind = _GetOpts(spec, user_argv, optind, self.errfmt) # Bug fix: bash-completion uses a *local* OPTIND ! Not global. state.SetStringDynamic(self.mem, 'OPTARG', optarg) state.SetStringDynamic(self.mem, 'OPTIND', str(optind)) if match.IsValidVarName(var_name): state.SetStringDynamic(self.mem, var_name, opt_char) else: # NOTE: The builtin has PARTIALLY filed. This happens in all shells # except mksh. raise error.Usage('got invalid variable name %r' % var_name, span_id=var_spid) return status
def Repr(argv, mem): """Given a list of variable names, print their values. 'repr a' is a lot easier to type than 'argv.py "${a[@]}"'. """ status = 0 for name in argv: if not match.IsValidVarName(name): util.error('%r is not a valid variable name', name) return 1 # TODO: Should we print flags too? val = mem.GetVar(name) if val.tag == value_e.Undef: print('%r is not defined' % name) status = 1 else: print('%s = %s' % (name, val)) return status
def Run(self, cmd_val): arg, arg_r = flag_spec.ParseOilCmdVal('push', cmd_val) var_name, var_spid = arg_r.ReadRequired2('requires a variable name') if var_name.startswith(':'): # optional : sigil var_name = var_name[1:] if not match.IsValidVarName(var_name): raise error.Usage('got invalid variable name %r' % var_name, span_id=var_spid) val = self.mem.GetValue(var_name) # TODO: value.Obj too if val.tag != value_e.MaybeStrArray: self.errfmt.Print("%r isn't an array", var_name, span_id=var_spid) return 1 val.strs.extend(arg_r.Rest()) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # NOTE: If first char is a colon, error reporting is different. Alpine # might not use that? spec_str = arg_r.ReadRequired('requires an argspec') var_name, var_spid = arg_r.ReadRequired2( 'requires the name of a variable to set') try: spec = self.spec_cache[spec_str] except KeyError: spec = _ParseOptSpec(spec_str) self.spec_cache[spec_str] = spec # TODO: OPTIND could be value.Int? try: optind = state.GetInteger(self.mem, 'OPTIND') except error.Runtime as e: self.errfmt.Print_(e.UserErrorString()) return 1 user_argv = self.mem.GetArgv() if arg_r.AtEnd() else arg_r.Rest() #util.log('user_argv %s', user_argv) status, opt_char, optarg, optind = _GetOpts(spec, user_argv, optind, self.errfmt) # Bug fix: bash-completion uses a *local* OPTIND ! Not global. state.SetStringDynamic(self.mem, 'OPTARG', optarg) state.SetStringDynamic(self.mem, 'OPTIND', str(optind)) if match.IsValidVarName(var_name): state.SetStringDynamic(self.mem, var_name, opt_char) else: # NOTE: The builtin has PARTIALLY filed. This happens in all shells # except mksh. raise error.Usage('got invalid variable name %r' % var_name, span_id=var_spid) return status
def __call__(self, cmd_val): arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # skip 'push' var_name, var_spid = arg_r.ReadRequired2('requires a variable name') if var_name.startswith(':'): # optional : sigil var_name = var_name[1:] if not match.IsValidVarName(var_name): raise args.UsageError('got invalid variable name %r' % var_name, span_id=var_spid) val = self.mem.GetVar(var_name) # TODO: value.Obj too if val.tag != value_e.MaybeStrArray: self.errfmt.Print("%r isn't an array", var_name, span_id=var_spid) return 1 val.strs.extend(arg_r.Rest()) return 0
def Run(self, cmd_val): status = 0 for i in xrange(1, len(cmd_val.argv)): name = cmd_val.argv[i] if name.startswith(':'): name = name[1:] if not match.IsValidVarName(name): raise error.Usage('got invalid variable name %r' % name, span_id=cmd_val.arg_spids[i]) cell = self.mem.GetCell(name) if cell is None: self.errfmt.Print("Couldn't find a variable named %r" % name, span_id=cmd_val.arg_spids[i]) status = 1 else: sys.stdout.write('%s = ' % name) cell.PrettyPrint() # may be color sys.stdout.write('\n') return status
def _EvalIndirectArrayExpansion(self, name, index): """Expands ${!ref} when $ref has the form `name[index]`. Args: name, index: arbitrary strings Returns: value, or None if invalid """ if not match.IsValidVarName(name): return None val = self.mem.GetVar(name) if val.tag == value_e.StrArray: if index in ('@', '*'): # TODO: maybe_decay_array return value.StrArray(val.strs) try: index_num = int(index) except ValueError: return None try: return value.Str(val.strs[index_num]) except IndexError: return value.Undef() elif val.tag == value_e.AssocArray: if index in ('@', '*'): raise NotImplementedError try: return value.Str(val.d[index]) except KeyError: return value.Undef() elif val.tag == value_e.Undef: return value.Undef() elif val.tag == value_e.Str: return None else: raise AssertionError
def Run(self, cmd_val): arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # skip 'json' action, action_spid = arg_r.Peek2() if action is None: raise error.Usage(_JSON_ACTION_ERROR) arg_r.Next() if action == 'write': arg, _ = JSON_WRITE_SPEC.Parse(arg_r) # GetVar() of each name and print it. for var_name in arg_r.Rest(): if var_name.startswith(':'): var_name = var_name[1:] val = self.mem.GetVar(var_name) with tagswitch(val) as case: if case(value_e.Undef): # TODO: blame the right span_id self.errfmt.Print("no variable named %r is defined", var_name) return 1 elif case(value_e.Str): obj = val.s elif case(value_e.MaybeStrArray): obj = val.strs elif case(value_e.AssocArray): obj = val.d elif case(value_e.Obj): obj = val.obj else: raise AssertionError(val) if arg.pretty: indent = arg.indent extra_newline = False else: # How yajl works: if indent is -1, then everything is on one line. indent = -1 extra_newline = True j = yajl.dump(obj, sys.stdout, indent=indent) if extra_newline: sys.stdout.write('\n') # TODO: Accept a block. They aren't hooked up yet. if cmd_val.block: # TODO: flatten value.{Str,Obj} into a flat dict? namespace = self.cmd_ev.EvalBlock(cmd_val.block) print(yajl.dump(namespace)) elif action == 'read': arg, _ = JSON_READ_SPEC.Parse(arg_r) # TODO: # Respect -validate=F var_name, name_spid = arg_r.ReadRequired2("expected variable name") if var_name.startswith(':'): var_name = var_name[1:] if not match.IsValidVarName(var_name): raise error.Usage('got invalid variable name %r' % var_name, span_id=name_spid) try: # Use a global _STDIN, because we get EBADF on a redirect if we use a # local. A Py_DECREF closes the file, which we don't want, because the # redirect is responsible for freeing it. # # https://github.com/oilshell/oil/issues/675 # # TODO: write a better binding like yajl.readfd() # # It should use streaming like here: # https://lloyd.github.io/yajl/ obj = yajl.load(_STDIN) except ValueError as e: self.errfmt.Print('json read: %s', e, span_id=action_spid) return 1 self.mem.SetVar(sh_lhs_expr.Name(var_name), value.Obj(obj), scope_e.LocalOnly) else: raise error.Usage(_JSON_ACTION_ERROR, span_id=action_spid) return 0
def _ApplyPrefixOp(self, val, op_id, token): """ Returns: value """ assert val.tag != value_e.Undef if op_id == Id.VSub_Pound: # LENGTH if val.tag == value_e.Str: # NOTE: Whether bash counts bytes or chars is affected by LANG # environment variables. # Should we respect that, or another way to select? set -o # count-bytes? # https://stackoverflow.com/questions/17368067/length-of-string-in-bash try: length = string_ops.CountUtf8Chars(val.s) except util.InvalidUtf8 as e: # TODO: Add location info from 'part'? Only the caller has it. if self.exec_opts.strict_word_eval: raise else: # NOTE: Doesn't make the command exit with 1; it just returns a # length of -1. util.warn(e.UserErrorString()) return value.Str('-1') elif val.tag == value_e.StrArray: # There can be empty placeholder values in the array. length = sum(1 for s in val.strs if s is not None) return value.Str(str(length)) elif op_id == Id.VSub_Bang: # ${!foo}, "indirect expansion" # NOTES: # - Could translate to eval('$' + name) or eval("\$$name") # - ${!array[@]} means something completely different. TODO: implement # that. # - It might make sense to suggest implementing this with associative # arrays? if val.tag == value_e.Str: # plain variable name, like 'foo' if match.IsValidVarName(val.s): return self.mem.GetVar(val.s) # positional argument, like '1' try: return self.mem.GetArgNum(int(val.s)) except ValueError: pass if val.s in ('@', '*'): # TODO maybe_decay_array return value.StrArray(self.mem.GetArgv()) # otherwise an array reference, like 'arr[0]' or 'arr[xyz]' or 'arr[@]' i = val.s.find('[') if i >= 0 and val.s[-1] == ']': name, index = val.s[:i], val.s[i+1:-1] result = self._EvalIndirectArrayExpansion(name, index) if result is not None: return result # Note that bash doesn't consider this fatal. It makes the # command exit with '1', but we don't have that ability yet? e_die('Bad indirect expansion: %r', val.s, token=token) elif val.tag == value_e.StrArray: indices = [str(i) for i, s in enumerate(val.strs) if s is not None] return value.StrArray(indices) else: raise AssertionError else: raise AssertionError(op_id)
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int """ printf: printf [-v var] format [argument ...] """ attrs, arg_r = flag_spec.ParseCmdVal('printf', cmd_val) arg = arg_types.printf(attrs.attrs) fmt, fmt_spid = arg_r.ReadRequired2('requires a format string') varargs, spids = arg_r.Rest2() #log('fmt %s', fmt) #log('vals %s', vals) arena = self.parse_ctx.arena if fmt in self.parse_cache: parts = self.parse_cache[fmt] else: line_reader = reader.StringLineReader(fmt, arena) # TODO: Make public lexer = self.parse_ctx._MakeLexer(line_reader) parser = _FormatStringParser(lexer) arena.PushSource(source.ArgvWord(fmt_spid)) try: parts = parser.Parse() except error.Parse as e: self.errfmt.PrettyPrintError(e) return 2 # parse error finally: arena.PopSource() self.parse_cache[fmt] = parts if 0: print() for part in parts: part.PrettyPrint() print() out = [] # type: List[str] arg_index = 0 num_args = len(varargs) backslash_c = False while True: for part in parts: UP_part = part if part.tag_() == printf_part_e.Literal: part = cast(printf_part__Literal, UP_part) token = part.token if token.id == Id.Format_EscapedPercent: s = '%' else: s = word_compile.EvalCStringToken(token) out.append(s) elif part.tag_() == printf_part_e.Percent: part = cast(printf_part__Percent, UP_part) flags = [] # type: List[str] if len(part.flags) > 0: for flag_token in part.flags: flags.append(flag_token.val) width = -1 # nonexistent if part.width: if part.width.id in (Id.Format_Num, Id.Format_Zero): width_str = part.width.val width_spid = part.width.span_id elif part.width.id == Id.Format_Star: if arg_index < num_args: width_str = varargs[arg_index] width_spid = spids[arg_index] arg_index += 1 else: width_str = '' # invalid width_spid = runtime.NO_SPID else: raise AssertionError() try: width = int(width_str) except ValueError: if width_spid == runtime.NO_SPID: width_spid = part.width.span_id self.errfmt.Print_("printf got invalid width %r" % width_str, span_id=width_spid) return 1 precision = -1 # nonexistent if part.precision: if part.precision.id == Id.Format_Dot: precision_str = '0' precision_spid = part.precision.span_id elif part.precision.id in (Id.Format_Num, Id.Format_Zero): precision_str = part.precision.val precision_spid = part.precision.span_id elif part.precision.id == Id.Format_Star: if arg_index < num_args: precision_str = varargs[arg_index] precision_spid = spids[arg_index] arg_index += 1 else: precision_str = '' precision_spid = runtime.NO_SPID else: raise AssertionError() try: precision = int(precision_str) except ValueError: if precision_spid == runtime.NO_SPID: precision_spid = part.precision.span_id self.errfmt.Print_( 'printf got invalid precision %r' % precision_str, span_id=precision_spid) return 1 #log('index=%d n=%d', arg_index, num_args) if arg_index < num_args: s = varargs[arg_index] word_spid = spids[arg_index] arg_index += 1 else: s = '' word_spid = runtime.NO_SPID typ = part.type.val if typ == 's': if precision >= 0: s = s[:precision] # truncate elif typ == 'q': s = qsn.maybe_shell_encode(s) elif typ == 'b': # Process just like echo -e, except \c handling is simpler. c_parts = [] # type: List[str] lex = match.EchoLexer(s) while True: id_, value = lex.Next() if id_ == Id.Eol_Tok: # Note: This is really a NUL terminator break # TODO: add span_id from argv tok = Token(id_, runtime.NO_SPID, value) p = word_compile.EvalCStringToken(tok) # Unusual behavior: '\c' aborts processing! if p is None: backslash_c = True break c_parts.append(p) s = ''.join(c_parts) elif typ in 'diouxX' or part.type.id == Id.Format_Time: try: d = int(s) except ValueError: if len(s) >= 1 and s[0] in '\'"': # TODO: utf-8 decode s[1:] to be more correct. Probably # depends on issue #366, a utf-8 library. # Note: len(s) == 1 means there is a NUL (0) after the quote.. d = ord(s[1]) if len(s) >= 2 else 0 elif part.type.id == Id.Format_Time and len( s) == 0 and word_spid == runtime.NO_SPID: # Note: No argument means -1 for %(...)T as in Bash Reference # Manual 4.2 "If no argument is specified, conversion behaves # as if -1 had been given." d = -1 else: if word_spid == runtime.NO_SPID: # Blame the format string blame_spid = part.type.span_id else: blame_spid = word_spid self.errfmt.Print_( 'printf expected an integer, got %r' % s, span_id=blame_spid) return 1 if typ in 'di': s = str(d) elif typ in 'ouxX': if d < 0: e_die( "Can't format negative number %d with %%%s", d, typ, span_id=part.type.span_id) if typ == 'u': s = str(d) elif typ == 'o': s = mylib.octal(d) elif typ == 'x': s = mylib.hex_lower(d) elif typ == 'X': s = mylib.hex_upper(d) elif part.type.id == Id.Format_Time: # %(...)T # Initialize timezone: # `localtime' uses the current timezone information initialized # by `tzset'. The function `tzset' refers to the environment # variable `TZ'. When the exported variable `TZ' is present, # its value should be reflected in the real environment # variable `TZ' before call of `tzset'. # # Note: unlike LANG, TZ doesn't seem to change behavior if it's # not exported. # # TODO: In Oil, provide an API that doesn't rely on libc's # global state. tzcell = self.mem.GetCell('TZ') if tzcell and tzcell.exported and tzcell.val.tag_( ) == value_e.Str: tzval = cast(value__Str, tzcell.val) posix.putenv('TZ', tzval.s) time_.tzset() # Handle special values: # User can specify two special values -1 and -2 as in Bash # Reference Manual 4.2: "Two special argument values may be # used: -1 represents the current time, and -2 represents the # time the shell was invoked." from # https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-printf if d == -1: # the current time ts = time_.time() elif d == -2: # the shell start time ts = self.shell_start_time else: ts = d s = time_.strftime(typ[1:-2], time_.localtime(ts)) if precision >= 0: s = s[:precision] # truncate else: raise AssertionError() else: raise AssertionError() if width >= 0: if len(flags): if '-' in flags: s = s.ljust(width, ' ') elif '0' in flags: s = s.rjust(width, '0') else: pass else: s = s.rjust(width, ' ') out.append(s) else: raise AssertionError() if backslash_c: # 'printf %b a\cb xx' - \c terminates processing! break if arg_index >= num_args: break # Otherwise there are more args. So cycle through the loop once more to # implement the 'arg recycling' behavior. result = ''.join(out) if arg.v: var_name = arg.v # Notes: # - bash allows a[i] here (as in unset and ${!x}), but we haven't # implemented it. # - TODO: get the span_id for arg.v! if not match.IsValidVarName(var_name): e_usage('got invalid variable name %r' % var_name) state.SetStringDynamic(self.mem, var_name, result) else: mylib.Stdout().write(result) return 0
def __call__(self, arg_vec): """ printf: printf [-v var] format [argument ...] """ arg_r = args.Reader(arg_vec.strs, spids=arg_vec.spids) arg_r.Next() # skip argv[0] arg, _ = PRINTF_SPEC.Parse(arg_r) fmt, fmt_spid = arg_r.ReadRequired2('requires a format string') varargs, spids = arg_r.Rest2() #from core.util import log #log('fmt %s', fmt) #log('vals %s', vals) arena = self.parse_ctx.arena if fmt in self.parse_cache: parts = self.parse_cache[fmt] else: line_reader = reader.StringLineReader(fmt, arena) # TODO: Make public lexer = self.parse_ctx._MakeLexer(line_reader) p = _FormatStringParser(lexer) arena.PushSource(source.ArgvWord(fmt_spid)) try: parts = p.Parse() except util.ParseError as e: self.errfmt.PrettyPrintError(e) return 2 # parse error finally: arena.PopSource() self.parse_cache[fmt] = parts if 0: print() for part in parts: part.PrettyPrint() print() out = [] arg_index = 0 num_args = len(varargs) while True: for part in parts: if isinstance(part, printf_part.Literal): token = part.token if token.id == Id.Format_EscapedPercent: s = '%' else: s = word_compile.EvalCStringToken(token.id, token.val) out.append(s) elif isinstance(part, printf_part.Percent): try: s = varargs[arg_index] word_spid = spids[arg_index] except IndexError: s = '' word_spid = const.NO_INTEGER typ = part.type.val if typ == 's': pass # val remains the same elif typ == 'q': s = string_ops.ShellQuoteOneLine(s) elif typ in 'di': try: d = int(s) except ValueError: # This works around the fact that in the arg recycling case, you have no spid. if word_spid == const.NO_INTEGER: self.errfmt.Print( "printf got invalid number %r for this substitution", s, span_id=part.type.span_id) else: self.errfmt.Print( "printf got invalid number %r", s, span_id=word_spid) return 1 s = str(d) else: raise AssertionError if part.width: width = int(part.width.val) if part.flag: flag = part.flag.val if flag == '-': s = s.ljust(width, ' ') elif flag == '0': s = s.rjust(width, '0') else: pass else: s = s.rjust(width, ' ') out.append(s) arg_index += 1 else: raise AssertionError if arg_index >= num_args: break # Otherwise there are more args. So cycle through the loop once more to # implement the 'arg recycling' behavior. result = ''.join(out) if arg.v: var_name = arg.v # Notes: # - bash allows a[i] here (as in unset and ${!x}), but we haven't # implemented it. # - TODO: get the span_id for arg.v! if not match.IsValidVarName(var_name): raise args.UsageError('got invalid variable name %r' % var_name) state.SetStringDynamic(self.mem, var_name, result) else: sys.stdout.write(result) return 0
def __call__(self, cmd_val): """ printf: printf [-v var] format [argument ...] """ arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # skip argv[0] arg, _ = PRINTF_SPEC.Parse(arg_r) fmt, fmt_spid = arg_r.ReadRequired2('requires a format string') varargs, spids = arg_r.Rest2() #log('fmt %s', fmt) #log('vals %s', vals) arena = self.parse_ctx.arena if fmt in self.parse_cache: parts = self.parse_cache[fmt] else: line_reader = reader.StringLineReader(fmt, arena) # TODO: Make public lexer = self.parse_ctx._MakeLexer(line_reader) p = _FormatStringParser(lexer) arena.PushSource(source.ArgvWord(fmt_spid)) try: parts = p.Parse() except error.Parse as e: self.errfmt.PrettyPrintError(e) return 2 # parse error finally: arena.PopSource() self.parse_cache[fmt] = parts if 0: print() for part in parts: part.PrettyPrint() print() out = [] arg_index = 0 num_args = len(varargs) while True: for part in parts: if isinstance(part, printf_part.Literal): token = part.token if token.id == Id.Format_EscapedPercent: s = '%' else: s = word_compile.EvalCStringToken(token.id, token.val) out.append(s) elif isinstance(part, printf_part.Percent): try: s = varargs[arg_index] word_spid = spids[arg_index] except IndexError: s = '' word_spid = runtime.NO_SPID typ = part.type.val if typ == 's': if part.precision: precision = int(part.precision.val) s = s[:precision] # truncate elif typ == 'q': s = string_ops.ShellQuoteOneLine(s) elif typ in 'diouxX': try: d = int(s) except ValueError: if len(s) >= 2 and s[0] in '\'"': # TODO: utf-8 decode s[1:] to be more correct. Probably # depends on issue #366, a utf-8 library. d = ord(s[1]) else: # This works around the fact that in the arg recycling case, you have no spid. if word_spid == runtime.NO_SPID: self.errfmt.Print( "printf got invalid number %r for this substitution", s, span_id=part.type.span_id) else: self.errfmt.Print( "printf got invalid number %r", s, span_id=word_spid) return 1 if typ in 'di': s = str(d) elif typ in 'ouxX': if d < 0: e_die( "Can't format negative number %d with %%%s", d, typ, span_id=part.type.span_id) if typ == 'u': s = str(d) elif typ == 'o': s = '%o' % d elif typ == 'x': s = '%x' % d elif typ == 'X': s = '%X' % d else: raise AssertionError else: raise AssertionError if part.width: width = int(part.width.val) if part.flag: flag = part.flag.val if flag == '-': s = s.ljust(width, ' ') elif flag == '0': s = s.rjust(width, '0') else: pass else: s = s.rjust(width, ' ') out.append(s) arg_index += 1 else: raise AssertionError if arg_index >= num_args: break # Otherwise there are more args. So cycle through the loop once more to # implement the 'arg recycling' behavior. result = ''.join(out) if arg.v: var_name = arg.v # Notes: # - bash allows a[i] here (as in unset and ${!x}), but we haven't # implemented it. # - TODO: get the span_id for arg.v! if not match.IsValidVarName(var_name): raise args.UsageError('got invalid variable name %r' % var_name) state.SetStringDynamic(self.mem, var_name, result) else: sys.stdout.write(result) return 0
def _StringToInteger(self, s, span_id=runtime.NO_SPID): # type: (str, int) -> int """Use bash-like rules to coerce a string to an integer. Runtime parsing enables silly stuff like $(( $(echo 1)$(echo 2) + 1 )) => 13 0xAB -- hex constant 042 -- octal constant 42 -- decimal constant 64#z -- arbitary base constant bare word: variable quoted word: string (not done?) """ if s.startswith('0x'): try: integer = int(s, 16) except ValueError: e_strict('Invalid hex constant %r', s, span_id=span_id) return integer if s.startswith('0'): try: integer = int(s, 8) except ValueError: e_strict('Invalid octal constant %r', s, span_id=span_id) return integer if '#' in s: b, digits = mylib.split_once(s, '#') try: base = int(b) except ValueError: e_strict('Invalid base for numeric constant %r', b, span_id=span_id) integer = 0 for ch in digits: if IsLower(ch): digit = ord(ch) - ord('a') + 10 elif IsUpper(ch): digit = ord(ch) - ord('A') + 36 elif ch == '@': # horrible syntax digit = 62 elif ch == '_': digit = 63 elif ch.isdigit(): digit = int(ch) else: e_strict('Invalid digits for numeric constant %r', digits, span_id=span_id) if digit >= base: e_strict('Digits %r out of range for base %d', digits, base, span_id=span_id) integer = integer * base + digit return integer try: # Normal base 10 integer. This includes negative numbers like '-42'. integer = int(s) except ValueError: # doesn't look like an integer # note: 'test' and '[' never evaluate recursively if self.exec_opts.eval_unsafe_arith() and self.parse_ctx: # Special case so we don't get EOF error if len(s.strip()) == 0: return 0 # For compatibility: Try to parse it as an expression and evaluate it. arena = self.parse_ctx.arena a_parser = self.parse_ctx.MakeArithParser(s) with alloc.ctx_Location(arena, source.Variable(span_id)): try: node2 = a_parser.Parse() # may raise error.Parse except error.Parse as e: ui.PrettyPrintError(e, arena) e_die('Parse error in recursive arithmetic', span_id=e.span_id) # Prevent infinite recursion of $(( 1x )) -- it's a word that evaluates # to itself, and you don't want to reparse it as a word. if node2.tag_() == arith_expr_e.Word: e_die("Invalid integer constant %r", s, span_id=span_id) else: integer = self.EvalToInt(node2) else: if len(s.strip()) == 0 or match.IsValidVarName(s): # x42 could evaluate to 0 e_strict("Invalid integer constant %r", s, span_id=span_id) else: # 42x is always fatal! e_die("Invalid integer constant %r", s, span_id=span_id) return integer
def __call__(self, cmd_val): arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # skip 'json' action, action_spid = arg_r.Peek2() if action is None: raise args.UsageError(_JSON_ACTION_ERROR) arg_r.Next() if action == 'write': arg, _ = JSON_WRITE_SPEC.Parse(arg_r) # GetVar() of each name and print it. for var_name in arg_r.Rest(): if var_name.startswith(':'): var_name = var_name[1:] val = self.mem.GetVar(var_name) with tagswitch(val) as case: if case(value_e.Undef): # TODO: blame the right span_id self.errfmt.Print("no variable named %r is defined", var_name) return 1 elif case(value_e.Str): obj = val.s elif case(value_e.MaybeStrArray): obj = val.strs elif case(value_e.AssocArray): obj = val.d elif case(value_e.Obj): obj = val.obj else: raise AssertionError(val) if arg.pretty: indent = arg.indent extra_newline = False else: # How yajl works: if indent is -1, then everything is on one line. indent = -1 extra_newline = True j = yajl.dump(obj, sys.stdout, indent=indent) if extra_newline: sys.stdout.write('\n') # TODO: Accept a block. They aren't hooked up yet. if cmd_val.block: # TODO: flatten value.{Str,Obj} into a flat dict? namespace = self.ex.EvalBlock(cmd_val.block) print(yajl.dump(namespace)) elif action == 'read': arg, _ = JSON_READ_SPEC.Parse(arg_r) # TODO: # Respect -validate=F var_name, name_spid = arg_r.ReadRequired2("expected variable name") if var_name.startswith(':'): var_name = var_name[1:] if not match.IsValidVarName(var_name): raise args.UsageError('got invalid variable name %r' % var_name, span_id=name_spid) # Have to use this over sys.stdin because of redirects # TODO: change binding to yajl.readfd() ? stdin = posix_.fdopen(0) try: obj = yajl.load(stdin) except ValueError as e: self.errfmt.Print('json read: %s', e, span_id=action_spid) return 1 self.mem.SetVar(sh_lhs_expr.Name(var_name), value.Obj(obj), (), scope_e.LocalOnly) else: raise args.UsageError(_JSON_ACTION_ERROR, span_id=action_spid) return 0
def Run(self, cmd_val): arg, arg_r = flag_spec.ParseOilCmdVal('repr', cmd_val) action, action_spid = arg_r.ReadRequired2( 'expected an action (proc, .cell, etc.)') # Actions that print unstable formats start with '.' if action == '.cell': argv, spids = arg_r.Rest2() status = 0 for i, name in enumerate(argv): if name.startswith(':'): name = name[1:] if not match.IsValidVarName(name): raise error.Usage('got invalid variable name %r' % name, span_id=spids[i]) cell = self.mem.GetCell(name) if cell is None: self.errfmt.Print("Couldn't find a variable named %r" % name, span_id=spids[i]) status = 1 else: sys.stdout.write('%s = ' % name) cell.PrettyPrint() # may be color sys.stdout.write('\n') elif action == 'proc': names, spids = arg_r.Rest2() if len(names): for i, name in enumerate(names): node = self.procs.get(name) if node is None: self.errfmt.Print_('Invalid proc %r' % name, span_id=spids[i]) return 1 else: names = sorted(self.procs) # QTSV header print('proc_name\tdoc_comment') for name in names: node = self.procs[name] # must exist body = node.body # TODO: not just command__ShFunction, but command__Proc! doc = '' if body.tag_() == command_e.BraceGroup: if body.doc_token: span_id = body.doc_token.span_id span = self.arena.GetLineSpan(span_id) line = self.arena.GetLine(span.line_id) # 1 to remove leading space doc = line[span.col+1 : span.col + span.length] # No limits on proc names print('%s\t%s' % (qsn.maybe_encode(name), qsn.maybe_encode(doc))) status = 0 else: e_usage('got invalid action %r' % action, span_id=action_spid) return status