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 _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
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
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
def _SetRegexMatches(self, matches): """For ~= to set the BASH_REMATCH array.""" state.SetGlobalArray(self.mem, 'BASH_REMATCH', matches)