Exemplo n.º 1
0
 def __init__(self,
              filename='<xonsh-code>',
              debug_level=0,
              parser_args=None,
              unload=True,
              config=None,
              login=True,
              xonsh_ctx=None):
     """Parameters
     ----------
     filename : str, optional
         File we are to execute.
     debug_level : int, optional
         Debugging level to use in lexing and parsing.
     parser_args : dict, optional
         Arguments to pass down to the parser.
     unload : bool, optional
         Whether or not to unload xonsh builtins upon deletion.
     config : str, optional
         Path to configuration file.
     xonsh_ctx : dict or None, optional
         Xonsh xontext to load as builtins.__xonsh_ctx__
     """
     parser_args = parser_args or {}
     self.parser = Parser(**parser_args)
     self.filename = filename
     self.debug_level = debug_level
     self.unload = unload
     self.ctxtransformer = CtxAwareTransformer(self.parser)
     load_builtins(execer=self, config=config, login=login, ctx=xonsh_ctx)
Exemplo n.º 2
0
 def __init__(
     self,
     filename="<xonsh-code>",
     debug_level=0,
     parser_args=None,
     unload=True,
     scriptcache=True,
     cacheall=False,
 ):
     """Parameters
     ----------
     filename : str, optional
         File we are to execute.
     debug_level : int, optional
         Debugging level to use in lexing and parsing.
     parser_args : dict, optional
         Arguments to pass down to the parser.
     scriptcache : bool, optional
         Whether or not to use a precompiled bytecode cache when execing
         code, default: True.
     cacheall : bool, optional
         Whether or not to cache all xonsh code, and not just files. If this
         is set to true, it will cache command line input too, default: False.
     """
     parser_args = parser_args or {}
     self.parser = Parser(**parser_args)
     self.filename = filename
     self._default_filename = filename
     self.debug_level = debug_level
     self.unload = unload
     self.scriptcache = scriptcache
     self.cacheall = cacheall
     self.ctxtransformer = CtxAwareTransformer(self.parser)
Exemplo n.º 3
0
 def __init__(self, filename='<xonsh-code>', debug_level=0, parser_args=None,
              unload=True, xonsh_ctx=None, scriptcache=True, cacheall=False):
     """Parameters
     ----------
     filename : str, optional
         File we are to execute.
     debug_level : int, optional
         Debugging level to use in lexing and parsing.
     parser_args : dict, optional
         Arguments to pass down to the parser.
     unload : bool, optional
         Whether or not to unload xonsh builtins upon deletion.
     xonsh_ctx : dict or None, optional
         Xonsh xontext to load as builtins.__xonsh_ctx__
     scriptcache : bool, optional
         Whether or not to use a precompiled bytecode cache when execing
         code, default: True.
     cacheall : bool, optional
         Whether or not to cache all xonsh code, and not just files. If this
         is set to true, it will cache command line input too, default: False.
     """
     parser_args = parser_args or {}
     self.parser = Parser(**parser_args)
     self.filename = filename
     self.debug_level = debug_level
     self.unload = unload
     self.scriptcache = scriptcache
     self.cacheall = cacheall
     self.ctxtransformer = CtxAwareTransformer(self.parser)
     load_builtins(execer=self, ctx=xonsh_ctx)
Exemplo n.º 4
0
 def __init__(self,
              filename='<xonsh-code>',
              debug_level=0,
              parser_args=None,
              unload=True,
              xonsh_ctx=None,
              scriptcache=True,
              cacheall=False):
     """Parameters
     ----------
     filename : str, optional
         File we are to execute.
     debug_level : int, optional
         Debugging level to use in lexing and parsing.
     parser_args : dict, optional
         Arguments to pass down to the parser.
     unload : bool, optional
         Whether or not to unload xonsh builtins upon deletion.
     xonsh_ctx : dict or None, optional
         Xonsh xontext to load as builtins.__xonsh_ctx__
     scriptcache : bool, optional
         Whether or not to use a precompiled bytecode cache when execing
         code, default: True.
     cacheall : bool, optional
         Whether or not to cache all xonsh code, and not just files. If this
         is set to true, it will cache command line input too, default: False.
     """
     parser_args = parser_args or {}
     self.parser = Parser(**parser_args)
     self.filename = filename
     self.debug_level = debug_level
     self.unload = unload
     self.scriptcache = scriptcache
     self.cacheall = cacheall
     self.ctxtransformer = CtxAwareTransformer(self.parser)
     load_builtins(execer=self, ctx=xonsh_ctx)
Exemplo n.º 5
0
 def __init__(self, filename='<xonsh-code>', debug_level=0, parser_args=None,
              unload=True, config=None, login=True, xonsh_ctx=None):
     """Parameters
     ----------
     filename : str, optional
         File we are to execute.
     debug_level : int, optional
         Debugging level to use in lexing and parsing.
     parser_args : dict, optional
         Arguments to pass down to the parser.
     unload : bool, optional
         Whether or not to unload xonsh builtins upon deletion.
     config : str, optional
         Path to configuration file.
     xonsh_ctx : dict or None, optional
         Xonsh xontext to load as builtins.__xonsh_ctx__
     """
     parser_args = parser_args or {}
     self.parser = Parser(**parser_args)
     self.filename = filename
     self.debug_level = debug_level
     self.unload = unload
     self.ctxtransformer = CtxAwareTransformer(self.parser)
     load_builtins(execer=self, config=config, login=login, ctx=xonsh_ctx)
Exemplo n.º 6
0
class Execer(object):
    """Executes xonsh code in a context."""

    def __init__(self, filename='<xonsh-code>', debug_level=0, parser_args=None,
                 unload=True, config=None, login=True, xonsh_ctx=None):
        """Parameters
        ----------
        filename : str, optional
            File we are to execute.
        debug_level : int, optional
            Debugging level to use in lexing and parsing.
        parser_args : dict, optional
            Arguments to pass down to the parser.
        unload : bool, optional
            Whether or not to unload xonsh builtins upon deletion.
        config : str, optional
            Path to configuration file.
        xonsh_ctx : dict or None, optional
            Xonsh xontext to load as builtins.__xonsh_ctx__
        """
        parser_args = parser_args or {}
        self.parser = Parser(**parser_args)
        self.filename = filename
        self.debug_level = debug_level
        self.unload = unload
        self.ctxtransformer = CtxAwareTransformer(self.parser)
        load_builtins(execer=self, config=config, login=login, ctx=xonsh_ctx)

    def __del__(self):
        if self.unload:
            unload_builtins()

    def parse(self, input, ctx, mode='exec', filename=None, transform=True):
        """Parses xonsh code in a context-aware fashion. For context-free
        parsing, please use the Parser class directly or pass in
        transform=False.
        """
        if filename is None:
            filename = self.filename
        if not transform:
            return self.parser.parse(input, filename=filename, mode=mode,
                                     debug_level=(self.debug_level > 1))

        # Parsing actually happens in a couple of phases. The first is a
        # shortcut for a context-free parser. Normally, all subprocess
        # lines should be wrapped in $(), to indicate that they are a
        # subproc. But that would be super annoying. Unfortnately, Python
        # mode - after indentation - is whitespace agnostic while, using
        # the Python token, subproc mode is whitespace aware. That is to say,
        # in Python mode "ls -l", "ls-l", and "ls - l" all parse to the
        # same AST because whitespace doesn't matter to the minus binary op.
        # However, these phases all have very different meaning in subproc
        # mode. The 'right' way to deal with this is to make the entire
        # grammar whitespace aware, and then ignore all of the whitespace
        # tokens for all of the Python rules. The lazy way implemented here
        # is to parse a line a second time with a $() wrapper if it fails
        # the first time. This is a context-free phase.
        tree, input = self._parse_ctx_free(input, mode=mode, filename=filename)
        if tree is None:
            return None

        # Now we need to perform context-aware AST transformation. This is
        # because the "ls -l" is valid Python. The only way that we know
        # it is not actually Python is by checking to see if the first token
        # (ls) is part of the execution context. If it isn't, then we will
        # assume that this line is supposed to be a subprocess line, assuming
        # it also is valid as a subprocess line.
        if ctx is None:
            ctx = set()
        elif isinstance(ctx, cabc.Mapping):
            ctx = set(ctx.keys())
        tree = self.ctxtransformer.ctxvisit(tree, input, ctx, mode=mode)
        return tree

    def compile(self, input, mode='exec', glbs=None, locs=None, stacklevel=2,
                filename=None, transform=True):
        """Compiles xonsh code into a Python code object, which may then
        be execed or evaled.
        """
        if filename is None:
            filename = self.filename
        if glbs is None or locs is None:
            frame = inspect.stack()[stacklevel][0]
            glbs = frame.f_globals if glbs is None else glbs
            locs = frame.f_locals if locs is None else locs
        ctx = set(dir(builtins)) | set(glbs.keys()) | set(locs.keys())
        tree = self.parse(input, ctx, mode=mode, filename=filename,
                          transform=transform)
        if tree is None:
            return None  # handles comment only input
        if transform:
            with warnings.catch_warnings():
                # we do some funky things with blocks that cause warnings
                warnings.simplefilter('ignore', SyntaxWarning)
                code = compile(tree, filename, mode)
        else:
            code = compile(tree, filename, mode)
        return code

    def eval(self, input, glbs=None, locs=None, stacklevel=2,
             filename=None, transform=True):
        """Evaluates (and returns) xonsh code."""
        if isinstance(input, types.CodeType):
            code = input
        else:
            if filename is None:
                filename = self.filename
            code = self.compile(input=input,
                                glbs=glbs,
                                locs=locs,
                                mode='eval',
                                stacklevel=stacklevel,
                                filename=filename,
                                transform=transform)
        if code is None:
            return None  # handles comment only input
        return eval(code, glbs, locs)

    def exec(self, input, mode='exec', glbs=None, locs=None, stacklevel=2,
             filename=None, transform=True):
        """Execute xonsh code."""
        if isinstance(input, types.CodeType):
            code = input
        else:
            if filename is None:
                filename = self.filename
            code = self.compile(input=input,
                                glbs=glbs,
                                locs=locs,
                                mode=mode,
                                stacklevel=stacklevel,
                                filename=filename,
                                transform=transform)
        if code is None:
            return None  # handles comment only input
        return exec(code, glbs, locs)

    def _parse_ctx_free(self, input, mode='exec', filename=None):
        last_error_line = last_error_col = -1
        parsed = False
        original_error = None
        if filename is None:
            filename = self.filename
        while not parsed:
            try:
                tree = self.parser.parse(input,
                                         filename=filename,
                                         mode=mode,
                                         debug_level=(self.debug_level > 1))
                parsed = True
            except IndentationError as e:
                if original_error is None:
                    raise e
                else:
                    raise original_error
            except SyntaxError as e:
                if original_error is None:
                    original_error = e
                if (e.loc is None) or (last_error_line == e.loc.lineno and
                                       last_error_col in (e.loc.column + 1,
                                                          e.loc.column)):
                    raise original_error from None
                last_error_col = e.loc.column
                last_error_line = e.loc.lineno
                idx = last_error_line - 1
                lines = input.splitlines()
                line = lines[idx]
                if input.endswith('\n'):
                    lines.append('')
                if len(line.strip()) == 0:
                    # whitespace only lines are not valid syntax in Python's
                    # interactive mode='single', who knew?! Just ignore them.
                    # this might cause actual sytax errors to have bad line
                    # numbers reported, but should only effect interactive mode
                    del lines[idx]
                    last_error_line = last_error_col = -1
                    input = '\n'.join(lines)
                    continue

                if last_error_line > 1 and lines[idx-1].rstrip()[-1:] == ':':
                    # catch non-indented blocks and raise error.
                    prev_indent = len(lines[idx-1]) - len(lines[idx-1].lstrip())
                    curr_indent = len(lines[idx]) - len(lines[idx].lstrip())
                    if prev_indent == curr_indent:
                        raise original_error
                lexer = self.parser.lexer
                maxcol = find_next_break(line, mincol=last_error_col,
                                         lexer=lexer)
                sbpline = subproc_toks(line, returnline=True,
                                       maxcol=maxcol, lexer=lexer)
                if sbpline is None:
                    # subprocess line had no valid tokens,
                    if len(line.partition('#')[0].strip()) == 0:
                        # likely because it only contained a comment.
                        del lines[idx]
                        last_error_line = last_error_col = -1
                        input = '\n'.join(lines)
                        continue
                    else:
                        # or for some other syntax error
                        raise original_error
                elif sbpline[last_error_col:].startswith('![![') or \
                        sbpline.lstrip().startswith('![!['):
                    # if we have already wrapped this in subproc tokens
                    # and it still doesn't work, adding more won't help
                    # anything
                    raise original_error
                else:
                    if self.debug_level:
                        msg = ('{0}:{1}:{2}{3} - {4}\n'
                               '{0}:{1}:{2}{3} + {5}')
                        mstr = '' if maxcol is None else ':' + str(maxcol)
                        msg = msg.format(self.filename, last_error_line,
                                         last_error_col, mstr, line, sbpline)
                        print(msg, file=sys.stderr)
                    lines[idx] = sbpline
                last_error_col += 3
                input = '\n'.join(lines)
        return tree, input
Exemplo n.º 7
0
class Execer(object):
    """Executes xonsh code in a context."""

    def __init__(
        self,
        filename="<xonsh-code>",
        debug_level=0,
        parser_args=None,
        unload=True,
        xonsh_ctx=None,
        scriptcache=True,
        cacheall=False,
    ):
        """Parameters
        ----------
        filename : str, optional
            File we are to execute.
        debug_level : int, optional
            Debugging level to use in lexing and parsing.
        parser_args : dict, optional
            Arguments to pass down to the parser.
        unload : bool, optional
            Whether or not to unload xonsh builtins upon deletion.
        xonsh_ctx : dict or None, optional
            Xonsh xontext to load as builtins.__xonsh__.ctx
        scriptcache : bool, optional
            Whether or not to use a precompiled bytecode cache when execing
            code, default: True.
        cacheall : bool, optional
            Whether or not to cache all xonsh code, and not just files. If this
            is set to true, it will cache command line input too, default: False.
        """
        parser_args = parser_args or {}
        self.parser = Parser(**parser_args)
        self.filename = filename
        self.debug_level = debug_level
        self.unload = unload
        self.scriptcache = scriptcache
        self.cacheall = cacheall
        self.ctxtransformer = CtxAwareTransformer(self.parser)
        load_builtins(execer=self, ctx=xonsh_ctx)
        load_proxies()

    def __del__(self):
        if self.unload:
            unload_proxies()
            unload_builtins()

    def parse(self, input, ctx, mode="exec", filename=None, transform=True):
        """Parses xonsh code in a context-aware fashion. For context-free
        parsing, please use the Parser class directly or pass in
        transform=False.
        """
        if filename is None:
            filename = self.filename
        if not transform:
            return self.parser.parse(
                input, filename=filename, mode=mode, debug_level=(self.debug_level > 2)
            )

        # Parsing actually happens in a couple of phases. The first is a
        # shortcut for a context-free parser. Normally, all subprocess
        # lines should be wrapped in $(), to indicate that they are a
        # subproc. But that would be super annoying. Unfortunately, Python
        # mode - after indentation - is whitespace agnostic while, using
        # the Python token, subproc mode is whitespace aware. That is to say,
        # in Python mode "ls -l", "ls-l", and "ls - l" all parse to the
        # same AST because whitespace doesn't matter to the minus binary op.
        # However, these phases all have very different meaning in subproc
        # mode. The 'right' way to deal with this is to make the entire
        # grammar whitespace aware, and then ignore all of the whitespace
        # tokens for all of the Python rules. The lazy way implemented here
        # is to parse a line a second time with a $() wrapper if it fails
        # the first time. This is a context-free phase.
        tree, input = self._parse_ctx_free(input, mode=mode, filename=filename)
        if tree is None:
            return None

        # Now we need to perform context-aware AST transformation. This is
        # because the "ls -l" is valid Python. The only way that we know
        # it is not actually Python is by checking to see if the first token
        # (ls) is part of the execution context. If it isn't, then we will
        # assume that this line is supposed to be a subprocess line, assuming
        # it also is valid as a subprocess line.
        if ctx is None:
            ctx = set()
        elif isinstance(ctx, cabc.Mapping):
            ctx = set(ctx.keys())
        tree = self.ctxtransformer.ctxvisit(
            tree, input, ctx, mode=mode, debug_level=self.debug_level
        )
        return tree

    def compile(
        self,
        input,
        mode="exec",
        glbs=None,
        locs=None,
        stacklevel=2,
        filename=None,
        transform=True,
    ):
        """Compiles xonsh code into a Python code object, which may then
        be execed or evaled.
        """
        if filename is None:
            filename = self.filename
        if glbs is None or locs is None:
            frame = inspect.stack()[stacklevel][0]
            glbs = frame.f_globals if glbs is None else glbs
            locs = frame.f_locals if locs is None else locs
        ctx = set(dir(builtins)) | set(glbs.keys()) | set(locs.keys())
        tree = self.parse(input, ctx, mode=mode, filename=filename, transform=transform)
        if tree is None:
            return None  # handles comment only input
        code = compile(tree, filename, mode)
        return code

    def eval(
        self, input, glbs=None, locs=None, stacklevel=2, filename=None, transform=True
    ):
        """Evaluates (and returns) xonsh code."""
        if isinstance(input, types.CodeType):
            code = input
        else:
            if filename is None:
                filename = self.filename
            code = self.compile(
                input=input,
                glbs=glbs,
                locs=locs,
                mode="eval",
                stacklevel=stacklevel,
                filename=filename,
                transform=transform,
            )
        if code is None:
            return None  # handles comment only input
        return eval(code, glbs, locs)

    def exec(
        self,
        input,
        mode="exec",
        glbs=None,
        locs=None,
        stacklevel=2,
        filename=None,
        transform=True,
    ):
        """Execute xonsh code."""
        if isinstance(input, types.CodeType):
            code = input
        else:
            if filename is None:
                filename = self.filename
            code = self.compile(
                input=input,
                glbs=glbs,
                locs=locs,
                mode=mode,
                stacklevel=stacklevel,
                filename=filename,
                transform=transform,
            )
        if code is None:
            return None  # handles comment only input
        return exec(code, glbs, locs)

    def _print_debug_wrapping(
        self, line, sbpline, last_error_line, last_error_col, maxcol=None
    ):
        """print some debugging info if asked for."""
        if self.debug_level > 1:
            msg = "{0}:{1}:{2}{3} - {4}\n" "{0}:{1}:{2}{3} + {5}"
            mstr = "" if maxcol is None else ":" + str(maxcol)
            msg = msg.format(
                self.filename, last_error_line, last_error_col, mstr, line, sbpline
            )
            print(msg, file=sys.stderr)

    def _parse_ctx_free(self, input, mode="exec", filename=None, logical_input=False):
        last_error_line = last_error_col = -1
        parsed = False
        original_error = None
        greedy = False
        if filename is None:
            filename = self.filename
        while not parsed:
            try:
                tree = self.parser.parse(
                    input,
                    filename=filename,
                    mode=mode,
                    debug_level=(self.debug_level > 2),
                )
                parsed = True
            except IndentationError as e:
                if original_error is None:
                    raise e
                else:
                    raise original_error
            except SyntaxError as e:
                if original_error is None:
                    original_error = e
                if (e.loc is None) or (
                    last_error_line == e.loc.lineno
                    and last_error_col in (e.loc.column + 1, e.loc.column)
                ):
                    raise original_error from None
                elif last_error_line != e.loc.lineno:
                    original_error = e
                last_error_col = e.loc.column
                last_error_line = e.loc.lineno
                idx = last_error_line - 1
                lines = input.splitlines()
                line, nlogical, idx = get_logical_line(lines, idx)
                if nlogical > 1 and not logical_input:
                    _, sbpline = self._parse_ctx_free(
                        line, mode=mode, filename=filename, logical_input=True
                    )
                    self._print_debug_wrapping(
                        line, sbpline, last_error_line, last_error_col, maxcol=None
                    )
                    replace_logical_line(lines, sbpline, idx, nlogical)
                    last_error_col += 3
                    input = "\n".join(lines)
                    continue
                if input.endswith("\n"):
                    lines.append("")
                if len(line.strip()) == 0:
                    # whitespace only lines are not valid syntax in Python's
                    # interactive mode='single', who knew?! Just ignore them.
                    # this might cause actual syntax errors to have bad line
                    # numbers reported, but should only affect interactive mode
                    del lines[idx]
                    last_error_line = last_error_col = -1
                    input = "\n".join(lines)
                    continue

                if last_error_line > 1 and lines[idx - 1].rstrip()[-1:] == ":":
                    # catch non-indented blocks and raise error.
                    prev_indent = len(lines[idx - 1]) - len(lines[idx - 1].lstrip())
                    curr_indent = len(lines[idx]) - len(lines[idx].lstrip())
                    if prev_indent == curr_indent:
                        raise original_error
                lexer = self.parser.lexer
                maxcol = (
                    None
                    if greedy
                    else find_next_break(line, mincol=last_error_col, lexer=lexer)
                )
                if not greedy and maxcol in (e.loc.column + 1, e.loc.column):
                    # go greedy the first time if the syntax error was because
                    # we hit an end token out of place. This usually indicates
                    # a subshell or maybe a macro.
                    if not balanced_parens(line, maxcol=maxcol):
                        greedy = True
                        maxcol = None
                sbpline = subproc_toks(
                    line, returnline=True, greedy=greedy, maxcol=maxcol, lexer=lexer
                )
                if sbpline is None:
                    # subprocess line had no valid tokens,
                    if len(line.partition("#")[0].strip()) == 0:
                        # likely because it only contained a comment.
                        del lines[idx]
                        last_error_line = last_error_col = -1
                        input = "\n".join(lines)
                        continue
                    elif not greedy:
                        greedy = True
                        continue
                    else:
                        # or for some other syntax error
                        raise original_error
                elif sbpline[last_error_col:].startswith(
                    "![!["
                ) or sbpline.lstrip().startswith("![!["):
                    # if we have already wrapped this in subproc tokens
                    # and it still doesn't work, adding more won't help
                    # anything
                    if not greedy:
                        greedy = True
                        continue
                    else:
                        raise original_error
                # replace the line
                self._print_debug_wrapping(
                    line, sbpline, last_error_line, last_error_col, maxcol=maxcol
                )
                replace_logical_line(lines, sbpline, idx, nlogical)
                last_error_col += 3
                input = "\n".join(lines)
        return tree, input
Exemplo n.º 8
0
class Execer(object):
    """Executes xonsh code in a context."""
    def __init__(
        self,
        filename="<xonsh-code>",
        debug_level=0,
        parser_args=None,
        unload=True,
        xonsh_ctx=None,
        scriptcache=True,
        cacheall=False,
    ):
        """Parameters
        ----------
        filename : str, optional
            File we are to execute.
        debug_level : int, optional
            Debugging level to use in lexing and parsing.
        parser_args : dict, optional
            Arguments to pass down to the parser.
        unload : bool, optional
            Whether or not to unload xonsh builtins upon deletion.
        xonsh_ctx : dict or None, optional
            Xonsh xontext to load as builtins.__xonsh__.ctx
        scriptcache : bool, optional
            Whether or not to use a precompiled bytecode cache when execing
            code, default: True.
        cacheall : bool, optional
            Whether or not to cache all xonsh code, and not just files. If this
            is set to true, it will cache command line input too, default: False.
        """
        parser_args = parser_args or {}
        self.parser = Parser(**parser_args)
        self.filename = filename
        self._default_filename = filename
        self.debug_level = debug_level
        self.unload = unload
        self.scriptcache = scriptcache
        self.cacheall = cacheall
        self.ctxtransformer = CtxAwareTransformer(self.parser)
        load_builtins(execer=self, ctx=xonsh_ctx)

    def __del__(self):
        if self.unload:
            unload_builtins()

    def parse(self, input, ctx, mode="exec", filename=None, transform=True):
        """Parses xonsh code in a context-aware fashion. For context-free
        parsing, please use the Parser class directly or pass in
        transform=False.
        """
        if filename is None:
            filename = self.filename
        if not transform:
            return self.parser.parse(input,
                                     filename=filename,
                                     mode=mode,
                                     debug_level=(self.debug_level > 2))

        # Parsing actually happens in a couple of phases. The first is a
        # shortcut for a context-free parser. Normally, all subprocess
        # lines should be wrapped in $(), to indicate that they are a
        # subproc. But that would be super annoying. Unfortunately, Python
        # mode - after indentation - is whitespace agnostic while, using
        # the Python token, subproc mode is whitespace aware. That is to say,
        # in Python mode "ls -l", "ls-l", and "ls - l" all parse to the
        # same AST because whitespace doesn't matter to the minus binary op.
        # However, these phases all have very different meaning in subproc
        # mode. The 'right' way to deal with this is to make the entire
        # grammar whitespace aware, and then ignore all of the whitespace
        # tokens for all of the Python rules. The lazy way implemented here
        # is to parse a line a second time with a $() wrapper if it fails
        # the first time. This is a context-free phase.
        tree, input = self._parse_ctx_free(input, mode=mode, filename=filename)
        if tree is None:
            return None

        # Now we need to perform context-aware AST transformation. This is
        # because the "ls -l" is valid Python. The only way that we know
        # it is not actually Python is by checking to see if the first token
        # (ls) is part of the execution context. If it isn't, then we will
        # assume that this line is supposed to be a subprocess line, assuming
        # it also is valid as a subprocess line.
        if ctx is None:
            ctx = set()
        elif isinstance(ctx, cabc.Mapping):
            ctx = set(ctx.keys())
        tree = self.ctxtransformer.ctxvisit(tree,
                                            input,
                                            ctx,
                                            mode=mode,
                                            debug_level=self.debug_level)
        return tree

    def compile(
        self,
        input,
        mode="exec",
        glbs=None,
        locs=None,
        stacklevel=2,
        filename=None,
        transform=True,
    ):
        """Compiles xonsh code into a Python code object, which may then
        be execed or evaled.
        """
        if filename is None:
            filename = self.filename
            self.filename = self._default_filename
        if glbs is None or locs is None:
            frame = inspect.stack()[stacklevel][0]
            glbs = frame.f_globals if glbs is None else glbs
            locs = frame.f_locals if locs is None else locs
        ctx = set(dir(builtins)) | set(glbs.keys()) | set(locs.keys())
        tree = self.parse(input,
                          ctx,
                          mode=mode,
                          filename=filename,
                          transform=transform)
        if tree is None:
            return None  # handles comment only input
        code = compile(tree, filename, mode)
        return code

    def eval(self,
             input,
             glbs=None,
             locs=None,
             stacklevel=2,
             filename=None,
             transform=True):
        """Evaluates (and returns) xonsh code."""
        if isinstance(input, types.CodeType):
            code = input
        else:
            if filename is None:
                filename = self.filename
            code = self.compile(
                input=input,
                glbs=glbs,
                locs=locs,
                mode="eval",
                stacklevel=stacklevel,
                filename=filename,
                transform=transform,
            )
        if code is None:
            return None  # handles comment only input
        return eval(code, glbs, locs)

    def exec(
        self,
        input,
        mode="exec",
        glbs=None,
        locs=None,
        stacklevel=2,
        filename=None,
        transform=True,
    ):
        """Execute xonsh code."""
        if isinstance(input, types.CodeType):
            code = input
        else:
            if filename is None:
                filename = self.filename
            code = self.compile(
                input=input,
                glbs=glbs,
                locs=locs,
                mode=mode,
                stacklevel=stacklevel,
                filename=filename,
                transform=transform,
            )
        if code is None:
            return None  # handles comment only input
        return exec(code, glbs, locs)

    def _print_debug_wrapping(self,
                              line,
                              sbpline,
                              last_error_line,
                              last_error_col,
                              maxcol=None):
        """print some debugging info if asked for."""
        if self.debug_level > 1:
            msg = "{0}:{1}:{2}{3} - {4}\n" "{0}:{1}:{2}{3} + {5}"
            mstr = "" if maxcol is None else ":" + str(maxcol)
            msg = msg.format(self.filename, last_error_line, last_error_col,
                             mstr, line, sbpline)
            print(msg, file=sys.stderr)

    def _parse_ctx_free(self,
                        input,
                        mode="exec",
                        filename=None,
                        logical_input=False):
        last_error_line = last_error_col = -1
        parsed = False
        original_error = None
        greedy = False
        if filename is None:
            filename = self.filename
        if logical_input:
            beg_spaces = starting_whitespace(input)
            input = input[len(beg_spaces):]
        while not parsed:
            try:
                tree = self.parser.parse(
                    input,
                    filename=filename,
                    mode=mode,
                    debug_level=(self.debug_level > 2),
                )
                parsed = True
            except IndentationError as e:
                if original_error is None:
                    raise e
                else:
                    raise original_error
            except SyntaxError as e:
                if original_error is None:
                    original_error = e
                if (e.loc is None) or (last_error_line == e.loc.lineno
                                       and last_error_col
                                       in (e.loc.column + 1, e.loc.column)):
                    raise original_error from None
                elif last_error_line != e.loc.lineno:
                    original_error = e
                last_error_col = e.loc.column
                last_error_line = e.loc.lineno
                idx = last_error_line - 1
                lines = input.splitlines()
                line, nlogical, idx = get_logical_line(lines, idx)
                if nlogical > 1 and not logical_input:
                    _, sbpline = self._parse_ctx_free(line,
                                                      mode=mode,
                                                      filename=filename,
                                                      logical_input=True)
                    self._print_debug_wrapping(line,
                                               sbpline,
                                               last_error_line,
                                               last_error_col,
                                               maxcol=None)
                    replace_logical_line(lines, sbpline, idx, nlogical)
                    last_error_col += 3
                    input = "\n".join(lines)
                    continue
                if input.endswith("\n"):
                    lines.append("")
                if len(line.strip()) == 0:
                    # whitespace only lines are not valid syntax in Python's
                    # interactive mode='single', who knew?! Just ignore them.
                    # this might cause actual syntax errors to have bad line
                    # numbers reported, but should only affect interactive mode
                    del lines[idx]
                    last_error_line = last_error_col = -1
                    input = "\n".join(lines)
                    continue

                if last_error_line > 1 and lines[idx - 1].rstrip()[-1:] == ":":
                    # catch non-indented blocks and raise error.
                    prev_indent = len(lines[idx - 1]) - len(
                        lines[idx - 1].lstrip())
                    curr_indent = len(lines[idx]) - len(lines[idx].lstrip())
                    if prev_indent == curr_indent:
                        raise original_error
                lexer = self.parser.lexer
                maxcol = (None if greedy else find_next_break(
                    line, mincol=last_error_col, lexer=lexer))
                if not greedy and maxcol in (e.loc.column + 1, e.loc.column):
                    # go greedy the first time if the syntax error was because
                    # we hit an end token out of place. This usually indicates
                    # a subshell or maybe a macro.
                    if not balanced_parens(line, maxcol=maxcol):
                        greedy = True
                        maxcol = None
                sbpline = subproc_toks(line,
                                       returnline=True,
                                       greedy=greedy,
                                       maxcol=maxcol,
                                       lexer=lexer)
                if sbpline is None:
                    # subprocess line had no valid tokens,
                    if len(line.partition("#")[0].strip()) == 0:
                        # likely because it only contained a comment.
                        del lines[idx]
                        last_error_line = last_error_col = -1
                        input = "\n".join(lines)
                        continue
                    elif not greedy:
                        greedy = True
                        continue
                    else:
                        # or for some other syntax error
                        raise original_error
                elif sbpline[last_error_col:].startswith(
                        "![![") or sbpline.lstrip().startswith("![!["):
                    # if we have already wrapped this in subproc tokens
                    # and it still doesn't work, adding more won't help
                    # anything
                    if not greedy:
                        greedy = True
                        continue
                    else:
                        raise original_error
                # replace the line
                self._print_debug_wrapping(line,
                                           sbpline,
                                           last_error_line,
                                           last_error_col,
                                           maxcol=maxcol)
                replace_logical_line(lines, sbpline, idx, nlogical)
                last_error_col += 3
                input = "\n".join(lines)
        if logical_input:
            input = beg_spaces + input
        return tree, input
Exemplo n.º 9
0
class Execer(object):
    """Executes xonsh code in a context."""
    def __init__(self,
                 filename='<xonsh-code>',
                 debug_level=0,
                 parser_args=None,
                 unload=True,
                 config=None,
                 login=True,
                 xonsh_ctx=None):
        """Parameters
        ----------
        filename : str, optional
            File we are to execute.
        debug_level : int, optional
            Debugging level to use in lexing and parsing.
        parser_args : dict, optional
            Arguments to pass down to the parser.
        unload : bool, optional
            Whether or not to unload xonsh builtins upon deletion.
        config : str, optional
            Path to configuration file.
        xonsh_ctx : dict or None, optional
            Xonsh xontext to load as builtins.__xonsh_ctx__
        """
        parser_args = parser_args or {}
        self.parser = Parser(**parser_args)
        self.filename = filename
        self.debug_level = debug_level
        self.unload = unload
        self.ctxtransformer = CtxAwareTransformer(self.parser)
        load_builtins(execer=self, config=config, login=login, ctx=xonsh_ctx)

    def __del__(self):
        if self.unload:
            unload_builtins()

    def parse(self, input, ctx, mode='exec', transform=True):
        """Parses xonsh code in a context-aware fashion. For context-free
        parsing, please use the Parser class directly or pass in
        transform=False.
        """
        if not transform:
            return self.parser.parse(input,
                                     filename=self.filename,
                                     mode=mode,
                                     debug_level=(self.debug_level > 1))

        # Parsing actually happens in a couple of phases. The first is a
        # shortcut for a context-free parser. Normally, all subprocess
        # lines should be wrapped in $(), to indicate that they are a
        # subproc. But that would be super annoying. Unfortnately, Python
        # mode - after indentation - is whitespace agnostic while, using
        # the Python token, subproc mode is whitespace aware. That is to say,
        # in Python mode "ls -l", "ls-l", and "ls - l" all parse to the
        # same AST because whitespace doesn't matter to the minus binary op.
        # However, these phases all have very different meaning in subproc
        # mode. The 'right' way to deal with this is to make the entire
        # grammar whitespace aware, and then ignore all of the whitespace
        # tokens for all of the Python rules. The lazy way implemented here
        # is to parse a line a second time with a $() wrapper if it fails
        # the first time. This is a context-free phase.
        tree, input = self._parse_ctx_free(input, mode=mode)
        if tree is None:
            return None

        # Now we need to perform context-aware AST transformation. This is
        # because the "ls -l" is valid Python. The only way that we know
        # it is not actually Python is by checking to see if the first token
        # (ls) is part of the execution context. If it isn't, then we will
        # assume that this line is supposed to be a subprocess line, assuming
        # it also is valid as a subprocess line.
        if ctx is None:
            ctx = set()
        elif isinstance(ctx, abc.Mapping):
            ctx = set(ctx.keys())
        tree = self.ctxtransformer.ctxvisit(tree, input, ctx, mode=mode)
        return tree

    def compile(self,
                input,
                mode='exec',
                glbs=None,
                locs=None,
                stacklevel=2,
                filename=None,
                transform=True):
        """Compiles xonsh code into a Python code object, which may then
        be execed or evaled.
        """
        if filename is None:
            filename = self.filename
        if glbs is None or locs is None:
            frame = inspect.stack()[stacklevel][0]
            glbs = frame.f_globals if glbs is None else glbs
            locs = frame.f_locals if locs is None else locs
        ctx = set(dir(builtins)) | set(glbs.keys()) | set(locs.keys())
        tree = self.parse(input, ctx, mode=mode, transform=transform)
        if tree is None:
            return None  # handles comment only input
        if transform:
            with warnings.catch_warnings():
                # we do some funky things with blocks that cause warnings
                warnings.simplefilter('ignore', SyntaxWarning)
                code = compile(tree, filename, mode)
        else:
            code = compile(tree, filename, mode)
        return code

    def eval(self, input, glbs=None, locs=None, stacklevel=2, transform=True):
        """Evaluates (and returns) xonsh code."""
        if isinstance(input, types.CodeType):
            code = input
        else:
            code = self.compile(input=input,
                                glbs=glbs,
                                locs=locs,
                                mode='eval',
                                stacklevel=stacklevel,
                                transform=transform)
        if code is None:
            return None  # handles comment only input
        return eval(code, glbs, locs)

    def exec(self,
             input,
             mode='exec',
             glbs=None,
             locs=None,
             stacklevel=2,
             transform=True):
        """Execute xonsh code."""
        if isinstance(input, types.CodeType):
            code = input
        else:
            code = self.compile(input=input,
                                glbs=glbs,
                                locs=locs,
                                mode=mode,
                                stacklevel=stacklevel,
                                transform=transform)
        if code is None:
            return None  # handles comment only input
        return exec(code, glbs, locs)

    def _parse_ctx_free(self, input, mode='exec'):
        last_error_line = last_error_col = -1
        parsed = False
        original_error = None
        while not parsed:
            try:
                tree = self.parser.parse(input,
                                         filename=self.filename,
                                         mode=mode,
                                         debug_level=(self.debug_level > 1))
                parsed = True
            except IndentationError as e:
                if original_error is None:
                    raise e
                else:
                    raise original_error
            except SyntaxError as e:
                if original_error is None:
                    original_error = e
                if (e.loc is None) or (last_error_line == e.loc.lineno
                                       and last_error_col
                                       in (e.loc.column + 1, e.loc.column)):
                    raise original_error
                last_error_col = e.loc.column
                last_error_line = e.loc.lineno
                idx = last_error_line - 1
                lines = input.splitlines()
                line = lines[idx]
                if input.endswith('\n'):
                    lines.append('')
                if len(line.strip()) == 0:
                    # whitespace only lines are not valid syntax in Python's
                    # interactive mode='single', who knew?! Just ignore them.
                    # this might cause actual sytax errors to have bad line
                    # numbers reported, but should only effect interactive mode
                    del lines[idx]
                    last_error_line = last_error_col = -1
                    input = '\n'.join(lines)
                    continue

                if last_error_line > 1 and lines[idx - 1].rstrip()[-1:] == ':':
                    # catch non-indented blocks and raise error.
                    prev_indent = len(lines[idx - 1]) - len(
                        lines[idx - 1].lstrip())
                    curr_indent = len(lines[idx]) - len(lines[idx].lstrip())
                    if prev_indent == curr_indent:
                        raise original_error
                lexer = self.parser.lexer
                maxcol = find_next_break(line,
                                         mincol=last_error_col,
                                         lexer=lexer)
                sbpline = subproc_toks(line,
                                       returnline=True,
                                       maxcol=maxcol,
                                       lexer=lexer)
                if sbpline is None:
                    # subprocess line had no valid tokens,
                    if len(line.partition('#')[0].strip()) == 0:
                        # likely because it only contained a comment.
                        del lines[idx]
                        last_error_line = last_error_col = -1
                        input = '\n'.join(lines)
                        continue
                    else:
                        # or for some other syntax error
                        raise original_error
                elif sbpline[last_error_col:].startswith('![![') or \
                     sbpline.lstrip().startswith('![!['):
                    # if we have already wrapped this in subproc tokens
                    # and it still doesn't work, adding more won't help
                    # anything
                    raise original_error
                else:
                    if self.debug_level:
                        msg = ('{0}:{1}:{2}{3} - {4}\n' '{0}:{1}:{2}{3} + {5}')
                        mstr = '' if maxcol is None else ':' + str(maxcol)
                        msg = msg.format(self.filename, last_error_line,
                                         last_error_col, mstr, line, sbpline)
                        print(msg, file=sys.stderr)
                    lines[idx] = sbpline
                last_error_col += 3
                input = '\n'.join(lines)
        return tree, input