def _PrintShValue(val, buf): # type: (value_t, mylib.BufWriter) -> None """Using maybe_shell_encode() for legacy xtrace_details.""" # NOTE: This is a bit like _PrintVariables for declare -p result = '?' UP_val = val with tagswitch(val) as case: if case(value_e.Str): val = cast(value__Str, UP_val) result = qsn.maybe_shell_encode(val.s) elif case(value_e.MaybeStrArray): val = cast(value__MaybeStrArray, UP_val) parts = ['('] for s in val.strs: parts.append(qsn.maybe_shell_encode(s)) parts.append(')') result = ' '.join(parts) elif case(value_e.AssocArray): val = cast(value__AssocArray, UP_val) parts = ['('] for k, v in iteritems(val.d): parts.append( '[%s]=%s' % (qsn.maybe_shell_encode(k), qsn.maybe_shell_encode(v))) parts.append(')') result = ' '.join(parts) buf.write(result)
def testShellEncode(self): # We don't want \u{} in shell self.assertEqual("$'\\x01'", qsn.maybe_shell_encode('\x01')) # We don't want \0 because shell uses \000 #self.assertEqual("$'\\x00'", qsn.maybe_shell_encode('\0')) # backslash handling self.assertEqual(r"$'\\'", qsn.maybe_shell_encode('\\'))
def _GetOpts(spec, argv, optind, errfmt): optarg = '' # not set by default try: current = argv[optind - 1] # 1-based indexing except IndexError: return 1, '?', optarg, optind if not current.startswith('-'): # The next arg doesn't look like a flag. return 1, '?', optarg, optind # It looks like an argument. Stop iteration by returning 1. if current not in spec: # Invalid flag optind += 1 return 0, '?', optarg, optind optind += 1 opt_char = current[-1] needs_arg = spec[current] if needs_arg: try: optarg = argv[optind - 1] # 1-based indexing except IndexError: errfmt.Print('getopts: option %r requires an argument.', current) tmp = [qsn.maybe_shell_encode(a) for a in argv] ui.Stderr('(getopts argv: %s)', ' '.join(tmp)) # Hm doesn't cause status 1? return 0, '?', optarg, optind optind += 1 return 0, opt_char, optarg, optind
def OnShAssignment(self, lval, op, val, flags, which_scopes): # type: (lvalue_t, assign_op_t, value_t, int, scope_t) -> None buf = self._ShTraceBegin() if not buf: return left = '?' UP_lval = lval with tagswitch(lval) as case: if case(lvalue_e.Named): lval = cast(lvalue__Named, UP_lval) left = lval.name elif case(lvalue_e.Indexed): lval = cast(lvalue__Indexed, UP_lval) left = '%s[%d]' % (lval.name, lval.index) elif case(lvalue_e.Keyed): lval = cast(lvalue__Keyed, UP_lval) left = '%s[%s]' % (lval.name, qsn.maybe_shell_encode(lval.key)) buf.write(left) # Only two possibilities here buf.write('+=' if op == assign_op_e.PlusEqual else '=') _PrintShValue(val, buf) buf.write('\n') self.f.write(buf.getvalue())
def testErrorRecoveryForInvalidUnicode(self): CASES = [ # Preliminaries ('a', "a"), ('one two', "'one two'"), ('\xce', "$'\\xce'"), ('\xce\xce\xbc', "$'\\xceμ'"), # byte then char ('\xce\xbc\xce', "$'μ\\xce'"), ('\xcea', "$'\\xcea'"), ('a\xce', "$'a\\xce'"), ('a\xce\xce', "$'a\\xce\\xce'"), # two invalid ('\xbc', "$'\\xbc'"), ('\xbc\xbc', "$'\\xbc\\xbc'"), #('\xbc\xbc\x01', "$'\\xbc\\xbc\\x01'"), ('\xbca', "$'\\xbca'"), ('a\xbc', "$'a\\xbc'"), ('\xbcab', "$'\\xbcab'"), ('\xbc\x00\x01', "$'\\xbc\\x00\\x01'"), ] for c, expected in CASES: print() print('CASE %r' % c) print('---') actual = qsn.maybe_shell_encode(c) print(actual) self.assertEqual(expected, actual)
def _GetOpts(spec, argv, optind, errfmt): # type: (Dict[str, bool], List[str], int, ErrorFormatter) -> Tuple[int, str, str, int] optarg = '' # not set by default try: current = argv[optind - 1] # 1-based indexing except IndexError: return 1, '?', optarg, optind if not current.startswith('-'): # The next arg doesn't look like a flag. return 1, '?', optarg, optind # It looks like an argument. Stop iteration by returning 1. if current not in spec: # Invalid flag optind += 1 return 0, '?', optarg, optind optind += 1 opt_char = current[-1] needs_arg = spec[current] if needs_arg: try: optarg = argv[optind - 1] # 1-based indexing except IndexError: # TODO: Add location info errfmt.Print_('getopts: option %r requires an argument.' % current) tmp = [qsn.maybe_shell_encode(a) for a in argv] stderr_line('(getopts argv: %s)', ' '.join(tmp)) # Hm doesn't cause status 1? return 0, '?', optarg, optind optind += 1 return 0, opt_char, optarg, optind
def DisplayLine(self): # type: () -> str # NOTE: This is the format the Tracer uses. # bash displays sleep $n & (code) # but OSH displays sleep 1 & (argv array) # We could switch the former but I'm not sure it's necessary. tmp = [qsn.maybe_shell_encode(a) for a in self.cmd_val.argv] return '[process] %s' % ' '.join(tmp)
def OnSimpleCommand(self, argv): # type: (List[str]) -> None # NOTE: I think tracing should be on by default? For post-mortem viewing. if not self.exec_opts.xtrace(): return first_char, prefix = self._EvalPS4() tmp = [qsn.maybe_shell_encode(a) for a in argv] cmd = ' '.join(tmp) self.f.log('%s%s%s', first_char, prefix, cmd)
def _GetOpts(spec, argv, my_state, errfmt): # type: (Dict[str, bool], List[str], GetOptsState, ErrorFormatter) -> Tuple[int, str] current = my_state.GetArg(argv) #log('current %s', current) if current is None: # out of range, etc. my_state.Fail() return 1, '?' if not current.startswith('-') or current == '-': my_state.Fail() return 1, '?' flag_char = current[my_state.flag_pos] if my_state.flag_pos < len(current) - 1: my_state.flag_pos += 1 # don't move past this arg yet more_chars = True else: my_state.IncIndex() my_state.flag_pos = 1 more_chars = False if flag_char not in spec: # Invalid flag return 0, '?' if spec[flag_char]: # does it need an argument? if more_chars: optarg = current[my_state.flag_pos:] else: optarg = my_state.GetArg(argv) if optarg is None: my_state.Fail() # TODO: Add location info errfmt.Print_('getopts: option %r requires an argument.' % current) tmp = [qsn.maybe_shell_encode(a) for a in argv] stderr_line('(getopts argv: %s)', ' '.join(tmp)) # Hm doesn't cause status 1? return 0, '?' my_state.IncIndex() my_state.SetArg(optarg) else: my_state.SetArg('') return 0, flag_char
def OnSimpleCommand(self, argv): # type: (List[str]) -> None """For legacy set -x. Called before we know if it's a builtin, external, or proc. """ buf = self._ShTraceBegin() if not buf: return # Redundant with OnProcessStart (external), PushMessage (proc), and OnBuiltin if self.exec_opts.xtrace_rich(): return # Legacy: Use SHELL encoding for i, arg in enumerate(argv): if i != 0: buf.write(' ') buf.write(qsn.maybe_shell_encode(arg)) buf.write('\n') self.f.write(buf.getvalue())
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int # TODO: # - How to integrate this with auto-completion? Have to handle '+'. if len(cmd_val.argv) == 1: # 'set' without args shows visible variable names and values. According # to POSIX: # - the names should be sorted, and # - the code should be suitable for re-input to the shell. We have a # spec test for this. # Also: # - autoconf also wants them to fit on ONE LINE. # http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#set mapping = self.mem.GetAllVars() for name in sorted(mapping): str_val = mapping[name] code_str = '%s=%s' % (name, qsn.maybe_shell_encode(str_val)) print(code_str) return 0 arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # skip 'set' arg = SET_SPEC.Parse(arg_r) # 'set -o' shows options. This is actually used by autoconf-generated # scripts! if arg.show_options: self.exec_opts.ShowOptions([]) return 0 SetShellOpts(self.exec_opts, arg.opt_changes, arg.shopt_changes) # Hm do we need saw_double_dash? if arg.saw_double_dash or not arg_r.AtEnd(): self.mem.SetArgv(arg_r.Rest()) return 0
def _Format(self, parts, varargs, spids, out): # type: (List[printf_part_t], List[str], List[int], List[str]) -> int """Hairy printf formatting logic.""" arg_index = 0 num_args = len(varargs) backslash_c = False while True: # loop over arguments for part in parts: # loop over parsed format string 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: # Note: This case is very long, but hard to refactor because of the # error cases and "recycling" of args! (arg_index, return 1, etc.) part = cast(printf_part__Percent, UP_part) # TODO: These calculations are independent of the data, so could be # cached 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 if arg_index < num_args: s = varargs[arg_index] word_spid = spids[arg_index] arg_index += 1 has_arg = True else: s = '' word_spid = runtime.NO_SPID has_arg = False typ = part.type.val if typ == 's': if precision >= 0: s = s[:precision] # truncate elif typ == 'q': # TODO: most shells give \' for single quote, while OSH gives $'\'' # this could matter when SSH'ing 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_, tok_val = 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, tok_val) 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 part.type.id == Id.Format_Time or typ in 'diouxX': # %(...)T and %d share this complex integer conversion logic try: d = int( s ) # note: spaces like ' -42 ' accepted and normalized except ValueError: # 'a is interpreted as the ASCII value of 'a' 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 # 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." elif not has_arg and part.type.id == Id.Format_Time: d = -1 else: blame_spid = word_spid if has_arg else part.type.span_id self.errfmt.Print_( 'printf expected an integer, got %r' % s, span_id=blame_spid) return 1 if part.type.id == Id.Format_Time: # 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: # typ in 'diouxX' # Disallowed because it depends on 32- or 64- bit if d < 0 and typ in 'ouxX': e_die( "Can't format negative number %d with %%%s", d, typ, span_id=part.type.span_id) if typ == 'o': s = mylib.octal(d) elif typ == 'x': s = mylib.hex_lower(d) elif typ == 'X': s = mylib.hex_upper(d) else: # diu s = str(d) # without spaces like ' -42 ' # There are TWO different ways to ZERO PAD, and they differ on # the negative sign! See spec/builtin-printf zero_pad = 0 # no zero padding if width >= 0 and '0' in flags: zero_pad = 1 # style 1 elif precision > 0 and len(s) < precision: zero_pad = 2 # style 2 if zero_pad: negative = (s[0] == '-') if negative: digits = s[1:] sign = '-' if zero_pad == 1: # [%06d] -42 becomes [-00042] (6 TOTAL) n = width - 1 else: # [%6.6d] -42 becomes [-000042] (1 for '-' + 6) n = precision else: digits = s sign = '' if zero_pad == 1: n = width else: n = precision s = sign + digits.rjust(n, '0') else: raise AssertionError() if width >= 0: if '-' in flags: s = s.ljust(width, ' ') 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. return 0
def testEncodeDecode(self): CASES = [ '', '"', "'", '\\', 'hello', '_my-report.c', 'a+b', '()[]{}', 'one two', 'one\ttwo\r\n', "'one\0two'", '\x00\x01', #'\xbc\x00\x01', u'[\u03bc]'.encode('utf-8'), '\xce\xbc', '\xce\xbc\xce', # char then byte '\xce\xce\xbc', # byte then char # two invalid bytes, then restart '\xce\xce\xce\xbe', # 1 2 3 4 u'\u007a \u03bb \u4e09 \U0001f618'.encode('utf-8'), # \xce\bb \xe4\xb8\x89 \xf0\x9f\x98\x98 '\xe4\xb8\x89', '\xe4\xb8a', '\xe4a', '\xf0\x9f\x98\x98', '\xf0\x9f\x98.', '\xf0\x9f.', '\xf0.', ] for c in CASES: print('-----') print('CASE %r' % c) print() sh = qsn.maybe_shell_encode(c) q1 = qsn.maybe_encode(c) q2 = qsn.encode(c) qu = qsn.encode(c, bit8_display=qsn.BIT8_U_ESCAPE) qx = qsn.encode(c, bit8_display=qsn.BIT8_X_ESCAPE) print(' sh %s' % sh) print('qsn maybe %s' % q1) print('qsn UTF-8 %s' % q2) print('qsn U %s' % qu) print('qsn X %s' % qx) decoded1 = qsn.decode(q1) print('decoded = %r' % decoded1) print() decoded2 = qsn.decode(q2) decoded_u = qsn.decode(qu) decoded_x = qsn.decode(qx) self.assertEqual(c, decoded1) self.assertEqual(c, decoded2) self.assertEqual(c, decoded_u) self.assertEqual(c, decoded_x) # character codes, e.g. U+03bc UNICODE_CASES = [ 0x03bc, 0x0001, 0x00010000, ] for c in UNICODE_CASES: print(repr(c)) s = unichr(c).encode('utf-8') # what it should decode to q = '\\u{%0x}' % c # the QSTR encoding print('qsn %s' % q) decoded = qsn.decode(q) print('decoded = %r' % decoded) print() self.assertEqual(s, decoded) OTHER_CASES = [ # '"' and '\"' are the same thing "'\\\"'", # never encoded, but still legal "", # Would have quotes "%%%", ] for c in OTHER_CASES: decoded = qsn.decode(c) print('qsn = %s' % c) print('decoded = %r' % decoded) print() # note: INVALID = [ # lone backslash "'\\", # illegal escape. Python's JSON library also disallows this, e.g. with # ValueError: Invalid \escape: line 1 column 2 (char 1) "'\\a'", ] for c in INVALID: try: s = qsn.decode(c) except RuntimeError as e: print(e) else: self.fail('Expected %r to be invalid' % c)
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int """ 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) backslash_c = False 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): flags = None if len(part.flags) > 0: flags = '' for flag_token in part.flags: flags += flag_token.val width = None if part.width: if part.width.id in (Id.Format_Num, Id.Format_Zero): width = part.width.val width_spid = part.width.span_id elif part.width.id == Id.Format_Star: if arg_index < num_args: width = varargs[arg_index] width_spid = spids[arg_index] arg_index += 1 else: width = '' width_spid = runtime.NO_SPID else: raise AssertionError() try: width = int(width) except ValueError: if width_spid == runtime.NO_SPID: width_spid = part.width.span_id self.errfmt.Print( "printf got invalid number %r for the width", s, span_id=width_spid) return 1 precision = None if part.precision: if part.precision.id == Id.Format_Dot: precision = '0' precision_spid = part.precision.span_id elif part.precision.id in (Id.Format_Num, Id.Format_Zero): precision = part.precision.val precision_spid = part.precision.span_id elif part.precision.id == Id.Format_Star: if arg_index < num_args: precision = varargs[arg_index] precision_spid = spids[arg_index] arg_index += 1 else: precision = '' precision_spid = runtime.NO_SPID else: raise AssertionError() try: precision = int(precision) except ValueError: if precision_spid == runtime.NO_SPID: precision_spid = part.precision.span_id self.errfmt.Print( "printf got invalid number %r for the precision", s, span_id=precision_spid) return 1 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 is not None: 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. 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 p = word_compile.EvalCStringToken(id_, value) # Unusual behavior: '\c' aborts processing! if p is None: backslash_c = True break parts.append(p) s = ''.join(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: # 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 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 d = time.time() elif d == -2: # the shell start time d = shell_start_time s = time.strftime(typ[1:-2], time.localtime(d)) if precision is not None: s = s[:precision] # truncate else: raise AssertionError() else: raise AssertionError() if width is not None: if 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): raise error.Usage('got invalid variable name %r' % var_name) state.SetStringDynamic(self.mem, var_name, result) else: sys.stdout.write(result) return 0
def _PrintVariables(mem, cmd_val, attrs, print_flags, builtin=_OTHER): # type: (Mem, cmd_value__Assign, _Attributes, bool, int) -> int """ Args: print_flags: whether to print flags builtin: is it the readonly or export builtin? """ flag = attrs.attrs # Turn dynamic vars to static. tmp_g = flag.get('g') tmp_a = flag.get('a') tmp_A = flag.get('A') flag_g = cast(value__Bool, tmp_g).b if tmp_g and tmp_g.tag_() == value_e.Bool else False flag_a = cast(value__Bool, tmp_a).b if tmp_a and tmp_a.tag_() == value_e.Bool else False flag_A = cast(value__Bool, tmp_A).b if tmp_A and tmp_A.tag_() == value_e.Bool else False tmp_n = flag.get('n') tmp_r = flag.get('r') tmp_x = flag.get('x') #log('FLAG %r', flag) # SUBTLE: export -n vs. declare -n. flag vs. OPTION. # flags are value.Bool, while options are Undef or Str. # '+', '-', or None flag_n = cast(value__Str, tmp_n).s if tmp_n and tmp_n.tag_() == value_e.Str else None flag_r = cast(value__Str, tmp_r).s if tmp_r and tmp_r.tag_() == value_e.Str else None flag_x = cast(value__Str, tmp_x).s if tmp_x and tmp_x.tag_() == value_e.Str else None lookup_mode = scope_e.Dynamic if cmd_val.builtin_id == builtin_i.local: if flag_g and not mem.IsGlobalScope(): return 1 lookup_mode = scope_e.LocalOnly elif flag_g: lookup_mode = scope_e.GlobalOnly if len(cmd_val.pairs) == 0: print_all = True cells = mem.GetAllCells(lookup_mode) names = sorted(cells) # type: List[str] else: print_all = False names = [] cells = {} for pair in cmd_val.pairs: name = pair.var_name if pair.rval and pair.rval.tag_() == value_e.Str: # Invalid: declare -p foo=bar # Add a sentinel so we skip it, but know to exit with status 1. s = cast(value__Str, pair.rval).s invalid = "%s=%s" % (name, s) names.append(invalid) cells[invalid] = None else: names.append(name) cells[name] = mem.GetCell(name, lookup_mode) count = 0 for name in names: cell = cells[name] if cell is None: continue # Invalid val = cell.val #log('name %r %s', name, val) if val.tag_() == value_e.Undef: continue if builtin == _READONLY and not cell.readonly: continue if builtin == _EXPORT and not cell.exported: continue if flag_n == '-' and not cell.nameref: continue if flag_n == '+' and cell.nameref: continue if flag_r == '-' and not cell.readonly: continue if flag_r == '+' and cell.readonly: continue if flag_x == '-' and not cell.exported: continue if flag_x == '+' and cell.exported: continue if flag_a and val.tag_() != value_e.MaybeStrArray: continue if flag_A and val.tag_() != value_e.AssocArray: continue decl = [] # type: List[str] if print_flags: flags = [] # type: List[str] if cell.nameref: flags.append('n') if cell.readonly: flags.append('r') if cell.exported: flags.append('x') if val.tag_() == value_e.MaybeStrArray: flags.append('a') elif val.tag_() == value_e.AssocArray: flags.append('A') if len(flags) == 0: flags.append('-') decl.extend(["declare -", ''.join(flags), " ", name]) else: decl.append(name) if val.tag_() == value_e.Str: str_val = cast(value__Str, val) decl.extend(["=", qsn.maybe_shell_encode(str_val.s)]) elif val.tag_() == value_e.MaybeStrArray: array_val = cast(value__MaybeStrArray, val) if None in array_val.strs: # Note: Arrays with unset elements are printed in the form: # declare -p arr=(); arr[3]='' arr[4]='foo' ... decl.append("=()") first = True for i, element in enumerate(array_val.strs): if element is not None: if first: decl.append(";") first = False decl.extend([ " ", name, "[", str(i), "]=", qsn.maybe_shell_encode(element) ]) else: body = [] # type: List[str] for element in array_val.strs: if len(body) > 0: body.append(" ") body.append(qsn.maybe_shell_encode(element)) decl.extend(["=(", ''.join(body), ")"]) elif val.tag_() == value_e.AssocArray: assoc_val = cast(value__AssocArray, val) body = [] for key in sorted(assoc_val.d): if len(body) > 0: body.append(" ") key_quoted = qsn.maybe_shell_encode(key, flags=qsn.MUST_QUOTE) value_quoted = qsn.maybe_shell_encode(assoc_val.d[key]) body.extend(["[", key_quoted, "]=", value_quoted]) if len(body) > 0: decl.extend(["=(", ''.join(body), ")"]) print(''.join(decl)) count += 1 if print_all or count == len(names): return 0 else: return 1
def testFlags(self): self.assertEqual("a", qsn.maybe_shell_encode('a')) self.assertEqual("'a'", qsn.maybe_shell_encode('a', flags=qsn.MUST_QUOTE))
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) with alloc.ctx_Location(arena, source.ArgvWord(fmt_spid)): try: parts = parser.Parse() except error.Parse as e: self.errfmt.PrettyPrintError(e) return 2 # parse error 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_, tok_val = 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, tok_val) 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 is not None: # TODO: get the span_id for arg.v! v_spid = runtime.NO_SPID arena = self.parse_ctx.arena a_parser = self.parse_ctx.MakeArithParser(arg.v) with alloc.ctx_Location(arena, source.ArgvWord(v_spid)): try: anode = a_parser.Parse() except error.Parse as e: ui.PrettyPrintError(e, arena) # show parse error e_usage('Invalid -v expression', span_id=v_spid) lval = self.arith_ev.EvalArithLhs(anode, v_spid) if not self.exec_opts.eval_unsafe_arith( ) and lval.tag_() != lvalue_e.Named: e_usage( '-v expected a variable name. shopt -s eval_unsafe_arith allows expressions', span_id=v_spid) state.SetRef(self.mem, lval, value.Str(result)) else: mylib.Stdout().write(result) return 0