Example #1
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.')

    # 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
Example #4
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()
Example #6
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()  # 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
Example #7
0
  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")
Example #8
0
  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')
Example #9
0
    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
Example #10
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)
Example #11
0
    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
Example #13
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()
Example #15
0
    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
Example #16
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

        # 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
Example #17
0
    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
Example #18
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
Example #19
0
    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
Example #21
0
    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
Example #22
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
Example #23
0
    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
Example #25
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 '')
Example #27
0
File: shell.py Project: dpercy/oil
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
Example #28
0
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
Example #29
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
Example #30
0
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)