1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
|
import os
import re
import sys
import tempfile
from pathlib import Path
from unittest import mock
load_grammar_line = '_GRAMMAR_FILE = os.path.join(os.path.dirname(__file__), "Grammar.txt")'
def is_supported_black_version():
# I'm caring too much about internals to be confident
# about future compatibility
try:
import black # noqa
import blib2to3 # noqa
import importlib_resources # noqa
except ImportError:
return
return not black.COMPILED and black.__version__ == "24.2.0"
def maybe_modify_black():
if os.environ.get("NOSE_OF_YETI_BLACK_COMPAT") == "true":
modify_black()
def replace_pygram():
from importlib.machinery import ModuleSpec, SourceFileLoader
from importlib.util import module_from_spec
import importlib_resources as resources
for k, m in list(sys.modules.items()):
if "blib2to3" in k or "black" in k:
del sys.modules[k]
with resources.as_file(
resources.files("noseOfYeti").joinpath("black", "Grammar.noy.txt")
) as noy_grammar_path:
if noy_grammar_path.exists():
location = Path(__import__("blib2to3").__file__).parent / "Grammar.noy.txt"
current = None
if location.exists():
with open(location) as crnt:
current = crnt.read()
with open(noy_grammar_path) as ngp:
content = ngp.read()
if content != current:
with open(location, "w") as fle:
fle.write(content)
with tempfile.TemporaryDirectory() as directory:
location = Path(directory) / "pygram.py"
with open(location, "w") as fle:
noy_grammar_line = (
'_GRAMMAR_FILE = os.path.join(os.path.dirname(__file__), "Grammar.noy.txt")'
)
fle.write(
(resources.files("blib2to3") / "pygram.py")
.read_text()
.replace(load_grammar_line, noy_grammar_line)
)
loader = SourceFileLoader("pygram", location)
mod = module_from_spec(ModuleSpec("pygram", loader))
mod.__file__ = str(resources.files("blib2to3").joinpath("pygram.py"))
mod.__package__ = "blib2to3"
loader.exec_module(mod)
sys.modules["blib2to3.pygram"] = mod
def modify_black(spec_codec=None):
"""
This will make it so calling black after this in the same session will
deal with noseOfYeti spec files.
"""
if not is_supported_black_version():
return
import blib2to3
from blib2to3 import pygram
if not str(pygram.__loader__.path).startswith(str(Path(blib2to3.__file__).parent)):
return
del pygram
del blib2to3
if spec_codec is None:
from noseOfYeti.tokeniser.spec_codec import codec, register
spec_codec = codec()
register(transform=False)
replace_pygram()
import black
from black.linegen import LineGenerator
from blib2to3.pgen2 import parse as blibparse
token = blibparse.token
Parser = blibparse.Parser
syms = black.nodes.syms
class ModifiedLineGenerator(LineGenerator):
def visit_setup_teardown_stmts(self, node):
yield from self.line()
yield from self.visit_default(node)
def visit_describe_stmt(self, node):
yield from self.line()
yield from self.visit_default(node)
def visit_it_stmt(self, node):
yield from self.line()
yield from self.visit_default(node)
class ModifiedParser(Parser):
def classify(self, type, value, context):
special = [
"it",
"ignore",
"context",
"describe",
"before_each",
"after_each",
]
if type == token.NAME and value in special:
dfa, state, node = self.stack[-1]
if node and node[-1]:
if node[-1][-1].type < 256 and node[-1][-1].type not in (
token.INDENT,
token.DEDENT,
token.NEWLINE,
token.ASYNC,
):
return [self.grammar.tokens.get(token.NAME)]
return super().classify(type, value, context)
original_assert_equivalent = black.assert_equivalent
def assert_equivalent(src, dst):
if src and re.match(r"#\s*coding\s*:\s*spec", src.split("\n")[0]):
src = spec_codec.translate(src, transform=True)
dst = spec_codec.translate(dst, transform=True)
original_assert_equivalent(src, dst)
original_is_parent_function_or_class = black.nodes.is_parent_function_or_class
def is_parent_function_or_class(node):
if node.type == syms.it_stmt:
assert node.parent is not None
return node.parent.type in {
syms.funcdef,
syms.classdef,
syms.describe_stmt,
syms.it_stmt,
}
else:
return original_is_parent_function_or_class(node)
original_is_function_or_class = black.nodes.is_function_or_class
def is_function_or_class(node):
if node.type in {syms.it_stmt, syms.describe_stmt}:
return True
return original_is_function_or_class(node)
mock.patch.object(
black.nodes, "is_parent_function_or_class", is_parent_function_or_class
).start()
mock.patch.object(black.nodes, "is_function_or_class", is_function_or_class).start()
blibparse.Parser = ModifiedParser
black.LineGenerator = ModifiedLineGenerator
black.assert_equivalent = assert_equivalent
|