示例#1
0
文件: deps.py 项目: tekknolagi/oil
    def _Visit(self, node):
        """
    """
        #log('VISIT %s', node.__class__.__name__)

        # NOTE: The tags are not unique!!!  We would need this:
        # if isinstance(node, ast.command) and node.tag == command_e.Simple:
        # But it's easier to check the __class__ attribute.

        cls = node.__class__
        if cls is command.Simple:
            #log('SimpleCommand %s', node.words)
            #log('--')
            #node.PrettyPrint()

            # Things to consider:
            # - source and .
            # - DONE builtins: get a list from builtin.py
            # - DONE functions: have to enter function definitions into a dictionary
            # - Commands that call others: sudo, su, find, xargs, etc.
            # - builtins that call others: exec, command
            #   - except not command -v!

            if not node.words:
                return

            w = node.words[0]
            ok, argv0, _ = word_.StaticEval(w)
            if not ok:
                log("Couldn't statically evaluate %r", w)
                return

            if (builtin.ResolveSpecial(argv0) == builtin_e.NONE
                    and builtin.ResolveAssign(argv0) == builtin_e.NONE
                    and builtin.Resolve(argv0) == builtin_e.NONE):
                self.progs_used[argv0] = True

            # NOTE: If argv1 is $0, then we do NOT print a warning!
            if argv0 == 'sudo':
                if len(node.words) < 2:
                    return
                w1 = node.words[1]
                ok, argv1, _ = word_.StaticEval(w1)
                if not ok:
                    log("Couldn't statically evaluate %r", w)
                    return

                # Should we mark them behind 'sudo'?  e.g. "sudo apt install"?
                self.progs_used[argv1] = True

        elif cls is command.ShFunction:
            self.funcs_defined[node.name] = True
示例#2
0
 def testStaticEvalWord(self):
     expr = r'\EOF'  # Quoted here doc delimiter
     w_parser = test_lib.InitWordParser(expr)
     w = w_parser.ReadWord(lex_mode_e.ShCommand)
     ok, s, quoted = word_.StaticEval(w)
     self.assertEqual(True, ok)
     self.assertEqual('EOF', s)
     self.assertEqual(True, quoted)
示例#3
0
  def _ReadPatSubVarOp(self):
    # type: () -> suffix_op__PatSub
    """
    Match     = ('/' | '#' | '%') WORD
    VarSub    = ...
              | VarOf '/' Match '/' WORD
    """
    # Exception: VSub_ArgUnquoted even if it's quoted
    # stop at eof_type=Lit_Slash, empty_ok=False
    UP_pat = self._ReadVarOpArg3(lex_mode_e.VSub_ArgUnquoted, Id.Lit_Slash, False)
    assert UP_pat.tag_() == word_e.Compound, UP_pat  # Because empty_ok=False
    pat = cast(compound_word, UP_pat)

    if len(pat.parts) == 1:
      ok, s, quoted = word_.StaticEval(pat)
      if ok and s == '/' and not quoted:  # Looks like ${a////c}, read again
        self._Next(lex_mode_e.VSub_ArgUnquoted)
        self._Peek()
        pat.parts.append(self.cur_token)

    if len(pat.parts) == 0:
      p_die('Pattern in ${x/pat/replace} must not be empty',
            token=self.cur_token)

    replace_mode = Id.Undefined_Tok
    # Check for / # % modifier on pattern.
    UP_first_part = pat.parts[0]
    if UP_first_part.tag_() == word_part_e.Literal:
      lit_id = cast(Token, UP_first_part).id
      if lit_id in (Id.Lit_Slash, Id.Lit_Pound, Id.Lit_Percent):
        pat.parts.pop(0)
        replace_mode = lit_id

    # NOTE: If there is a modifier, the pattern can be empty, e.g.
    # ${s/#/foo} and ${a/%/foo}.

    if self.token_type == Id.Right_DollarBrace:
      # e.g. ${v/a} is the same as ${v/a/}  -- empty replacement string
      return suffix_op.PatSub(pat, None, replace_mode)

    if self.token_type == Id.Lit_Slash:
      replace = self._ReadVarOpArg(lex_mode_e.VSub_ArgUnquoted)  # do not stop at /

      self._Peek()
      if self.token_type != Id.Right_DollarBrace:
        # NOTE: I think this never happens.
        # We're either in the VS_ARG_UNQ or VS_ARG_DQ lex state, and everything
        # there is Lit_ or Left_, except for }.
        p_die("Expected } after replacement string, got %s",
              ui.PrettyId(self.token_type), token=self.cur_token)

      return suffix_op.PatSub(pat, replace, replace_mode)

    # Happens with ${x//} and ${x///foo}, see test/parse-errors.sh
    p_die('Expected } or / to close pattern', token=self.cur_token)
示例#4
0
  def _ReadPatSubVarOp(self, lex_mode):
    # type: (lex_mode_t) -> suffix_op__PatSub
    """
    Match     = ('/' | '#' | '%') WORD
    VarSub    = ...
              | VarOf '/' Match '/' WORD
    """
    pat = self._ReadVarOpArg(lex_mode, eof_type=Id.Lit_Slash, empty_ok=False)
    assert isinstance(pat, word__Compound)  # Because empty_ok=False

    if len(pat.parts) == 1:
      ok, s, quoted = word_.StaticEval(pat)
      if ok and s == '/' and not quoted:  # Looks like ${a////c}, read again
        self._Next(lex_mode)
        self._Peek()
        p = word_part.Literal(self.cur_token)
        pat.parts.append(p)

    if len(pat.parts) == 0:
      p_die('Pattern in ${x/pat/replace} must not be empty',
            token=self.cur_token)

    replace_mode = Id.Undefined_Tok
    # Check for / # % modifier on pattern.
    first_part = pat.parts[0]
    if isinstance(first_part, word_part__Literal):
      lit_id = first_part.token.id
      if lit_id in (Id.Lit_Slash, Id.Lit_Pound, Id.Lit_Percent):
        pat.parts.pop(0)
        replace_mode = lit_id

    # NOTE: If there is a modifier, the pattern can be empty, e.g.
    # ${s/#/foo} and ${a/%/foo}.

    if self.token_type == Id.Right_DollarBrace:
      # e.g. ${v/a} is the same as ${v/a/}  -- empty replacement string
      return suffix_op.PatSub(pat, None, replace_mode)

    if self.token_type == Id.Lit_Slash:
      replace = self._ReadVarOpArg(lex_mode)  # do not stop at /

      self._Peek()
      if self.token_type != Id.Right_DollarBrace:
        # NOTE: I think this never happens.
        # We're either in the VS_ARG_UNQ or VS_ARG_DQ lex state, and everything
        # there is Lit_ or Left_, except for }.
        p_die("Expected } after replacement string, got %s", self.cur_token,
              token=self.cur_token)

      return suffix_op.PatSub(pat, replace, replace_mode)

    # Happens with ${x//} and ${x///foo}, see test/parse-errors.sh
    p_die("Expected } after pat sub, got %r", self.cur_token.val,
          token=self.cur_token)
示例#5
0
    def testGitComment(self):
        # ;# is a comment!  Gah.
        # Conclusion: Comments are NOT LEXICAL.  They are part of word parsing.

        node = assert_ParseCommandList(
            self, """\
. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
""")
        self.assertEqual(command_e.Sentence, node.tag)
        self.assertEqual(2, len(node.child.words))

        # This is NOT a comment
        node = assert_ParseCommandList(self, """\
echo foo#bar
""")
        self.assertEqual(command_e.Simple, node.tag)
        self.assertEqual(2, len(node.words))
        _, s, _ = word_.StaticEval(node.words[1])
        self.assertEqual('foo#bar', s)

        # This is a comment
        node = assert_ParseCommandList(self, """\
echo foo #comment
""")
        self.assertEqual(command_e.Simple, node.tag)
        self.assertEqual(2, len(node.words))
        _, s, _ = word_.StaticEval(node.words[1])
        self.assertEqual('foo', s)

        # Empty comment
        node = assert_ParseCommandList(self, """\
echo foo #
""")
        self.assertEqual(command_e.Simple, node.tag)
        self.assertEqual(2, len(node.words))
        _, s, _ = word_.StaticEval(node.words[1])
        self.assertEqual('foo', s)
示例#6
0
    def DoCommand(self, node, local_symbols, at_top_level=False):
        if node.tag == command_e.CommandList:
            # TODO: How to distinguish between echo hi; echo bye; and on separate
            # lines
            for child in node.children:
                self.DoCommand(child, local_symbols, at_top_level=at_top_level)

        elif node.tag == command_e.Simple:
            # How to preserve spaces between words?  Do you want to do it?
            # Well you need to test this:
            #
            # echo foo \
            #   bar

            # TODO: Need to print until the left most part of the phrase?  the phrase
            # is a word, binding, redirect.
            #self.cursor.PrintUntil()

            if node.more_env:
                (left_spid, ) = node.more_env[0].spids
                self.cursor.PrintUntil(left_spid)
                self.f.write('env ')

                # We only need to transform the right side, not left side.
                for pair in node.more_env:
                    self.DoWordInCommand(pair.val, local_symbols)

            # More translations:
            # - . to source
            # - eval to sh-eval

            if node.words:
                first_word = node.words[0]
                ok, val, quoted = word_.StaticEval(first_word)
                word0_spid = word_.LeftMostSpanForWord(first_word)
                if ok and not quoted:
                    if val == '[':
                        last_word = node.words[-1]
                        # Check if last word is ]
                        ok, val, quoted = word_.StaticEval(last_word)
                        if ok and not quoted and val == ']':
                            # Replace [ with 'test'
                            self.cursor.PrintUntil(word0_spid)
                            self.cursor.SkipUntil(word0_spid + 1)
                            self.f.write('test')

                            for w in node.words[1:-1]:
                                self.DoWordInCommand(w, local_symbols)

                            # Now omit ]
                            last_spid = word_.LeftMostSpanForWord(last_word)
                            self.cursor.PrintUntil(last_spid -
                                                   1)  # Get the space before
                            self.cursor.SkipUntil(last_spid +
                                                  1)  # ] takes one spid
                            return
                        else:
                            raise RuntimeError('Got [ without ]')

                    elif val == '.':
                        self.cursor.PrintUntil(word0_spid)
                        self.cursor.SkipUntil(word0_spid + 1)
                        self.f.write('source')
                        return

            for w in node.words:
                self.DoWordInCommand(w, local_symbols)

            # NOTE: This will change to "phrase"?  Word or redirect.
            for r in node.redirects:
                self.DoRedirect(r, local_symbols)

            # TODO: Print the terminator.  Could be \n or ;
            # Need to print env like PYTHONPATH = 'foo' && ls
            # Need to print redirects:
            # < > are the same.  << is here string, and >> is assignment.
            # append is >+

            # TODO: static_eval of simple command
            # - [ -> "test".  Eliminate trailing ].
            # - . -> source, etc.

        elif node.tag == command_e.ShAssignment:
            self.DoShAssignment(node, at_top_level, local_symbols)

        elif node.tag == command_e.Pipeline:
            # Obscure: |& turns into |- or |+ for stderr.
            # TODO:
            # if ! true; then -> if not true {

            # if ! echo | grep; then -> if not { echo | grep } {
            # }
            # not is like do {}, but it negates the return value I guess.

            for child in node.children:
                self.DoCommand(child, local_symbols)

        elif node.tag == command_e.AndOr:
            for child in node.children:
                self.DoCommand(child, local_symbols)

        elif node.tag == command_e.Sentence:
            # 'ls &' to 'fork ls'
            # Keep ; the same.
            self.DoCommand(node.child, local_symbols)

        # This has to be different in the function case.
        elif node.tag == command_e.BraceGroup:
            # { echo hi; } -> do { echo hi }
            # For now it might be OK to keep 'do { echo hi; }
            #left_spid, right_spid = node.spids
            (left_spid, ) = node.spids

            self.cursor.PrintUntil(left_spid)
            self.cursor.SkipUntil(left_spid + 1)
            self.f.write('do {')

            for child in node.children:
                self.DoCommand(child, local_symbols)

        elif node.tag == command_e.Subshell:
            # (echo hi) -> shell echo hi
            # (echo hi; echo bye) -> shell {echo hi; echo bye}

            (left_spid, right_spid) = node.spids

            self.cursor.PrintUntil(left_spid)
            self.cursor.SkipUntil(left_spid + 1)
            self.f.write('shell {')

            self.DoCommand(node.child, local_symbols)

            #self._DebugSpid(right_spid)
            #self._DebugSpid(right_spid + 1)

            #print('RIGHT SPID', right_spid)
            self.cursor.PrintUntil(right_spid)
            self.cursor.SkipUntil(right_spid + 1)
            self.f.write('}')

        elif node.tag == command_e.DParen:
            # (( a == 0 )) is sh-expr ' a == 0 '
            #
            # NOTE: (( n++ )) is auto-translated to sh-expr 'n++', but could be set
            # n++.
            left_spid, right_spid = node.spids
            self.cursor.PrintUntil(left_spid)
            self.cursor.SkipUntil(left_spid + 1)
            self.f.write("sh-expr '")
            self.cursor.PrintUntil(right_spid - 1)  # before ))
            self.cursor.SkipUntil(right_spid +
                                  1)  # after )) -- each one is a token
            self.f.write("'")

        elif node.tag == command_e.DBracket:
            # [[ 1 -eq 2 ]] to (1 == 2)
            self.DoBoolExpr(node.expr)

        elif node.tag == command_e.ShFunction:
            # TODO: skip name
            #self.f.write('proc %s' % node.name)

            # New symbol table for every function.
            new_local_symbols = {}

            # Should be the left most span, including 'function'
            self.cursor.PrintUntil(node.spids[0])

            self.f.write('proc ')
            self.f.write(node.name)
            self.cursor.SkipUntil(node.spids[2])

            if node.body.tag == command_e.BraceGroup:
                # Don't add "do" like a standalone brace group.  Just use {}.
                for child in node.body.children:
                    self.DoCommand(child, new_local_symbols)
            else:
                pass
                # Add {}.
                # proc foo {
                #   shell {echo hi; echo bye}
                # }
                #self.DoCommand(node.body)

        elif node.tag == command_e.BraceGroup:
            for child in node.children:
                self.DoCommand(child, local_symbols)

        elif node.tag == command_e.DoGroup:
            do_spid, done_spid = node.spids
            self.cursor.PrintUntil(do_spid)
            self.cursor.SkipUntil(do_spid + 1)
            self.f.write('{')

            for child in node.children:
                self.DoCommand(child, local_symbols)

            self.cursor.PrintUntil(done_spid)
            self.cursor.SkipUntil(done_spid + 1)
            self.f.write('}')

        elif node.tag == command_e.ForEach:
            # Need to preserve spaces between words, because there can be line
            # wrapping.
            # for x in a b c \
            #    d e f; do

            _, in_spid, semi_spid = node.spids

            if in_spid == runtime.NO_SPID:
                #self.cursor.PrintUntil()  # 'for x' and then space
                self.f.write('for %s in @Argv ' % node.iter_name)
                self.cursor.SkipUntil(node.body.spids[0])
            else:
                self.cursor.PrintUntil(in_spid +
                                       1)  # 'for x in' and then space
                self.f.write('[')
                for w in node.iter_words:
                    self.DoWordInCommand(w, local_symbols)
                self.f.write(']')
                #print("SKIPPING SEMI %d" % semi_spid, file=sys.stderr)

            if semi_spid != runtime.NO_SPID:
                self.cursor.PrintUntil(semi_spid)
                self.cursor.SkipUntil(semi_spid + 1)

            self.DoCommand(node.body, local_symbols)

        elif node.tag == command_e.ForExpr:
            # Change (( )) to ( ), and then _FixDoGroup
            pass

        elif node.tag == command_e.WhileUntil:

            # Skip 'until', and replace it with 'while not'
            if node.keyword.id == Id.KW_Until:
                kw_spid = node.keyword.span_id
                self.cursor.PrintUntil(kw_spid)
                self.f.write('while not')
                self.cursor.SkipUntil(kw_spid + 1)

            if node.cond.tag_() == condition_e.Shell:
                commands = node.cond.commands
                # Skip the semi-colon in the condition, which is ususally a Sentence
                if len(commands) == 1 and commands[0].tag_(
                ) == command_e.Sentence:
                    self.DoCommand(commands[0].child, local_symbols)
                    semi_spid = commands[0].terminator.span_id
                    self.cursor.SkipUntil(semi_spid + 1)

            self.DoCommand(node.body, local_symbols)

        elif node.tag == command_e.If:
            else_spid, fi_spid = node.spids

            # if foo; then -> if foo {
            # elif foo; then -> } elif foo {
            for i, arm in enumerate(node.arms):
                elif_spid, then_spid = arm.spids
                if i != 0:  # 'if' not 'elif' on the first arm
                    self.cursor.PrintUntil(elif_spid)
                    self.f.write('} ')

                cond = arm.cond
                if cond.tag_() == condition_e.Shell:
                    if len(
                            cond.commands
                    ) == 1 and cond.commands[0].tag == command_e.Sentence:
                        sentence = cond.commands[0]
                        self.DoCommand(sentence, local_symbols)

                        # Remove semi-colon
                        semi_spid = sentence.terminator.span_id
                        self.cursor.PrintUntil(semi_spid)
                        self.cursor.SkipUntil(semi_spid + 1)
                    else:
                        for child in cond.commands:
                            self.DoCommand(child, local_symbols)

                self.cursor.PrintUntil(then_spid)
                self.cursor.SkipUntil(then_spid + 1)
                self.f.write('{')

                for child in arm.action:
                    self.DoCommand(child, local_symbols)

            # else -> } else {
            if node.else_action:
                self.cursor.PrintUntil(else_spid)
                self.f.write('} ')
                self.cursor.PrintUntil(else_spid + 1)
                self.f.write(' {')

                for child in node.else_action:
                    self.DoCommand(child, local_symbols)

            # fi -> }
            self.cursor.PrintUntil(fi_spid)
            self.cursor.SkipUntil(fi_spid + 1)
            self.f.write('}')

        elif node.tag == command_e.Case:
            case_spid, in_spid, esac_spid = node.spids
            self.cursor.PrintUntil(case_spid)
            self.cursor.SkipUntil(case_spid + 1)
            self.f.write('match')

            # Reformat "$1" to $1
            self.DoWordInCommand(node.to_match, local_symbols)

            self.cursor.PrintUntil(in_spid)
            self.cursor.SkipUntil(in_spid + 1)
            self.f.write('{')  # matchstr $var {

            # each arm needs the ) and the ;; node to skip over?
            for arm in node.arms:
                left_spid, rparen_spid, dsemi_spid, last_spid = arm.spids
                #print(left_spid, rparen_spid, dsemi_spid)

                self.cursor.PrintUntil(left_spid)
                # Hm maybe keep | because it's semi-deprecated?  You acn use
                # reload|force-relaod {
                # }
                # e/reload|force-reload/ {
                # }
                # / 'reload' or 'force-reload' / {
                # }
                #
                # Yeah it's the more abbreviated syntax.

                # change | to 'or'
                for pat in arm.pat_list:
                    pass

                self.f.write('with ')
                # Remove the )
                self.cursor.PrintUntil(rparen_spid)
                self.cursor.SkipUntil(rparen_spid + 1)

                for child in arm.action:
                    self.DoCommand(child, local_symbols)

                if dsemi_spid != runtime.NO_SPID:
                    # Remove ;;
                    self.cursor.PrintUntil(dsemi_spid)
                    self.cursor.SkipUntil(dsemi_spid + 1)
                elif last_spid != runtime.NO_SPID:
                    self.cursor.PrintUntil(last_spid)
                else:
                    raise AssertionError(
                        "Expected with dsemi_spid or last_spid in case arm")

            self.cursor.PrintUntil(esac_spid)
            self.cursor.SkipUntil(esac_spid + 1)
            self.f.write('}')  # strmatch $var {

        elif node.tag == command_e.NoOp:
            pass

        elif node.tag == command_e.ControlFlow:
            # No change for break / return / continue
            pass

        elif node.tag == command_e.TimeBlock:
            self.DoCommand(node.pipeline, local_symbols)

        else:
            #log('Command not handled: %s', node)
            raise AssertionError(node.__class__.__name__)
示例#7
0
    def DoRedirect(self, node, local_symbols):
        #print(node, file=sys.stderr)
        op_spid = node.op.span_id
        op_id = node.op.id
        self.cursor.PrintUntil(op_spid)

        # TODO:
        # - Do < and <& the same way.
        # - How to handle here docs and here docs?
        # - >> becomes >+ or >-, or maybe >>>

        #if node.tag == redir_e.Redir:
        if False:
            if node.fd == runtime.NO_SPID:
                if op_id == Id.Redir_Great:
                    self.f.write('>')  # Allow us to replace the operator
                    self.cursor.SkipUntil(op_spid + 1)
                elif op_id == Id.Redir_GreatAnd:
                    self.f.write('> !')  # Replace >& 2 with > !2
                    spid = word_.LeftMostSpanForWord(node.arg_word)
                    self.cursor.SkipUntil(spid)
                    #self.DoWordInCommand(node.arg_word)

            else:
                # NOTE: Spacing like !2>err.txt vs !2 > err.txt can be done in the
                # formatter.
                self.f.write('!%d ' % node.fd)
                if op_id == Id.Redir_Great:
                    self.f.write('>')
                    self.cursor.SkipUntil(op_spid + 1)
                elif op_id == Id.Redir_GreatAnd:
                    self.f.write('> !')  # Replace 1>& 2 with !1 > !2
                    spid = word_.LeftMostSpanForWord(node.arg_word)
                    self.cursor.SkipUntil(spid)

            self.DoWordInCommand(node.arg_word, local_symbols)

        #elif node.tag == redir_e.HereDoc:
        elif False:
            ok, delimiter, delim_quoted = word_.StaticEval(node.here_begin)
            if not ok:
                p_die('Invalid here doc delimiter', word=node.here_begin)

            # Turn everything into <<.  We just change the quotes
            self.f.write('<<')

            #here_begin_spid2 = word_.RightMostSpanForWord(node.here_begin)
            if delim_quoted:
                self.f.write(" '''")
            else:
                self.f.write(' """')

            delim_end_spid = word_.RightMostSpanForWord(node.here_begin)
            self.cursor.SkipUntil(delim_end_spid + 1)

            #self.cursor.SkipUntil(here_begin_spid + 1)

            # Now print the lines.  TODO: Have a flag to indent these to the level of
            # the owning command, e.g.
            #   cat <<EOF
            # EOF
            # Or since most here docs are the top level, you could just have a hack
            # for a fixed indent?  TODO: Look at real use cases.
            for part in node.stdin_parts:
                self.DoWordPart(part, local_symbols)

            self.cursor.SkipUntil(node.here_end_span_id + 1)
            if delim_quoted:
                self.f.write("'''\n")
            else:
                self.f.write('"""\n')

            # Need
            #self.cursor.SkipUntil(here_end_spid2)

        else:
            raise AssertionError(node.__class__.__name__)

        # <<< 'here word'
        # << 'here word'
        #
        # 2> out.txt
        # !2 > out.txt

        # cat 1<< EOF
        # hello $name
        # EOF
        # cat !1 << """
        # hello $name
        # """
        #
        # cat << 'EOF'
        # no expansion
        # EOF
        #   cat <<- 'EOF'
        #   no expansion and indented
        #
        # cat << '''
        # no expansion
        # '''
        #   cat << '''
        #   no expansion and indented
        #   '''

        # Warn about multiple here docs on a line.
        # As an obscure feature, allow
        # cat << \'ONE' << \"TWO"
        # 123
        # ONE
        # 234
        # TWO
        # The _ is an indicator that it's not a string to be piped in.
        pass
示例#8
0
    def testPatSub(self):
        w = _assertReadWord(self, '${var/pat/replace}')
        op = _GetSuffixOp(self, w)
        self.assertUnquoted('pat', op.pat)
        self.assertUnquoted('replace', op.replace)
        self.assertEqual(Id.Undefined_Tok, op.replace_mode)

        w = _assertReadWord(self, '${var//pat/replace}')  # sub all
        op = _GetSuffixOp(self, w)
        self.assertUnquoted('pat', op.pat)
        self.assertUnquoted('replace', op.replace)
        self.assertEqual(Id.Lit_Slash, op.replace_mode)

        w = _assertReadWord(self, '${var/%pat/replace}')  # prefix
        op = _GetSuffixOp(self, w)
        self.assertUnquoted('pat', op.pat)
        self.assertUnquoted('replace', op.replace)
        self.assertEqual(Id.Lit_Percent, op.replace_mode)

        w = _assertReadWord(self, '${var/#pat/replace}')  # suffix
        op = _GetSuffixOp(self, w)
        self.assertUnquoted('pat', op.pat)
        self.assertUnquoted('replace', op.replace)
        self.assertEqual(Id.Lit_Pound, op.replace_mode)

        w = _assertReadWord(self, '${var/pat}')  # no replacement
        w = _assertReadWord(self, '${var//pat}')  # no replacement
        op = _GetSuffixOp(self, w)
        self.assertUnquoted('pat', op.pat)
        self.assertEqual(None, op.replace)
        self.assertEqual(Id.Lit_Slash, op.replace_mode)

        # replace with slash
        w = _assertReadWord(self, '${var/pat//}')
        op = _GetSuffixOp(self, w)
        self.assertUnquoted('pat', op.pat)
        self.assertUnquoted('/', op.replace)

        # replace with two slashes unquoted
        w = _assertReadWord(self, '${var/pat///}')
        op = _GetSuffixOp(self, w)
        self.assertUnquoted('pat', op.pat)
        self.assertUnquoted('//', op.replace)

        # replace with two slashes quoted
        w = _assertReadWord(self, '${var/pat/"//"}')
        op = _GetSuffixOp(self, w)
        self.assertUnquoted('pat', op.pat)

        ok, s, quoted = word_.StaticEval(op.replace)
        self.assertTrue(ok)
        self.assertEqual('//', s)
        self.assertTrue(quoted)

        # Real example found in the wild!
        # http://www.oilshell.org/blog/2016/11/07.html
        w = _assertReadWord(self, r'${var////\\/}')
        op = _GetSuffixOp(self, w)
        self.assertEqual(Id.Lit_Slash, op.replace_mode)

        self.assertUnquoted('/', op.pat)

        ok, s, quoted = word_.StaticEval(op.replace)
        self.assertTrue(ok)
        self.assertEqual(r'\/', s)
示例#9
0
 def assertUnquoted(self, expected, w):
     ok, s, quoted = word_.StaticEval(w)
     self.assertTrue(ok)
     self.assertEqual(expected, s)
     self.assertFalse(quoted)
示例#10
0
    def _visit_command_Simple(self, node):
        if not node.words:
            return

        w_ob1 = node.words[0]
        ok1, word1, _ = word_.StaticEval(w_ob1)
        if not ok1:
            logger.debug(
                "Couldn't statically evaluate 1st word object of command: %r",
                w_ob1)
            return
        else:
            self.record_word(w_ob1, word1)

        # If there's a second word, let's go ahead and pop it off.
        # We don't know we need it, but enough cases do that it's easier.
        if len(node.words) > 1:
            w_ob2 = node.words[1]
            ok2, word2, _ = word_.StaticEval(w_ob2)
            # we don't care if it succeeded, yet
        else:
            if word1 in WATCH_FIRSTWORDS:
                # just a chance to bail out on a broken script
                # may not be worth the code...
                raise Exception(
                    "Trying to handle {:} but it lacks a required argument".
                    format(word1),
                    node,
                )

        # CAUTION: some prefixable commands/builtins are ~infinitely nestable.
        # "builtin builtin builtin builtin builtin command whoami" is perfectly valid.
        # The current code won't see the dep on an external command 'whoami'.

        # TODO: Does it make sense to use the presence buildin/command etc
        # as smells that trigger extra scrutinty? i.e., "builtin source" may
        # be a reasonable smell that the script or something it sources overrides
        # source?

        # TODO: should builtin be here? Currently not because we don't want to replace them...
        if word1 in (".", "source", "sudo", "command", "eval", "exec",
                     "alias"):
            logger.info("Visiting command: %s %s", word1, word2)
            if not ok2:
                logger.info("   Command is dynamic")
                # DEBUG: print(node)
                for part in w_ob2.parts:
                    bad_token = find_dynamic_token(part)
                    if bad_token:
                        # Letting $1-style subs through for now. There are
                        # In practice, these could be paths we want to
                        # resolve, *or* be perfectly fine as is.
                        if bad_token.id == Id.VSub_Number:
                            # TODO: if there's a good way to walk back out
                            # could record the outer context as sourcing
                            # its arguments. But the naive version doesn't
                            # give us much more than outright rejecting here.
                            # A non-naive version would need to figure out
                            # which numbered argument it was, be able to figure
                            # out if set/shift were used to fiddle with it,
                            # and walk it all the way back out to the string
                            # passed into the original function call to treat
                            # *that* as the token to resolve.
                            return
                        # TODO: practice below mostly considers part vars like
                        # $HOME/blah or $PREFIX/blah, but there are other
                        # patterns a more sophisticated version could address.
                        # At the moment those would need to be manually
                        # patched. I'd like to follow this definition back to
                        # the vardef and register it for substitution if it's
                        # a simple string, flatten here and reconsider.

                        # Letting ${name}-style subs through only if they're in
                        # a list of allowed names. (goal: require conscious
                        # exceptions, but make them easy to add)
                        elif (bad_token.id == Id.VSub_Name
                              and bad_token.val in allowed_executable_varsubs):
                            return
                        # Letting $name-style subs through only if they're in
                        # a list of allowed names. (goal: require conscious
                        # exceptions, but make them easy to add)
                        elif (bad_token.id == Id.VSub_DollarName
                              # [1:] to leave off the $
                              and bad_token.val[1:]
                              in allowed_executable_varsubs):
                            return
                        else:
                            raise ResolutionError(
                                # TODO: crap phrasing
                                "Can't resolve %r with an argument that can't be statically parsed",
                                w_ob1.parts[0].val,
                                word=w_ob2,
                                token=bad_token,
                                status=2,
                                arena=self.arena,
                            )

                raise Exception(
                    "Not sure. I thought 'ok' was only False when we hit a dynamic token, but we just searched for a dynamic token and didn't find one. Reconsider everything you know.",
                    part,
                    w_ob2,
                )
            else:
                logger.info("   Command is static")
                # No magic
                if word1 in ("sudo", "command", "eval", "exec"):
                    self.record_word(w_ob2, word2)
                elif word1 in (".", "source"):
                    # CAUTION: in a multi-module library, we'll have to think very carefully about how to look up targets in order to parse them, but *avoid* translating the source statement into an absolute URI. (If this is sticky, another option might be a post-substitute to replace the build-path with the output path?)
                    target = lookup(word2)
                    logger.debug("Looked up source: %r -> %r", word2, target)
                    # it was already a valid absolute path
                    if target == word2 and target[0] == "/":
                        # TODO: I'm not sure if we should do anything about absolute paths
                        # but if so, this is where we'd do it.
                        # raise ResolutionError(
                        #     "Do we want to object to absolute paths like %r ?",
                        #     word2,
                        #     word=w_ob2,
                        #     status=2,
                        # )
                        self.record_source(w_ob2, word2, target)
                    # it seems to resolve relative filenames for files in the current
                    # directory, no matter what path is set to...
                    elif target == word2:
                        # TODO: I guess these all need to get prefxed with $out?
                        self.record_source(w_ob2, word2, target)
                    # it resolved to a new location
                    elif target:
                        self.record_source(w_ob2, word2, target)
                    # It didn't resolve, or it was an invalid absolute path
                    else:
                        # self.unresolved_source.add(target)
                        # I was recording this, but maybe we should just raise an exception
                        raise ResolutionError(
                            "Unable to resolve source target %r to a known file",
                            word2,
                            word=w_ob2,
                            status=2,
                            arena=self.arena,
                        )
                elif word1 == "alias":
                    # try to handle all observed alias representations
                    alias = word2.strip("\"='").split("=")[0]
                    self.aliases.add(alias)