diff --git a/.gitignore b/.gitignore index 9e796c5..cafa79e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ parsing_profile sphinxcontrib_doxylink.egg-info/ dist/ build/ +examples/my_lib.tag diff --git a/doc/index.rst b/doc/index.rst index 88ea0f8..a62ac0a 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -180,6 +180,13 @@ Configuration values 'qtogre_doxygen.pdf': '/home/matt/qtogre/doxygen.pdf', } +.. confval:: doxylink_parse_error_ignore_regexes + + A list of regular expressions that can be used to ignore specific errors reported from the parser. + Default is ``[]``. This is useful if you have a lot of errors that you know are not important. + For example, you may want to ignore errors related to a specific namespace. + The regular expression is matched against the error message using Python's + `re.search `_ function. Bug reports ----------- diff --git a/examples/conf.py b/examples/conf.py index 3127ffa..fffeb08 100644 --- a/examples/conf.py +++ b/examples/conf.py @@ -10,5 +10,6 @@ doxylink = { 'my_lib': (os.path.abspath('./my_lib.tag'), 'https://examples.com/'), } +doxylink_parse_error_ignore_regexes = [r"DEFINE.*"] master_doc = 'index' diff --git a/examples/my_lib.h b/examples/my_lib.h index 89c774d..fff1d8b 100644 --- a/examples/my_lib.h +++ b/examples/my_lib.h @@ -46,3 +46,6 @@ enum Color { red, green, blue }; // An enum class enum class Color_c { red, green, blue }; + +// A function that triggers a warning from the parser +void DEFINE_bool(show, false, "Enable visualization"); diff --git a/sphinxcontrib/doxylink/__init__.py b/sphinxcontrib/doxylink/__init__.py index a920715..5cae1bc 100644 --- a/sphinxcontrib/doxylink/__init__.py +++ b/sphinxcontrib/doxylink/__init__.py @@ -1,10 +1,11 @@ __version__ = "1.12.4" - def setup(app): from .doxylink import setup_doxylink_roles app.add_config_value('doxylink', {}, 'env') app.add_config_value('doxylink_pdf_files', {}, 'env') + app.add_config_value('doxylink_parse_error_ignore_regexes', + default=[], types=[str], rebuild='env') app.connect('builder-inited', setup_doxylink_roles) return { diff --git a/sphinxcontrib/doxylink/doxylink.py b/sphinxcontrib/doxylink/doxylink.py index 9a5411b..3a73393 100644 --- a/sphinxcontrib/doxylink/doxylink.py +++ b/sphinxcontrib/doxylink/doxylink.py @@ -134,8 +134,8 @@ def is_url(str_to_validate: str) -> bool: class SymbolMap: """A SymbolMap maps symbols to Entries.""" - def __init__(self, xml_doc: ET.ElementTree) -> None: - entries = parse_tag_file(xml_doc) + def __init__(self, xml_doc: ET.ElementTree, parse_error_ignore_regexes: Optional[List[str]] = None) -> None: + entries = parse_tag_file(xml_doc, parse_error_ignore_regexes) # Sort the entry list for use with bisect self._entries = sorted(entries) @@ -225,7 +225,7 @@ def __getitem__(self, item: str) -> Entry: return self._disambiguate(symbol, candidates) -def parse_tag_file(doc: ET.ElementTree) -> List[Entry]: +def parse_tag_file(doc: ET.ElementTree, parse_error_ignore_regexes: Optional[List[str]]) -> List[Entry]: """ Takes in an XML tree from a Doxygen tag file and returns a list that looks something like: @@ -290,7 +290,22 @@ def parse_tag_file(doc: ET.ElementTree) -> List[Entry]: entries.append( Entry(name=member_symbol, kind=member_kind, file=member_file, arglist=normalised_arglist)) except ParseException as e: - print(f'Skipping {member_kind} {member_symbol}{arglist}. Error reported from parser was: {e}') + message = f'Skipping {member_kind} {member_symbol}{arglist}. Error reported from parser was: {e}' + should_report = True + + if parse_error_ignore_regexes: + for pattern in parse_error_ignore_regexes: + try: + if re.search(pattern, message): + should_report = False + break + except re.error: + # Invalid regex pattern - ignore it + continue + + if should_report: + report_warning(None, message) # Use None as env since we don't have access to it here + continue else: # Put the simple things directly into the list entries.append(Entry(name=member_symbol, kind=member_kind, file=member_file, arglist=None)) @@ -303,6 +318,11 @@ def join(*args): def create_role(app, tag_filename, rootdir, cache_name, pdf=""): + parse_error_ignore_regexes = getattr(app.config, 'doxylink_parse_error_ignore_regexes', []) + + if parse_error_ignore_regexes: + report_info(app.env, f'Using parse error ignore patterns: {", ".join(parse_error_ignore_regexes)}') + # Tidy up the root directory path if not rootdir.endswith(('/', '\\')): rootdir = join(rootdir, os.sep) @@ -330,22 +350,22 @@ def _parse(): if not hasattr(app.env, 'doxylink_cache'): # no cache present at all, initialise it report_info(app.env, 'No cache at all, rebuilding...') - mapping = SymbolMap(_parse()) + mapping = SymbolMap(_parse(), parse_error_ignore_regexes) app.env.doxylink_cache = {cache_name: {'mapping': mapping, 'mtime': modification_time, 'version': __version__}} elif not app.env.doxylink_cache.get(cache_name): # Main cache is there but the specific sub-cache for this tag file is not report_info(app.env, 'Sub cache is missing, rebuilding...') - mapping = SymbolMap(_parse()) + mapping = SymbolMap(_parse(), parse_error_ignore_regexes) app.env.doxylink_cache[cache_name] = {'mapping': mapping, 'mtime': modification_time, 'version': __version__} elif app.env.doxylink_cache[cache_name]['mtime'] < modification_time: # tag file has been modified since sub-cache creation report_info(app.env, 'Sub-cache is out of date, rebuilding...') - mapping = SymbolMap(_parse()) + mapping = SymbolMap(_parse(), parse_error_ignore_regexes) app.env.doxylink_cache[cache_name] = {'mapping': mapping, 'mtime': modification_time} elif not app.env.doxylink_cache[cache_name].get('version') or app.env.doxylink_cache[cache_name].get('version') != __version__: # sub-cache doesn't have a version or the version doesn't match report_info(app.env, 'Sub-cache schema version doesn\'t match, rebuilding...') - mapping = SymbolMap(_parse()) + mapping = SymbolMap(_parse(), parse_error_ignore_regexes) app.env.doxylink_cache[cache_name] = {'mapping': mapping, 'mtime': modification_time, 'version': __version__} else: # The cache is up to date diff --git a/tests/test_doxylink.py b/tests/test_doxylink.py index a0524e4..e386c9c 100644 --- a/tests/test_doxylink.py +++ b/tests/test_doxylink.py @@ -90,7 +90,7 @@ def test_file_different(examples_tag_file, symbol1, symbol2): def test_parse_tag_file(examples_tag_file): tag_file = ET.parse(examples_tag_file) - mapping = doxylink.parse_tag_file(tag_file) + mapping = doxylink.parse_tag_file(tag_file, None) def has_entry(name): """ @@ -197,3 +197,60 @@ def test_process_configuration_warn(rootdir, pdf_filename, builder, msg): with LogCapture() as l: doxylink.process_configuration(app, 'doxygen/project.tag', rootdir, pdf_filename) l.check(('sphinx.sphinxcontrib.doxylink.doxylink', 'WARNING', msg)) + + +def test_parse_error_ignore_regexes(): + # Create a modified tag file content with problematic entries + problematic_xml = """ + + + test.h + test_8h + + foo + (transform_pb2.Rotation2f a) + 1234 + + + bad + (*int i) + 5678 + + + baz + (int i, float f) + 9012 + + + unexpected + (*int i) + 5678 + + +""" + + # Write temporary tag file + test_tag_file = 'test_temp.tag' + with open(test_tag_file, 'w') as f: + f.write(problematic_xml) + + try: + tag_file = ET.parse(test_tag_file) + patterns = [r'kipping function test\.h::foo', r'kipping.*bad'] + + with LogCapture() as log: + mapping = doxylink.parse_tag_file(tag_file, patterns) + + # Verify that the mapping still contains valid entries + assert any(entry.name.endswith('baz') for entry in mapping) + + # Verify that messages matching our patterns were not logged + assert not any('test.h::foo' in record.msg for record in log.records) + assert not any('bar' in record.msg for record in log.records) + + # Verify other error messages were logged + assert any('Skipping' in record.msg for record in log.records) + + finally: + if os.path.exists(test_tag_file): + os.unlink(test_tag_file) diff --git a/tox.ini b/tox.ini index 26319f8..9ddb266 100644 --- a/tox.ini +++ b/tox.ini @@ -3,14 +3,14 @@ envlist = benchmark, test, examples, doc isolated_build = True [testenv:benchmark] -whitelist_externals = poetry +allowlist_externals = poetry commands= poetry install python tests/test_parser.py [testenv:examples] changedir = examples -whitelist_externals = +allowlist_externals = doxygen poetry commands= @@ -19,13 +19,13 @@ commands= sphinx-build -W -b html . {envtmpdir}/examples/_build [testenv:test] -whitelist_externals = poetry +allowlist_externals = poetry commands= poetry install pytest [testenv:doc] -whitelist_externals = poetry +allowlist_externals = poetry commands= poetry install sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees doc {envtmpdir}/linkcheck