Example #1
0
def Cd(argv, mem, dir_stack):
  arg, i = CD_SPEC.Parse(argv)
  # TODO: error checking, etc.
  # TODO: ensure that if multiple flags are provided, the *last* one overrides
  # the others.

  try:
    dest_dir = argv[i]
  except IndexError:
    val = mem.GetVar('HOME')
    if val.tag == value_e.Undef:
      util.error("$HOME isn't defined")
      return 1
    elif val.tag == value_e.Str:
      dest_dir = val.s
    elif val.tag == value_e.StrArray:
      util.error("$HOME shouldn't be an array.")
      return 1

  if dest_dir == '-':
    old = mem.GetVar('OLDPWD', scope_e.GlobalOnly)
    if old.tag == value_e.Undef:
      log('OLDPWD not set')
      return 1
    elif old.tag == value_e.Str:
      dest_dir = old.s
      print(dest_dir)  # Shells print the directory
    elif old.tag == value_e.StrArray:
      # TODO: Prevent the user from setting OLDPWD to array (or maybe they
      # can't even set it at all.)
      raise AssertionError('Invalid OLDPWD')

  pwd = mem.GetVar('PWD')
  assert pwd.tag == value_e.Str, pwd  # TODO: Need a general scheme to avoid

  # Calculate new directory, chdir() to it, then set PWD to it.  NOTE: We can't
  # call posix.getcwd() because it can raise OSError if the directory was
  # removed (ENOENT.)
  abspath = os_path.join(pwd.s, dest_dir)  # make it absolute, for cd ..
  if arg.P:
    # -P means resolve symbolic links, then process '..'
    real_dest_dir = libc.realpath(abspath)
  else:
    # -L means process '..' first.  This just does string manipulation.  (But
    # realpath afterward isn't correct?)
    real_dest_dir = os_path.normpath(abspath)

  try:
    posix.chdir(real_dest_dir)
  except OSError as e:
    # TODO: Add line number, etc.
    util.error("cd %r: %s", real_dest_dir, posix.strerror(e.errno))
    return 1

  state.SetGlobalString(mem, 'OLDPWD', pwd.s)
  state.SetGlobalString(mem, 'PWD', real_dest_dir)
  dir_stack.Reset()  # for pushd/popd/dirs
  return 0
Example #2
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
Example #3
0
def Popd(argv, mem, dir_stack):
    dest_dir = dir_stack.Pop()
    if dest_dir is None:
        util.error('popd: directory stack is empty')
        return 1

    try:
        posix.chdir(dest_dir)
    except OSError as e:
        util.error("popd: %r: %s", dest_dir, posix.strerror(e.errno))
        return 1

    _PrintDirStack(dir_stack, SINGLE_LINE, mem.GetVar('HOME'))
    state.SetGlobalString(mem, 'PWD', dest_dir)
    return 0
Example #4
0
def _PopDirStack(mem, dir_stack, errfmt):
    """Helper for popd and cd { ... }."""
    dest_dir = dir_stack.Pop()
    if dest_dir is None:
        errfmt.Print('popd: directory stack is empty')
        return 1

    try:
        posix.chdir(dest_dir)
    except OSError as e:
        # Happens if a directory is deleted in pushing and popping
        errfmt.Print("popd: %r: %s", dest_dir, posix.strerror(e.errno))
        return 1

    state.SetGlobalString(mem, 'PWD', dest_dir)
    mem.SetPwd(dest_dir)
Example #5
0
  def __call__(self, arg_vec):
    dest_dir = self.dir_stack.Pop()
    if dest_dir is None:
      self.errfmt.Print('popd: directory stack is empty')
      return 1

    try:
      posix.chdir(dest_dir)
    except OSError as e:
      # Happens if a directory is deleted in pushing and popping
      self.errfmt.Print("popd: %r: %s", dest_dir, posix.strerror(e.errno))
      return 1

    _PrintDirStack(self.dir_stack, SINGLE_LINE, self.mem.GetVar('HOME'))
    state.SetGlobalString(self.mem, 'PWD', dest_dir)
    return 0
Example #6
0
def Pushd(argv, mem, dir_stack):
    num_args = len(argv)
    if num_args <= 0:
        util.error('pushd: no other directory')
        return 1
    elif num_args > 1:
        util.error('pushd: too many arguments')
        return 1

    dest_dir = os_path.abspath(argv[0])
    try:
        posix.chdir(dest_dir)
    except OSError as e:
        util.error("pushd: %r: %s", dest_dir, posix.strerror(e.errno))
        return 1

    dir_stack.Push(dest_dir)
    _PrintDirStack(dir_stack, SINGLE_LINE, mem.GetVar('HOME'))
    state.SetGlobalString(mem, 'PWD', dest_dir)
    return 0
Example #7
0
  def __call__(self, arg_vec):
    num_args = len(arg_vec.strs) - 1
    if num_args == 0:
      # TODO: It's suppose to try another dir before doing this?
      self.errfmt.Print('pushd: no other directory')
      return 1
    elif num_args > 1:
      raise args.UsageError('got too many arguments')

    dest_dir = os_path.abspath(arg_vec.strs[1])
    try:
      posix.chdir(dest_dir)
    except OSError as e:
      self.errfmt.Print("pushd: %r: %s", dest_dir, posix.strerror(e.errno),
                        span_id=arg_vec.spids[1])
      return 1

    self.dir_stack.Push(dest_dir)
    _PrintDirStack(self.dir_stack, SINGLE_LINE, self.mem.GetVar('HOME'))
    state.SetGlobalString(self.mem, 'PWD', dest_dir)
    return 0
Example #8
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))
Example #9
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