From dd9b039d025cffc7170523c18f602c200bb29ee2 Mon Sep 17 00:00:00 2001 From: sven Date: Fri, 8 Jun 2018 06:15:07 -0700 Subject: [PATCH] doxygenStripFromPath => doxygen.stripFromPath --- docs/conf.py | 21 ++- docs/reference/configs.rst | 12 +- exhale/__init__.py | 5 + exhale/configs.py | 260 ++++++++++++++++++------------------- exhale/deploy.py | 20 +-- exhale/graph.py | 4 +- 6 files changed, 155 insertions(+), 167 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 21311927..9dec3747 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -229,22 +229,31 @@ # Output file base name for HTML help builder. htmlhelp_basename = 'ExhaleDoc' -rst_epilog = "" - # convenience replacements for writing e.g. |containmentFolder| and linking to the # member of exhale.configs.Config.containmentFolder import itertools from exhale.configs import Config -config_replacements_list = [ + +configs_Config_repl_list = [ 'containmentFolder' # TODO: after migration, just use this # pack[0] for pack in itertools.chain(Config.REQUIRED_KV, Config.OPTIONAL_KV) ] -configs_replacements = "\n".join( +configs_Config_replacements = '\n'.join( '.. |{config}| replace:: :data:`~exhale.configs.Config.{config}`'.format(config=config) - for config in config_replacements_list + for config in configs_Config_repl_list +) +configs_DoxygenConfig_repl_list = [ + 'stripFromPath' +] +configs_DoxygenConfig_replacements = '\n'.join( + '.. |{config}| replace:: :data:`~exhale.configs.DoxygenConfig.{config}`'.format(config=config) + for config in configs_DoxygenConfig_repl_list ) -rst_epilog = "{0}\n{1}".format(rst_epilog, configs_replacements) +rst_epilog = '\n'.join([ + configs_Config_replacements, + configs_DoxygenConfig_replacements +]) def setup(app): diff --git a/docs/reference/configs.rst b/docs/reference/configs.rst index 1eb5f6b2..fb3c835d 100644 --- a/docs/reference/configs.rst +++ b/docs/reference/configs.rst @@ -10,6 +10,9 @@ Exhale Configs Module .. autoclass:: exhale.configs.Config :members: +.. autoclass:: exhale.configs.DoxygenConfig + :members: + .. _required_configs: Required Configuration Arguments @@ -77,8 +80,6 @@ Required Arguments for Exhale .. autodata:: exhale.configs.rootFileTitle -.. autodata:: exhale.configs.doxygenStripFromPath - Optional Configuration Arguments ---------------------------------------------------------------------------------------- @@ -310,8 +311,7 @@ something like this, where special treatment is given to File pages specifically .. tip:: - The value of :data:`~exhale.configs.doxygenStripFromPath` **directly** - affects what path is displayed here. + The value of |stripFromPath| **directly** affects what path is displayed here. .. danger:: @@ -395,8 +395,6 @@ something like this, where special treatment is given to File pages specifically .. autodata:: exhale.configs.pageLevelConfigMeta -.. autodata:: exhale.configs.repoRedirectURL - Using Contents Directives ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -630,7 +628,7 @@ get confused by. OUTPUT_DIRECTORY = {out} # Tell doxygen to strip the path names (RTD builds produce long abs paths...) STRIP_FROM_PATH = {strip} - '''.format(out=doxy_dir, strip=configs.doxygenStripFromPath)) + '''.format(out=doxy_dir, strip=config.doxygen.stripFromPath)) # The configurations you specified external_configs = textwrap.dedent(configs.exhaleDoxygenStdin) diff --git a/exhale/__init__.py b/exhale/__init__.py index 6569ae87..a63b3664 100644 --- a/exhale/__init__.py +++ b/exhale/__init__.py @@ -73,6 +73,11 @@ def _upgrade_doxygen(app): for from_key, to_key in mapping: _upgrade_flat_to_nested_dict(app, from_key, 'doxygen', to_key) +def _upgrade_root_file(app): + mapping = ( + '' + ) + def _auto_upgrade(app): _upgrade_doxygen(app) diff --git a/exhale/configs.py b/exhale/configs.py index 9292de97..24495cb1 100644 --- a/exhale/configs.py +++ b/exhale/configs.py @@ -74,7 +74,6 @@ def make_default_configs(): # "containmentFolder": None, # "rootFileName": None, # "rootFileTitle": None, - # "doxygenStripFromPath": None, # Build process logging, colors, and debugging "verboseBuild": False, "alwaysColorize": True, @@ -98,7 +97,6 @@ def make_default_configs(): # Page level customization "includeTemplateParamOrderList": False, "pageLevelConfigMeta": "", - "repoRedirectURL": "", # Contents directives "contentsDirectives": True, "contentsTitle": "Contents", @@ -110,17 +108,13 @@ def make_default_configs(): "exhaleExecutesDoxygen": True, "doxygen": { "silent": False - }, - "exhaleUseDoxyfile": False, - "exhaleDoxygenStdin": None, - "exhaleSilentDoxygen": False + } } # exhale_args = { # "doxygen": { -# "stripFromPath": "..", #doxygenStripFromPath # "doxyfile": "path", #exhaleUseDoxyfile # "stdin": "INPUT = ../include", #exhaleDoxygenStdin # "silent": False # exhale @@ -128,7 +122,94 @@ def make_default_configs(): # } class DoxygenConfig(object): + """ + The configurations required to run Doxygen. + + **Parameters** + + .. todo:: documentmeplz + + **Attributes** + + .. attr:: stripFromPath + :type: python:str + + The value to send to Doxygen via the ``STRIP_FROM_PATH`` configuration. This + value is **required** to be specified for **all** projects, *regardless* of + whether or not Exhale is executing Doxygen. It must be provided because Doxygen + seems to selectively ignore this (on Read the Docs, Travis, etc), so Exhale must + manually strip the path. + + The value should be a string representing the (relative or absolute) path to be + stripped from the final documentation. As with |containmentFolder|, relative + paths are relative to wherever ``conf.py`` is. Consider the following directory + structure: + + .. code-block:: none + + my_project/ + ├───docs/ + │ conf.py + │ + └───include/ + └───my_project/ + common.hpp + + In this scenario, if you supplied ``"stripFromPath": ".."``, then the file page + for ``common.hpp`` would list its declaration as + ``include/my_project/common.hpp``. If you instead set it to be ``"../include"``, + then the file page for ``common.hpp`` would list its declaration as just + ``my_project/common.hpp``. + + As a consequence, modification of this variable directly affects what shows up in + the file hierarchy. In the previous example, the difference would really just be + whether or not all files are nestled underneath a global ``include`` folder or + not. + + .. warning:: + + It is **your** responsibility to ensure that the value you provide for this + configuration is valid. The file hierarchy will almost certainly break if + you give nonsense. + + .. note:: + + Depending on your project layout, some links may be broken in the above + example if you use ``"../include"`` that work when you use ``".."``. To get + your docs working, revert to ``".."``. If you're feeling nice, raise an issue + on GitHub --- I haven't been able to track this one down yet :/ + + Particularly, this seems to happen with projects that have duplicate filenames + in different folders, e.g.: + + .. code-block:: none + + include/ + └───my_project/ + │ common.hpp + │ + └───viewing/ + common.hpp + """ + ALLOWED_PUBLIC_KV = [ + ('stripFromPath', six.string_types), + ('doxyfile', bool), + ('stdin', six.string_types), + ('silent', bool) + ] + """ + List of tuples of string keys to allowed type value for ``'doxygen'`` dictionary. + + Validated **externally** in :class:`exhale.configs.Config._validate_doxygen_configs` + in order to allow dictionary unpacking in ``__setattr__`` function. + """ + def __init__(self, outputDirectory=None, stripFromPath=None, doxyfile=None, stdin=None, silent=False, ignoreList=[]): + if not isinstance(outputDirectory, six.string_types): + raise ValueError('DoxygenConfig:outputDirectory parameter was not a string.') + if not os.path.isabs(outputDirectory): + raise ValueError('DoxygenConfig:outputDirectory parameter must be an absolute path.') + self.outputDirectory = outputDirectory self.xmlOutputDirectory = os.path.join(outputDirectory, "xml") self.stripFromPath = stripFromPath @@ -212,7 +293,6 @@ class Config(object): ("containmentFolder", six.string_types, True), ("rootFileName", six.string_types, False), ("rootFileTitle", six.string_types, False), - ("doxygenStripFromPath", six.string_types, True) ] # REQUIRED_KV = [("doxygen", dict)] @@ -241,7 +321,6 @@ class Config(object): # Page Level Customization ("includeTemplateParamOrderList", bool), ("pageLevelConfigMeta", six.string_types), - ("repoRedirectURL", six.string_types), ("contentsDirectives", bool), ("contentsTitle", six.string_types), ("contentsSpecifiers", list), @@ -290,7 +369,7 @@ def __init__(self, app, project_name): # Apply the project-specific configurations last and validate option configs configs = utils.deep_update(configs, project_configs) - self._validate_required_configs(configs) + self._validate_doxygen_configs(configs) self._validate_optional_configs(configs) # Absolute paths in Exhale are resolved against app.confdir (where `conf.py` is) @@ -301,7 +380,12 @@ def make_absolute(path): # Make sure paths are absolute for remainder of execution configs['containmentFolder'] = make_absolute(configs['containmentFolder']) - configs['doxygen']['stripFromPath'] = make_absolute(configs['doxygen']['stripFromPath']) + stripFromPath = make_absolute(configs['doxygen']['stripFromPath']) + if not os.path.exists(stripFromPath): + raise ConfigError( + '"stripFromPath" of [{0}] does not exist!'.format(stripFromPath) + ) + configs['doxygen']['stripFromPath'] = stripFromPath # At last, set all of the attributes for this configuration object for key in configs: @@ -327,13 +411,6 @@ def _value_error(self, key, expected, got): key=key, expected=expected, got=got ) - def _validate_required_configs(self, final_configs): - doxygen = final_configs.get('doxygen', None) - if not doxygen: - raise ValueError('doxygen key not provided, sub-key "stripFromPath" is required.') - - - def _validate_optional_configs(self, final_configs): for key, expected_type in Config.OPTIONAL_KV: # Override the default settings if the key was provided @@ -345,7 +422,6 @@ def _validate_optional_configs(self, final_configs): self.keys_processed.append(key) self._validate_contents_directives(final_configs) - self._validate_doxygen_configs(final_configs) def _validate_contents_directives(self, final_configs): # verify contentsSpecifiers can be used as expected @@ -369,31 +445,37 @@ def _validate_contents_directives(self, final_configs): self.keys_processed.append("kindsWithContentsDirectives") def _validate_doxygen_configs(self, final_configs): - exhaleExecutesDoxygen = final_configs["exhaleExecutesDoxygen"] - exhaleUseDoxyfile = final_configs["exhaleUseDoxyfile"] - exhaleDoxygenStdin = final_configs["exhaleDoxygenStdin"] - if exhaleExecutesDoxygen: - # Cannot use both, only one or the other - if exhaleUseDoxyfile and (exhaleDoxygenStdin is not None): + doxygen = final_configs.get('doxygen', None) + + # TODO: let is_dictionary_with_string_keys have message templates supplied? + if not doxygen: + raise ConfigError('doxygen key not provided, sub-key "stripFromPath" is required.') + verify.is_dictionary_with_string_keys(doxygen, 'doxygen') + + # Make sure the values of the specified keys are appropriate + for key, value_type in DoxygenConfig.ALLOWED_PUBLIC_KV: + if key in doxygen: + value = doxygen[key] + if not isinstance(value, value_type): + raise ConfigError(self._value_error(key, value_type, type(value))) + + if 'stripFromPath' not in doxygen: + raise ConfigError('"doxygen" sub-key "stripFromPath" is required.') + + if final_configs['exhaleExecutesDoxygen']: + stdin = doxygen.get('stdin', None) + doxyfile = doxygen.get('doxyfile', None) + if not stdin and not doxyfile: raise ConfigError( - "You must choose one of `exhaleUseDoxyfile` or `exhaleDoxygenStdin`, not both." + '"exhaleExecutesDoxygen" was True, but neither "stdin" nor ' + '"doxyfile" keys in the "doxygen" dictionary were provided. ' + 'Exhale must know how to execute Doxygen, and encourages the stdin ' + 'approach (e.g. "stdin": "INPUT = ../include").' ) - # The Doxyfile *must* be at the same level as conf.py - # This is done so that when separate source / build directories are being - # used, we can guarantee where the Doxyfile is. - if exhaleUseDoxyfile: - doxyfile_path = os.path.abspath(os.path.join( - self.app.confdir, "Doxyfile" - )) - if not os.path.exists(doxyfile_path): - raise ConfigError( - "The file [{0}] does not exist".format(doxyfile_path) - ) - - for key in ["exhaleExecutesDoxygen", "exhaleUseDoxyfile", "exhaleDoxygenStdin"]: - self.keys_processed.append(key) - + if doxyfile: + # TODO: resolve path against self.app.confdir + raise NotImplementedError('This will change to paths rather than bool.') @@ -506,62 +588,6 @@ def _validate_doxygen_configs(self, final_configs): An example value could be ``"Library API"``. ''' -doxygenStripFromPath = None -''' -**Required** - When building on Read the Docs, there seem to be issues regarding the Doxygen - variable ``STRIP_FROM_PATH`` when built remotely. That is, it isn't stripped at - all. This value enables Exhale to manually strip the path. - -**Value in** ``exhale_args`` (str) - The value of the key ``"doxygenStripFromPath"`` should be a string representing the - (relative or absolute) path to be stripped from the final documentation. As with - |containmentFolder|, relative paths are relative to the Sphinx source directory - (where ``conf.py`` is). Consider the following directory structure:: - - my_project/ - ├───docs/ - │ conf.py - │ - └───include/ - └───my_project/ - common.hpp - - In this scenario, if you supplied ``"doxygenStripFromPath" = ".."``, then the file - page for ``common.hpp`` would list its declaration as - ``include/my_project/common.hpp``. If you instead set it to be ``"../include"``, - then the file page for ``common.hpp`` would list its declaration as just - ``my_project/common.hpp``. - - As a consequence, modification of this variable directly affects what shows up in - the file view hierarchy. In the previous example, the difference would really just - be whether or not all files are nestled underneath a global ``include`` folder or - not. - - .. warning:: - - It is **your** responsibility to ensure that the value you provide for this - configuration is valid. The file view hierarchy will almost certainly break if - you give nonsense. - - .. note:: - - Depending on your project layout, some links may be broken in the above example - if you use ``"../include"`` that work when you use ``".."``. To get your docs - working, revert to ``".."``. If you're feeling nice, raise an issue on GitHub - and let me know --- I haven't been able to track this one down yet :/ - - Particularly, this seems to happen with projects that have duplicate filenames - in different folders, e.g.:: - - include/ - └───my_project/ - │ common.hpp - │ - └───viewing/ - common.hpp -''' - ######################################################################################## ## # ## Additional configurations available to further customize the output of exhale. # @@ -1025,36 +1051,6 @@ def _validate_doxygen_configs(self, final_configs): for more information. ''' -repoRedirectURL = None -''' -.. todo:: - - **This feature is NOT implemented yet**! Hopefully soon. It definitely gets under - my skin. It's mostly documented just to show up in the ``todolist`` for me ;) - -**Optional** - When using the Sphinx RTD theme, there is a button placed in the top-right saying - something like "Edit this on GitHub". Since the documents are all being generated - dynamically (and not supposed to be tracked by ``git``), the links all go nowhere. - Set this so Exhale can try and fix this. - -**Value in** ``exhale_args`` (str) - The url of the repository your documentation is being generated from. - - .. warning:: - - Seriously this isn't implemented. I may not even need this from you. The harder - part is figuring out how to map a given nodes "``def_in_file``" to the correct - URL. I should be able to get the URL from ``git remote`` and construct the - URL from that and ``git branch``. Probably just some path hacking with - ``git rev-parse --show-toplevel`` and comparing that to - :data:`~exhale.configs.doxygenStripFromPath`? - - Please feel free to `add your input here`__. - - __ https://github.com/svenevs/exhale/issues/2 -''' - # Using Contents Directives ############################################################ contentsDirectives = True ''' @@ -1207,7 +1203,7 @@ def _validate_doxygen_configs(self, final_configs): section. 2. ``STRIP_FROM_PATH`` is configured to be identical to what is specified with - :data:`~exhale.configs.doxygenStripFromPath`. + |stripFromPath|. I have no idea what happens when these conflict, but it likely will never result in valid documentation. @@ -1421,7 +1417,6 @@ def apply_sphinx_configurations(app): req_kv = [ ("rootFileName", six.string_types, False), ("rootFileTitle", six.string_types, False), - # ("doxygenStripFromPath", six.string_types, True) ] for key, expected_type, make_absolute in req_kv: # Used in error checking later @@ -1502,12 +1497,6 @@ def apply_sphinx_configurations(app): "Exhale is reStructuredText only, but '.rst' was not found in `source_suffix` list of `conf.py`." ) - # Make sure the doxygen strip path is an exclude-able path - # if not os.path.exists(doxygenStripFromPath): - # raise ConfigError( - # "The path given as `doxygenStripFromPath` ({0}) does not exist!".format(doxygenStripFromPath) - # ) - #################################################################################### # Gather the optional input for exhale. # #################################################################################### @@ -1535,7 +1524,6 @@ def apply_sphinx_configurations(app): # Page Level Customization ("includeTemplateParamOrderList", bool), ("pageLevelConfigMeta", six.string_types), - ("repoRedirectURL", six.string_types), ("contentsDirectives", bool), ("contentsTitle", six.string_types), ("contentsSpecifiers", list), diff --git a/exhale/deploy.py b/exhale/deploy.py index af028159..aae110a4 100644 --- a/exhale/deploy.py +++ b/exhale/deploy.py @@ -211,7 +211,7 @@ def generate_doxygen_xml(config): # There are two doxygen specs that we explicitly disallow # # 1. OUTPUT_DIRECTORY: this is *ALREADY* specified implicitly via breathe - # 2. STRIP_FROM_PATH: this is a *REQUIRED* config (`doxygenStripFromPath`) + # 2. STRIP_FROM_PATH: this is a *REQUIRED* config (`doxygen.stripFromPath`) # # There is one doxygen spec that is REQUIRED to be given: # @@ -250,22 +250,10 @@ def generate_doxygen_xml(config): if not _valid_config(config.doxygen.stdin, "STRIP_FROM_PATH", False): return textwrap.dedent(''' - `exhaleDoxygenStdin` may *NOT* specify `STRIP_FROM_PATH`. Exhale does + `doxygen["stdin"]` may *NOT* specify `STRIP_FROM_PATH`. Exhale does this internally by using the value you provided to `exhale_args` in - your `conf.py` for the key `doxygenStripFromPath`. - - Based on what you had in `conf.py`, Exhale will be using: - - {strip} - - NOTE: the above is what you specified directly in `exhale_args`. Exhale - will be using an absolute path to send to Doxygen. It is: - - {absolute} - '''.format( - strip=configs._the_app.config.exhale_args["doxygenStripFromPath"], - absolute=configs.doxygenStripFromPath - )) + your `conf.py` for the key `doxygen["stripFromPath"]`. + ''') if not _valid_config(config.doxygen.stdin, "INPUT", True): return textwrap.dedent(''' diff --git a/exhale/graph.py b/exhale/graph.py index b47c9b51..a03690ec 100644 --- a/exhale/graph.py +++ b/exhale/graph.py @@ -1659,7 +1659,7 @@ def fileRefDiscovery(self): # hack to make things work right on RTD # TODO: do this at construction rather than as a post process! - if configs.doxygenStripFromPath is not None: + if self.config.doxygen.stripFromPath is not None: for node in itertools.chain(self.files, self.dirs): if node.kind == "file": manip = node.location @@ -1667,7 +1667,7 @@ def fileRefDiscovery(self): manip = node.name abs_strip_path = os.path.normpath(os.path.abspath( - configs.doxygenStripFromPath + self.config.doxygen.stripFromPath )) if manip.startswith(abs_strip_path): manip = os.path.relpath(manip, abs_strip_path)