Beispiel #1
0
Datei: dev.py Projekt: ilyash/oil
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('\\'))
Beispiel #3
0
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
Beispiel #4
0
Datei: dev.py Projekt: ilyash/oil
    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)
Beispiel #6
0
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
Beispiel #7
0
    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)
Beispiel #8
0
    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)
Beispiel #9
0
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
Beispiel #10
0
Datei: dev.py Projekt: ilyash/oil
    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())
Beispiel #11
0
    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
Beispiel #12
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
Beispiel #15
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))
Beispiel #17
0
    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