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
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
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
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)
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
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
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
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))
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