def Matches(self, comp): # TODO: Delete COMPREPLY here? It doesn't seem to be defined in bash by # default. argv, comp_words = comp.GetApiInput() state.SetGlobalArray(self.ex.mem, 'COMP_WORDS', comp_words) state.SetGlobalString(self.ex.mem, 'COMP_CWORD', str(comp.index)) state.SetGlobalString(self.ex.mem, 'COMP_LINE', comp.line) state.SetGlobalString(self.ex.mem, 'COMP_POINT', str(comp.end)) 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() # Lame: 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 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 Cd(argv, mem, dir_stack): arg, i = CD_SPEC.Parse(argv) # TODO: error checking, etc. 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') # NOTE: We can't call os.getcwd() because it can raise OSError if the # directory was removed (ENOENT.) pwd = mem.GetVar('PWD') assert pwd.tag == value_e.Str, pwd # TODO: Need a general scheme to avoid state.SetGlobalString(mem, 'OLDPWD', pwd.s) try: os.chdir(dest_dir) except OSError as e: # TODO: Add line number, etc. util.error("cd %r: %s", dest_dir, os.strerror(e.errno)) return 1 # Set $PWD. # '-L' is the default behavior; no need to check it # TODO: ensure that if multiple flags are provided, the *last* one overrides # the others pwd = os.path.realpath(dest_dir) if arg.P else dest_dir state.SetGlobalString(mem, 'PWD', pwd) dir_stack.Reset() # for pushd/popd/dirs return 0
def Cd(argv, mem, dir_stack): # TODO: Parse flags, error checking, etc. try: dest_dir = argv[0] 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') # NOTE: We can't call os.getcwd() because it can raise OSError if the # directory was removed (ENOENT.) pwd = mem.GetVar('PWD') assert pwd.tag == value_e.Str, pwd # TODO: Need a general scheme to avoid state.SetGlobalString(mem, 'OLDPWD', pwd.s) try: os.chdir(dest_dir) except OSError as e: # TODO: Add line number, etc. util.error("cd %r: %s", dest_dir, os.strerror(e.errno)) return 1 state.SetGlobalString(mem, 'PWD', dest_dir) # Set $PWD. dir_stack.Reset() # for pushd/popd/dirs return 0
def Cd(argv, mem): # TODO: Parse flags, error checking, etc. try: dest_dir = argv[0] 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.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') # Save OLDPWD. state.SetGlobalString(mem, 'OLDPWD', os.getcwd()) try: os.chdir(dest_dir) except OSError as e: # TODO: Add line number, etc. util.error("cd %r: %s", dest_dir, os.strerror(e.errno)) return 1 state.SetGlobalString(mem, 'PWD', dest_dir) return 0
def GetOpts(argv, mem): """ Vars to set: OPTIND - initialized to 1 at startup OPTARG - argument Vars used: OPTERR: disable printing of error messages """ try: # NOTE: If first char is a colon, error reporting is different. Alpine # might not use that? spec_str = argv[0] var_name = argv[1] except IndexError: raise args.UsageError('getopts optstring name [arg]') try: spec = _GETOPTS_CACHE[spec_str] except KeyError: spec = _ParseOptSpec(spec_str) _GETOPTS_CACHE[spec_str] = spec # These errors are fatal errors, not like the builtin exiting with code 1. # Because the invariants of the shell have been violated! v = mem.GetVar('OPTIND') if v.tag != value_e.Str: e_die('OPTIND should be a string, got %r', v) try: optind = int(v.s) except ValueError: e_die("OPTIND doesn't look like an integer, got %r", v.s) status, opt_char, optarg, optind = _GetOpts(spec, mem, optind) state.SetGlobalString(mem, var_name, opt_char) state.SetGlobalString(mem, 'OPTARG', optarg) state.SetGlobalString(mem, 'OPTIND', str(optind)) return status
def _PopDirStack(mem, dir_stack, errfmt): # type: (Mem, DirStack, ErrorFormatter) -> bool """Helper for popd and cd { ... }.""" dest_dir = dir_stack.Pop() if dest_dir is None: errfmt.Print_('popd: directory stack is empty') return False err_num = pyos.Chdir(dest_dir) if err_num != 0: # Happens if a directory is deleted in pushing and popping errfmt.Print_("popd: %r: %s" % (dest_dir, posix.strerror(err_num))) return False state.SetGlobalString(mem, 'PWD', dest_dir) mem.SetPwd(dest_dir) return True
def Matches(self, words, index, prefix): # TODO: # - Set COMP_CWORD etc. in ex.mem -- in the global namespace I guess # - Then parse the reply here # This is like a stack code: # for word in words: # self.ex.PushString(word) # self.ex.PushString('COMP_WORDS') # self.ex.MakeArray() # self.ex.PushString(str(index)) # self.ex.PushString('COMP_CWORD') # TODO: Get the name instead! # self.ex.PushString(self.func_name) # self.ex.Call() # call wit no arguments # self.ex.PushString('COMP_REPLY') # How does this one work? # reply = [] # self.ex.GetArray(reply) state.SetGlobalArray(self.ex.mem, 'COMP_WORDS', words) state.SetGlobalString(self.ex.mem, 'COMP_CWORD', str(index)) self.ex.RunFunc(self.func, []) # call with no arguments # Should be COMP_REPLY to follow naming convention! Lame. val = state.GetGlobal(self.ex.mem, 'COMPREPLY') if val.tag == value_e.Undef: log('COMPREPLY not defined') return if val.tag != value_e.StrArray: log('ERROR: COMPREPLY should be an array, got %s', val) return reply = val.strs print('REPLY', reply) #reply = ['g1', 'g2', 'h1', 'i1'] for name in sorted(reply): if name.startswith(prefix): yield name + ' ' # full word
def _PopDirStack(mem, dir_stack, errfmt): # type: (Mem, DirStack, ErrorFormatter) -> bool """Helper for popd and cd { ... }.""" dest_dir = dir_stack.Pop() if dest_dir is None: errfmt.Print('popd: directory stack is empty') return False 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 False state.SetGlobalString(mem, 'PWD', dest_dir) mem.SetPwd(dest_dir) return True
def Matches(self, comp): # type: (Api) -> List[str] # Have to clear the response every time. TODO: Reuse the object? state.SetGlobalArray(self.cmd_ev.mem, 'COMPREPLY', []) # New completions should use COMP_ARGV, a construct specific to OSH> state.SetGlobalArray(self.cmd_ev.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.cmd_ev.mem, 'COMP_WORDS', comp_words) state.SetGlobalString(self.cmd_ev.mem, 'COMP_CWORD', str(comp_cword)) state.SetGlobalString(self.cmd_ev.mem, 'COMP_LINE', comp.line) state.SetGlobalString(self.cmd_ev.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.cmd_ev.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.cmd_ev.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. stderr_line('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 main(argv): # type: (List[str]) -> int arena = alloc.Arena() dollar0 = argv[0] debug_stack = [] # type: List[state.DebugFrame] mem = state.Mem(dollar0, argv, arena, debug_stack) opt_hook = state.OptHook() parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, opt_hook) # Dummy value; not respecting aliases! aliases = {} # type: Dict[str, str] # parse `` and a[x+1]=bar differently state.SetGlobalString(mem, 'SHELLOPTS', '') oil_grammar = None # type: Grammar if mylib.PYTHON: loader = pyutil.GetResourceLoader() oil_grammar = meta.LoadOilGrammar(loader) parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar) argv = argv[1:] # remove binary name i, flag_a, flag_c, flag_n = Parse(argv) argv = argv[i:] # truncate if flag_c: # This path is easier to run through GDB line_reader = reader.StringLineReader(flag_c, arena) src = source.CFlag() # type: source_t elif len(argv) == 0: line_reader = reader.FileLineReader(mylib.Stdin(), arena) src = source.Stdin('') elif len(argv) == 1: path = argv[0] f = mylib.open(path) line_reader = reader.FileLineReader(f, arena) src = source.MainFile(path) else: raise AssertionError(argv) arena.PushSource(src) c_parser = parse_ctx.MakeOshParser(line_reader) # C++ doesn't have the abbreviations yet (though there are some differences # like omitting spids) #tree = node.AbbreviatedTree() if flag_n: try: node = main_loop.ParseWholeFile(c_parser) except error.Parse as e: ui.PrettyPrintError(e, arena) return 2 assert node is not None if flag_a: tree = node.PrettyTree() ast_f = fmt.DetectConsoleOutput(mylib.Stdout()) fmt.PrintTree(tree, ast_f) ast_f.write('\n') return 0 # New osh_eval.py instantiations errfmt = ui.ErrorFormatter(arena) splitter = split.SplitContext(mem) arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, parse_ctx, errfmt) bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, parse_ctx, errfmt) word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, splitter, errfmt) arith_ev.word_ev = word_ev word_ev.arith_ev = arith_ev procs = {} # type: Dict[str, command__ShFunction] assign_builtins = {} # type: Dict[int, _AssignBuiltin] new_var = builtin_assign.NewVar(mem, procs, errfmt) assign_builtins[builtin_i.declare] = new_var assign_builtins[builtin_i.typeset] = new_var assign_builtins[builtin_i.local] = new_var #assign_builtins = { # # ShAssignment (which are pure) # builtin_i.declare: new_var, # builtin_i.typeset: new_var, # builtin_i.local: new_var, # builtin_i.export_: builtin_assign.Export(mem, errfmt), # builtin_i.readonly: builtin_assign.Readonly(mem, errfmt), #} cmd_deps = cmd_eval.Deps() cmd_deps.mutable_opts = mutable_opts cmd_deps.traps = {} cmd_deps.trap_nodes = [] # TODO: Clear on fork() to avoid duplicates cmd_deps.dumper = dev.CrashDumper('') builtins = {} # type: Dict[int, _Builtin] builtins[builtin_i.echo] = Echo() builtins[builtin_i.shopt] = Shopt(mutable_opts) builtins[builtin_i.set] = Set(mutable_opts) ex = NullExecutor(builtins) trace_f = util.DebugFile(mylib.Stderr()) tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, word_ev, trace_f) cmd_ev = cmd_eval.CommandEvaluator(mem, exec_opts, errfmt, procs, assign_builtins, arena, cmd_deps) # vm.InitCircularDeps cmd_ev.arith_ev = arith_ev cmd_ev.bool_ev = bool_ev cmd_ev.word_ev = word_ev cmd_ev.tracer = tracer cmd_ev.shell_ex = ex bool_ev.word_ev = word_ev status = main_loop.Batch(cmd_ev, c_parser, arena, is_main=True) 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 = test_lib.InitParseContext(arena=arena) mem = state.Mem('', [], arena, []) parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, None) mem.exec_opts = exec_opts mutable_opts.Init() # # 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() cmd_ev = 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.GetValue('PASSED') self.assertEqual(value_e.MaybeStrArray, val.tag, "[case %d] Expected array, got %s" % (i, 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))