Ejemplo n.º 1
0
    def Matches(self, comp):
        # Have to clear the response every time.  TODO: Reuse the object?
        state.SetGlobalArray(self.ex.mem, 'COMPREPLY', [])

        # New completions should use COMP_ARGV, a construct specific to OSH>
        state.SetGlobalArray(self.ex.mem, 'COMP_ARGV', comp.partial_argv)

        # Old completions may use COMP_WORDS.  It is split by : and = to emulate
        # bash's behavior.
        # More commonly, they will call _init_completion and use the 'words' output
        # of that, ignoring COMP_WORDS.
        comp_words = []
        for a in comp.partial_argv:
            AdjustArg(a, [':', '='], comp_words)
        if comp.index == -1:  # cmopgen
            comp_cword = comp.index
        else:
            comp_cword = len(comp_words) - 1  # weird invariant

        state.SetGlobalArray(self.ex.mem, 'COMP_WORDS', comp_words)
        state.SetGlobalString(self.ex.mem, 'COMP_CWORD', str(comp_cword))
        state.SetGlobalString(self.ex.mem, 'COMP_LINE', comp.line)
        state.SetGlobalString(self.ex.mem, 'COMP_POINT', str(comp.end))

        argv = [comp.first, comp.to_complete, comp.prev]
        self.log('Running completion function %r with arguments %s',
                 self.func.name, argv)

        status = self.ex.RunFuncForCompletion(self.func, argv)
        if status == 124:
            self.log('Got status 124 from %r', self.func.name)
            raise _RetryCompletion()

        # Read the response.  We set it above, so this error would only happen if
        # the user unset it.
        # NOTE: 'COMP_REPLY' would follow the naming convention!
        val = state.GetGlobal(self.ex.mem, 'COMPREPLY')
        if val.tag == value_e.Undef:
            util.error('Ran function %s but COMPREPLY was not defined',
                       self.func.name)
            return []

        if val.tag != value_e.StrArray:
            log('ERROR: COMPREPLY should be an array, got %s', val)
            return []
        self.log('COMPREPLY %s', val)

        # Return this all at once so we don't have a generator.  COMPREPLY happens
        # all at once anyway.
        return val.strs
Ejemplo n.º 2
0
  def _RunPipeline(self, node):
    pi = process.Pipeline()

    # First n-1 processes (which is empty when n == 1)
    n = len(node.children)
    for i in xrange(n - 1):
      p = self._MakeProcess(node.children[i])
      pi.Add(p)

    # Last piece of code is in THIS PROCESS.  'echo foo | read line; echo $line'
    pi.AddLast((self, node.children[n-1]))

    pipe_status = pi.Run(self.waiter, self.fd_state)
    state.SetGlobalArray(self.mem, 'PIPESTATUS', [str(p) for p in pipe_status])

    if self.exec_opts.pipefail:
      # The status is that of the last command that is non-zero.
      status = 0
      for st in pipe_status:
        if st != 0:
          status = st
    else:
      status = pipe_status[-1]  # status of last one is pipeline status

    return status
Ejemplo n.º 3
0
def Wait(argv, waiter, job_state, mem):
    """
  wait: wait [-n] [id ...]
      Wait for job completion and return exit status.

      Waits for each process identified by an ID, which may be a process ID or a
      job specification, and reports its termination status.  If ID is not
      given, waits for all currently active child processes, and the return
      status is zero.  If ID is a a job specification, waits for all processes
      in that job's pipeline.

      If the -n option is supplied, waits for the next job to terminate and
      returns its exit status.

      Exit Status:
      Returns the status of the last ID; fails if ID is invalid or an invalid
      option is given.

  # Job spec, %1 %2, %%, %?a etc.

  http://mywiki.wooledge.org/BashGuide/JobControl#jobspec

  This is different than a PID?  But it does have a PID.
  """
    arg, i = WAIT_SPEC.Parse(argv)
    pids = argv[i:]

    if arg.n:
        # wait -n returns the exit status of the process.  But how do you know
        # WHICH process?  That doesn't seem useful.
        log('wait next')
        if waiter.Wait():
            return waiter.last_status
        else:
            return 127  # nothing to wait for

    if not pids:
        log('wait all')
        # TODO: get all background jobs from JobState?
        i = 0
        while True:
            if not waiter.Wait():
                break  # nothing to wait for
            i += 1
            if job_state.AllDone():
                break

        log('waited for %d processes', i)
        return 0

    # Get list of jobs.  Then we need to check if they are ALL stopped.
    # Returns the exit code of the last one on the COMMAND LINE, not the exit
    # code of last one to FINSIH.

    status = 1  # error
    for pid in pids:
        # NOTE: osh doesn't accept 'wait %1' yet
        try:
            jid = int(pid)
        except ValueError:
            util.error('Invalid argument %r', pid)
            return 127

        job = job_state.jobs.get(jid)
        if job is None:
            util.error('No such job: %s', jid)
            return 127

        st = job.WaitUntilDone(waiter)
        if isinstance(st, list):
            status = st[-1]
            state.SetGlobalArray(mem, 'PIPESTATUS', [str(p) for p in st])
        else:
            status = st

    return status
Ejemplo n.º 4
0
    def testMatchesOracle(self):
        for i, case in enumerate(bash_oracle.CASES):  # generated data
            flags = case.get('_init_completion_flags')
            if flags is None:
                continue

            # This was input
            code_str = case['code']
            assert code_str.endswith('\t')

            log('')
            log('--- Case %d: %r with flags %s', i, code_str, flags)
            log('')
            #print(case)

            oracle_comp_words = case['COMP_WORDS']
            oracle_comp_cword = case['COMP_CWORD']
            oracle_comp_line = case['COMP_LINE']
            oracle_comp_point = case['COMP_POINT']

            # Init completion data
            oracle_words = case['words']
            oracle_cur = case['cur']
            oracle_prev = case['prev']
            oracle_cword = case['cword']
            oracle_split = case['split']

            #
            # First test some invariants on the oracle's data.
            #

            self.assertEqual(code_str[:-1], oracle_comp_line)
            # weird invariant that always holds.  So isn't COMP_CWORD useless?
            self.assertEqual(int(oracle_comp_cword),
                             len(oracle_comp_words) - 1)
            # Another weird invariant.  Note this is from the bash ORACLE, not from
            # our mocks.
            self.assertEqual(int(oracle_comp_point), len(code_str) - 1)

            #
            # Now run a piece of code that compares OSH's actual data against the
            # oracle.
            #

            init_code = _INIT_TEMPLATE % {
                'flags': ' '.join(flags),
                'command': oracle_comp_words[0]
            }

            arena = test_lib.MakeArena('<InitCompletionTest>')
            parse_ctx = parse_lib.ParseContext(arena, {})
            mem = state.Mem('', [], {}, arena)

            #
            # Allow our code to access oracle data
            #
            state.SetGlobalArray(mem, 'ORACLE_COMP_WORDS', oracle_comp_words)
            state.SetGlobalString(mem, 'ORACLE_COMP_CWORD', oracle_comp_cword)
            state.SetGlobalString(mem, 'ORACLE_COMP_LINE', oracle_comp_line)
            state.SetGlobalString(mem, 'ORACLE_COMP_POINT', oracle_comp_point)

            state.SetGlobalArray(mem, 'ORACLE_words', oracle_words)
            state.SetGlobalString(mem, 'ORACLE_cur', oracle_cur)
            state.SetGlobalString(mem, 'ORACLE_prev', oracle_prev)
            state.SetGlobalString(mem, 'ORACLE_cword', oracle_cword)
            state.SetGlobalString(mem, 'ORACLE_split', oracle_split)

            comp_lookup = completion.Lookup()
            ex = test_lib.EvalCode(init_code,
                                   parse_ctx,
                                   comp_lookup=comp_lookup,
                                   mem=mem)

            r = _MakeRootCompleter(comp_lookup=comp_lookup)
            comp = MockApi(code_str[:-1])
            m = list(r.Matches(comp))
            log('matches = %s', m)

            # Unterminated quote in case 5.  Nothing to complete.
            # TODO: use a label
            if i == 5:
                continue

            # Our test shell script records what passed in an array.
            val = mem.GetVar('PASSED')
            self.assertEqual(value_e.StrArray, val.tag,
                             "Expected array, got %s" % val)
            actually_passed = val.strs

            should_pass = [
                'COMP_WORDS',
                'COMP_CWORD',
                'COMP_LINE',
                'COMP_POINT',  # old API
                'words',
                'cur',
                'prev',
                'cword',
                'split'  # new API
            ]

            if i == 4:
                should_pass.remove('COMP_WORDS')
                should_pass.remove('COMP_CWORD')
                should_pass.remove('cword')
                should_pass.remove('words')  # double quotes aren't the same

            for t in should_pass:
                self.assert_(t in actually_passed,
                             "%r was expected to pass (case %d)" % (t, i))

        log('Ran %d cases', len(bash_oracle.CASES))
Ejemplo n.º 5
0
  def Matches(self, comp):
    # Have to clear the response every time.  TODO: Reuse the object?
    state.SetGlobalArray(self.ex.mem, 'COMPREPLY', [])

    # New completions should use COMP_ARGV, a construct specific to OSH>
    state.SetGlobalArray(self.ex.mem, 'COMP_ARGV', comp.partial_argv)

    # Old completions may use COMP_WORDS.  It is split by : and = to emulate
    # bash's behavior. 
    # More commonly, they will call _init_completion and use the 'words' output
    # of that, ignoring COMP_WORDS.
    comp_words = []
    for a in comp.partial_argv:
      AdjustArg(a, [':', '='], comp_words)
    if comp.index == -1:  # cmopgen
      comp_cword = comp.index
    else:
      comp_cword = len(comp_words) - 1  # weird invariant

    state.SetGlobalArray(self.ex.mem, 'COMP_WORDS', comp_words)
    state.SetGlobalString(self.ex.mem, 'COMP_CWORD', str(comp_cword))
    state.SetGlobalString(self.ex.mem, 'COMP_LINE', comp.line)
    state.SetGlobalString(self.ex.mem, 'COMP_POINT', str(comp.end))

    argv = [comp.first, comp.to_complete, comp.prev]
    self.log('Running completion function %r with arguments %s',
             self.func.name, argv)

    self.comp_lookup.ClearCommandsChanged()
    status = self.ex.RunFuncForCompletion(self.func, argv)
    commands_changed = self.comp_lookup.GetCommandsChanged()

    self.log('comp.first %s, commands_changed: %s', comp.first,
             commands_changed)

    if status == 124:
      cmd = os_path.basename(comp.first) 
      if cmd in commands_changed:
        self.log('Got status 124 from %r and %s commands changed',
                 self.func.name, commands_changed)
        raise _RetryCompletion()
      else:
        # This happens with my own completion scripts.  bash doesn't show an
        # error.
        self.log(
            "Function %r returned 124, but the completion spec for %r wasn't "
            "changed", self.func.name, cmd)
        return []

    # Read the response.
    # NOTE: 'COMP_REPLY' would follow the naming convention!
    val = state.GetGlobal(self.ex.mem, 'COMPREPLY')
    if val.tag == value_e.Undef:
      # We set it above, so this error would only happen if the user unset it.
      # Not changing it means there were no completions.
      # TODO: This writes over the command line; it would be better to use an
      # error object.
      ui.Stderr('osh: Ran function %r but COMPREPLY was unset',
                self.func.name)
      return []

    if val.tag != value_e.MaybeStrArray:
      log('ERROR: COMPREPLY should be an array, got %s', val)
      return []
    self.log('COMPREPLY %s', val)

    # Return this all at once so we don't have a generator.  COMPREPLY happens
    # all at once anyway.
    return val.strs
Ejemplo n.º 6
0
 def _SetRegexMatches(self, matches):
   """For ~= to set the BASH_REMATCH array."""
   state.SetGlobalArray(self.mem, 'BASH_REMATCH', matches)