Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-sybil for openSUSE:Factory checked in at 2026-01-28 15:06:38 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-sybil (Old) and /work/SRC/openSUSE:Factory/.python-sybil.new.1928 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-sybil" Wed Jan 28 15:06:38 2026 rev:22 rq:1329543 version:9.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-sybil/python-sybil.changes 2025-10-27 14:37:32.133051232 +0100 +++ /work/SRC/openSUSE:Factory/.python-sybil.new.1928/python-sybil.changes 2026-01-28 15:07:27.206766624 +0100 @@ -1,0 +2,12 @@ +Tue Jan 27 17:18:24 UTC 2026 - Dirk Müller <[email protected]> + +- update to 9.3.0: + * Add support for MyST code-cell :ref:`directives + <syntax/directives>`. + * Provide :func:`sybil.testing.check_sybil`, + :func:`sybil.testing.check_parser` and + :func:`sybil.testing.check_lexer` to help test custom lexers + and parsers. + * Add documentation around testing custom lexers and parsers. + +------------------------------------------------------------------- Old: ---- sybil-9.2.0.tar.gz New: ---- sybil-9.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-sybil.spec ++++++ --- /var/tmp/diff_new_pack.h2Mxa3/_old 2026-01-28 15:07:27.982798859 +0100 +++ /var/tmp/diff_new_pack.h2Mxa3/_new 2026-01-28 15:07:27.986799025 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-sybil # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -26,7 +26,7 @@ %endif %{?sle15_python_module_pythons} Name: python-sybil%{psuffix} -Version: 9.2.0 +Version: 9.3.0 Release: 0 Summary: Automated testing of examples in documentation License: MIT ++++++ sybil-9.2.0.tar.gz -> sybil-9.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/CHANGELOG.rst new/sybil-9.3.0/CHANGELOG.rst --- old/sybil-9.2.0/CHANGELOG.rst 2025-08-08 10:58:52.000000000 +0200 +++ new/sybil-9.3.0/CHANGELOG.rst 2025-12-02 10:27:14.000000000 +0100 @@ -1,6 +1,16 @@ Changes ======= +9.3.0 (2 Dec 2025) +------------------ + +- Add support for MyST ``code-cell`` :ref:`directives <syntax/directives>`. + +- Provide :func:`sybil.testing.check_sybil`, :func:`sybil.testing.check_parser` and + :func:`sybil.testing.check_lexer` to help test custom lexers and parsers. + +- Add documentation around testing custom lexers and parsers. + 9.2.0 (8 Aug 2025) ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/conftest.py new/sybil-9.3.0/conftest.py --- old/sybil-9.2.0/conftest.py 2025-08-08 10:58:52.000000000 +0200 +++ new/sybil-9.3.0/conftest.py 2025-12-02 10:27:14.000000000 +0100 @@ -26,9 +26,12 @@ def _find_python_files() -> List[Tuple[Path, str]]: paths = [] - for path in Path(__file__).parent.rglob('*.py'): - source = Path(path).read_text() - paths.append((path, source)) + for root_path in Path(__file__).parent.iterdir(): + if root_path.name.startswith('.'): + continue + for path in root_path.rglob('*.py'): + source = Path(path).read_text() + paths.append((path, source)) return paths diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/docs/api.rst new/sybil-9.3.0/docs/api.rst --- old/sybil-9.2.0/docs/api.rst 2025-08-08 10:58:52.000000000 +0200 +++ new/sybil-9.3.0/docs/api.rst 2025-12-02 10:27:14.000000000 +0100 @@ -159,3 +159,17 @@ .. autoclass:: sybil.evaluators.doctest.DocTestEvaluator .. autoclass:: sybil.evaluators.python.PythonEvaluator + + +Testing +------- + +Meta-testing of components you've written to work with Sybil. +See :doc:`parsers`. + +.. autofunction:: sybil.testing.check_sybil + +.. autofunction:: sybil.testing.check_parser + +.. autofunction:: sybil.testing.check_lexer + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/docs/examples/myst/codeblock-python-code-cell.md new/sybil-9.3.0/docs/examples/myst/codeblock-python-code-cell.md --- old/sybil-9.2.0/docs/examples/myst/codeblock-python-code-cell.md 1970-01-01 01:00:00.000000000 +0100 +++ new/sybil-9.3.0/docs/examples/myst/codeblock-python-code-cell.md 2025-12-02 10:27:14.000000000 +0100 @@ -0,0 +1,22 @@ +% invisible-code-block: python +% +% # This could be some state setup needed to demonstrate things +% initialized = True + +This fenced code block defines a function: + +```python + + def prefix(text: str) -> str: + return 'prefix: '+text +``` + +This MyST `code-cell` directive then uses it: + +```{code-cell} python + prefixed = prefix('some text') +``` + +<!--- invisible-code-block: python +assert prefixed == 'prefix: some text', prefixed +---> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/docs/markdown.rst new/sybil-9.3.0/docs/markdown.rst --- old/sybil-9.2.0/docs/markdown.rst 2025-08-08 10:58:52.000000000 +0200 +++ new/sybil-9.3.0/docs/markdown.rst 2025-12-02 10:27:14.000000000 +0100 @@ -10,7 +10,7 @@ __ https://commonmark.org/ -__ https://github.github.com/markdown/ +__ https://github.github.com/gfm/ .. _markdown-doctest-parser: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/docs/myst.rst new/sybil-9.3.0/docs/myst.rst --- old/sybil-9.2.0/docs/myst.rst 2025-08-08 10:58:52.000000000 +0200 +++ new/sybil-9.3.0/docs/myst.rst 2025-12-02 10:27:14.000000000 +0100 @@ -12,7 +12,8 @@ A selection of parsers are included that can extract and check doctest examples in ``python`` `fenced code blocks`__, -MyST ``code-block`` :ref:`directives <syntax/directives>` and +MyST ``code-block`` :ref:`directives <syntax/directives>`, +MyST ``code-cell`` :ref:`directives <syntax/directives>`, and MyST ``doctest`` :ref:`directives <syntax/directives>`. __ https://spec.commonmark.org/0.30/#fenced-code-blocks @@ -137,16 +138,18 @@ ----------- The codeblock parsers extract examples from `fenced code blocks`__, -MyST ``code-block`` :ref:`directives <syntax/directives>` and "invisible" +MyST ``code-block`` :ref:`directives <syntax/directives>`, MyST ``code-cell`` +:ref:`directives <syntax/directives>` (used by `mystmd`__), and "invisible" code blocks in both styles of Markdown mult-line comment. __ https://spec.commonmark.org/0.30/#fenced-code-blocks +__ https://mystmd.org/ Python ~~~~~~ -Python examples can be checked in either ``python`` `fenced code blocks`__ or -MyST ``code-block`` :ref:`directives <syntax/directives>` using the +Python examples can be checked in either ``python`` `fenced code blocks`__, +MyST ``code-block``, or MyST ``code-cell`` :ref:`directives <syntax/directives>` using the :class:`sybil.parsers.myst.PythonCodeBlockParser`. __ https://spec.commonmark.org/0.30/#fenced-code-blocks @@ -173,6 +176,19 @@ from tests.helpers import check_path check_path('examples/myst/codeblock-python.md', sybil, expected=4) +The ``code-cell`` directive, which is used by `mystmd`__ for Jupyter notebook integration, +is also supported: + +__ https://mystmd.org/ + +.. literalinclude:: examples/myst/codeblock-python-code-cell.md + :language: markdown + +.. invisible-code-block: python + + from tests.helpers import check_path + check_path('examples/myst/codeblock-python-code-cell.md', sybil, expected=4) + .. _myst-codeblock-other: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/docs/parsers.rst new/sybil-9.3.0/docs/parsers.rst --- old/sybil-9.2.0/docs/parsers.rst 2025-08-08 10:58:52.000000000 +0200 +++ new/sybil-9.3.0/docs/parsers.rst 2025-12-02 10:27:14.000000000 +0100 @@ -64,15 +64,15 @@ actual = check_output(command).strip().decode('ascii') assert actual == expected, repr(actual) + ' != ' + repr(expected) - parser = CodeBlockParser(language='bash', evaluator=evaluate_bash_block) + bash_parser = CodeBlockParser(language='bash', evaluator=evaluate_bash_block) - sybil = Sybil(parsers=[parser], pattern='*.rst') + sybil = Sybil(parsers=[bash_parser], pattern='*.rst') .. invisible-code-block: python - from tests.helpers import check_text - check_text(bash_document_text, sybil) + from sybil.testing import check_sybil + check_sybil(sybil, bash_document_text) Another alternative would be to start with the :class:`lexer for ReST directives <sybil.parsers.rest.lexers.DirectiveLexer>`. @@ -104,8 +104,8 @@ .. invisible-code-block: python - from tests.helpers import check_text - check_text(bash_document_text, sybil) + from sybil.testing import check_sybil + check_sybil(sybil, bash_document_text) .. _parser-from-scratch: @@ -140,5 +140,162 @@ .. invisible-code-block: python - from tests.helpers import check_text - check_text(bash_document_text, sybil) + from sybil.testing import check_sybil + check_sybil(sybil, bash_document_text) + +Of course, you should also write tests for your parser, showing it both succeeding and failing. +Here are examples for the Bash parser implementation at the start of this section, making use +of :func:`~sybil.testing.check_parser` to check a single example in a string against the supplied +:data:`~sybil.typing.Parser`: + +.. code-block:: python + + from sybil.testing import check_parser + from testfixtures import ShouldAssert + + def test_bash_success() -> None: + check_parser( + bash_parser, + text=""" + .. code-block:: bash + + $ echo hi there + hi there + """, + ) + + def test_bash_failure() -> None: + with ShouldAssert("'this is wrong' != 'hi there'"): + check_parser( + bash_parser, + text=""" + .. code-block:: bash + + $ echo this is wrong + hi there + """, + ) + +.. invisible-code-block: python + + test_bash_success() + test_bash_failure() + +Developing with Lexers +~~~~~~~~~~~~~~~~~~~~~~ + +Sybil has a fairly rich selection of :term:`parsers <Parser>` and :term:`lexers <Lexer>` such that +even if your source format isn't directly supported, you may not have too much work to do in order +to support it. + +Take `Docusaurus code blocks`__, which add parameters to Markdown fenced code blocks. Suppose we +want to implement a parser which will execute Python code blocks in this format: + +.. code-block:: markdown + + ```python title="hello.py" + print("hello") + ``` + +__ https://docusaurus.io/docs/markdown-features/code-blocks + +Firstly, let's implement a lexer that understands this extension to the markdown format: + +.. code-block:: python + + from sybil.parsers.markdown.lexers import RawFencedCodeBlockLexer + + class DocusaurusCodeBlockLexer(RawFencedCodeBlockLexer): + + def __init__(self) -> None: + super().__init__( + info_pattern=re.compile( + r'^(?P<language>\w+)(?:\s+(?P<params>.+))?$\n', re.MULTILINE + ), + ) + + def __call__(self, document: Document) -> Iterable[Region]: + for lexed in super().__call__(document): + lexemes = lexed.lexemes + raw_params = lexemes.pop('params', None) + params = lexemes['params'] = {} + if raw_params: + for match in re.finditer(r'(?P<key>\w+)="(?P<value>[^"]*)"', raw_params): + params[match.group('key')] = match.group('value') + yield lexed + +We can write a unit test that verifies this lexer works as follows: + +.. code-block:: python + + from sybil import Region + from sybil.testing import check_lexer + + def test_docusaurus_lexing() -> None: + regions = check_lexer( + lexer=DocusaurusCodeBlockLexer(), + source_text=""" + ```jsx title="/src/components/HelloCodeTitle.js" + function HelloCodeTitle(props) { + return <h1>Hello, {props.name}</h1>; + } + ``` + """, + expected_text=( + ' ```jsx title="/src/components/HelloCodeTitle.js"\n' + ' function HelloCodeTitle(props) {\n' + ' return <h1>Hello, {props.name}</h1>;\n' + ' }\n ```' + ), + expected_lexemes={ + 'language': 'jsx', + 'params': {'title': '/src/components/HelloCodeTitle.js'}, + 'source': ( + 'function HelloCodeTitle(props) {\n' + ' return <h1>Hello, {props.name}</h1>;\n}' + '\n' + ), + } + ) + +.. invisible-code-block: python + + test_docusaurus_lexing() + +Once we're confident that the lexer is working as required, we can use it with the existing +:class:`~sybil.parsers.abstract.codeblock.AbstractCodeBlockParser` as follows: + +.. code-block:: python + + from sybil.evaluators.python import PythonEvaluator + from sybil.parsers.abstract.codeblock import AbstractCodeBlockParser + + class DocusaurusCodeBlockParser(AbstractCodeBlockParser): + def __init__(self) -> None: + super().__init__( + lexers=[DocusaurusCodeBlockLexer()], + language='python', + evaluator=PythonEvaluator(), + language_lexeme_name = 'language', + ) + +This can then be tested as follows: + +.. code-block:: python + + from sybil.testing import check_parser + + def test_docusaurus_parsing() -> None: + document = check_parser( + DocusaurusCodeBlockParser(), + text=""" + ```python title="hello.py" + x = 1 + ``` + """, + ) + assert document.namespace['x'] == 1 + +.. invisible-code-block: python + + test_docusaurus_parsing() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/docs/rest.rst new/sybil-9.3.0/docs/rest.rst --- old/sybil-9.2.0/docs/rest.rst 2025-08-08 10:58:52.000000000 +0200 +++ new/sybil-9.3.0/docs/rest.rst 2025-12-02 10:27:14.000000000 +0100 @@ -177,8 +177,8 @@ .. invisible-code-block: python - from tests.helpers import check_text - check_text(bash_document_text, sybil) + from sybil.testing import check_sybil + check_sybil(sybil, bash_document_text) Alternatively, we can create our own parser class and use it as follows: @@ -203,8 +203,8 @@ .. invisible-code-block: python - from tests.helpers import check_text - check_text(bash_document_text, sybil) + from sybil.testing import check_sybil + check_sybil(sybil, bash_document_text) .. _capture-parser: @@ -243,7 +243,7 @@ .. invisible-code-block: python - document = check_text(capture_example, sybil) + document = check_sybil(sybil, capture_example) expected_listing = document.namespace['expected_listing'] The above documentation source, when parsed by this parser and then evaluated, @@ -271,7 +271,7 @@ .. invisible-code-block: python - document = check_text(capture_example, sybil) + document = check_sybil(sybil, capture_example) json_source = document.namespace['json_source'] The JSON source can now be used as follows: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/setup.py new/sybil-9.3.0/setup.py --- old/sybil-9.2.0/setup.py 2025-08-08 10:58:52.000000000 +0200 +++ new/sybil-9.3.0/setup.py 2025-12-02 10:27:14.000000000 +0100 @@ -11,7 +11,7 @@ setup( name='sybil', - version='9.2.0', + version='9.3.0', author='Chris Withers', author_email='[email protected]', license='MIT', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/sybil/parsers/abstract/codeblock.py new/sybil-9.3.0/sybil/parsers/abstract/codeblock.py --- old/sybil-9.2.0/sybil/parsers/abstract/codeblock.py 2025-08-08 10:58:52.000000000 +0200 +++ new/sybil-9.3.0/sybil/parsers/abstract/codeblock.py 2025-12-02 10:27:14.000000000 +0100 @@ -36,12 +36,14 @@ lexers: Sequence[Lexer], language: Optional[str] = None, evaluator: Optional[Evaluator] = None, + language_lexeme_name: str = 'arguments', ) -> None: self.lexers = LexerCollection(lexers) if language is not None: self.language = language assert self.language, 'language must be specified!' self._evaluator: Optional[Evaluator] = evaluator + self._language_lexeme_name = language_lexeme_name def evaluate(self, example: Example) -> Optional[str]: """ @@ -52,7 +54,7 @@ def __call__(self, document: Document) -> Iterable[Region]: for region in self.lexers(document): - if region.lexemes['arguments'] == self.language: + if region.lexemes[self._language_lexeme_name] == self.language: region.parsed = region.lexemes['source'] region.evaluator = self._evaluator or self.evaluate yield region diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/sybil/parsers/myst/codeblock.py new/sybil-9.3.0/sybil/parsers/myst/codeblock.py --- old/sybil-9.2.0/sybil/parsers/myst/codeblock.py 2025-08-08 10:58:52.000000000 +0200 +++ new/sybil-9.3.0/sybil/parsers/myst/codeblock.py 2025-12-02 10:27:14.000000000 +0100 @@ -31,7 +31,7 @@ mapping={'language': 'arguments', 'source': 'source'}, ), DirectiveLexer( - directive=r'(sourcecode|code-block|code)', + directive=r'(sourcecode|code-block|code-cell|code)', arguments='.+', ), DirectiveInPercentCommentLexer( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/sybil/testing.py new/sybil-9.3.0/sybil/testing.py --- old/sybil-9.2.0/sybil/testing.py 1970-01-01 01:00:00.000000000 +0100 +++ new/sybil-9.3.0/sybil/testing.py 2025-12-02 10:27:14.000000000 +0100 @@ -0,0 +1,49 @@ +from pathlib import Path + +from tempfile import NamedTemporaryFile + +from sybil import Sybil, Document +from sybil.typing import Parser, Lexer, LexemeMapping + + +def check_sybil(sybil: Sybil, text: str) -> Document: + """ + Run the supplied text through the supplied Sybil instance and evaluate the single + example it contains. + """ + with NamedTemporaryFile() as temp: + temp.write(text.encode()) + temp.flush() + document = sybil.parse(Path(temp.name)) + examples = list(document.examples()) + assert len(examples) == 1, f'Expected exactly one example, got: {examples}' + examples[0].evaluate() + return document + + +def check_parser(parser: Parser, text: str) -> Document: + """ + Run the supplied text through the supplied parser and evaluate the single + example it contains. + + This is for testing :data:`~sybil.typing.Parser` implementations. + """ + sybil = Sybil(parsers=[parser], pattern='*') + return check_sybil(sybil, text) + + +def check_lexer( + lexer: Lexer, source_text: str, expected_text: str, expected_lexemes: LexemeMapping +) -> None: + """ + Run the supplied text through the supplied lexer make sure it lexes a single + region and captures the expected text and lexemes. + + This is for testing :data:`~sybil.typing.Lexer` implementations. + """ + document = Document(source_text, 'sample.txt') + regions = list(lexer(document)) + assert len(regions) == 1, f'Expected exactly one region, got: {regions}' + region = regions[0] + assert expected_text == document.text[region.start:region.end] + assert region.lexemes == expected_lexemes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/tests/helpers.py new/sybil-9.3.0/tests/helpers.py --- old/sybil-9.2.0/tests/helpers.py 2025-08-08 10:58:52.000000000 +0200 +++ new/sybil-9.3.0/tests/helpers.py 2025-12-02 10:27:14.000000000 +0100 @@ -5,7 +5,6 @@ from os.path import dirname, join from pathlib import Path from shutil import copytree -from tempfile import NamedTemporaryFile from textwrap import dedent from traceback import TracebackException from typing import Optional, Tuple, List, Union @@ -67,7 +66,7 @@ def check_lexed_text_regions(text: str, lexer: Lexer, *, expected: List[Region]) -> None: document = Document(text, 'sample.txt') - actual =list(lexer(document)) + actual = list(lexer(document)) compare( expected=region_details(document, expected), actual=region_details(document, actual), @@ -102,16 +101,6 @@ compare(expected=expected_skips, actual=actual_skips) -def check_text(text: str, sybil: Sybil): - with NamedTemporaryFile() as temp: - temp.write(text.encode('ascii')) - temp.flush() - document = sybil.parse(Path(temp.name)) - (example,) = document - example.evaluate() - return document - - def check_tree(expected: str, path: str): raw = seedir( DOCS / path, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/tests/samples/myst-code-cell.md new/sybil-9.3.0/tests/samples/myst-code-cell.md --- old/sybil-9.2.0/tests/samples/myst-code-cell.md 1970-01-01 01:00:00.000000000 +0100 +++ new/sybil-9.3.0/tests/samples/myst-code-cell.md 2025-12-02 10:27:14.000000000 +0100 @@ -0,0 +1,6 @@ +This is a code-cell block with an extra flag: + +```{code-cell} python +:hide-code: +assert 1 + 1 == 2 +``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/tests/test_myst_codeblock.py new/sybil-9.3.0/tests/test_myst_codeblock.py --- old/sybil-9.2.0/tests/test_myst_codeblock.py 2025-08-08 10:58:52.000000000 +0200 +++ new/sybil-9.3.0/tests/test_myst_codeblock.py 2025-12-02 10:27:14.000000000 +0100 @@ -210,3 +210,8 @@ def test_sourcecode_directive(): sybil = Sybil([PythonCodeBlockParser()]) check_path(sample_path('myst-sourcecode.md'), sybil, expected=1) + + +def test_code_cell_directive(): + sybil = Sybil([PythonCodeBlockParser()]) + check_path(sample_path('myst-code-cell.md'), sybil, expected=1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sybil-9.2.0/tests/test_testing.py new/sybil-9.3.0/tests/test_testing.py --- old/sybil-9.2.0/tests/test_testing.py 1970-01-01 01:00:00.000000000 +0100 +++ new/sybil-9.3.0/tests/test_testing.py 2025-12-02 10:27:14.000000000 +0100 @@ -0,0 +1,18 @@ +from testfixtures import ShouldAssert + +from sybil import Sybil +from sybil.testing import check_sybil, check_lexer + + +class TestCheckSybil: + + def test_no_matches(self): + with ShouldAssert("Expected exactly one example, got: []"): + check_sybil(Sybil([]), "") + + +class TestCheckLexer: + + def test_no_matches(self): + with ShouldAssert("Expected exactly one region, got: []"): + check_lexer(lambda _: [], "", expected_text="", expected_lexemes={})
