Source code for efrotools.emacs

# Released under the MIT License. See LICENSE for details.
#
"""Stuff intended to be used from emacs"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING


if TYPE_CHECKING:
    pass


def _py_symbol_at_column(line: str, col: int) -> str:
    start = col
    while start > 0 and line[start - 1] != ' ':
        start -= 1
    end = col
    while end < len(line) and line[end] != ' ':
        end += 1
    return line[start:end]


[docs] def py_examine( projroot: Path, filename: Path, line: int, column: int, selection: str | None, operation: str, ) -> None: """Given file position info, performs some code inspection.""" # pylint: disable=too-many-positional-arguments # pylint: disable=too-many-locals # pylint: disable=cyclic-import import astroid import re from efrotools import code # Pull in our pylint plugin which really just adds astroid filters. # That way our introspection here will see the same thing as pylint's does. with open(filename, encoding='utf-8') as infile: fcontents = infile.read() if '#@' in fcontents: raise RuntimeError('#@ marker found in file; this breaks examinations.') flines = fcontents.splitlines() if operation == 'pylint_infer': # See what asteroid can infer about the target symbol. symbol = ( selection if selection is not None else _py_symbol_at_column(flines[line - 1], column) ) # Insert a line after the provided one which is just the symbol so # that we can ask for its value alone. match = re.match(r'\s*', flines[line - 1]) whitespace = match.group() if match is not None else '' sline = whitespace + symbol + ' #@' flines = flines[:line] + [sline] + flines[line:] node = astroid.extract_node('\n'.join(flines)) inferred = list(node.infer()) print(symbol + ':', ', '.join([str(i) for i in inferred])) elif operation in ('mypy_infer', 'mypy_locals'): # Ask mypy for the type of the target symbol. symbol = ( selection if selection is not None else _py_symbol_at_column(flines[line - 1], column) ) # Insert a line after the provided one which is just the symbol so # that we can ask for its value alone. match = re.match(r'\s*', flines[line - 1]) whitespace = match.group() if match is not None else '' if operation == 'mypy_infer': sline = whitespace + 'reveal_type(' + symbol + ')' else: sline = whitespace + 'reveal_locals()' flines = flines[:line] + [sline] + flines[line:] # Write a temp file and run the check on it. # Let's use ' flycheck_*' for the name since pipeline scripts # are already set to ignore those files. tmppath = Path(filename.parent, 'flycheck_mp_' + filename.name) with tmppath.open('w', encoding='utf-8') as outfile: outfile.write('\n'.join(flines)) try: code.mypy_files(projroot, [str(tmppath)], check=False) except Exception as exc: print('error running mypy:', exc) tmppath.unlink() elif operation == 'pylint_node': flines[line - 1] += ' #@' node = astroid.extract_node('\n'.join(flines)) print(node) elif operation == 'pylint_tree': flines[line - 1] += ' #@' node = astroid.extract_node('\n'.join(flines)) print(node.repr_tree()) else: print('unknown operation: ' + operation)
# Docs-generation hack; import some stuff that we likely only forward-declared # in our actual source code so that docs tools can find it. from typing import (Coroutine, Any, Literal, Callable, Generator, Awaitable, Sequence, Self) import asyncio from concurrent.futures import Future from pathlib import Path from enum import Enum