def AppBundleMain(argv): b = os.path.basename(argv[0]) main_name, ext = os.path.splitext(b) if main_name in ('opy_', 'opy') and ext: # opy_.py or opy.ovm try: first_arg = argv[1] except IndexError: raise error.Usage('Missing required applet name.') # TODO: We don't have this if first_arg in ('-h', '--help'): #builtin.Help(['bundle-usage'], util.GetResourceLoader()) raise NotImplementedError('OPy help not implemented') sys.exit(0) if first_arg in ('-V', '--version'): _ShowVersion() sys.exit(0) main_name = first_arg argv0 = argv[1] main_argv = argv[2:] else: argv0 = argv[0] main_argv = argv[1:] if main_name == 'opy': status = OpyMain(argv0, main_argv) return status elif main_name == 'opyc': return opy_main.OpyCommandMain(main_argv) else: raise error.Usage('Invalid applet name %r.' % main_name)
def AppBundleMain(argv): # type: (List[str]) -> int login_shell = False b = os_path.basename(argv[0]) main_name, ext = os_path.splitext(b) if main_name.startswith('-'): login_shell = True main_name = main_name[1:] if main_name == 'oil' and ext: # oil.py or oil.ovm try: first_arg = argv[1] except IndexError: raise error.Usage('Missing required applet name.') if first_arg in ('-h', '--help'): builtin_misc.Help(['bundle-usage'], pyutil.GetResourceLoader()) sys.exit(0) if first_arg in ('-V', '--version'): version_str = pyutil.GetVersion() _ShowVersion(version_str) sys.exit(0) main_name = first_arg if main_name.startswith('-'): # TODO: Remove duplication above login_shell = True main_name = main_name[1:] argv0 = argv[1] main_argv = argv[2:] else: argv0 = argv[0] main_argv = argv[1:] if main_name in ('osh', 'sh'): status = ShellMain('osh', argv0, main_argv, login_shell) _tlog('done osh main') return status elif main_name == 'oshc': try: return OshCommandMain(main_argv) except error.Usage as e: ui.Stderr('oshc usage error: %s', e.msg) return 2 elif main_name == 'oil': return ShellMain('oil', argv0, main_argv, login_shell) # For testing latency elif main_name == 'true': return 0 elif main_name == 'false': return 1 elif main_name == 'readlink': return readlink.main(main_argv) else: raise error.Usage('Invalid applet name %r.' % main_name)
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int # NOTE: This builtin doesn't do anything in non-interactive mode in bash? # It silently exits zero. # zsh -c 'history' produces an error. readline_mod = self.readline_mod if not readline_mod: raise error.Usage("OSH wasn't compiled with the readline module.") arg, arg_index = HISTORY_SPEC.ParseCmdVal(cmd_val) # Clear all history if arg.c: readline_mod.clear_history() return 0 # Delete history entry by id number if arg.d: # >= 0: cmd_index = arg.d - 1 try: readline_mod.remove_history_item(cmd_index) except ValueError: raise error.Usage("couldn't find item %d" % arg.d) return 0 # Returns 0 items in non-interactive mode? num_items = readline_mod.get_current_history_length() #log('len = %d', num_items) rest = cmd_val.argv[arg_index:] if len(rest) == 0: start_index = 1 elif len(rest) == 1: arg0 = rest[0] try: num_to_show = int(arg0) except ValueError: raise error.Usage('Invalid argument %r' % arg0) start_index = max(1, num_items + 1 - num_to_show) else: raise error.Usage('Too many arguments') # TODO: # - Exclude lines that don't parse from the history! bash and zsh don't do # that. # - Consolidate multiline commands. for i in xrange(start_index, num_items + 1): # 1-based index item = readline_mod.get_history_item(i) self.f.write('%5d %s\n' % (i, item)) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # NOTE: If first char is a colon, error reporting is different. Alpine # might not use that? spec_str = arg_r.ReadRequired('requires an argspec') var_name, var_spid = arg_r.ReadRequired2( 'requires the name of a variable to set') spec = self.spec_cache.get(spec_str) if spec is None: spec = _ParseOptSpec(spec_str) self.spec_cache[spec_str] = spec user_argv = self.mem.GetArgv() if arg_r.AtEnd() else arg_r.Rest() #util.log('user_argv %s', user_argv) status, flag_char = _GetOpts(spec, user_argv, self.my_state, self.errfmt) if match.IsValidVarName(var_name): state.SetStringDynamic(self.mem, var_name, flag_char) else: # NOTE: The builtin has PARTIALLY set state. This happens in all shells # except mksh. raise error.Usage('got invalid variable name %r' % var_name, span_id=var_spid) return status
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int # There are no flags, but we need it to respect -- arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # skip 'eval' arg = EVAL_SPEC.Parse(arg_r) if self.exec_opts.strict_eval_builtin(): code_str, eval_spid = arg_r.ReadRequired2('requires code string') if not arg_r.AtEnd(): raise error.Usage('requires exactly 1 argument') else: code_str = ' '.join(cmd_val.argv[arg_r.i:]) # code_str could be EMPTY, so just use the first one eval_spid = cmd_val.arg_spids[0] line_reader = reader.StringLineReader(code_str, self.arena) c_parser = self.parse_ctx.MakeOshParser(line_reader) src = source.EvalArg(eval_spid) self.arena.PushSource(src) try: return main_loop.Batch(self.cmd_ev, c_parser, self.arena) finally: self.arena.PopSource()
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # skip 'hash' arg = HASH_SPEC.Parse(arg_r) rest = arg_r.Rest() if arg.r: if rest: raise error.Usage('got extra arguments after -r') self.search_path.ClearCache() return 0 status = 0 if rest: for cmd in rest: # enter in cache full_path = self.search_path.CachedLookup(cmd) if full_path is None: ui.Stderr('hash: %r not found', cmd) status = 1 else: # print cache commands = self.search_path.CachedCommands() commands.sort() for cmd in commands: print(cmd) return status
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int # How does this differ from 'fg'? It doesn't wait and it sets controlling # terminal? raise error.Usage("isn't implemented")
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int argv = cmd_val.argv[1:] if len(argv) == 0: # umask() has a dumb API: you can't get it without modifying it first! # NOTE: dash disables interrupts around the two umask() calls, but that # shouldn't be a concern for us. Signal handlers won't call umask(). mask = posix.umask(0) posix.umask(mask) # print('0%03o' % mask) # octal format return 0 if len(argv) == 1: a = argv[0] try: new_mask = int(a, 8) except ValueError: # NOTE: This happens if we have '8' or '9' in the input too. stderr_line("osh warning: umask with symbolic input isn't implemented") return 1 else: posix.umask(new_mask) return 0 raise error.Usage('umask: unexpected arguments')
def Run(self, cmd_val): arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() arg, _ = GETLINE_SPEC.Parse(arg_r) if arg.cstr: # TODO: implement it # returns error if it can't decode raise NotImplementedError() var_name, var_spid = arg_r.ReadRequired2('requires a variable name') if var_name.startswith(':'): # optional : sigil var_name = var_name[1:] next_arg, next_spid = arg_r.Peek2() if next_arg is not None: raise error.Usage('got extra argument', span_id=next_spid) line = _ReadLine() if len(line) == 0: # EOF return 1 if not arg.end: if line.endswith('\r\n'): line = line[:-2] elif line.endswith('\n'): line = line[:-1] self.mem.SetVar(sh_lhs_expr.Name(var_name), value.Str(line), scope_e.LocalOnly) return 0
def e_usage(msg, *pos_args, **kwargs): # type: (str, *Any, **Any) -> NoReturn """Convenience wrapper for arg parsing / validation errors. Usually causes a builtin to fail with status 2, but the script can continue if 'set +o errexit'. Main programs like bin/oil also use this. """ raise error.Usage(msg, *pos_args, **kwargs)
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int argv = cmd_val.argv[1:] arg, arg_index = ECHO_SPEC.ParseLikeEcho(argv) argv = argv[arg_index:] backslash_c = False # \c terminates input if arg.e: new_argv = [] for a in argv: parts = [] # type: List[str] lex = match.EchoLexer(a) while not backslash_c: id_, value = lex.Next() if id_ == Id.Eol_Tok: # Note: This is really a NUL terminator break p = word_compile.EvalCStringToken(id_, value) # Unusual behavior: '\c' prints what is there and aborts processing! if p is None: backslash_c = True break parts.append(p) new_argv.append(''.join(parts)) if backslash_c: # no more args either break # Replace it argv = new_argv if self.exec_opts.strict_echo(): n = len(argv) if n == 0: pass elif n == 1: sys.stdout.write(argv[0]) else: # TODO: span_id could be more accurate raise error.Usage( "takes at most one arg when strict_echo is on (hint: add quotes)" ) else: #log('echo argv %s', argv) for i, a in enumerate(argv): if i != 0: sys.stdout.write(' ') # arg separator sys.stdout.write(a) if not arg.n and not backslash_c: sys.stdout.write('\n') return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int if len(cmd_val.arg_spids) > 1: raise error.Usage('got extra argument', span_id=cmd_val.arg_spids[1]) if not _PopDirStack(self.mem, self.dir_stack, self.errfmt): return 1 # error _PrintDirStack(self.dir_stack, SINGLE_LINE, self.mem.GetVar('HOME')) return 0
def AppBundleMain(argv): b = os.path.basename(argv[0]) main_name, ext = os.path.splitext(b) if main_name in ('opy_', 'opy') and ext: # opy_.py or opy.ovm try: first_arg = argv[1] except IndexError: raise error.Usage('Missing required applet name.') main_name = first_arg argv0 = argv[1] main_argv = argv[2:] else: argv0 = argv[0] main_argv = argv[1:] if main_name == 'opyc': return opy_main.OpyCommandMain(main_argv) else: raise error.Usage('Invalid applet name %r.' % main_name)
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int argv = cmd_val.argv call_spid = cmd_val.arg_spids[0] try: path = argv[1] except IndexError: raise error.Usage('missing required argument') resolved = self.search_path.Lookup(path, exec_required=False) if resolved is None: resolved = path try: f = self.fd_state.Open(resolved) # Shell can't use descriptors 3-9 except OSError as e: self.errfmt.Print('source %r failed: %s', path, pyutil.strerror_OS(e), span_id=cmd_val.arg_spids[1]) return 1 try: line_reader = reader.FileLineReader(f, self.arena) c_parser = self.parse_ctx.MakeOshParser(line_reader) # A sourced module CAN have a new arguments array, but it always shares # the same variable scope as the caller. The caller could be at either a # global or a local scope. source_argv = argv[2:] self.mem.PushSource(path, source_argv) src = source.SourcedFile(path, call_spid) self.arena.PushSource(src) try: status = main_loop.Batch(self.cmd_ev, c_parser, self.arena) finally: self.arena.PopSource() self.mem.PopSource(source_argv) return status except _ControlFlow as e: if e.IsReturn(): return e.StatusCode() else: raise finally: f.close()
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int argv = cmd_val.argv if len(argv) == 1: raise error.Usage('unalias NAME...') status = 0 for i in xrange(1, len(argv)): name = argv[i] try: del self.aliases[name] except KeyError: self.errfmt.Print('No alias named %r', name, span_id=cmd_val.arg_spids[i]) status = 1 return status
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # NOTE: If first char is a colon, error reporting is different. Alpine # might not use that? spec_str = arg_r.ReadRequired('requires an argspec') var_name, var_spid = arg_r.ReadRequired2( 'requires the name of a variable to set') try: spec = self.spec_cache[spec_str] except KeyError: spec = _ParseOptSpec(spec_str) self.spec_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 = self.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) user_argv = arg_r.Rest() or self.mem.GetArgv() #util.log('user_argv %s', user_argv) status, opt_char, optarg, optind = _GetOpts(spec, user_argv, optind, self.errfmt) # Bug fix: bash-completion uses a *local* OPTIND ! Not global. state.SetStringDynamic(self.mem, 'OPTARG', optarg) state.SetStringDynamic(self.mem, 'OPTIND', str(optind)) if match.IsValidVarName(var_name): state.SetStringDynamic(self.mem, var_name, opt_char) else: # NOTE: The builtin has PARTIALLY filed. This happens in all shells # except mksh. raise error.Usage('got invalid variable name %r' % var_name, span_id=var_spid) return status
def Run(self, cmd_val): arg, arg_r = flag_spec.ParseOilCmdVal('push', cmd_val) var_name, var_spid = arg_r.ReadRequired2('requires a variable name') if var_name.startswith(':'): # optional : sigil var_name = var_name[1:] if not match.IsValidVarName(var_name): raise error.Usage('got invalid variable name %r' % var_name, span_id=var_spid) val = self.mem.GetValue(var_name) # TODO: value.Obj too if val.tag != value_e.MaybeStrArray: self.errfmt.Print("%r isn't an array", var_name, span_id=var_spid) return 1 val.strs.extend(arg_r.Rest()) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # NOTE: If first char is a colon, error reporting is different. Alpine # might not use that? spec_str = arg_r.ReadRequired('requires an argspec') var_name, var_spid = arg_r.ReadRequired2( 'requires the name of a variable to set') try: spec = self.spec_cache[spec_str] except KeyError: spec = _ParseOptSpec(spec_str) self.spec_cache[spec_str] = spec # TODO: OPTIND could be value.Int? try: optind = state.GetInteger(self.mem, 'OPTIND') except error.Runtime as e: self.errfmt.Print_(e.UserErrorString()) return 1 user_argv = self.mem.GetArgv() if arg_r.AtEnd() else arg_r.Rest() #util.log('user_argv %s', user_argv) status, opt_char, optarg, optind = _GetOpts(spec, user_argv, optind, self.errfmt) # Bug fix: bash-completion uses a *local* OPTIND ! Not global. state.SetStringDynamic(self.mem, 'OPTARG', optarg) state.SetStringDynamic(self.mem, 'OPTIND', str(optind)) if match.IsValidVarName(var_name): state.SetStringDynamic(self.mem, var_name, opt_char) else: # NOTE: The builtin has PARTIALLY filed. This happens in all shells # except mksh. raise error.Usage('got invalid variable name %r' % var_name, span_id=var_spid) return status
def Run(self, cmd_val): status = 0 for i in xrange(1, len(cmd_val.argv)): name = cmd_val.argv[i] if name.startswith(':'): name = name[1:] if not match.IsValidVarName(name): raise error.Usage('got invalid variable name %r' % name, span_id=cmd_val.arg_spids[i]) cell = self.mem.GetCell(name) if cell is None: self.errfmt.Print("Couldn't find a variable named %r" % name, span_id=cmd_val.arg_spids[i]) status = 1 else: sys.stdout.write('%s = ' % name) cell.PrettyPrint() # may be color sys.stdout.write('\n') return status
def ShellMain(lang, argv0, argv, login_shell): # type: (str, str, List[str], bool) -> int """Used by bin/osh and bin/oil. Args: lang: 'osh' or 'oil' argv0, argv: So we can also invoke bin/osh as 'oil.ovm osh'. Like busybox. login_shell: Was - on the front? """ # Differences between osh and oil: # - --help? I guess Oil has a SUPERSET of OSH options. # - oshrc vs oilrc # - the parser and executor # - Change the prompt in the interactive shell? assert lang in ('osh', 'oil'), lang arg_r = args.Reader(argv) try: opts = OSH_SPEC.Parse(arg_r) except error.Usage as e: ui.Stderr('osh usage error: %s', e.msg) return 2 # NOTE: This has a side effect of deleting _OVM_* from the environment! # TODO: Thread this throughout the program, and get rid of the global # variable in core/util.py. Rename to InitResourceLaoder(). It's now only # used for the 'help' builtin and --version. loader = pyutil.GetResourceLoader() if opts.help: builtin_misc.Help(['%s-usage' % lang], loader) return 0 version_str = pyutil.GetVersion() if opts.version: # OSH version is the only binary in Oil right now, so it's all one version. _ShowVersion(version_str) return 0 no_str = None # type: str debug_stack = [] if arg_r.AtEnd(): dollar0 = argv0 else: dollar0 = arg_r.Peek() # the script name, or the arg after -c # Copy quirky bash behavior. frame0 = state.DebugFrame(dollar0, 'main', no_str, state.LINE_ZERO, 0, 0) debug_stack.append(frame0) # Copy quirky bash behavior. frame1 = state.DebugFrame(no_str, no_str, no_str, runtime.NO_SPID, 0, 0) debug_stack.append(frame1) arena = alloc.Arena() errfmt = ui.ErrorFormatter(arena) mem = state.Mem(dollar0, argv[arg_r.i + 1:], arena, debug_stack) state.InitMem(mem, posix.environ, version_str) builtin_funcs.Init(mem) procs = {} # type: Dict[str, command__ShFunction] job_state = process.JobState() fd_state = process.FdState(errfmt, job_state, mem) opt_hook = ShellOptHook(line_input) parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, opt_hook) # TODO: only MutableOpts needs mem, so it's not a true circular dep. mem.exec_opts = exec_opts # circular dep if opts.show_options: # special case: sh -o mutable_opts.ShowOptions([]) return 0 # Set these BEFORE processing flags, so they can be overridden. if lang == 'oil': mutable_opts.SetShoptOption('oil:all', True) builtin_pure.SetShellOpts(mutable_opts, opts.opt_changes, opts.shopt_changes) aliases = {} # feedback between runtime and parser oil_grammar = meta.LoadOilGrammar(loader) if opts.one_pass_parse and not exec_opts.noexec(): raise error.Usage('--one-pass-parse requires noexec (-n)') parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar) parse_ctx.Init_OnePassParse(opts.one_pass_parse) # Three ParseContext instances SHARE aliases. comp_arena = alloc.Arena() comp_arena.PushSource(source.Unused('completion')) trail1 = parse_lib.Trail() # one_pass_parse needs to be turned on to complete inside backticks. TODO: # fix the issue where ` gets erased because it's not part of # set_completer_delims(). comp_ctx = parse_lib.ParseContext(comp_arena, parse_opts, aliases, oil_grammar) comp_ctx.Init_Trail(trail1) comp_ctx.Init_OnePassParse(True) hist_arena = alloc.Arena() hist_arena.PushSource(source.Unused('history')) trail2 = parse_lib.Trail() hist_ctx = parse_lib.ParseContext(hist_arena, parse_opts, aliases, oil_grammar) hist_ctx.Init_Trail(trail2) # Deps helps manages dependencies. These dependencies are circular: # - cmd_ev and word_ev, arith_ev -- for command sub, arith sub # - arith_ev and word_ev -- for $(( ${a} )) and $x$(( 1 )) # - cmd_ev and builtins (which execute code, like eval) # - prompt_ev needs word_ev for $PS1, which needs prompt_ev for @P cmd_deps = cmd_eval.Deps() cmd_deps.mutable_opts = mutable_opts # TODO: In general, cmd_deps are shared between the mutually recursive # evaluators. Some of the four below are only shared between a builtin and # the CommandEvaluator, so we could put them somewhere else. cmd_deps.traps = {} cmd_deps.trap_nodes = [] # TODO: Clear on fork() to avoid duplicates waiter = process.Waiter(job_state, exec_opts) my_pid = posix.getpid() debug_path = '' debug_dir = posix.environ.get('OSH_DEBUG_DIR') if opts.debug_file: # --debug-file takes precedence over OSH_DEBUG_DIR debug_path = opts.debug_file elif debug_dir: debug_path = os_path.join(debug_dir, '%d-osh.log' % my_pid) if debug_path: # This will be created as an empty file if it doesn't exist, or it could be # a pipe. try: debug_f = util.DebugFile(fd_state.Open(debug_path, mode='w')) except OSError as e: ui.Stderr("osh: Couldn't open %r: %s", debug_path, posix.strerror(e.errno)) return 2 else: debug_f = util.NullDebugFile() cmd_deps.debug_f = debug_f # Not using datetime for dependency reasons. TODO: maybe show the date at # the beginning of the log, and then only show time afterward? To save # space, and make space for microseconds. (datetime supports microseconds # but time.strftime doesn't). iso_stamp = time.strftime("%Y-%m-%d %H:%M:%S") debug_f.log('%s [%d] OSH started with argv %s', iso_stamp, my_pid, argv) if debug_path: debug_f.log('Writing logs to %r', debug_path) interp = posix.environ.get('OSH_HIJACK_SHEBANG', '') search_path = state.SearchPath(mem) ext_prog = process.ExternalProgram(interp, fd_state, errfmt, debug_f) splitter = split.SplitContext(mem) # split() builtin # TODO: Accept IFS as a named arg? split('a b', IFS=' ') builtin_funcs.SetGlobalFunc( mem, 'split', lambda s, ifs=None: splitter.SplitForWordEval(s, ifs=ifs)) # glob() builtin # TODO: This is instantiation is duplicated in osh/word_eval.py globber = glob_.Globber(exec_opts) builtin_funcs.SetGlobalFunc(mem, 'glob', lambda s: globber.OilFuncCall(s)) # This could just be OSH_DEBUG_STREAMS='debug crash' ? That might be # stuffing too much into one, since a .json crash dump isn't a stream. crash_dump_dir = posix.environ.get('OSH_CRASH_DUMP_DIR', '') cmd_deps.dumper = dev.CrashDumper(crash_dump_dir) if opts.xtrace_to_debug_file: trace_f = debug_f else: trace_f = util.DebugFile(sys.stderr) comp_lookup = completion.Lookup() # Various Global State objects to work around readline interfaces compopt_state = completion.OptionState() comp_ui_state = comp_ui.State() prompt_state = comp_ui.PromptState() dir_stack = state.DirStack() new_var = builtin_assign.NewVar(mem, procs, errfmt) 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), } true_ = builtin_pure.Boolean(0) builtins = { builtin_i.echo: builtin_pure.Echo(exec_opts), builtin_i.printf: builtin_printf.Printf(mem, parse_ctx, errfmt), builtin_i.pushd: builtin_misc.Pushd(mem, dir_stack, errfmt), builtin_i.popd: builtin_misc.Popd(mem, dir_stack, errfmt), builtin_i.dirs: builtin_misc.Dirs(mem, dir_stack, errfmt), builtin_i.pwd: builtin_misc.Pwd(mem, errfmt), builtin_i.times: builtin_misc.Times(), builtin_i.read: builtin_misc.Read(splitter, mem), builtin_i.help: builtin_misc.Help(loader, errfmt), builtin_i.history: builtin_misc.History(line_input), builtin_i.cat: builtin_misc.Cat(), # for $(<file) # Completion (more added below) builtin_i.compopt: builtin_comp.CompOpt(compopt_state, errfmt), builtin_i.compadjust: builtin_comp.CompAdjust(mem), # interactive builtin_i.bind: builtin_lib.Bind(line_input, errfmt), # test / [ differ by need_right_bracket builtin_i.test: builtin_bracket.Test(False, exec_opts, mem, errfmt), builtin_i.bracket: builtin_bracket.Test(True, exec_opts, mem, errfmt), builtin_i.shift: builtin_assign.Shift(mem), # Pure builtin_i.set: builtin_pure.Set(mutable_opts, mem), builtin_i.shopt: builtin_pure.Shopt(mutable_opts), builtin_i.alias: builtin_pure.Alias(aliases, errfmt), builtin_i.unalias: builtin_pure.UnAlias(aliases, errfmt), builtin_i.type: builtin_pure.Type(procs, aliases, search_path), builtin_i.hash: builtin_pure.Hash(search_path), builtin_i.getopts: builtin_pure.GetOpts(mem, errfmt), builtin_i.colon: true_, # a "special" builtin builtin_i.true_: true_, builtin_i.false_: builtin_pure.Boolean(1), # Process builtin_i.exec_: builtin_process.Exec(mem, ext_prog, fd_state, search_path, errfmt), builtin_i.wait: builtin_process.Wait(waiter, job_state, mem, errfmt), builtin_i.jobs: builtin_process.Jobs(job_state), builtin_i.fg: builtin_process.Fg(job_state, waiter), builtin_i.bg: builtin_process.Bg(job_state), builtin_i.umask: builtin_process.Umask(), # Oil builtin_i.push: builtin_oil.Push(mem, errfmt), builtin_i.append: builtin_oil.Append(mem, errfmt), builtin_i.write: builtin_oil.Write(mem, errfmt), builtin_i.getline: builtin_oil.Getline(mem, errfmt), builtin_i.repr: builtin_oil.Repr(mem, errfmt), builtin_i.use: builtin_oil.Use(mem, errfmt), builtin_i.opts: builtin_oil.Opts(mem, errfmt), } arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, parse_ctx, errfmt) bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, parse_ctx, errfmt) expr_ev = expr_eval.OilEvaluator(mem, procs, errfmt) word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, splitter, errfmt) cmd_ev = cmd_eval.CommandEvaluator(mem, exec_opts, errfmt, procs, assign_builtins, arena, cmd_deps) shell_ex = executor.ShellExecutor(mem, exec_opts, mutable_opts, procs, builtins, search_path, ext_prog, waiter, job_state, fd_state, errfmt) # PromptEvaluator rendering is needed in non-interactive shells for @P. prompt_ev = prompt.Evaluator(lang, parse_ctx, mem) tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, word_ev, trace_f) # Wire up circular dependencies. vm.InitCircularDeps(arith_ev, bool_ev, expr_ev, word_ev, cmd_ev, shell_ex, prompt_ev, tracer) # # Add builtins that depend on various evaluators # builtins[builtin_i.unset] = builtin_assign.Unset(mem, exec_opts, procs, parse_ctx, arith_ev, errfmt) builtins[builtin_i.eval] = builtin_meta.Eval(parse_ctx, exec_opts, cmd_ev) source_builtin = builtin_meta.Source(parse_ctx, search_path, cmd_ev, fd_state, errfmt) builtins[builtin_i.source] = source_builtin builtins[builtin_i.dot] = source_builtin builtins[builtin_i.builtin] = builtin_meta.Builtin(shell_ex, errfmt) builtins[builtin_i.command] = builtin_meta.Command(shell_ex, procs, aliases, search_path) spec_builder = builtin_comp.SpecBuilder(cmd_ev, parse_ctx, word_ev, splitter, comp_lookup) complete_builtin = builtin_comp.Complete(spec_builder, comp_lookup) builtins[builtin_i.complete] = complete_builtin builtins[builtin_i.compgen] = builtin_comp.CompGen(spec_builder) # These builtins take blocks, and thus need cmd_ev. builtins[builtin_i.cd] = builtin_misc.Cd(mem, dir_stack, cmd_ev, errfmt) builtins[builtin_i.json] = builtin_oil.Json(mem, cmd_ev, errfmt) sig_state = process.SignalState() sig_state.InitShell() builtins[builtin_i.trap] = builtin_process.Trap(sig_state, cmd_deps.traps, cmd_deps.trap_nodes, parse_ctx, errfmt) # History evaluation is a no-op if line_input is None. hist_ev = history.Evaluator(line_input, hist_ctx, debug_f) if opts.c is not None: arena.PushSource(source.CFlag()) line_reader = reader.StringLineReader(opts.c, arena) if opts.i: # -c and -i can be combined mutable_opts.set_interactive() elif opts.i: # force interactive arena.PushSource(source.Stdin(' -i')) line_reader = py_reader.InteractiveLineReader(arena, prompt_ev, hist_ev, line_input, prompt_state) mutable_opts.set_interactive() else: script_name = arg_r.Peek() if script_name is None: if sys.stdin.isatty(): arena.PushSource(source.Interactive()) line_reader = py_reader.InteractiveLineReader( arena, prompt_ev, hist_ev, line_input, prompt_state) mutable_opts.set_interactive() else: arena.PushSource(source.Stdin('')) line_reader = reader.FileLineReader(sys.stdin, arena) else: arena.PushSource(source.MainFile(script_name)) try: f = fd_state.Open(script_name) except OSError as e: ui.Stderr("osh: Couldn't open %r: %s", script_name, posix.strerror(e.errno)) return 1 line_reader = reader.FileLineReader(f, arena) # TODO: assert arena.NumSourcePaths() == 1 # TODO: .rc file needs its own arena. c_parser = parse_ctx.MakeOshParser(line_reader) if exec_opts.interactive(): # bash: 'set -o emacs' is the default only in the interactive shell mutable_opts.set_emacs() # Calculate ~/.config/oil/oshrc or oilrc # Use ~/.config/oil to avoid cluttering the user's home directory. Some # users may want to ln -s ~/.config/oil/oshrc ~/oshrc or ~/.oshrc. # https://unix.stackexchange.com/questions/24347/why-do-some-applications-use-config-appname-for-their-config-data-while-other home_dir = passwd.GetMyHomeDir() assert home_dir is not None rc_path = opts.rcfile or os_path.join(home_dir, '.config/oil', lang + 'rc') history_filename = os_path.join(home_dir, '.config/oil', 'history_' + lang) if line_input: # NOTE: We're using a different WordEvaluator here. ev = word_eval.CompletionWordEvaluator(mem, exec_opts, splitter, errfmt) ev.arith_ev = arith_ev ev.expr_ev = expr_ev ev.prompt_ev = prompt_ev ev.CheckCircularDeps() root_comp = completion.RootCompleter(ev, mem, comp_lookup, compopt_state, comp_ui_state, comp_ctx, debug_f) term_width = 0 if opts.completion_display == 'nice': try: term_width = libc.get_terminal_width() except IOError: # stdin not a terminal pass if term_width != 0: display = comp_ui.NiceDisplay(term_width, comp_ui_state, prompt_state, debug_f, line_input) else: display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state, debug_f) _InitReadline(line_input, history_filename, root_comp, display, debug_f) _InitDefaultCompletions(cmd_ev, complete_builtin, comp_lookup) else: # Without readline module display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state, debug_f) sig_state.InitInteractiveShell(display) # NOTE: Call this AFTER _InitDefaultCompletions. try: SourceStartupFile(rc_path, lang, parse_ctx, cmd_ev) except util.UserExit as e: return e.status line_reader.Reset() # After sourcing startup file, render $PS1 prompt_plugin = prompt.UserPlugin(mem, parse_ctx, cmd_ev) try: status = main_loop.Interactive(opts, cmd_ev, c_parser, display, prompt_plugin, errfmt) if cmd_ev.MaybeRunExitTrap(): status = cmd_ev.LastStatus() except util.UserExit as e: status = e.status return status if exec_opts.noexec(): status = 0 try: node = main_loop.ParseWholeFile(c_parser) except error.Parse as e: ui.PrettyPrintError(e, arena) status = 2 if status == 0: if opts.parser_mem_dump: # only valid in -n mode # This might be superstition, but we want to let the value stabilize # after parsing. bash -c 'cat /proc/$$/status' gives different results # with a sleep. time.sleep(0.001) input_path = '/proc/%d/status' % posix.getpid() with open(input_path) as f, open(opts.parser_mem_dump, 'w') as f2: contents = f.read() f2.write(contents) log('Wrote %s to %s (--parser-mem-dump)', input_path, opts.parser_mem_dump) ui.PrintAst(node, opts) else: if opts.parser_mem_dump: raise error.Usage('--parser-mem-dump can only be used with -n') _tlog('Execute(node)') try: status = main_loop.Batch(cmd_ev, c_parser, arena, is_main=True) if cmd_ev.MaybeRunExitTrap(): status = cmd_ev.LastStatus() except util.UserExit as e: status = e.status # NOTE: 'exit 1' is ControlFlow and gets here, but subshell/commandsub # don't because they call sys.exit(). if opts.runtime_mem_dump: # This might be superstition, but we want to let the value stabilize # after parsing. bash -c 'cat /proc/$$/status' gives different results # with a sleep. time.sleep(0.001) input_path = '/proc/%d/status' % posix.getpid() with open(input_path) as f, open(opts.runtime_mem_dump, 'w') as f2: contents = f.read() f2.write(contents) log('Wrote %s to %s (--runtime-mem-dump)', input_path, opts.runtime_mem_dump) # NOTE: We haven't closed the file opened with fd_state.Open return status
def Run(self, cmd_val): arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # skip 'json' action, action_spid = arg_r.Peek2() if action is None: raise error.Usage(_JSON_ACTION_ERROR) arg_r.Next() if action == 'write': arg, _ = JSON_WRITE_SPEC.Parse(arg_r) # GetVar() of each name and print it. for var_name in arg_r.Rest(): if var_name.startswith(':'): var_name = var_name[1:] val = self.mem.GetVar(var_name) with tagswitch(val) as case: if case(value_e.Undef): # TODO: blame the right span_id self.errfmt.Print("no variable named %r is defined", var_name) return 1 elif case(value_e.Str): obj = val.s elif case(value_e.MaybeStrArray): obj = val.strs elif case(value_e.AssocArray): obj = val.d elif case(value_e.Obj): obj = val.obj else: raise AssertionError(val) if arg.pretty: indent = arg.indent extra_newline = False else: # How yajl works: if indent is -1, then everything is on one line. indent = -1 extra_newline = True j = yajl.dump(obj, sys.stdout, indent=indent) if extra_newline: sys.stdout.write('\n') # TODO: Accept a block. They aren't hooked up yet. if cmd_val.block: # TODO: flatten value.{Str,Obj} into a flat dict? namespace = self.cmd_ev.EvalBlock(cmd_val.block) print(yajl.dump(namespace)) elif action == 'read': arg, _ = JSON_READ_SPEC.Parse(arg_r) # TODO: # Respect -validate=F var_name, name_spid = arg_r.ReadRequired2("expected variable name") if var_name.startswith(':'): var_name = var_name[1:] if not match.IsValidVarName(var_name): raise error.Usage('got invalid variable name %r' % var_name, span_id=name_spid) try: # Use a global _STDIN, because we get EBADF on a redirect if we use a # local. A Py_DECREF closes the file, which we don't want, because the # redirect is responsible for freeing it. # # https://github.com/oilshell/oil/issues/675 # # TODO: write a better binding like yajl.readfd() # # It should use streaming like here: # https://lloyd.github.io/yajl/ obj = yajl.load(_STDIN) except ValueError as e: self.errfmt.Print('json read: %s', e, span_id=action_spid) return 1 self.mem.SetVar(sh_lhs_expr.Name(var_name), value.Obj(obj), scope_e.LocalOnly) else: raise error.Usage(_JSON_ACTION_ERROR, span_id=action_spid) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('wait', cmd_val) arg = arg_types.wait(attrs.attrs) job_ids, arg_spids = arg_r.Rest2() if arg.n: # wait -n returns the exit status of the JOB. # You don't know WHICH process, which is odd. # TODO: this should wait for the next JOB, which may be multiple # processes. # Bash has a wait_for_any_job() function, which loops until the jobs # table changes. # # target_count = self.job_state.NumRunning() - 1 # while True: # if not self.waiter.WaitForOne(): # break # # if self.job_state.NumRunning == target_count: # break # #log('wait next') if self.waiter.WaitForOne(): return self.waiter.last_status else: return 127 # nothing to wait for if len(job_ids) == 0: #log('wait all') i = 0 while True: # BUG: If there is a STOPPED process, this will hang forever, because # we don't get ECHILD. # Not sure it matters since you can now Ctrl-C it. if not self.waiter.WaitForOne(): break # nothing to wait for i += 1 if self.job_state.NoneAreRunning(): 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 FINISH. status = 1 # error for i, job_id in enumerate(job_ids): span_id = arg_spids[i] # The % syntax is sort of like ! history sub syntax, with various queries. # https://stackoverflow.com/questions/35026395/bash-what-is-a-jobspec if job_id.startswith('%'): raise error.Usage( "doesn't support bash-style jobspecs (got %r)" % job_id, span_id=span_id) # Does it look like a PID? try: pid = int(job_id) except ValueError: raise error.Usage('expected PID or jobspec, got %r' % job_id, span_id=span_id) job = self.job_state.JobFromPid(pid) if job is None: self.errfmt.Print("%s isn't a child of this shell", pid, span_id=span_id) return 127 # TODO: Does this wait for pipelines? job_status = job.JobWait(self.waiter) UP_job_status = job_status with tagswitch(job_status) as case: if case(job_status_e.Proc): job_status = cast(job_status__Proc, UP_job_status) status = job_status.code elif case(job_status_e.Pipeline): # TODO: handle PIPESTATUS? job_status = cast(job_status__Pipeline, UP_job_status) # Is this right? status = job_status.codes[-1] else: raise AssertionError return status
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('read', cmd_val) arg = arg_types.read(attrs.attrs) names = arg_r.Rest() # Don't respect any of the other options here? This is buffered I/O. if arg.line: # read --line var_name, var_spid = arg_r.Peek2() if var_name is None: var_name = '_line' else: if var_name.startswith(':'): # optional : sigil var_name = var_name[1:] arg_r.Next() next_arg, next_spid = arg_r.Peek2() if next_arg is not None: raise error.Usage('got extra argument', span_id=next_spid) return self._Line(arg, var_name) if arg.q: e_usage('--qsn can only be used with --line') if arg.all: # read --all var_name, var_spid = arg_r.Peek2() if var_name is None: var_name = '_all' else: if var_name.startswith(':'): # optional : sigil var_name = var_name[1:] arg_r.Next() next_arg, next_spid = arg_r.Peek2() if next_arg is not None: raise error.Usage('got extra argument', span_id=next_spid) return self._All(var_name) if arg.q: e_usage('--qsn not implemented yet') fd = self.stdin.fileno() if arg.t >= 0.0: if arg.t != 0.0: e_die("read -t isn't implemented (except t=0)") else: return 0 if pyos.InputAvailable(fd) else 1 bits = 0 if self.stdin.isatty(): bits |= pyos.TERM_ICANON if arg.s: # silent bits |= pyos.TERM_ECHO if arg.p is not None: # only if tty mylib.Stderr().write(arg.p) if bits == 0: status = self._Read(arg, names) else: term = pyos.TermState(fd, ~bits) try: status = self._Read(arg, names) finally: term.Restore() return status
def Run(self, cmd_val): argv = cmd_val.argv[1:] arg_r = args.Reader(argv) arg = COMPADJUST_SPEC.Parse(arg_r) var_names = arg_r.Rest() # Output variables to set for name in var_names: # Ironically we could complete these if name not in ['cur', 'prev', 'words', 'cword']: raise error.Usage('Invalid output variable name %r' % name) #print(arg) # TODO: How does the user test a completion function programmatically? Set # COMP_ARGV? val = self.mem.GetVar('COMP_ARGV') if val.tag != value_e.MaybeStrArray: raise error.Usage("COMP_ARGV should be an array") comp_argv = val.strs # These are the ones from COMP_WORDBREAKS that we care about. The rest occur # "outside" of words. break_chars = [':', '='] if arg.s: # implied break_chars.remove('=') # NOTE: The syntax is -n := and not -n : -n =. omit_chars = arg.n or '' for c in omit_chars: if c in break_chars: break_chars.remove(c) # argv adjusted according to 'break_chars'. adjusted_argv = [] for a in comp_argv: completion.AdjustArg(a, break_chars, adjusted_argv) if 'words' in var_names: state.SetArrayDynamic(self.mem, 'words', adjusted_argv) n = len(adjusted_argv) cur = adjusted_argv[-1] prev = '' if n < 2 else adjusted_argv[-2] if arg.s: if cur.startswith( '--') and '=' in cur: # Split into flag name and value prev, cur = cur.split('=', 1) split = 'true' else: split = 'false' # Do NOT set 'split' without -s. Caller might not have declared it. # Also does not respect var_names, because we don't need it. state.SetStringDynamic(self.mem, 'split', split) if 'cur' in var_names: state.SetStringDynamic(self.mem, 'cur', cur) if 'prev' in var_names: state.SetStringDynamic(self.mem, 'prev', prev) if 'cword' in var_names: # Same weird invariant after adjustment state.SetStringDynamic(self.mem, 'cword', str(n - 1)) return 0
def OpyCommandMain(argv): """Dispatch to the right action.""" # TODO: Use core/arg_def. #opts, argv = Options().parse_args(argv) try: action = argv[0] except IndexError: raise error.Usage('opy: Missing required subcommand.') argv = argv[1:] # TODO: Should I do input.ReadRequiredArg()? # That will shift the input. if action in ( 'parse', 'parse-with', 'compile', 'dis', 'ast', 'symbols', 'cfg', 'compile-ovm', 'eval', 'repl', 'run', 'run-ovm'): loader = pyutil.GetResourceLoader() f = loader.open(GRAMMAR_REL_PATH) contents = f.read() f.close() gr = grammar.Grammar() gr.loads(contents) # In Python 2 code, always use from __future__ import print_function. try: del gr.keywords["print"] except KeyError: pass symbols = Symbols(gr) pytree.Init(symbols) # for type_repr() pretty printing transformer.Init(symbols) # for _names and other dicts compiler = skeleton.Compiler(gr) else: # e.g. pgen2 doesn't use any of these. Maybe we should make a different # tool. compiler = None # TODO: Also have a run_spec for 'opyc run'. compile_spec = arg_def.OilFlags('opy') compile_spec.Flag('-emit-docstring', args.Bool, default=True, help='Whether to emit docstrings') compile_spec.Flag('-fast-ops', args.Bool, default=True, help='Whether to emit LOAD_FAST, STORE_FAST, etc.') compile_spec.Flag('-oil-subset', args.Bool, default=False, help='Only allow the constructs necessary to implement' 'Oil. Example: using multiple inheritance will abort ' 'compilation.') # # Actions # if action == 'pgen2': grammar_path = argv[0] marshal_path = argv[1] WriteGrammar(grammar_path, marshal_path) elif action == 'stdlib-parse': # This is what the compiler/ package was written against. import parser py_path = argv[1] with open(py_path) as f: st = parser.suite(f.read()) tree = st.totuple() printer = TupleTreePrinter(HostStdlibNames()) printer.Print(tree) n = CountTupleTree(tree) log('COUNT %d', n) elif action == 'lex': py_path = argv[0] with open(py_path) as f: tokens = tokenize.generate_tokens(f.readline) for typ, val, start, end, unused_line in tokens: print('%10s %10s %-10s %r' % (start, end, token.tok_name[typ], val)) elif action == 'lex-names': # Print all the NAME tokens. for py_path in argv: log('Lexing %s', py_path) with open(py_path) as f: tokens = tokenize.generate_tokens(f.readline) for typ, val, start, end, unused_line in tokens: if typ == token.NAME: print(val) elif action == 'parse': py_path = argv[0] with open(py_path) as f: tokens = tokenize.generate_tokens(f.readline) p = parse.Parser(gr) pnode = driver.PushTokens(p, tokens, gr, 'file_input') printer = ParseTreePrinter(transformer._names) # print raw nodes printer.Print(pnode) # Parse with an arbitrary grammar, but the Python lexer. elif action == 'parse-with': grammar_path = argv[0] start_symbol = argv[1] code_str = argv[2] with open(grammar_path) as f: gr = pgen.MakeGrammar(f) f = cStringIO.StringIO(code_str) tokens = tokenize.generate_tokens(f.readline) p = parse.Parser(gr) # no convert= try: pnode = driver.PushTokens(p, tokens, gr, start_symbol) except parse.ParseError as e: # Extract location information and show it. _, _, (lineno, offset) = e.opaque # extra line needed for '\n' ? lines = code_str.splitlines() + [''] line = lines[lineno-1] log(' %s', line) log(' %s^', ' '*offset) log('Parse Error: %s', e) return 1 printer = ParseTreePrinter(transformer._names) # print raw nodes printer.Print(pnode) elif action == 'ast': # output AST opt, i = compile_spec.ParseArgv(argv) py_path = argv[i] with open(py_path) as f: graph = compiler.Compile(f, opt, 'exec', print_action='ast') elif action == 'symbols': # output symbols opt, i = compile_spec.ParseArgv(argv) py_path = argv[i] with open(py_path) as f: graph = compiler.Compile(f, opt, 'exec', print_action='symbols') elif action == 'cfg': # output Control Flow Graph opt, i = compile_spec.ParseArgv(argv) py_path = argv[i] with open(py_path) as f: graph = compiler.Compile(f, opt, 'exec', print_action='cfg') elif action == 'compile': # 'opyc compile' is pgen2 + compiler2 # spec.Arg('action', ['foo', 'bar']) # But that leads to some duplication. opt, i = compile_spec.ParseArgv(argv) py_path = argv[i] out_path = argv[i+1] with open(py_path) as f: co = compiler.Compile(f, opt, 'exec') log("Compiled to %d bytes of top-level bytecode", len(co.co_code)) # Write the .pyc file with open(out_path, 'wb') as out_f: h = misc.getPycHeader(py_path) out_f.write(h) marshal.dump(co, out_f) elif action == 'compile-ovm': # NOTE: obsolete from ovm2 import oheap2 opt, i = compile_spec.ParseArgv(argv) py_path = argv[i] out_path = argv[i+1] # Compile to Python bytecode (TODO: remove ovm_codegen.py) mode = 'exec' with open(py_path) as f: co = compiler.Compile(f, opt, mode) if 1: with open(out_path, 'wb') as out_f: oheap2.Write(co, out_f) return 0 log("Compiled to %d bytes of top-level bytecode", len(co.co_code)) # Write the .pyc file with open(out_path, 'wb') as out_f: if 1: out_f.write(co.co_code) else: h = misc.getPycHeader(py_path) out_f.write(h) marshal.dump(co, out_f) log('Wrote only the bytecode to %r', out_path) elif action == 'eval': # Like compile, but parses to a code object and prints it opt, i = compile_spec.ParseArgv(argv) py_expr = argv[i] f = skeleton.StringInput(py_expr, '<eval input>') co = compiler.Compile(f, opt, 'eval') v = dis_tool.Visitor() v.show_code(co) print() print('RESULT:') print(eval(co)) elif action == 'repl': # Like eval in a loop while True: py_expr = raw_input('opy> ') f = skeleton.StringInput(py_expr, '<REPL input>') # TODO: change this to 'single input'? Why doesn't this work? co = compiler.Compile(f, opt, 'eval') v = dis_tool.Visitor() v.show_code(co) print(eval(co)) elif action == 'dis-tables': out_dir = argv[0] pyc_paths = argv[1:] out = TableOutput(out_dir) for pyc_path in pyc_paths: with open(pyc_path) as f: magic, unixtime, timestamp, code = dis_tool.unpack_pyc(f) WriteDisTables(pyc_path, code, out) out.Close() elif action == 'dis': opt, i = compile_spec.ParseArgv(argv) path = argv[i] v = dis_tool.Visitor() if path.endswith('.py'): with open(path) as f: co = compiler.Compile(f, opt, 'exec') log("Compiled to %d bytes of top-level bytecode", len(co.co_code)) v.show_code(co) else: # assume pyc_path with open(path, 'rb') as f: v.Visit(f) elif action == 'dis-md5': pyc_paths = argv if not pyc_paths: raise error.Usage('dis-md5: At least one .pyc path is required.') for path in pyc_paths: h = hashlib.md5() with open(path) as f: magic = f.read(4) h.update(magic) ignored_timestamp = f.read(4) while True: b = f.read(64 * 1024) if not b: break h.update(b) print('%6d %s %s' % (os.path.getsize(path), h.hexdigest(), path)) elif action == 'run': # Compile and run, without writing pyc file # TODO: Add an option like -v in __main__ #level = logging.DEBUG if args.verbose else logging.WARNING #logging.basicConfig(level=level) #logging.basicConfig(level=logging.DEBUG) opt, i = compile_spec.ParseArgv(argv) py_path = argv[i] opy_argv = argv[i:] if py_path.endswith('.py'): with open(py_path) as f: co = compiler.Compile(f, opt, 'exec') num_ticks = execfile.run_code_object(co, opy_argv) elif py_path.endswith('.pyc') or py_path.endswith('.opyc'): with open(py_path) as f: f.seek(8) # past header. TODO: validate it! co = marshal.load(f) num_ticks = execfile.run_code_object(co, opy_argv) else: raise error.Usage('Invalid path %r' % py_path) elif action == 'run-ovm': # Compile and run, without writing pyc file opt, i = compile_spec.ParseArgv(argv) py_path = argv[i] opy_argv = argv[i+1:] if py_path.endswith('.py'): #mode = 'exec' mode = 'ovm' # OVM bytecode is different! with open(py_path) as f: co = compiler.Compile(f, opt, mode) log('Compiled to %d bytes of OVM code', len(co.co_code)) num_ticks = ovm.run_code_object(co, opy_argv) elif py_path.endswith('.pyc') or py_path.endswith('.opyc'): with open(py_path) as f: f.seek(8) # past header. TODO: validate it! co = marshal.load(f) num_ticks = ovm.run_code_object(co, opy_argv) else: raise error.Usage('Invalid path %r' % py_path) else: raise error.Usage('Invalid action %r' % action)
def Build(self, argv, arg, base_opts): """Given flags to complete/compgen, return a UserSpec.""" cmd_ev = self.cmd_ev actions = [] # NOTE: bash doesn't actually check the name until completion time, but # obviously it's better to check here. if arg.F: func_name = arg.F func = cmd_ev.procs.get(func_name) if func is None: raise error.Usage('Function %r not found' % func_name) actions.append( completion.ShellFuncAction(cmd_ev, func, self.comp_lookup)) # NOTE: We need completion for -A action itself!!! bash seems to have it. for name in arg.actions: if name == 'alias': a = _FixedWordsAction(self.parse_ctx.aliases) elif name == 'binding': # TODO: Where do we get this from? a = _FixedWordsAction(['vi-delete']) elif name == 'command': # compgen -A command in bash is SIX things: aliases, builtins, # functions, keywords, external commands relative to the current # directory, and external commands in $PATH. actions.append(_FixedWordsAction(consts.BUILTIN_NAMES)) actions.append(_FixedWordsAction(self.parse_ctx.aliases)) actions.append(_FixedWordsAction(cmd_ev.procs)) actions.append(_FixedWordsAction(lexer_def.OSH_KEYWORD_NAMES)) actions.append(completion.FileSystemAction(exec_only=True)) # Look on the file system. a = completion.ExternalCommandAction(cmd_ev.mem) elif name == 'directory': a = completion.FileSystemAction(dirs_only=True) elif name == 'file': a = completion.FileSystemAction() elif name == 'function': a = _FixedWordsAction(cmd_ev.procs) elif name == 'job': a = _FixedWordsAction(['jobs-not-implemented']) elif name == 'user': a = completion.UsersAction() elif name == 'variable': a = completion.VariablesAction(cmd_ev.mem) elif name == 'helptopic': # Note: it would be nice to have 'helpgroup' for help -i too a = _FixedWordsAction(help_.TOPICS) elif name == 'setopt': names = [ opt.name for opt in option_def.All() if opt.builtin == 'set' ] a = _FixedWordsAction(names) elif name == 'shopt': names = [ opt.name for opt in option_def.All() if opt.builtin == 'shopt' ] a = _FixedWordsAction(names) elif name == 'signal': a = _FixedWordsAction(['TODO:signals']) elif name == 'stopped': a = _FixedWordsAction(['jobs-not-implemented']) else: raise NotImplementedError(name) actions.append(a) # e.g. -W comes after -A directory if arg.W is not None: # could be '' # NOTES: # - Parsing is done at REGISTRATION time, but execution and splitting is # done at COMPLETION time (when the user hits tab). So parse errors # happen early. w_parser = self.parse_ctx.MakeWordParserForPlugin(arg.W) arena = self.parse_ctx.arena try: arg_word = w_parser.ReadForPlugin() except error.Parse as e: ui.PrettyPrintError(e, arena) raise # Let 'complete' or 'compgen' return 2 a = completion.DynamicWordsAction(self.word_ev, self.splitter, arg_word, arena) actions.append(a) extra_actions = [] if base_opts.get('plusdirs'): extra_actions.append(completion.FileSystemAction(dirs_only=True)) # These only happen if there were zero shown. else_actions = [] if base_opts.get('default'): else_actions.append(completion.FileSystemAction()) if base_opts.get('dirnames'): else_actions.append(completion.FileSystemAction(dirs_only=True)) if not actions and not else_actions: raise error.Usage('No actions defined in completion: %s' % argv) p = completion.DefaultPredicate if arg.X: filter_pat = arg.X if filter_pat.startswith('!'): p = completion.GlobPredicate(False, filter_pat[1:]) else: p = completion.GlobPredicate(True, filter_pat) return completion.UserSpec(actions, extra_actions, else_actions, p, prefix=arg.P or '', suffix=arg.S or '')
def Main(lang, arg_r, environ, login_shell, loader, line_input): # type: (str, args.Reader, Dict[str, str], bool, pyutil._ResourceLoader, Any) -> int """The full shell lifecycle. Used by bin/osh and bin/oil. Args: lang: 'osh' or 'oil' argv0, arg_r: command line arguments environ: environment login_shell: Was - on the front? loader: to get help, version, grammar, etc. line_input: optional GNU readline """ # Differences between osh and oil: # - --help? I guess Oil has a SUPERSET of OSH options. # - oshrc vs oilrc # - shopt -s oil:all # - Change the prompt in the interactive shell? # osh-pure: # - no oil grammar # - no expression evaluator # - no interactive shell, or line_input # - no process.* # process.{ExternalProgram,Waiter,FdState,JobState,SignalState} -- we want # to evaluate config files without any of these # Modules not translated yet: completion, comp_ui, builtin_comp, process # - word evaluator # - shouldn't glob? set -o noglob? or hard failure? # - ~ shouldn't read from the file system # - I guess it can just be the HOME=HOME? # Builtin: # shellvm -c 'echo hi' # shellvm <<< 'echo hi' argv0 = arg_r.Peek() assert argv0 is not None arg_r.Next() assert lang in ('osh', 'oil'), lang try: attrs = flag_spec.ParseMore('main', arg_r) except error.Usage as e: stderr_line('osh usage error: %s', e.msg) return 2 flag = arg_types.main(attrs.attrs) arena = alloc.Arena() errfmt = ui.ErrorFormatter(arena) help_builtin = builtin_misc.Help(loader, errfmt) if flag.help: help_builtin.Run(pure.MakeBuiltinArgv(['%s-usage' % lang])) return 0 if flag.version: # OSH version is the only binary in Oil right now, so it's all one version. pyutil.ShowAppVersion('Oil', loader) return 0 no_str = None # type: str debug_stack = [] # type: List[state.DebugFrame] if arg_r.AtEnd(): dollar0 = argv0 else: dollar0 = arg_r.Peek() # the script name, or the arg after -c # Copy quirky bash behavior. frame0 = state.DebugFrame(dollar0, 'main', no_str, state.LINE_ZERO, 0, 0) debug_stack.append(frame0) # Copy quirky bash behavior. frame1 = state.DebugFrame(no_str, no_str, no_str, runtime.NO_SPID, 0, 0) debug_stack.append(frame1) script_name = arg_r.Peek() # type: Optional[str] arg_r.Next() mem = state.Mem(dollar0, arg_r.Rest(), arena, debug_stack) version_str = pyutil.GetVersion(loader) state.InitMem(mem, environ, version_str) builtin_funcs.Init(mem) procs = {} # type: Dict[str, command__ShFunction] job_state = process.JobState() fd_state = process.FdState(errfmt, job_state, mem) opt_hook = ShellOptHook(line_input) parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, opt_hook) # TODO: only MutableOpts needs mem, so it's not a true circular dep. mem.exec_opts = exec_opts # circular dep if attrs.show_options: # special case: sh -o mutable_opts.ShowOptions([]) return 0 # Set these BEFORE processing flags, so they can be overridden. if lang == 'oil': mutable_opts.SetShoptOption('oil:all', True) builtin_pure.SetShellOpts(mutable_opts, attrs.opt_changes, attrs.shopt_changes) # feedback between runtime and parser aliases = {} # type: Dict[str, str] oil_grammar = pyutil.LoadOilGrammar(loader) if flag.one_pass_parse and not exec_opts.noexec(): raise error.Usage('--one-pass-parse requires noexec (-n)') parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar) parse_ctx.Init_OnePassParse(flag.one_pass_parse) # Three ParseContext instances SHARE aliases. comp_arena = alloc.Arena() comp_arena.PushSource(source.Unused('completion')) trail1 = parse_lib.Trail() # one_pass_parse needs to be turned on to complete inside backticks. TODO: # fix the issue where ` gets erased because it's not part of # set_completer_delims(). comp_ctx = parse_lib.ParseContext(comp_arena, parse_opts, aliases, oil_grammar) comp_ctx.Init_Trail(trail1) comp_ctx.Init_OnePassParse(True) hist_arena = alloc.Arena() hist_arena.PushSource(source.Unused('history')) trail2 = parse_lib.Trail() hist_ctx = parse_lib.ParseContext(hist_arena, parse_opts, aliases, oil_grammar) hist_ctx.Init_Trail(trail2) # Deps helps manages dependencies. These dependencies are circular: # - cmd_ev and word_ev, arith_ev -- for command sub, arith sub # - arith_ev and word_ev -- for $(( ${a} )) and $x$(( 1 )) # - cmd_ev and builtins (which execute code, like eval) # - prompt_ev needs word_ev for $PS1, which needs prompt_ev for @P cmd_deps = cmd_eval.Deps() cmd_deps.mutable_opts = mutable_opts # TODO: In general, cmd_deps are shared between the mutually recursive # evaluators. Some of the four below are only shared between a builtin and # the CommandEvaluator, so we could put them somewhere else. cmd_deps.traps = {} cmd_deps.trap_nodes = [] # TODO: Clear on fork() to avoid duplicates waiter = process.Waiter(job_state, exec_opts) my_pid = posix.getpid() debug_path = '' debug_dir = environ.get('OSH_DEBUG_DIR') if flag.debug_file is not None: # --debug-file takes precedence over OSH_DEBUG_DIR debug_path = flag.debug_file elif debug_dir is not None: debug_path = os_path.join(debug_dir, '%d-osh.log' % my_pid) if len(debug_path): # This will be created as an empty file if it doesn't exist, or it could be # a pipe. try: debug_f = util.DebugFile( fd_state.OpenForWrite(debug_path)) # type: util._DebugFile except OSError as e: stderr_line("osh: Couldn't open %r: %s", debug_path, posix.strerror(e.errno)) return 2 else: debug_f = util.NullDebugFile() cmd_deps.debug_f = debug_f # Not using datetime for dependency reasons. TODO: maybe show the date at # the beginning of the log, and then only show time afterward? To save # space, and make space for microseconds. (datetime supports microseconds # but time.strftime doesn't). if mylib.PYTHON: iso_stamp = time.strftime("%Y-%m-%d %H:%M:%S") debug_f.log('%s [%d] OSH started with argv %s', iso_stamp, my_pid, arg_r.argv) if len(debug_path): debug_f.log('Writing logs to %r', debug_path) interp = environ.get('OSH_HIJACK_SHEBANG', '') search_path = state.SearchPath(mem) ext_prog = process.ExternalProgram(interp, fd_state, errfmt, debug_f) splitter = split.SplitContext(mem) # split() builtin # TODO: Accept IFS as a named arg? split('a b', IFS=' ') builtin_funcs.SetGlobalFunc( mem, 'split', lambda s, ifs=None: splitter.SplitForWordEval(s, ifs=ifs)) # glob() builtin # TODO: This is instantiation is duplicated in osh/word_eval.py globber = glob_.Globber(exec_opts) builtin_funcs.SetGlobalFunc(mem, 'glob', lambda s: globber.OilFuncCall(s)) # This could just be OSH_DEBUG_STREAMS='debug crash' ? That might be # stuffing too much into one, since a .json crash dump isn't a stream. crash_dump_dir = environ.get('OSH_CRASH_DUMP_DIR', '') cmd_deps.dumper = dev.CrashDumper(crash_dump_dir) if flag.xtrace_to_debug_file: trace_f = debug_f else: trace_f = util.DebugFile(mylib.Stderr()) comp_lookup = completion.Lookup() # Various Global State objects to work around readline interfaces compopt_state = completion.OptionState() comp_ui_state = comp_ui.State() prompt_state = comp_ui.PromptState() dir_stack = state.DirStack() # # Initialize builtins that don't depend on evaluators # builtins = {} # type: Dict[int, vm._Builtin] pure.AddPure(builtins, mem, procs, mutable_opts, aliases, search_path, errfmt) pure.AddIO(builtins, mem, dir_stack, exec_opts, splitter, errfmt) AddProcess(builtins, mem, ext_prog, fd_state, job_state, waiter, search_path, errfmt) AddOil(builtins, mem, errfmt) builtins[builtin_i.help] = help_builtin # Interactive, depend on line_input builtins[builtin_i.bind] = builtin_lib.Bind(line_input, errfmt) builtins[builtin_i.history] = builtin_lib.History(line_input, mylib.Stdout()) # # Assignment builtins # assign_b = {} # type: Dict[int, vm._AssignBuiltin] new_var = builtin_assign.NewVar(mem, procs, errfmt) assign_b[builtin_i.declare] = new_var assign_b[builtin_i.typeset] = new_var assign_b[builtin_i.local] = new_var assign_b[builtin_i.export_] = builtin_assign.Export(mem, errfmt) assign_b[builtin_i.readonly] = builtin_assign.Readonly(mem, errfmt) # # Initialize Evaluators # arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, parse_ctx, errfmt) bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, parse_ctx, errfmt) expr_ev = expr_eval.OilEvaluator(mem, procs, splitter, errfmt) word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, splitter, errfmt) cmd_ev = cmd_eval.CommandEvaluator(mem, exec_opts, errfmt, procs, assign_b, arena, cmd_deps) shell_ex = executor.ShellExecutor(mem, exec_opts, mutable_opts, procs, builtins, search_path, ext_prog, waiter, job_state, fd_state, errfmt) # PromptEvaluator rendering is needed in non-interactive shells for @P. prompt_ev = prompt.Evaluator(lang, parse_ctx, mem) tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, word_ev, trace_f) # Wire up circular dependencies. vm.InitCircularDeps(arith_ev, bool_ev, expr_ev, word_ev, cmd_ev, shell_ex, prompt_ev, tracer) # # Initialize builtins that depend on evaluators # # note: 'printf -v a[i]' and 'unset a[i]' require same deps builtins[builtin_i.printf] = builtin_printf.Printf(mem, exec_opts, parse_ctx, arith_ev, errfmt) builtins[builtin_i.unset] = builtin_assign.Unset(mem, exec_opts, procs, parse_ctx, arith_ev, errfmt) builtins[builtin_i.eval] = builtin_meta.Eval(parse_ctx, exec_opts, cmd_ev) source_builtin = builtin_meta.Source(parse_ctx, search_path, cmd_ev, fd_state, errfmt) builtins[builtin_i.source] = source_builtin builtins[builtin_i.dot] = source_builtin builtins[builtin_i.builtin] = builtin_meta.Builtin(shell_ex, errfmt) builtins[builtin_i.command] = builtin_meta.Command(shell_ex, procs, aliases, search_path) spec_builder = builtin_comp.SpecBuilder(cmd_ev, parse_ctx, word_ev, splitter, comp_lookup) complete_builtin = builtin_comp.Complete(spec_builder, comp_lookup) builtins[builtin_i.complete] = complete_builtin builtins[builtin_i.compgen] = builtin_comp.CompGen(spec_builder) builtins[builtin_i.compopt] = builtin_comp.CompOpt(compopt_state, errfmt) builtins[builtin_i.compadjust] = builtin_comp.CompAdjust(mem) # These builtins take blocks, and thus need cmd_ev. builtins[builtin_i.cd] = builtin_misc.Cd(mem, dir_stack, cmd_ev, errfmt) builtins[builtin_i.json] = builtin_oil.Json(mem, cmd_ev, errfmt) sig_state = pyos.SignalState() sig_state.InitShell() builtins[builtin_i.trap] = builtin_process.Trap(sig_state, cmd_deps.traps, cmd_deps.trap_nodes, parse_ctx, errfmt) # History evaluation is a no-op if line_input is None. hist_ev = history.Evaluator(line_input, hist_ctx, debug_f) if flag.c is not None: arena.PushSource(source.CFlag()) line_reader = reader.StringLineReader(flag.c, arena) # type: reader._Reader if flag.i: # -c and -i can be combined mutable_opts.set_interactive() elif flag.i: # force interactive arena.PushSource(source.Stdin(' -i')) line_reader = py_reader.InteractiveLineReader(arena, prompt_ev, hist_ev, line_input, prompt_state) mutable_opts.set_interactive() else: if script_name is None: stdin = mylib.Stdin() if stdin.isatty(): arena.PushSource(source.Interactive()) line_reader = py_reader.InteractiveLineReader( arena, prompt_ev, hist_ev, line_input, prompt_state) mutable_opts.set_interactive() else: arena.PushSource(source.Stdin('')) line_reader = reader.FileLineReader(stdin, arena) else: arena.PushSource(source.MainFile(script_name)) try: f = fd_state.Open(script_name) except OSError as e: stderr_line("osh: Couldn't open %r: %s", script_name, posix.strerror(e.errno)) return 1 line_reader = reader.FileLineReader(f, arena) # TODO: assert arena.NumSourcePaths() == 1 # TODO: .rc file needs its own arena. c_parser = parse_ctx.MakeOshParser(line_reader) if exec_opts.interactive(): # bash: 'set -o emacs' is the default only in the interactive shell mutable_opts.set_emacs() # Calculate ~/.config/oil/oshrc or oilrc # Use ~/.config/oil to avoid cluttering the user's home directory. Some # users may want to ln -s ~/.config/oil/oshrc ~/oshrc or ~/.oshrc. # https://unix.stackexchange.com/questions/24347/why-do-some-applications-use-config-appname-for-their-config-data-while-other home_dir = pyos.GetMyHomeDir() assert home_dir is not None history_filename = os_path.join(home_dir, '.config/oil/history_%s' % lang) if line_input: # NOTE: We're using a different WordEvaluator here. ev = word_eval.CompletionWordEvaluator(mem, exec_opts, splitter, errfmt) ev.arith_ev = arith_ev ev.expr_ev = expr_ev ev.prompt_ev = prompt_ev ev.CheckCircularDeps() root_comp = completion.RootCompleter(ev, mem, comp_lookup, compopt_state, comp_ui_state, comp_ctx, debug_f) term_width = 0 if flag.completion_display == 'nice': try: term_width = libc.get_terminal_width() except IOError: # stdin not a terminal pass if term_width != 0: display = comp_ui.NiceDisplay( term_width, comp_ui_state, prompt_state, debug_f, line_input) # type: comp_ui._IDisplay else: display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state, debug_f) comp_ui.InitReadline(line_input, history_filename, root_comp, display, debug_f) _InitDefaultCompletions(cmd_ev, complete_builtin, comp_lookup) else: # Without readline module display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state, debug_f) sig_state.InitInteractiveShell(display) rc_path = flag.rcfile or os_path.join(home_dir, '.config/oil/%src' % lang) try: # NOTE: Should be called AFTER _InitDefaultCompletions. SourceStartupFile(fd_state, rc_path, lang, parse_ctx, cmd_ev) except util.UserExit as e: return e.status line_reader.Reset() # After sourcing startup file, render $PS1 prompt_plugin = prompt.UserPlugin(mem, parse_ctx, cmd_ev) try: status = main_loop.Interactive(flag, cmd_ev, c_parser, display, prompt_plugin, errfmt) except util.UserExit as e: status = e.status box = [status] cmd_ev.MaybeRunExitTrap(box) status = box[0] return status if flag.rcfile: # bash doesn't have this warning, but it's useful stderr_line('osh warning: --rcfile ignored in non-interactive shell') if exec_opts.noexec(): status = 0 try: node = main_loop.ParseWholeFile(c_parser) except error.Parse as e: ui.PrettyPrintError(e, arena) status = 2 if status == 0: if flag.parser_mem_dump is not None: # only valid in -n mode input_path = '/proc/%d/status' % posix.getpid() pyutil.CopyFile(input_path, flag.parser_mem_dump) ui.PrintAst(node, flag) else: if flag.parser_mem_dump is not None: raise error.Usage('--parser-mem-dump can only be used with -n') try: status = main_loop.Batch(cmd_ev, c_parser, arena, cmd_flags=cmd_eval.IsMainProgram) except util.UserExit as e: status = e.status box = [status] cmd_ev.MaybeRunExitTrap(box) status = box[0] # NOTE: 'exit 1' is ControlFlow and gets here, but subshell/commandsub # don't because they call sys.exit(). if flag.runtime_mem_dump is not None: input_path = '/proc/%d/status' % posix.getpid() pyutil.CopyFile(input_path, flag.runtime_mem_dump) # NOTE: We haven't closed the file opened with fd_state.Open return status
def OshCommandMain(argv): """Run an 'oshc' tool. 'osh' is short for "osh compiler" or "osh command". TODO: - oshc --help oshc deps --path: the $PATH to use to find executables. What about libraries? NOTE: we're leaving out su -c, find, xargs, etc.? Those should generally run functions using the $0 pattern. --chained-command sudo """ try: action = argv[0] except IndexError: raise error.Usage('Missing required subcommand.') if action not in SUBCOMMANDS: raise error.Usage('Invalid subcommand %r.' % action) if action == 'parse-glob': # Pretty-print the AST produced by osh/glob_.py print('TODO:parse-glob') return 0 if action == 'parse-printf': # Pretty-print the AST produced by osh/builtin_printf.py print('TODO:parse-printf') return 0 arena = alloc.Arena() try: script_name = argv[1] arena.PushSource(source.MainFile(script_name)) except IndexError: arena.PushSource(source.Stdin()) f = sys.stdin else: try: f = open(script_name) except IOError as e: stderr_line("oshc: Couldn't open %r: %s", script_name, posix.strerror(e.errno)) return 2 aliases = {} # Dummy value; not respecting aliases! loader = pyutil.GetResourceLoader() oil_grammar = pyutil.LoadOilGrammar(loader) opt0_array = state.InitOpts() no_stack = None # type: List[bool] # for mycpp opt_stacks = [no_stack] * option_i.ARRAY_SIZE # type: List[List[bool]] parse_opts = optview.Parse(opt0_array, opt_stacks) # parse `` and a[x+1]=bar differently parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar) parse_ctx.Init_OnePassParse(True) line_reader = reader.FileLineReader(f, arena) c_parser = parse_ctx.MakeOshParser(line_reader) try: node = main_loop.ParseWholeFile(c_parser) except error.Parse as e: ui.PrettyPrintError(e, arena) return 2 assert node is not None f.close() # Columns for list-* # path line name # where name is the binary path, variable name, or library path. # bin-deps and lib-deps can be used to make an app bundle. # Maybe I should list them together? 'deps' can show 4 columns? # # path, line, type, name # # --pretty can show the LST location. # stderr: show how we're following imports? if action == 'translate': osh2oil.PrintAsOil(arena, node) elif action == 'arena': # for debugging osh2oil.PrintArena(arena) elif action == 'spans': # for debugging osh2oil.PrintSpans(arena) elif action == 'format': # TODO: autoformat code raise NotImplementedError(action) elif action == 'deps': deps.Deps(node) elif action == 'undefined-vars': # could be environment variables raise NotImplementedError() else: raise AssertionError # Checked above return 0
def Run(self, cmd_val): arg, arg_r = flag_spec.ParseOilCmdVal('repr', cmd_val) action, action_spid = arg_r.ReadRequired2( 'expected an action (proc, .cell, etc.)') # Actions that print unstable formats start with '.' if action == '.cell': argv, spids = arg_r.Rest2() status = 0 for i, name in enumerate(argv): if name.startswith(':'): name = name[1:] if not match.IsValidVarName(name): raise error.Usage('got invalid variable name %r' % name, span_id=spids[i]) cell = self.mem.GetCell(name) if cell is None: self.errfmt.Print("Couldn't find a variable named %r" % name, span_id=spids[i]) status = 1 else: sys.stdout.write('%s = ' % name) cell.PrettyPrint() # may be color sys.stdout.write('\n') elif action == 'proc': names, spids = arg_r.Rest2() if len(names): for i, name in enumerate(names): node = self.procs.get(name) if node is None: self.errfmt.Print_('Invalid proc %r' % name, span_id=spids[i]) return 1 else: names = sorted(self.procs) # QTSV header print('proc_name\tdoc_comment') for name in names: node = self.procs[name] # must exist body = node.body # TODO: not just command__ShFunction, but command__Proc! doc = '' if body.tag_() == command_e.BraceGroup: if body.doc_token: span_id = body.doc_token.span_id span = self.arena.GetLineSpan(span_id) line = self.arena.GetLine(span.line_id) # 1 to remove leading space doc = line[span.col+1 : span.col + span.length] # No limits on proc names print('%s\t%s' % (qsn.maybe_encode(name), qsn.maybe_encode(doc))) status = 0 else: e_usage('got invalid action %r' % action, span_id=action_spid) return status
def AppBundleMain(argv): # type: (List[str]) -> int # NOTE: This has a side effect of deleting _OVM_* from the environment! loader = pyutil.GetResourceLoader() b = os_path.basename(argv[0]) main_name, ext = os_path.splitext(b) arg_r = args.Reader(argv) if main_name == 'oil' and ext: # oil.py or oil.ovm arg_r.Next() first_arg = arg_r.Peek() if first_arg is None: raise error.Usage('Missing required applet name.') if first_arg in ('-h', '--help'): errfmt = None # not needed here help_builtin = builtin_misc.Help(loader, errfmt) help_builtin.Run(pure.MakeBuiltinArgv(['bundle-usage'])) sys.exit(0) if first_arg in ('-V', '--version'): pyutil.ShowAppVersion('Oil', loader) sys.exit(0) main_name = first_arg login_shell = False if main_name.startswith('-'): login_shell = True main_name = main_name[1:] if main_name in ('osh', 'sh'): # TODO: # - Initialize a different shell if line_input isn't present status = shell.Main('osh', arg_r, posix.environ, login_shell, loader, line_input) _tlog('done osh main') return status elif main_name == 'osh-pure': # TODO: pure.Main() pass elif main_name == 'oshc': arg_r.Next() main_argv = arg_r.Rest() try: return OshCommandMain(main_argv) except error.Usage as e: stderr_line('oshc usage error: %s', e.msg) return 2 elif main_name == 'oil': return shell.Main('oil', arg_r, posix.environ, login_shell, loader, line_input) elif main_name == 'tea': main_argv = arg_r.Rest() return TeaMain(main_argv) # For testing latency elif main_name == 'true': return 0 elif main_name == 'false': return 1 elif main_name == 'readlink': main_argv = arg_r.Rest() return readlink.main(main_argv) else: raise error.Usage('Invalid applet name %r.' % main_name)