1from sphinx.application import Sphinx 2from sphinx.environment import BuildEnvironment 3 4import os 5from typing import List, Set 6 7""" 8Store the modified time of the various doxygen xml files against the 9reStructuredText file that they are referenced from so that we know which 10reStructuredText files to rebuild if the doxygen xml is modified. 11 12We store the information in the environment object as 'breathe_file_state' 13so that it is pickled down and stored between builds as Sphinx is designed to do. 14 15(mypy doesn't like dynamically added attributes, hence all references to it are ignored) 16""" 17 18 19class MTimeError(Exception): 20 pass 21 22 23def _getmtime(filename: str): 24 try: 25 return os.path.getmtime(filename) 26 except OSError: 27 raise MTimeError('Cannot find file: %s' % os.path.realpath(filename)) 28 29 30def update(app: Sphinx, source_file: str) -> None: 31 if not hasattr(app.env, "breathe_file_state"): 32 app.env.breathe_file_state = {} # type: ignore 33 34 new_mtime = _getmtime(source_file) 35 mtime, docnames = app.env.breathe_file_state.setdefault( # type: ignore 36 source_file, (new_mtime, set())) 37 38 assert app.env is not None 39 docnames.add(app.env.docname) 40 41 app.env.breathe_file_state[source_file] = (new_mtime, docnames) # type: ignore 42 43 44def _get_outdated(app: Sphinx, env: BuildEnvironment, 45 added: Set[str], changed: Set[str], removed: Set[str]) -> List[str]: 46 if not hasattr(app.env, "breathe_file_state"): 47 return [] 48 49 stale = [] 50 for filename, info in app.env.breathe_file_state.items(): # type: ignore 51 old_mtime, docnames = info 52 if _getmtime(filename) > old_mtime: 53 stale.extend(docnames) 54 return list(set(stale).difference(removed)) 55 56 57def _purge_doc(app: Sphinx, env: BuildEnvironment, docname: str) -> None: 58 if not hasattr(app.env, "breathe_file_state"): 59 return 60 61 toremove = [] 62 for filename, info in app.env.breathe_file_state.items(): # type: ignore 63 _, docnames = info 64 docnames.discard(docname) 65 if not docnames: 66 toremove.append(filename) 67 68 for filename in toremove: 69 del app.env.breathe_file_state[filename] # type: ignore 70 71 72def setup(app: Sphinx): 73 app.connect("env-get-outdated", _get_outdated) 74 app.connect("env-purge-doc", _purge_doc) 75