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
|
import os
import textwrap
from dataclasses import dataclass
from typing import Iterable
from sybil import Document, Region, Example
from sybil.parsers.rest.lexers import DirectiveLexer
from testfixtures import diff
@dataclass
class FileBlock:
path: str
content: str
action: str
class FileParser:
"""
A `Sybil <http://sybil.readthedocs.io>`__ parser that
parses certain ReST sections to read and write files in the
configured :class:`~testfixtures.TempDirectory`.
:param name: This is the name of the :class:`~testfixtures.TempDirectory` to use
in the Sybil test namespace.
"""
def __init__(self, name: str):
self.name = name
self.lexer = DirectiveLexer('topic', arguments='.+')
def __call__(self, document: Document) -> Iterable[Region]:
for region in self.lexer(document):
options = region.lexemes.get('options')
if options is not None:
class_ = options.get('class')
if class_ in ('read-file', 'write-file'):
lines = region.lexemes['source'].splitlines(keepends=True)
index = 0
if lines[index].strip() == '::':
index += 1
source = textwrap.dedent(''.join(lines[index:])).lstrip()
if source[-1] != '\n':
source += '\n'
region.parsed = FileBlock(
path=region.lexemes['arguments'],
content=source,
action=class_.split('-')[0]
)
region.evaluator = self.evaluate
yield region
def evaluate(self, example: Example) -> str | None:
block: FileBlock = example.parsed
temp_directory = example.namespace[self.name]
if block.action == 'read':
actual = temp_directory.as_path(block.path).read_text().replace(os.linesep, '\n')
if actual != block.content:
return diff(
block.content,
actual,
'File %r, line %i:' % (example.path, example.line),
'Reading from "%s":' % temp_directory.as_string(block.path)
)
if block.action == 'write':
temp_directory.write(block.path, block.content)
return None
|