pax_global_header00006660000000000000000000000064142030525060014507gustar00rootroot0000000000000052 comment=80338ac80fdd0d9bf232699ef743a53e0a5e0856 exhale-0.3.1/000077500000000000000000000000001420305250600127565ustar00rootroot00000000000000exhale-0.3.1/.github/000077500000000000000000000000001420305250600143165ustar00rootroot00000000000000exhale-0.3.1/.github/workflows/000077500000000000000000000000001420305250600163535ustar00rootroot00000000000000exhale-0.3.1/.github/workflows/test_extras.yaml000066400000000000000000000032331420305250600216050ustar00rootroot00000000000000name: Extras on: push: branches: - master pull_request: branches: - master jobs: build_docs_linkcheck: name: Docs / Linkcheck runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@v2 - name: Use Python 3.9 uses: actions/setup-python@v2 with: python-version: '3.9' - name: Install Tools run: | sudo apt-get install -y graphviz pip install -U tox # NOTE: the RTD PR build is enabled to view docs, this tests with nitpicky # flags that will catch non-critical warnings. - name: Test Docs run: | tox -e docs - name: Test Linkcheck run: | tox -e linkcheck # TODO: this will become `lint` which will have more checks. build_flake8: name: Flake8 runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@v2 - name: Use Python 3.9 uses: actions/setup-python@v2 with: python-version: '3.9' - name: Install Tools run: | pip install -U tox - name: Test Flake8 run: | tox -e flake8 build_dist: name: Packaging runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@v2 # Use minimum version of python needed for this project in setup.cfg. - name: Use Python 3.6 uses: actions/setup-python@v2 with: python-version: '3.6' - name: Install Tools run: | pip install -U tox - name: Test Packaging run: | tox -e dist exhale-0.3.1/.github/workflows/test_python.yaml000066400000000000000000000064071420305250600216260ustar00rootroot00000000000000name: Python on: push: branches: - master pull_request: branches: - master jobs: build: name: ${{ matrix.os }} / ${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: # os: [macos-latest, ubuntu-latest, windows-latest] # TODO: either obtain doxygen 1.8.20 on mac, or find out why everything # breaks in doxygen 1.9.2. os: [ubuntu-latest, windows-latest] python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v2 ########################################################################## - name: Install Doxygen (macOS) if: contains(matrix.os, 'macos') run: | # brew tap-new $USER/local-doxygen # brew extract --version=1.8.20 doxygen $USER/local-doxygen # HOMEBREW_NO_AUTO_UPDATE=1 brew install -v doxygen@1.8.20 HOMEBREW_NO_AUTO_UPDATE=1 brew install doxygen - name: Install Doxygen (Ubuntu) if: contains(matrix.os, 'ubuntu') run: | sudo apt-get install -y doxygen - name: Install Doxygen (Windows) if: contains(matrix.os, 'windows') run: | choco install doxygen.install --version 1.8.20 echo "C:\Program Files\doxygen\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append ########################################################################## - name: Doxygen Version Dump run: doxygen --version ########################################################################## - name: Use Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install Python Tools run: | pip install -U tox codecov coverage - name: Test Python ${{ matrix.python-version }} / sphinx==3.0.0 env: # NOTE: in the future, we may want to use a larger matrix with more # combinations of sphinx and breathe, but for now it is not necessary. SPHINX_VERSION: '==3.0.0' run: | tox -e py -- --cov-report xml:coverage.xml --cov - name: Upload Code Coverage for Python ${{ matrix.python-version }} / sphinx==3.0.0 run: | codecov --required -X gcov -f coverage.xml --name "[GHA] ${{ matrix.os }}-py${{ matrix.python-version }}-sphinx3.0.0" # Don't let code coverage utilities share anything, force clean it all. - name: Cleanup Python ${{ matrix.python-version }} / sphinx==3.0.0 Artifacts run: | mv .gitignore nolongerignored git clean -n git clean -f git reset --hard - name: Test Python ${{ matrix.python-version }} / sphinx 4.x env: # NOTE: in the future, we may want to use a larger matrix with more # combinations of sphinx and breathe, but for now it is not necessary. SPHINX_VERSION: '>=4<5' run: | tox -e py -- --cov-report xml:coverage.xml --cov - name: Upload Code Coverage for Python ${{ matrix.python-version }} / sphinx 4.x run: | codecov --required -X gcov -f coverage.xml --name "[GHA] ${{ matrix.os }}-py${{ matrix.python-version }}-sphinx4.x" exhale-0.3.1/.github/workflows/testing_projects.yaml000066400000000000000000000060131420305250600226250ustar00rootroot00000000000000name: Projects on: push: branches: - master pull_request: branches: - master jobs: build_linux: name: Linux runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@v2 - name: Use Python 3.8 uses: actions/setup-python@v2 with: python-version: '3.8' - name: Install Build Tools run: | pip install -U pip pip install codecov gcovr # cpp_long_names test runs code from testing/tests/cpp_long_names.py, # so we need to install the runtime dependencies of Exhale's Python tests. pip install . pytest pytest-raises>=0.10 sudo apt-get install -y \ cmake \ gcc \ g++ \ gfortran \ make - name: Build Tool Versions run: | gcc --version g++ --version gfortran --version cmake --version - name: Build and Upload Coverage run: | cd testing/projects mkdir build cd build cmake .. make -j make coverage-xml codecov -X gcov -f .\coverage.xml --name linux_cxx build_windows: name: Windows runs-on: windows-latest strategy: fail-fast: false steps: - uses: actions/checkout@v2 - name: Use Python 3.8 uses: actions/setup-python@v2 with: python-version: '3.8' - name: Install Build Tools run: | pip install -U pip pip install codecov gcovr # cpp_long_names test runs code from testing/tests/cpp_long_names.py, # so we need to install the runtime dependencies of Exhale's Python tests. pip install . pytest pytest-raises>=0.10 choco install opencppcoverage echo "C:\Program Files\OpenCppCoverage" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - name: Setup Windows Dev Env uses: ilammy/msvc-dev-cmd@v1.9.0 with: arch: x64 - name: Install Ninja uses: seanmiddleditch/gha-setup-ninja@master - name: Build Tool Versions run: | cl ninja --version cmake --version - name: Build and Upload Coverage run: | cd testing\projects mkdir build cd build cmake .. -G Ninja ninja ninja coverage-xml # Gerrymander generated coverage report to include paths from the # repository root (rather than let GitHub Actions build folder paths # leak in -- D:\a\exhale\exhale). (Get-Content .\coverage.xml) -replace '(.*)filename=\"a\\exhale\\exhale\\(.*)\"(.*)', '$1 filename="$2"$3' | Set-Content .\coverage.xml Copy-Item .\coverage.xml -Destination D:\a\exhale\exhale cd D:\a\exhale\exhale type coverage.xml $codecov_cmd = '& codecov -X gcov -f .\coverage.xml --name windows_cxx' Invoke-Expression $codecov_cmd exhale-0.3.1/.gitignore000066400000000000000000000027351420305250600147550ustar00rootroot00000000000000# These files are generated dynamically every time `make html` is run docs/DEFAULT_DOXYGEN_STDIN_BASE_value.rst docs/LANG_TO_LEX_value.rst # Visual Studio Code settings .vscode/ # cpp_long_names python / cxx tests generate this on *nix testing/projects/cpp_long_names/include/directory/** # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # temp testing files testing/projects/*/docs_*/** # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ testenv/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ exhale-0.3.1/.readthedocs.yml000066400000000000000000000010531420305250600160430ustar00rootroot00000000000000# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-20.04 tools: python: "3.9" apt_packages: - graphviz # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py fail_on_warning: true # Optionally declare the Python requirements required to build your docs python: install: - requirements: docs/requirements.txt exhale-0.3.1/LICENSE000066400000000000000000000027611420305250600137710ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2017-2022, Stephen McDowell All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. exhale-0.3.1/README.rst000066400000000000000000000222201420305250600144430ustar00rootroot00000000000000Exhale ======================================================================================== |docs| |pypi| |coverage| |license| .. begin_badges .. |docs| image:: https://readthedocs.org/projects/exhale/badge/?version=latest :alt: Documentation Status :target: https://exhale.readthedocs.io/en/latest/?badge=latest .. |pypi| image:: https://badge.fury.io/py/exhale.svg :alt: Latest Version :target: https://badge.fury.io/py/exhale .. |coverage| image:: https://codecov.io/gh/svenevs/exhale/branch/master/graph/badge.svg :alt: Code Coverage Report :target: https://codecov.io/gh/svenevs/exhale .. |license| image:: https://img.shields.io/github/license/svenevs/exhale.svg :alt: License BSD-3 Clause :target: https://github.com/svenevs/exhale/blob/master/LICENSE .. end_badges Automatic C++ library API documentation generator using Doxygen, Sphinx, and Breathe. Exhale revives Doxygen's class, file, and page hierarchies in reStructuredText documents to make these hierarchies available in Sphinx documented projects. .. end_intro .. contents:: Contents :local: :backlinks: none .. _is_it_for_me: Is it For Me? ---------------------------------------------------------------------------------------- Exhale might **not** be the tool you are looking for! It was designed to be as intuitive and flexible as possible, but it does require more machinery to get everything started. **Why use it?** You would use Exhale if you want to have beautiful Sphinx generated websites, but also see the value of the Class, File, and Page hierarchies provided by Doxygen. From running Doxygen for you, to organizing your full API every time, you won't need to worry about your documentation getting out of sync with the code --- it's regenerated on the fly every time. **Why not use it?** It may be more involved than you need. Check out the ``breathe-apidoc`` tool that comes with your installation of ``breathe``. It is quite similar to the `Sphinx API doc tool `_, and that may be all you are looking for to get your documentation displayed. If you are working with a small enough framework, you may also be satisfied with just using the ``.. autodoxygenindex::`` directive from ``breathe``. It works very well! **The Main Difference** The Class, File, and Page hierarchies are only available in Sphinx via Exhale 😊 Depending on the size and complexity of your project, ``breathe-apidoc`` or ``autodoxygenindex`` may be more appropriate. **Important Consideration** Exhale is known to work well on small to medium sized projects. Supporting larger projects in Sphinx is a much more involved process that may or may not be resolved over time -- this has to do with a lot more than just Exhale, but the nature of Exhale (generation of a large number of ``.rst`` files) definitely does not help. Please see the `full documentation `_ for more information on how to get started. See it in Action ---------------------------------------------------------------------------------------- The `ExhaleCompanion `_ repository has three builds to demonstrate the different options with respect to creating a Tree View, as well as details of specific HTML Theme choices: +------------------------------+------------------+----------------------+ | HTML Theme Choice | TreeView Created | ExhaleCompanion Docs | +==============================+==================+======================+ | `RTD Theme `_ | Yes | |rtd-docs| | +------------------------------+------------------+----------------------+ | `Bootstrap Theme `_ | Yes | |bootstrap-docs| | +------------------------------+------------------+----------------------+ | `Alabaster`_ | No | |alabaster-docs| | +------------------------------+------------------+----------------------+ .. _companion: https://github.com/svenevs/exhale-companion .. _rtd: http://sphinx-rtd-theme.readthedocs.io/en/latest/ .. _bstrap: https://ryan-roemer.github.io/sphinx-bootstrap-theme/ .. _alabaster: http://alabaster.readthedocs.io/en/latest/ .. |rtd-docs| image:: https://readthedocs.org/projects/my-favorite-documentation-test/badge/?version=latest :target: http://my-favorite-documentation-test.readthedocs.io/en/latest/ .. |bootstrap-docs| image:: https://readthedocs.org/projects/my-favorite-documentation-test/badge/?version=bootstrap :target: http://my-favorite-documentation-test.readthedocs.io/en/bootstrap .. |alabaster-docs| image:: https://readthedocs.org/projects/my-favorite-documentation-test/badge/?version=alabaster :target: http://my-favorite-documentation-test.readthedocs.io/en/alabaster Installation ---------------------------------------------------------------------------------------- Exhale is a `Sphinx Extension`__ that depends on `Breathe`_ for access to the Doxygen reStructuredText directives, and both `BeautifulSoup`_ and `lxml`_ for parsing the generated Doxygen XML documentation. Exhale is `hosted on PyPI `_, meaning you can install it through the normal mechanisms, e.g., __ http://www.sphinx-doc.org/en/stable/extensions.html .. _BeautifulSoup: https://www.crummy.com/software/BeautifulSoup/bs4/doc/ .. _lxml: http://lxml.de/ .. _Breathe: https://breathe.readthedocs.io/en/latest/ .. code-block:: console $ python -m pip install exhale This will install Exhale, as well as all of its dependencies. Exhale Version Compatibility with Python, Sphinx, and Breathe ---------------------------------------------------------------------------------------- +----------------+----------------+----------------+-----------------+ | Exhale Version | Python Version | Sphinx Version | Breathe Version | +================+================+================+=================+ | 0.3.0 | >=3.6 | >=3.0,<5 | >=4.32.0 | +----------------+----------------+----------------+-----------------+ | 0.2.1-0.2.4 | 2.7, 3.3+ | >=1.6.1 | "Any" | +----------------+----------------+----------------+-----------------+ | <=0.2.0 | 2.7, 3.3+ | >=1.0 | "Any" | +----------------+----------------+----------------+-----------------+ For your project's documentation requirements, adopting ``exhale@0.3.0`` means you just need to specify directly that your ``docs/requirements.txt`` or similar express:: exhale>=0.3.0 The ``exhale@0.3.0`` release uses the same dependencies required by ``breathe@4.32.0``. If you need python 3.5 support, pin your documentation requirements to:: sphinx>=2.0 breathe>=4.13.0 exhale<0.3.0 For Python 2.7, you should pin your documentation requirements to:: sphinx==1.8.5 breathe==4.12.0 exhale<0.3.0 **Order matters**, namely that ``sphinx`` and ``breathe`` appear / are installed before ``exhale``. Exhale 0.2.* releases support Python 2.7, but users need to be aware of the dependencies between Python, Sphinx, and Breathe versions. .. _credit: Credit ---------------------------------------------------------------------------------------- This project could not exist without the already excellent tools available: Doxygen, Sphinx, Breathe, and many others. In particular, though, for the Tree View hierarchies to be successful, I vendor copies of two excellent libraries that I make no claims to. They are vendored with your installation of Exhale, in accordance with each project's license: 1. For non-bootstrap, I used Kate Morley's excellent and lightweight collapsibleLists_ including the sample CSS / images on that post. She includes a generous `CC0 1.0 license `_ for these files, as well as the rest of her website. For every HTML Theme I have tried, except for ones using bootstrap, this library works reliably and consistently. It matches the Sphinx RTD theme quite well, too! 2. For bootstrap, I used Jon Miles' comprehensive `bootstrap-treeview`__ library. Jon Miles hosts this library using the `Apache v2 license `_. This library is exceptionally well thought out and enables an impressive amount of customization. At this time, Exhale does not expose any of the available customizations. If there is a specific one you'd like to see, please join the `discussion here `_. Both of these libraries and copies of their licenses can be found in the `data folder of the source code `_. .. _collapsibleLists: http://code.iamkate.com/javascript/collapsible-lists/ __ https://github.com/jonmiles/bootstrap-treeview License ---------------------------------------------------------------------------------------- This project uses a BSD 3-clause license, in hopes that it will be accessible to most projects. If you require a different license, please raise an issue and I will consider a dual license. The full license is `available here `_. exhale-0.3.1/UPLOAD_NOTES.md000066400000000000000000000004431420305250600152350ustar00rootroot00000000000000# Upload Notes For when I forget how I did this... ```console # Build the source distribution and universal wheel # saved into dist/ folder $ tox -e dist # Upload! $ twine upload dist/* ``` Make sure that your `~/.pypirc` has `username` under `[pypi]` or specify it to `twine` directly. exhale-0.3.1/codecov.yml000066400000000000000000000015101420305250600151200ustar00rootroot00000000000000# .github/workflows/*.yaml conditionally enable / disable tests. # https://docs.codecov.com/docs/commit-status#excluding-tests-example coverage: status: project: default: false tests: # At this time we don't care about coverage changes as far as status # blockers go. Code coverage is nice, but not a requirement, reviewers # should use their best judgement... target: auto threshold: 99% paths: - "exhale/" - "testing/*.py" - "testing/tests/*.py" - "!testing/projects/" projects: target: auto threshold: 99% paths: - "!exhale/" - "!testing/*.py" - "!testing/tests/*.py" - "testing/projects/" # https://docs.codecov.com/docs/pull-request-comments comment: false exhale-0.3.1/docs/000077500000000000000000000000001420305250600137065ustar00rootroot00000000000000exhale-0.3.1/docs/Makefile000066400000000000000000000011351420305250600153460ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = Exhale SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) exhale-0.3.1/docs/_extensions/000077500000000000000000000000001420305250600162445ustar00rootroot00000000000000exhale-0.3.1/docs/_extensions/autotested.py000066400000000000000000000045701420305250600210050ustar00rootroot00000000000000######################################################################################## # License: CC0 1.0 # # # # See full license text here: https://creativecommons.org/publicdomain/zero/1.0/ # ######################################################################################## # In the event that this code is useful to you, please feel free to use it without # # attribution. The CC0 1.0 license is chosen in the event that you need a license. # # # # Note that this code is basically just a bad modification of the Sphinx tutorial: # # http://www.sphinx-doc.org/en/latest/extdev/tutorial.html # ######################################################################################## import re import textwrap from docutils import nodes from docutils.parsers.rst import Directive from docutils.statemachine import StringList from sphinx.locale import _ def visit_autotested_node(self, node): self.visit_admonition(node) def depart_autotested_node(self, node): self.depart_admonition(node) class autotested(nodes.Admonition, nodes.Element): pass class AutoTestedDirective(Directive): has_content = True def run(self): if len(self.content) != 0: raise ValueError("`autotested` directive does not allow any arguments.") # Seriously there _HAS_ to be a better way to do this... # Creating something of length 1 so that I can rage-replace it self.content = StringList("?") self.content[0] = textwrap.dedent('''\ This method is tested automatically for every derived type of :class:`~testing.base.ExhaleTestCase` that is not decorated with :func:`~testing.decorators.no_run`. The metaclass :class:`~testing.base.ExhaleTestCaseMetaclass` generates a testing method ``test_common`` that invokes this method. ''') autotested_node = autotested("\n".join(self.content)) autotested_node += nodes.title(_("Automatically Tested"), _("Automatically Tested")) self.state.nested_parse(self.content, self.content_offset, autotested_node) return [autotested_node] exhale-0.3.1/docs/_extensions/exhaleversion.py000066400000000000000000000045511420305250600214770ustar00rootroot00000000000000######################################################################################## # License: CC0 1.0 # # # # See full license text here: https://creativecommons.org/publicdomain/zero/1.0/ # ######################################################################################## # In the event that this code is useful to you, please feel free to use it without # # attribution. The CC0 1.0 license is chosen in the event that you need a license. # # # # Note that this code is basically just a bad modification of the Sphinx tutorial: # # http://www.sphinx-doc.org/en/latest/extdev/tutorial.html # ######################################################################################## import re import textwrap from docutils import nodes from docutils.parsers.rst import Directive from docutils.statemachine import StringList from sphinx.locale import _ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.absolute())) from github_url import get_github_base_url def visit_exhaleversion_node(self, node): self.visit_admonition(node) def depart_exhaleversion_node(self, node): self.depart_admonition(node) class exhaleversion(nodes.Admonition, nodes.Element): pass class ExhaleVersionDirective(Directive): has_content = True def run(self): if len(self.content) != 0: raise ValueError("`exhaleversion` directive does not allow any arguments.") # Seriously there _HAS_ to be a better way to do this... # Creating something of length 1 so that I can rage-replace it self.content = StringList("?") base = get_github_base_url() commit = base.split("/")[-1] self.content[0] = textwrap.dedent(f'''\ This documentation was generated using exhale version |version| from `commit {commit} <{base}>`_. ''') exhaleversion_node = exhaleversion("\n".join(self.content)) exhaleversion_node += nodes.title(_("Version"), _("Version")) self.state.nested_parse(self.content, self.content_offset, exhaleversion_node) return [exhaleversion_node] exhale-0.3.1/docs/_extensions/github_url.py000066400000000000000000000045761420305250600207760ustar00rootroot00000000000000import re from subprocess import Popen, PIPE github_base_url = None # get a github specific URL to where the root of the project on current commit def get_github_base_url(): global github_base_url # just making sure this only needs to run once if github_base_url: return github_base_url # otherwise, first execution so grab it, set it, and return it git_remote_proc = Popen( ["git", "remote", "get-url", "origin"], stdout=PIPE, stderr=PIPE ) git_remote_out, git_remote_err = git_remote_proc.communicate() if git_remote_proc.returncode != 0: raise RuntimeError( "Non-zero exit code [{0}] from git remote. STDERR: {1}".format( git_remote_proc.returncode, git_remote_err.decode("ascii") ) ) # extract user so that pull requests don't fail the docs linkcheck test git_remote_out = git_remote_out.decode("ascii") user = None # HTTPS url: https://github.com/svenevs/exhale.git https_re = r"https://github\.com/(.*)/exhale\.git" https_match = re.match(https_re, git_remote_out) if https_match: user = https_match.groups()[0] # HTTPS url as done by GitHub Actions. https_re = r"https://github\.com/(.*)/exhale" # no .git https_match = re.match(https_re, git_remote_out) if https_match: user = https_match.groups()[0] # SSH url: git@github.com:svenevs/exhale.git ssh_re = r"git@github\.com:(.*)/exhale(\.git)?" ssh_match = re.match(ssh_re, git_remote_out) if ssh_match: user = ssh_match.groups()[0] if not user: raise RuntimeError( "Could not parse GitHub user information from remote: {}".format(git_remote_out) ) # Get the commit hash git_rev_parse_proc = Popen( ["git", "rev-parse", "HEAD"], stdout=PIPE, stderr=PIPE ) git_rev_parse_out, git_rev_parse_err = git_rev_parse_proc.communicate() if git_rev_parse_proc.returncode != 0: raise RuntimeError( "Non-zero exit code [{0}] from git rev-parse. STDERR: {1}".format( git_rev_parse_proc.returncode, git_rev_parse_err.decode("ascii") ) ) git_rev_parse_out = git_rev_parse_out.decode("ascii") github_base_url = "https://github.com/{user}/exhale/tree/{sha}".format( user=user.strip(), sha=git_rev_parse_out.strip() ) return github_base_url exhale-0.3.1/docs/_extensions/testproject.py000066400000000000000000000032251420305250600211660ustar00rootroot00000000000000import textwrap from docutils import nodes from docutils.parsers.rst import Directive from sphinx.locale import _ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.absolute())) from github_url import get_github_base_url def visit_testproject_node(self, node): self.visit_admonition(node) def depart_testproject_node(self, node): self.depart_admonition(node) class testproject(nodes.Admonition, nodes.Element): pass class TestProjectDirective(Directive): has_content = True def run(self): if len(self.content) != 1: raise ValueError( "`testproject` directive needs exactly one argument (the name of the test folder). " "Example: .. testproject:: c_maths" ) self.content[0] = textwrap.dedent('''\ The ``{project}`` test project. - Additional documentation: :mod:`testing.projects.{project_fixed}`. - Source code for `{project} available here <{baseurl}/{project_dir}>`_. See also: :data:`ExhaleTestCase.test_project `. '''.format( project=self.content[0], project_dir=self.content[0].replace(" ", "\\ "), project_fixed=self.content[0].replace(" ", "_"), # cpp with spaces project baseurl=f"{get_github_base_url()}/testing/projects" )) project_node = testproject("\n".join(self.content)) project_node += nodes.title(_("Test Project Source"), _("Test Project Source")) self.state.nested_parse(self.content, self.content_offset, project_node) return [project_node] exhale-0.3.1/docs/_intersphinx/000077500000000000000000000000001420305250600164205ustar00rootroot00000000000000exhale-0.3.1/docs/_intersphinx/README.md000066400000000000000000000041301420305250600176750ustar00rootroot00000000000000# Creating a Custom Intersphinx Mapping for BeautifulSoup ## The Issue The BeautifulSoup intersphinx mapping does not work; see [bug here][bug]. [bug]: https://bugs.launchpad.net/beautifulsoup/+bug/1453370 So we'll just do it manually. ## The Tool Use the tool `sphobjinv` to do this (`pip install sphobjinv`). - [Explanation of syntax][syntax]. [syntax]: http://sphinx-objectsinv-encoderdecoder.readthedocs.io/en/latest/syntax.html ## How to Reproduce 1. Downloaded original objects.inv from bs4 docs ```console $ curl -O https://www.crummy.com/software/BeautifulSoup/bs4/doc/objects.inv $ mv objects.inv bs4_objects.inv ``` 2. Converted that bad boy to human readable (the file `bs4_objects.txt`). ```console $ sphobjinv convert plain bs4_objects.inv bs4_objects.txt ``` 3. Any time I have a class I want to add a reference for, just add that line to `bs4_objects.txt`. Recall that there is a specific [syntax][syntax] associated with these files. 4. When you add a new line, simply run (assuming you are in this directory) ```console $ sphobjinv convert zlib bs4_objects.txt bs4_objects.inv ``` 5. Re-run sphinx, usually you need to clean it for the doctree index to be rebuilt. ```console $ make clean html ``` ## Note These changes apply because we have **2** things in `conf.py`: 1. In the `extensions` list, we have `"sphinx.ext.intersphinx"`. 2. We have configured our intersphinx mapping to point to here (as opposed to the one being hosted online): ```py intersphinx_mapping = { 'bs4': ('https://www.crummy.com/software/BeautifulSoup/bs4/doc/', "_intersphinx/bs4_objects.inv") } ``` ## In the Documentation So now we can do something like ```rst - See :class:`bs4.BeautifulSoup` - See :meth:`bs4.BeautifulSoup.get_text` - See :class:`bs4.element.Tag` ``` because we added the following line to our `bs4_objects.txt`: ``` bs4.BeautifulSoup py:class 1 index.html#beautifulsoup - bs4.BeautifulSoup.get_text py:method 1 index.html#get-text - bs4.element.Tag py:class 1 index.html#tag - ``` exhale-0.3.1/docs/_intersphinx/bs4_objects.inv000066400000000000000000000012421420305250600213360ustar00rootroot00000000000000# Sphinx inventory version 2 # Project: Beautiful Soup # Version: 4 # The remainder of this file is compressed using zlib. xڕ <R=UjԕCog,;>Xfj 2z zkCoO?<WKuN'܏`Z=chy D' v|y8>U/g-k\̄yZL_ }D Si+Ki*$ӂIlA8ڠp x)H3,;(n:L"ķ 8Jny,,klPPNO,hh+Vl%\<)e(jihĿ,][Zowٺ<ӪSJ̤abCӠ4@NXgw&m IP:H!*gFveYH]-ـ}ueB)ȟ1oFӃxtXj'$OҘ%Qsp4:B-oռ^ WPxwM%lg@5gj code > span { color: #2980B9;; } /* Make internal links stand out form teletype text. */ a[class='reference internal'] > code > span { color: #138a36; } /* Change inline code text color. */ code > span { color: #343131; } exhale-0.3.1/docs/changelog.rst000066400000000000000000000154251420305250600163760ustar00rootroot00000000000000Changelog ======================================================================================== .. contents:: Release Notes :local: :backlinks: none v0.3.1 ---------------------------------------------------------------------------------------- - Fix regression introduced by :pr:`139` where custom ``"class"`` and ``"struct"`` :data:`~exhale.configs.customSpecificationsMapping` were being overwritten (:pr:`154`). v0.3.0 ---------------------------------------------------------------------------------------- - Do not write source files for empty hierarchies (:pr:`134`, :pr:`147`). - Support specialized template functions (:pr:`117`). - Prevent sphinx from processing files that are incorporated via a ``.. include::`` directive by renaming them to ``.rst.include`` suffix (:pr:`136`). - Add ``:project: {app.config.breathe_default_project}`` to every breathe directive to make the monkeypatch (:issue:`27`) work (:pr:`139`, :pr:`148`). - Do not require :data:`~exhale.configs.containmentFolder` to be a "direct" subdirectory of ``app.srcdir``, allow any arbitrary subdirectory (:pr:`144`). - Update how css and js are added using a dubious check into the sphinx internals before adding css / js to avoid duplicates (:pr:`144`). v0.2.4 ---------------------------------------------------------------------------------------- - Use the correct PyPI name ``beautifulsoup4`` rather than ``bs4`` (:pr:`120`). - Fix deprecated ``MutableMapping`` import for python 3.10 support (:pr:`124`). - Enable parallel builds (use the right ``setup`` function...) (:pr:`126`). - Add support for ``.. doxygenpage::`` (:pr:`114`). **Huge** thanks to: - `@hidmic `_ for the initial implementiation, and - `@2bndy5 `_ and `@clalancette `_ for their efforts in improving the doxygen-breathe-exhale-sphinx ecosystem (and consequently, encouraging me to resume work on this project). - Escape ``*`` in template page titles (:pr:`118`). - Fix titles / links for directories with underscores (:pr:`127`). v0.2.3 ---------------------------------------------------------------------------------------- - Allow unabridged API to exclude different kinds (:pr:`67`). :data:`~exhale.configs.unabridgedOrphanKinds` allows users to exclude a specific kind from getting dumped in the unabridged API beneath the hierarchies. By default, the unabridged API will exclude ``"file"`` and ``"dir"``, given that the file hierarchy already includes these. v0.2.2 ---------------------------------------------------------------------------------------- - Make sure spaces in directory / filenames are quoted when sent to Doxygen (:pr:`60`). v0.2.1 ---------------------------------------------------------------------------------------- - Fix bug where a ``union`` child of a ``namespace`` does not link correctly in the class hierarchy (:pr:`40`). - Do not force pygments lexer to unconditionally use ``cpp`` for ```` pages. Doxygen encodes a language, which is parsed and converted to the appropriate pygments lexer (:pr:`42`). - Added new configuration variable :data:`~exhale.configs.lexerMapping` for additional control over pygments language to use. - This marks the beginning of mixed-language support, but much more needs to be done for this! - Most overloaded functions should now work. See :pr:`45` for more information, including function overloads that do **not** currently work. - For consistency, the full api listing includes fully qualified function names (previously: ``Function foo``, now: ``Function namespace::foo(int)``). - Stopped using deprecated sphinx API (:pr:`47`). - Tree view hierarchies are minified by default (:pr:`48`). - Added new configuration variable :data:`~exhale.configs.minifyTreeView`. - Parent directory links generated for ``directory`` and ``file`` pages (:pr:`49`). - Tree view and namespace exclusion bypass configuration variable :data:`~exhale.configs.listingExclude` added (:pr:`50`). - Better logic for finding the file node that defined a given compound. - Doxygen produces inconsistencies with the paths, some are Windows paths and some are \*nix paths. These should now all be corrected using ``os.path.normpath``. v0.2.0 ---------------------------------------------------------------------------------------- - Exhale no longer produces filenames that are longer than the operating system can handle (:pr:`35`). - Internal links have changed, more heavily discouraged in docs. Ideally the internal link generation scheme will not need to change again, but they might. - Exhale can handle absurdly long file paths by using the ``\\?\`` prefix on Windows. If you run into a situation where this affects you, Sphinx actually cannot handle this. So try and build in a higher directory, e.g. ``C:\your_project`` (paths greater than ``260`` characters cause this issue). - First *mostly* functional release for Windows (there were many locations where ``os.path.normpath`` needed to be used. - **Bug**: bug introduced where a ``union`` child of a ``namespace`` does not link correctly in the class hierarchy. v0.1.8 ---------------------------------------------------------------------------------------- - Fix bug that prevents :ref:`usage_customizing_breathe_output` from working. Was checking ``isinstance(val_t, six.string_types)``, but should have been checking ``isinstance(val, six.string_types)``. - Fix / improve key guessing for when an invalid key is given in ``exhale_args``. v0.1.7 ---------------------------------------------------------------------------------------- - Colorized printing on Read The Docs is disabled, since their build logs online don't display the color. - Doxygen ``stdout`` and ``stderr`` are directed to ``/dev/null`` on Read The Docs. See :issue:`14`. v0.1.6 ---------------------------------------------------------------------------------------- - First release with manual namespace documentation parsing (same as files). - Limitations described in :ref:`file_and_namespace_level_documentation_in_exhale`. - Namespace documentation `example here `_. - Fixed bad error message when multiple potential file parents are found, which produced an exception preventing the rest of the build. Full description can be found in :pr:`12`. .. _nspace_example: https://my-favorite-documentation-test.readthedocs.io/en/latest/api/namespace_arbitrary.html#namespace-arbitrary v0.1.5 ---------------------------------------------------------------------------------------- - Page level configuration metadata added to all pages (rather than just leaf-like pages). - Fixed ``textwrap.dedent`` inconsistencies when more than one nested type is enumerated. exhale-0.3.1/docs/conf.py000066400000000000000000000131571420305250600152140ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup ------------------------------------------------------------------------ import sys from pathlib import Path from textwrap import dedent repo_root = Path(__file__).parent.absolute().parent sys.path.insert(0, str(repo_root)) # path to `exhale` import exhale # -- Project information --------------------------------------------------------------- needs_sphinx = "4.3.1" project = "Exhale" copyright = "2017-2022, Stephen McDowell" author = "Stephen McDowell" version = exhale.__version__ release = exhale.__version__ # -- General configuration ------------------------------------------------------------- extensions = [ "sphinx.ext.autodoc", "sphinx.ext.todo", "sphinx.ext.graphviz", "sphinx.ext.intersphinx", # NOTE: viewcode isn't working, probably because of how my docs are setup # and the lack of __all__ definitions? "sphinx.ext.viewcode", "sphinx_issues" ] # make linkcheck does not support GitHub README.md anchors (they are synthetic anchors) linkcheck_ignore = [ r"https://github.com/jonmiles/bootstrap-treeview#.*" ] templates_path = ["_templates"] exclude_patterns = ["_build"] # -- Options for HTML output ----------------------------------------------------------- html_title = "Exhale: Automatic C++ Library API Generation" html_short_title = "Exhale" html_theme = "sphinx_rtd_theme" html_theme_options = { "navigation_depth": -1 } html_static_path = ["_static"] html_css_files = ["custom.css"] # -- sphinx.ext.autodoc Extension Configuration ---------------------------------------- autodoc_member_order = "bysource" # -- sphinx.ext.todo Extension Configuration ------------------------------------------- todo_include_todos = True todo_link_only = True # -- sphinx.ext.viewcode Extension Configuration --------------------------------------- viewcode_follow_imported_members = True # -- sphinx_issues Extension Configuration --------------------------------------------- # See: https://github.com/sloria/sphinx-issues issues_github_path = "svenevs/exhale" # -- Intersphinx Extension Configuration ----------------------------------------------- intersphinx_mapping = { "python": ("http://docs.python.org/", None), "sphinx": ("http://www.sphinx-doc.org/en/master", None), "pytest": ("https://docs.pytest.org/en/latest/", None), # See _intersphinx/README.md "bs4": ( "https://www.crummy.com/software/BeautifulSoup/bs4/doc", "_intersphinx/bs4_objects.inv" ) } def setup(app): # https://github.com/sphinx-doc/sphinx/issues/5562#issuecomment-434296574 # So that I can link to e.g., :confval:`sphinx:html_static_path`. app.add_object_type("confval", "confval", objname="configuration value", indextemplate="pair: %s; configuration value") # Add on the various extensions. sys.path.insert(0, str(repo_root / "docs" / "_extensions")) from autotested import autotested, visit_autotested_node, depart_autotested_node, AutoTestedDirective app.add_node(autotested, html=(visit_autotested_node, depart_autotested_node)) app.add_directive("autotested", AutoTestedDirective) from exhaleversion import exhaleversion, visit_exhaleversion_node, depart_exhaleversion_node, ExhaleVersionDirective app.add_node(exhaleversion, html=(visit_exhaleversion_node, depart_exhaleversion_node)) app.add_directive("exhaleversion", ExhaleVersionDirective) from testproject import testproject, visit_testproject_node, depart_testproject_node, TestProjectDirective app.add_node(testproject, html=(visit_testproject_node, depart_testproject_node)) app.add_directive("testproject", TestProjectDirective) #################################################################################### # Multiline string documentation # #################################################################################### from textwrap import dedent from exhale.configs import DEFAULT_DOXYGEN_STDIN_BASE default_parts = DEFAULT_DOXYGEN_STDIN_BASE.strip().splitlines() multiline_var = "\n ".join(p for p in default_parts) with open("DEFAULT_DOXYGEN_STDIN_BASE_value.rst", "w") as dcv: dcv.write(dedent(""" The value of this variable is a multiline string with contents: .. code-block:: py """)) dcv.write(" {multiline_var}".format(multiline_var=multiline_var)) dcv.write(dedent(""" .. note:: The above value is presented for readability, when using this variable take note of any leading or trailing ``\\n`` characters. """)) from exhale.utils import LANG_TO_LEX longest = 0 for key in LANG_TO_LEX: longest = max(longest, len(key)) with open("LANG_TO_LEX_value.rst", "w") as ltlv: ltlv.write(dedent(""" .. code-block:: py LANG_TO_LEX = { """)) nkeys = len(LANG_TO_LEX) idx = 0 for key in LANG_TO_LEX: nspace = longest - len(key) + 1 spacing = " " * nspace ltlv.write(' "{key}":{spacing}"{value}"'.format( key=key, spacing=spacing, value=LANG_TO_LEX[key] )) if idx < nkeys - 1: ltlv.write(",") ltlv.write("\n") idx += 1 ltlv.write(" }\n\n") exhale-0.3.1/docs/faq.rst000066400000000000000000000242731420305250600152170ustar00rootroot00000000000000FAQ ======================================================================================== .. contents:: Contents :local: :backlinks: none Nothing is working, where did I go wrong? ---------------------------------------------------------------------------------------- Sorry to hear that. Please try comparing how your documentation is set up with the companion_ website. If things look similar enough, or something isn't clear, raise an issue on GitHub. I'll do my best to support what I can, and if similar questions come up then I can add them to this FAQ. Make sure you set the :data:`~exhale.configs.verboseBuild` to ``True``, there may be some useful information being printed there. .. _companion: http://my-favorite-documentation-test.readthedocs.io/en/latest/ Can I use the formidable Intersphinx extension? ---------------------------------------------------------------------------------------- Heck yes! This has almost nothing to do with Exhale, and everything to do with Sphinx. I've prepared a crash-course on how to get it up and running on the companion site's `Using Intersphinx `_ page. .. _using_intersphinx: http://my-favorite-documentation-test.readthedocs.io/en/latest/using_intersphinx.html Why does it build locally, but not on Read the Docs? ---------------------------------------------------------------------------------------- Most likely Exhale is failing to build if you are getting this. Check the build logs on RTD, and also make sure that you are using the correct version of python you desire by configuring the `read the docs yaml `_. .. _rtd_yaml: https://docs.readthedocs.io/en/stable/config-file/v2.html Note that you may specify a version of python, as well as a path to a ``requirements.txt`` to have your project's documentation dependencies installed. Why are there build warnings about generated files not being included? ---------------------------------------------------------------------------------------- This means that you have changed your API in a way that the files being generated have changed. For example, you changed the template parameters on something, or you deleted a class. So now when you run ``make html``, the original generated files are still floating around down there even though the current execution of Exhale will not use them. Solution: see the :ref:`quickstart_clean_target` section of the Quickstart guide. Unicode support? ---------------------------------------------------------------------------------------- Every action Exhale performs with respect to strings is done using unicode strings. Or at least I tried my very best to make sure unicode support works. 1. At the top of every file: .. code-block:: py from __future__ import unicode_literals This is why all of the documentation on this site for strings has a leading ``u``. 2. Every file written to: .. code-block:: py with codecs.open(file_name, "w", "utf-8") as f: 3. When using Python **3**, ``bytes`` conversion is done as: .. code-block:: py doxygen_input = bytes(doxygen_input, "utf-8") The last one may *potentially* cause problems, but in local testing it seems to be OK. E.g., if you only specify **ASCII** in your ``conf.py``, everything should work out in the end. .. note:: Sphinx / reStructuredText supports ``utf-8`` no problem. The only potential concern is communicating with Doxygen on stdin like this, but it's worked without issue for me so I kept it. Please speak up if you are experiencing encoding / decoding specific issues in Exhale! Why does my documentation take so long to build? ---------------------------------------------------------------------------------------- This is a byproduct of what is actually being done by Exhale. If you look at the build output of Exhale when you execute ``make html``, parsing and generating the documents takes on the order of seconds. What takes long is Sphinx, and the time it takes is directly proportional to the size of the API being documented. The larger the API, the more individual reStructuredText documents there are being created. Meaning there are more documents that Sphinx has to read *and* write. .. note:: The ``sphinx-bootstrap-theme`` is noticeably slower than others. I have suspicions as to why, but have not actually investigated potential fixes. Why are the Sphinx RTD theme "Edit on GitHub" links are invalid? ---------------------------------------------------------------------------------------- Because I haven't figured out how to implement this correctly yet. Feel free to give input `on the issue`__. They point to nowhere because you aren't tracking the generated API with ``git`` (nor should you be). __ https://github.com/svenevs/exhale/issues/2 .. tip:: There is an existing hack you can use to at least make the links go somewhere that exists. Use the page-level metadata feature of Exhale and point it to the root of your repository: .. code-block:: py exhale_args = { # ... required / optional arguments ... "pageLevelConfigMeta": ":github_url: https://github.com/username/project" } .. note:: The consequence of fixing the link is that *locally* the "View Page Source" that would let you see the generated reStructuredText (e.g. to get the link name) is now gone. You will have to open the file *manually* in a text editor. Recall that the generated files get placed in the folder specified by :data:`~exhale.configs.containmentFolder`. .. _faq_metaprogramming_and_template_specialization: Metaprogramming and template (specialization)? ---------------------------------------------------------------------------------------- Yes and no. Partial and full template specialization are supported, but not elegantly. 1. Currently there are no links from partial and full specializations back to their original (unspecialized) type. This may change in a future release. Finding the unspecialized type is complicated due to how things are presented by Doxygen. 2. Template classes / structs were given the most attention. Functions may or may not work. 3. **All** template classes, specialized or not, **produce build warnings**. These warnings come from Breathe. The documentation appears, but the layout is a little strange. For specializations in particular, they seem to produce an extra ``template <>`` in the output. For example, with a ``template DerivedClass``, **Partial Specialization** ``template DerivedClass`` Produces ``template template<> DerivedClass``. **Full Specialization** ``template <> DerivedClass`` Produces ``template <> template<> DerivedClass`` 4. Where metaprogramming is concerned, it is more likely that Doxygen's preprocessor needs to have everything ``PREDEFINED``. YMMV. .. tip:: If all else fails, you can force Doxygen to skip things. See the next FAQ entry. How can I get Doxygen to skip code it cannot process? ---------------------------------------------------------------------------------------- It depends on what you need. If it's something like a macro that isn't expanding correctly, you can try pre-defining it. Otherwise, you skip it with preprocessor symbols that are only defined when the documentation is building. See the :ref:`Doxygen PREDEFINED ` section. I thought reStructuredText was supported, why does it look weird? ---------------------------------------------------------------------------------------- If you're using complicated syntax (e.g., more than ``**bold**`` or listings), you will likely want to put that documentation in a raw reStructuredText verbatim block. This basically tells Doxygen to skip it, allowing for Breathe / Exhale to then process it directly. See the :ref:`Doxygen ALIASES ` section for how to do this. The likely problem: Doxygen runs **first**. It supports Markdown, and it is probably transforming your documentation based off Markdown rules before Breathe / Sphinx / Exhale even gain access to it. Forcing a verbatim reStructuredText environment means that Doxygen simply passes the raw docstring unadulterated forward. You aren't including my ``enum``'s, ``defines``, etc. Why Not? ---------------------------------------------------------------------------------------- This happens because Doxygen is not including them. See the :ref:`document_your_files_son` section. Why aren't my Classes, Structs, Enums, or Unions associated with the right file? ---------------------------------------------------------------------------------------- I'm not entirely sure. Fortunately, you can specify the path explicitly for these. See the :ref:`file_associations` section. Personally, I tend to just default to always specifying the path manually. My documentation is setup using groups, how can I use Exhale? ---------------------------------------------------------------------------------------- I do not support ``groups`` with Doxygen, as I assume if you have gone through the effort to group everything then you have a desire to manually control the output. Breathe already has an excellent ``doxygengroup`` directive, and you should use that. How do I modify the pygments highlighter for a program listing? ---------------------------------------------------------------------------------------- By default Exhale will use :data:`utils.LANG_TO_LEX ` to choose the pygments syntax highlighter for ``.. code-block:: ``. For most projects the defaults here should work as expected. If you need to change it, set :data:`~exhale.configs.lexerMapping` in your ``conf.py``. Ok seriously, why is there SO MUCH documentation on a documentation system? ---------------------------------------------------------------------------------------- It's your choice whether or not you read it. Back when I was getting started on all of this stuff it was **overwhelming**. I did my best to recall where I got confused, as well as highlight some of the tricks I've picked up over the years. Basically, it's because I'm hopeful that I can save *at least one person* from falling into some of the more confusing "traps" I've encountered. exhale-0.3.1/docs/index.rst000066400000000000000000000011711420305250600155470ustar00rootroot00000000000000.. include:: ../README.rst :end-before: end_intro Please read the :ref:`overview` section if you are new to documenting code with either Doxygen, Sphinx, or Breathe. A :ref:`quickstart_guide` gives the bare minimum needed to get things working. .. exhaleversion:: .. contents:: Contents :local: :backlinks: none .. include:: ../README.rst :start-after: _is_it_for_me: Full Documentation ---------------------------------------------------------------------------------------- .. toctree:: :maxdepth: 2 overview quickstart usage reference mastering_doxygen faq testing todo changelog exhale-0.3.1/docs/make.bat000066400000000000000000000014441420305250600153160ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=python -msphinx ) set SOURCEDIR=. set BUILDDIR=_build set SPHINXPROJ=Exhale if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The Sphinx module was not found. Make sure you have Sphinx installed, echo.then set the SPHINXBUILD environment variable to point to the full echo.path of the 'sphinx-build' executable. Alternatively you may add the echo.Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd exhale-0.3.1/docs/mastering_doxygen.rst000066400000000000000000000551321420305250600201740ustar00rootroot00000000000000.. _mastering_doxygen: Mastering Doxygen ======================================================================================== My hope for this document is that it will provide you with enough information if you are just getting started with documenting a C++ project. I make no claims to this being all you will need, the information presented here comes from a **lot** of trial and error and arriving at a workflow that seemed to work out. Your mileage **will** vary, things **will break**, but if you are patient (and belligerent) enough, the end result is totally worth it. With that, let's begin! .. contents:: Contents :local: :backlinks: none What is Doxygen, and How do I Approach it? ---------------------------------------------------------------------------------------- `Doxygen`__ is a documentation (doxy) generation (gen) system. You should approach it with fear, awe, and humility. And remember to never look it in the eyes. __ http://www.doxygen.nl/ Doxygen on its own is a fascinating tool. It's stupendously flexible, and immensely powerful. I mean let's think about what it's actually doing: it's parsing and extracting documentation from C++ (among other possible languages), which in its own right cannot even be parsed using pushdown automata. Read this `amusing SO answer`__ for why it's so complex! __ https://stackoverflow.com/a/14589567/3814202 With a little appreciation for **what** Doxygen is actually doing for you, don't take for granted that it likely isn't going to work *perfectly* out-of-the-box. It **will** get **most** things right with almost no effort! But if your code is complicated, expect failure, taking solace in the fact that Doxygen's exceptional flexibility allows for you to fix the errors. **Recipe for Success** 1. Start the documentation as early as you can. Don't document it once you want to release. 2. Since you're reading this page, I'd say the easiest thing to "just get the docs going" would be to use the STDIN approach provided by Exhale. 3. When things aren't appearing correctly, add the **line** ``GENERATE_HTML = YES`` to your input to :data:`~exhale.configs.exhaleDoxygenStdin` and look at the actual Doxygen HTML pages. - If it is broken there, there's no way it's going to work with Exhale. - If it is working there but not with Exhale, you at least have a starting point. 4. **DO NOT IGNORE THE ERRORS FROM DOXYGEN**. I've never understood why people think that it's ok for their documentation build to be emitting hundreds of warnings. Fix them, or live with it. The choice is yours. **Recipe for Failure** 1. Assume that you can just plug-and-play and never read any documentation on the documentation systems. 2. Ignore the warnings and errors from Doxygen, Breathe, Exhale, and Sphinx. Crash Course on Documentation with Doxygen ---------------------------------------------------------------------------------------- There is a lot to make sure you do in terms of the documentation you write in a C++ file to make Doxygen work. First and foremost, there is a comprehensive `Doxygen manual`__ that describes anything and everything. Depending on the kind of person you are, browsing the manual may be the best option. I personally went the "hardcore" (aka overwhelming) approach of just reading the entire generated ``Doxyfile``. You can acquire a shiny new ``Doxyfile`` by executing ``doxygen -g`` in your terminal in a directory where there is no ``Doxyfile`` present. __ http://www.doxygen.nl/manual/index.html .. tip:: If you take this approach, open the ``Doxyfile`` in a text editor and view it with ``make`` syntax. That will at least make it bearable. The Core Variables **************************************************************************************** Amidst all of the options, there are really only a handful that you need to get things started. ``INPUT = ../some/path`` You need to tell Doxygen where to look for your code! This can be either a relative or absolute path. Relative paths are preferred, because an absolute path means you will be the only person who can actually build the documentation. Where relative paths are concerned, these are **relative to where Doxygen executes from**. In *pure* Doxygen, this is typically where the ``Doxyfile`` is. In Exhale, these are relative to where ``conf.py`` is. .. note:: Where Exhale is concerned, this is the **only** *required* Doxygen configuration when using :data:`~exhale.configs.exhaleDoxygenStdin`. .. tip:: Exhale sets all of these for you, they are described here for you to know what they are doing. ``OUTPUT_DIRECTORY = ./a/different/path`` This tells Doxygen where to store the output of the documentation it is generating. Supposing you specified ``OUTPUT_DIRECTORY = ./_doxygen``, and you specified to Doxygen ``GENERATE_HTML = YES``, ``GENERATE_LATEX = YES``, and ``GENERATE_XML = YES``, it would create the folder ``./_doxygen``, with subdirectories such as ``html`` or ``xml``. For Exhale, since you already needed to supply the path to the ``xml`` output directory for Breathe, this configuration is inferred. Or rather, Exhale searches for ``OUTPUT_DIRECTORY`` when using :data:`~exhale.configs.exhaleDoxygenStdin` and raises an exception if it is found. ``RECURSIVE = YES`` Assuming your project has more than one directory, you specify ``INPUT`` to be the top-level of where your header files are, and setting this to ``YES`` tells Doxygen to recurse the directory structure. ``FULL_PATH_NAMES = YES`` In *pure* Doxygen, you may not want this. In Exhale, you always do. When set to ``NO``, Doxygen performs some clever renaming, and discards all parts of paths that can be removed *while still keeping files unique*. The consequence for Exhale is that when this is done, there is no way to know the original directory structure. ``STRIP_FROM_PATH = ../some/path`` However, if you ask for ``FULL_PATH_NAMES``, you will be displeased by the results. This variable informs Doxygen to strip out a common prefix path from all the paths generated in the documentation. .. warning:: Exhale requires that you specify this variable through ``exhale_args``. If it is detected in the input to :data:`~exhale.configs.exhaleDoxygenStdin`, an exception is raised. This is a detail specific to hosting on Read the Docs that in all honesty I've never found the cause of. It likely has to do with the environment setup. So in recap, really the only required variables you need to give are ``INPUT`` and ``OUTPUT_DIRECTORY``. I highlight the above variables to indicate what the defaults Exhale expects out of your configuration. Additional Variables with Important Impacts **************************************************************************************** .. _doxygen_aliases: ``ALIASES`` In particular, the two aliases Exhale provides come from Breathe, and allow you to wield full-blown reStructuredText (including directives, grid tables, and more) in a "verbatim" environment. The aliases as sent to Doxygen: .. code-block:: make # Allow for rst directives and advanced functions e.g. grid tables ALIASES = "rst=\verbatim embed:rst:leading-asterisk" ALIASES += "endrst=\endverbatim" This allows you to do something like this in your code: .. code-block:: cpp /** * \file * * \brief This file does not even exist in the real world. * * \rst * There is a :math:`N^2` environment for reStructuredText! * * +-------------------+-------------------+ * | Grid Tables | Are Beautiful | * +===================+===================+ * | Easy to read | In code and docs | * +-------------------+-------------------+ * | Exceptionally flexible and powerful | * +-------+-------+-------+-------+-------+ * | Col 1 | Col 2 | Col 3 | Col 4 | Col 5 | * +-------+-------+-------+-------+-------+ * * \endrst */ .. note:: This ``\rst`` environment is actually quite useful as an override. Doxygen by default enables Markdown. For the most part, you can ignore this, but in the times where Markdown and reStructuredText create conflicts, being able to force reStructuredText is the **only** solution. .. _doxygen_preprocessing: ``ENABLE_PREPROCESSING = YES`` Its rather unlikely you will ever get a full C++ project to produce the expected documentation without using the preprocessor. ``MACRO_EXPANSION = YES`` Similarly, if you use macros you'll want to make sure that Doxygen expands them. ``SKIP_FUNCTION_MACROS = NO`` Though it is not always capable of actually doing the macros, try and let Doxygen's preprocessor do what it can. ``EXPAND_ONLY_PREDEF = NO`` Unless you want to enumerate every single preprocessor constant / macro expansion, tell Doxygen to try and expand everything it can. .. _doxygen_predefined: ``PREDEFINED`` Exhale adds the following two predefined preprocessor symbols: .. code-block:: make # extra defs for to help with building the _right_ version of the docs PREDEFINED = DOXYGEN_DOCUMENTATION_BUILD PREDEFINED += DOXYGEN_SHOULD_SKIP_THIS These are useful for when you either have code that is breaking the Doxygen documentation (e.g. heavy templating / metaprogramming), or need to control the compilation trajectory to where a docstring lives. For example .. code-block:: cpp #if !defined(DOXYGEN_SHOULD_SKIP_THIS) // forward declarations in particular will make Doxygen think that the // class is defined in a different file! class Forward; struct Declaration; #endif // DOXYGEN_SHOULD_SKIP_THIS // platform specific code #if defined(__APPLE__) || defined(DOXYGEN_DOCUMENTATION_BUILD) /// This method is only needed on Apple void they_think_they_are_special(); /** * This definition changes depending on the platform, but we can just * document it once. * * - Apple: ``12`` * - Linux: ``21`` * - Windows: ``0`` */ #define SOME_CONSTANT 12 #elif defined(__linux__) #define SOME_CONSTANT 21 #else #define SOME_CONSTANT 0 #endif If / when the Doxygen preprocessor is not expanding things correctly, use this list to predefine what things should be expanding to. For example, a macro I like to use originally taken from Wenzel Jakob's `NanoGUI`__ for making namespaces a little more readable looks like this: .. code-block:: cpp #define NAMESPACE_BEGIN(name) namespace name { #define NAMESPACE_END(name) } Doxygen gets confused by this, but for say ``namespace nanogui`` we can just predefine it for Doxygen: .. code-block:: make PREDEFINED += NAMESPACE_BEGIN(nanogui)="namespace nanogui {" PREDEFINED += NAMESPACE_END(nanogui)="}" __ http://nanogui.readthedocs.io/en/latest/ Adding Documentation to the Code **************************************************************************************** See the `Doxygen docblocks documentation`__ for all of the different options you have at your disposal. I'll call attention to a couple of useful commands commonly used in documenting specific aspects: +-----------------+--------------------------------------------------------------------+ | Doxygen Command | Doxygen Documentation Action | +=================+====================================================================+ | ``\ref`` | Add link to another item being documented. | +-----------------+--------------------------------------------------------------------+ | ``\brief`` | Add brief documentation to a given construct. | +-----------------+--------------------------------------------------------------------+ | ``\param`` | Add documentation for a specific parameter. | +-----------------+--------------------------------------------------------------------+ | ``\tparam`` | Add documentation for a specific template parameter. | +-----------------+--------------------------------------------------------------------+ | ``\throw`` | Add documentation for a specific exception that can be thrown. | +-----------------+--------------------------------------------------------------------+ | ``\return`` | Add documentation for the return value. | +-----------------+--------------------------------------------------------------------+ | Explicit Control Over Contstructs (e.g., Adding Documentation Apart from Definition )| +-----------------+--------------------------------------------------------------------+ | ``\struct`` | To document a ``struct``. | +-----------------+--------------------------------------------------------------------+ | ``\union`` | To document a ``union``. | +-----------------+--------------------------------------------------------------------+ | ``\enum`` | To document an ``enum`` type. | +-----------------+--------------------------------------------------------------------+ | ``\fn`` | To document a function. | +-----------------+--------------------------------------------------------------------+ | ``\var`` | To document a variable or ``typedef`` or ``enum value``. | +-----------------+--------------------------------------------------------------------+ | ``\def`` | To document a ``#define``. | +-----------------+--------------------------------------------------------------------+ | ``\typedef`` | To document a ``typedef``. | +-----------------+--------------------------------------------------------------------+ | ``\file`` | To document a file. | +-----------------+--------------------------------------------------------------------+ | ``\namespace`` | To document a ``namespace``. | +-----------------+--------------------------------------------------------------------+ | Inline Formatting (see :ref:`file_and_namespace_level_documentation_in_exhale`) | +-----------------+--------------------------------------------------------------------+ | ``\b`` | Bold a single word (e.g. ``\b bold``). | +-----------------+--------------------------------------------------------------------+ | ``\em`` | Emphasize a single word (e.g. ``\em emphasis``). | +-----------------+--------------------------------------------------------------------+ | ``\c`` | Teletype a single word (e.g. ``\c computeroutput``). | +-----------------+--------------------------------------------------------------------+ __ http://www.doxygen.nl/manual/docblocks.html Doxygen Documentation Pitfalls ---------------------------------------------------------------------------------------- .. _document_your_files_son: File Documentation is Necessary for More than just Files! **************************************************************************************** If you want a file documented, you **must** have ``\file`` somewhere in a documentation string in the file. **However**, if you want something like an ``enum`` or ``define`` to show up in the documentation, you **must document the file** (even if the file level documentation is empty)! From the Doxygen documentation reiteration_: .. Let's repeat that, because it is often overlooked: to document global objects (functions, typedefs, enum, macros, etc), you must document the file in which they are defined. .. _reiteration: http://www.doxygen.nl/manual/docblocks.html .. _file_associations: Associating Documentation with the Right File **************************************************************************************** Classes, Structs, Enums, and Unions typically need additional care in order for them to appear in the hierarchy correctly. If you have a file in a directory, the Doxygen FAQ_ explains that you need to specify this location: .. You can also document your class as follows: .. code-block:: cpp /** * \class MyClassName include.h path/include.h * * Docs for MyClassName */ So a minimal working example of the file ``directory/file.h`` defining ``struct thing`` might look like: .. code-block:: cpp #pragma once /** \file */ /** * \struct thing file.h directory/file.h * * \brief The documentation about the thing. */ struct thing { /// The thing that makes the thing. thing() {} }; .. _FAQ: http://www.doxygen.nl/manual/faq.html#faq_code_inc Features Available by Using Sphinx / Breathe / Exhale by way of reStructuredText ---------------------------------------------------------------------------------------- Especially if you already know Markdown, reStructuredText syntax can be a little frustrating. I love both equally for different reasons, but certain actions had to take place in writing Exhale that necessitate using reStructuredText. The following is a mini-guide on the syntax, with links to more resources. Basic Formatting **************************************************************************************** **Bold Text** Bold text is done with **two** asterisks: ``**bold**``. *Italic Text* Italic text is done with **one** asterisk: ``*italic*``. .. danger:: Unlike most Markdown parsers, ``_italic_`` with underscores is **not** going to work. It has to do with how hyperlinks work. ``Teletype Text`` Teletype text is done with **two** backticks: ````teletype text```` .. danger:: Single backticks will **not** do teletype text! This also has to do with how hyperlinks in reStructuredText work. Listings **************************************************************************************** See the `official documentation`__. __ http://docutils.sourceforge.net/docs/user/rst/quickref.html#bullet-lists Tables **************************************************************************************** .. tip:: Everything from here on may cause issues with Doxygen. Use the ``\rst`` verbatim environment described in the :ref:`Doxygen Aliases ` section. Use `grid tables`__!!! __ http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#grid-tables Hyperlinks **************************************************************************************** I'll confess that reStructuredText hyperlinks are probably the most confusing. Best to leave the explaining to the `official documentation`__. __ http://docutils.sourceforge.net/docs/user/rst/quickref.html#hyperlink-targets Useful Directives **************************************************************************************** reStructuredText is particularly sensitive to whitespace. Where directives are concerned, it may be uncomfortable for you but you actually indent by **three** spaces. The reason is simple: it lines up visually. Every directive starts with two ``.``, followed by a single space, then the directive, followed by **two** ``:``. So it looks like this: .. code-block:: rst .. directive:: primary argument :specifications: There is exactly *ONE* blank line between the specifications and the text that is a part of the directive. 1. Not every directive requires (or supports) a primary argument. 2. Not every directive requires (or supports) specifications. - :ref:`Admonitions ` - :ref:`Indexing / Including Other Files ` - :ref:`Code Listings ` .. _admonitions: **Admonitions** Sphinx enables you to include a few different admonitions. Note that which ``html_theme`` you choose in ``conf.py`` determines how they are displayed. With the admonitions, there are no arguments or specifications. If it is a short note you can specify it all on one line. If it is longer, make sure you keep the blank line between the directive and the text. .. note:: .. code-block:: rst .. note:: This is a note! .. tip:: .. code-block:: rst .. tip:: This is a tip! .. warning:: .. code-block:: rst .. warning:: This is a warning! .. danger:: .. code-block:: rst .. danger:: This is a danger (aka super-warning)! .. _indexing_and_including: **Indexing / Including Other Files** The two directives you will use for this will be ``.. toctree::`` and ``.. include::``. ``toctree`` Toctrees are "Table of Contents" trees. See the `Sphinx Toctree Docs`__. __ https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-toctree ``include`` I learned of the ``include`` directive by way of writing Exhale, and call attention to it because of the ``:start-after:`` and ``:end-before:`` specifiers. It's particularly nice to use in order to have a shared ``README.rst`` for your code and documentation. View the source code of `exhale/docs/index.rst`__ to see how that works. __ https://raw.githubusercontent.com/svenevs/exhale/master/docs/index.rst .. _code_listings: **Code Listings** If you hail from Markdown, keep in mind that it's actually very similar. Instead of using fenced code blocks, you're using a directive. .. code-block:: rst .. code-block:: cpp // This code is highlighted using the cpp lexer void foo() { /* ... */ } results in .. code-block:: cpp // This code is highlighted using the cpp lexer void foo() { /* ... */ } You have another option, which is to use **two** colons after a paragraph and then indent by **four** spaces. This is also similar to Markdown, only the two colons are required. The downside to this approach is you are at the disposal of Sphinx to determine what the language is. .. code-block:: rst This is a paragraph:: def foo(): pass This is a paragraph:: def foo(): pass Noting that in the above output there **is** a **single** ``:`` after ``paragraph``. exhale-0.3.1/docs/overview.rst000066400000000000000000000077161420305250600163210ustar00rootroot00000000000000.. _overview: Overview ======================================================================================== Exhale is an automatic C++ library API generation utility. It attempts to port the Doxygen hierarchy view presentations for classes and files into the Sphinx domain. See the :ref:`quickstart_guide` for the bare minimum you need to give to integrate it into your project. What does it do? ---------------------------------------------------------------------------------------- Exhale takes your Doxygen XML documentation and generates a large number of reStructuredText for you automatically. Most of the documents created are simply wrapper pages to use the various directives made available by Breathe. It begins by explicitly parsing the Doxygen XML, and re-constructing the graph of relationships. Things such as what file a given class was defined in, or what namespace it belongs to. Once the graph has been reconstructed and traversed, the API reStructuredText documents are generated and linked to one another, as well as the root library document. The intent of the root library document is for you to just include it in your top-level index ``toctree`` directive. Refer to :ref:`usage_advanced_usage` for how the root library document will be presented as well as how to customize it. The individual and root library page are an attempt to emulate the output of what Doxygen would for their html class and file hierarchies. Many similarities exist, such as the inclusion of ``struct`` before ``class`` in ranking over alphabetical. However, I took a few liberties to change the output to include things I felt were more useful, such as including ``enum`` in the Class Hierarchy. .. note:: Every generated file has a reStructuredText label that you can use to highlight specific items such as an important class or a file. Refer to :ref:`usage_external_linkage` for more information. What does it need to do that? ---------------------------------------------------------------------------------------- First and foremost, if you do not have valid Doxygen documentation, Exhale may produce false relationships, orphaned documents, etc. Generally speaking, if your Doxygen build is producing warnings, this is why Exhale is not creating what you expect. .. tip:: Doxygen is a complex and advanced tool! In particular, you will likely need to finesse the Doxygen preprocessor for everything to work out as expected. Refer to the :ref:`doxygen_documentaion_specifics` section for more information. From there, Exhale needs the following information, all provided in ``conf.py``: 1. You need to configure the Breathe extension's ``breathe_projects`` variable. 2. You need to configure the Breathe extension's ``breathe_default_project`` variable. 3. The containment folder exhale is allowed to assume control over. This is where all of the reStructuredText documents are being generated, and it must be a subdirectory of your Sphinx source directory (e.g., a subdirectory of the folder that your ``conf.py`` is in). 4. The name of the root library document you want generated, which **you** will be including in a ``toctree`` directive somewhere. 5. The title of the root library document, since it is part of a ``toctree``. 6. The path to strip from the Doxygen documentation. This is required since without it the full paths of files are used. Particularly for Read The Docs, this is rather unattractive. This list sounds a lot more intimidating than it is. The way you should think of this list is more like this: 1. Exhale depends on the Breathe extension, so configure that correctly. 2. Exhale is generating a large number of documents for you, it needs to know where to put them. Since the user needs to include one of these documents in their own documentation, Exhale requires you to make these arguments explicitly so you are conscious of where they are getting put. See the :ref:`quickstart_guide` for getting everything up and running. exhale-0.3.1/docs/quickstart.rst000066400000000000000000000344661420305250600166470ustar00rootroot00000000000000.. _quickstart_guide: Quickstart Guide ======================================================================================== You will need to edit **2** files: ``conf.py`` to configure the extensions, and ``index.rst`` (or whatever document you choose) to include the generated api in a ``toctree`` directive. Both ``conf.py`` and ``index.rst`` are part of a default Sphinx documentation project, the next section will walk you through how to create a new Sphinx project and the subsequent sections explain the modifications required on each document. .. contents:: Contents :local: :backlinks: none Getting Started with Sphinx ---------------------------------------------------------------------------------------- To get a project started with Sphinx, we will want to run the ``sphinx-quickstart`` utility. Assuming you are already working in a git repsoitory, the canonical location to do this in would be ``{repo_root}/docs``. So let's go ahead and make that folder and get our Sphinx project started: .. code-block:: console $ cd /path/to/my/repo $ mkdir docs $ cd docs The output below is getting broken up into sections to explain what is happening, but when you run this from your terminal you will need to of course complete it from start to finish. .. code-block:: console $ sphinx-quickstart Welcome to the Sphinx 4.3.1 quickstart utility. Please enter values for the following settings (just press Enter to accept a default value, if one is given in brackets). Selected root path: . You have two options for placing the build directory for Sphinx output. Either, you use a directory "_build" within the root path, or you separate "source" and "build" directories within the root path. > Separate source and build directories (y/n) [n]: n The default choice ``n`` says "keep things together". This is my personal preference, but it is not overwhelmingly significant. +-----------------------------+---------------------------+ | Choosing ``n`` | Choosing ``y`` | +=============================+===========================+ | .. code-block:: text | .. code-block:: text | | | | | docs/ | docs/ | | ├── _build | ├── build | | │ build artifacts | │ build artifacts | | │ go here | │ go here | | ├── conf.py | ├── make.bat | | ├── index.rst | ├── Makefile | | ├── make.bat | └── source | | ├── Makefile | ├── conf.py | | ├── _static | ├── index.rst | | └── _templates | ├── _static | | | └── _templates | +-----------------------------+---------------------------+ The main thing to understand is that where Exhale is concerned, **all relative paths specified in any configuration variables are relative to wherever** ``conf.py`` **lives**. The ``build artfiacts go here`` section is to just explain that if you do ``make html``, then in the ``n`` case a folder ``docs/_build/html`` will be created, and in the ``y`` case it will be in ``docs/build/html``. Repeat for ``make linkcheck``. In either case, you will definitely want to add ``docs/_build`` or ``docs/build`` to your ``.gitignore`` or related version control ignore file. The only other thing worth pointing out here is that by default sphinx reates the ``_static`` and ``_templates`` directories for you. At first start, you don't use those but: ``_static`` Where you would track things like a ``custom.css`` stylesheet, any logo icons, a ``custom.js`` javascript file, etc. See :confval:`sphinx:html_static_path`. ``_templates`` Where you would store custom jinja2 templates to override settings in your chosen :confval:`sphinx:html_theme`. The templates are read from :confval:`sphinx:templates_path` which defaults to ``_templates``. See also: `Sphinx Templating `_. Finishing the ``sphinx-quickstart`` command output, we enter the relevant metadata about our project so that sphinx can populate as much as possible of our ``conf.py`` for us: .. code-block:: console $ sphinx-quickstart ... The project name will occur in several places in the built documentation. > Project name: Super Project > Author name(s): Myself ThePerson, Robotic Armistice > Project release []: 0.1.0 If the documents are to be written in a language other than English, you can select a language here by its language code. Sphinx will then translate text that it generates into that language. For a list of supported codes, see https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-language. > Project language [en]: Creating file /path/to/docs/conf.py. Creating file /path/to/docs/index.rst. Creating file /path/to/docs/Makefile. Creating file /path/to/docs/make.bat. Finished: An initial directory structure has been created. You should now populate your master file /path/to/docs/index.rst and create other documentation source files. Use the Makefile to build the docs, like so: make builder where "builder" is one of the supported builders, e.g. html, latex or linkcheck There is also the official `Sphinx Quickstart Guide`__ with more information on builders and whatnot. __ https://www.sphinx-doc.org/en/master/usage/quickstart.html Bonus: Sphinx Good-to-Know **************************************************************************************** The ``conf.py`` file generated does not have a whole lot left in it anymore, but it's worth pointing out a couple of important usage features of the ``conf.py`` file that are not immediately obvious until you start working. Where do Magic Variables Come From? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Any variable listed on :py:mod:`Sphinx Configurations Module ` can be added to this ``conf.py`` file. There are a **lot** of options available to you, it's worth taking a scroll through to see what kind of customization can be done! For example, see the docs on :confval:`sphinx:rst_epilog` -- that makes it so that you could inject your own custom (and even programmatically defined) substitutions to make life easy. Do I have to Write reStructuredText? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ **No**, this is not a requirement. However, it needs to be enabled in your project since Exhale generates ``.rst`` documents. You will want to modify the :confval:`sphinx:source_suffix` value in ``conf.py`` to include markdown, if that is what you want to write in. You may also be interested in looking at `MyST `_ if you want to write in markdown, but need to call "directives". These -- directives -- are what Exhale needs to do its thing. .. _setup_app: WTF is ``setup(app)``?! ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ One of my most favorite, but not very well documented, features of ``conf.py`` is that effectively **every** sphinx project is a Sphinx Extension of sorts. At the bottom of your ``conf.py`` file, if you add a magic ``def setup(app):`` method then you will be able to do all sorts of things like adding custom `"roles"`__ or "directives". The main point is that ``app`` is going to be a :class:`~sphinx:sphinx.application.Sphinx` instance so any method there is fair game. __ https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html Noting that in recent times, adding a custom ``_static/custom.css`` file or ``_static/super_hack.js`` file has become easier via the :confval:`sphinx:html_css_files` and :confval:`sphinx:html_js_files`, this example is just to demonstrate that using those configuration values could also be accomplished like this: .. code-block:: py # See discussion above about html_static_path, let's assume that the files # docs/_static/custom.css and docs/_static/super_hack.js exist. html_static_path = ["_static"] # ... other configurations ... def setup(app): app.add_css_file("custom.css") app.add_js_file("super_hack.js", async="async") In most cases, you won't really have a need for the ``setup(app)`` method, but as soon as you want to do anything beyond what the default configurations in Sphinx allow, you'll be able to do it in this method. Setup the Extensions in ``conf.py`` ---------------------------------------------------------------------------------------- Assuming your Doxygen documentation is in order, and you already have your Sphinx project ready to go, we need to configure the Breathe and Exhale extensions. For this guide I assume the following directory structure:: my_project/ │ ├── docs/ │ ├── conf.py │ └── index.rst │ ├── include/ │ └── common.hpp │ └── src/ └── common.cpp This structure is not required, but you'll need to change values accordingly. .. warning:: When using *relative* paths, these are always relative to ``conf.py``. In the above structure I do **not** have a "separate source and build directory" from Sphinx. If you do, make sure you are using the correct paths. .. code-block:: py # The `extensions` list should already be in here from `sphinx-quickstart` extensions = [ # there may be others here already, e.g. 'sphinx.ext.mathjax' 'breathe', 'exhale' ] # Setup the breathe extension breathe_projects = { "My Project": "./_doxygen/xml" } breathe_default_project = "My Project" # Setup the exhale extension exhale_args = { # These arguments are required "containmentFolder": "./api", "rootFileName": "library_root.rst", "doxygenStripFromPath": "..", # Heavily encouraged optional argument (see docs) "rootFileTitle": "Library API", # Suggested optional arguments "createTreeView": True, # TIP: if using the sphinx-bootstrap-theme, you need # "treeViewIsBootstrap": True, "exhaleExecutesDoxygen": True, "exhaleDoxygenStdin": "INPUT = ../include" } # Tell sphinx what the primary language being documented is. primary_domain = 'cpp' # Tell sphinx what the pygments highlight language should be. highlight_language = 'cpp' With the above settings, Exhale would produce the ``docs/api`` folder, the file ``docs/api/library_root.rst`` (among many others), and it would use Doxygen to parse ``docs/../include`` and save the output in ``docs/_doxygen``. Meaning the following structure would be created:: my_project/ ├── docs/ │ ├── api/ │ │ └── library_root.rst │ │ │ ├── conf.py │ ├── index.rst │ │ │ └── _doxygen/ │ └── xml/ │ └── index.xml │ ├── include/ │ └── common.hpp │ └── src/ └── common.cpp .. note:: You are by no means required to use Exhale to generate Doxygen. If you choose not to I assume you have the wherewithal to figure it out on your own. See also the :ref:`setup_app` section, in that method would be a good place to invoke doxygen. Or use CMake. Or whatever. Make Your Documentation Link to the Generated API ---------------------------------------------------------------------------------------- So the final step is, in your ``index.rst`` or some other ``toctree`` directive, tell Sphinx to link in the generated ``"{containmentFolder}/{rootFileName}"`` document: .. raw:: html
   .. toctree::
      :maxdepth: 2

      about
      api/library_root
.. _quickstart_clean_target: Optional: Create a Proper Clean Target ---------------------------------------------------------------------------------------- The ``sphinx-quickstart`` utility will create a ``Makefile`` for you, you are advised to create an *explicit* ``clean`` target that removes the generated utilities. 1. You can just as easily specify to ``breathe_projects`` a path such as ``_build/_doxygen/xml``, or ``../build/_doxygen/xml`` if you have separate source and build directories. This will ensure that a ``make clean`` will delete these. To avoid confusing users who are new to Sphinx, I encourage something in the same directory as ``conf.py`` for simplicity. 2. The generated API **must** appear in the Sphinx source directory. If you put it under ``_build``, it will not get parsed. So bust out the ``Makefile`` provided by Sphinx Quickstart and add ``clean`` to the ``.PHONY`` line, and the ``clean`` target as shown below (assuming you've been using the paths specified above): .. code-block:: make .PHONY: help Makefile clean clean: rm -rf _doxygen/ api/ @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .. danger:: ``make`` **requires** ``TAB`` characters! If you just copy-pasted that, **you got space characters** (sorry). .. note:: The above code **must** appear **before** the ``%: Makefile`` "catch-all" target that Sphinx produced by default. Otherwise...well the catch-all target catches all! Hosting your Documentation Online ---------------------------------------------------------------------------------------- Now that you have a sphinx project able to build your documentation, you will want to find a home to host your project online. If you just want to get documentation out the door, try `Read the Docs `_. You can also do something like `GitHub Pages `_, but that process is a little bit more involved since you'll need to deploy to a ``gh-pages`` branch. Search online for ``sphinx github pages``, hopefully over time somebody will create a better GitHub action that allows e.g., versioned hosting of docs. exhale-0.3.1/docs/reference.rst000066400000000000000000000167761420305250600164170ustar00rootroot00000000000000Developer Reference Documentation ======================================================================================== .. todo:: Exhale is being updated in waves as I have free time. It was originally written to be tracked by the user next to ``conf.py``. Keeping it in a single file made this as convenient as possible for users, but was ultimately inconvenient to maintain. Right now, the :mod:`~exhale.graph` module is largely the same as it was in the single file library version. Eventually this will be whittled down into the other modules present in the library (e.g., modularizing writing nodes to file, parsing of xml). Project Layout ---------------------------------------------------------------------------------------- The brief overview of what is where in this project: **Configs** The :mod:`~exhale.configs` module contains everything related to what can be configured via ``conf.py``, plus a few constants and book-keeping variables. At the bottom of this file is where you will find the bridge between Sphinx and Exhale (the function :func:`~exhale.configs.apply_sphinx_configurations`). **Deploy** The :mod:`~exhale.deploy` module is responsible assisting in the creation of the Doxygen documentation (see :data:`~exhale.configs.exhaleExecutesDoxygen`) as well as "exploding" the documentation into all of the various reStructuredText documents. The :func:`~exhale.deploy.explode` is what triggers the creation of the graph, and is called by :func:`~exhale.configs.apply_sphinx_configurations`. **Graph** The :mod:`~exhale.graph` module is the main representation of all the various items being documented. This is by far the most important, and confusing, module. The brief version is that the :func:`~exhale.deploy.explode` function will create an :class:`~exhale.graph.ExhaleRoot` object, which could be thought of as the equivalent of the ``index.xml`` produced by Doxygen. The ``ExhaleRoot`` object will parse the Doxygen xml files and instantiate :class:`~exhale.graph.ExhaleNode` objects to represent the different items being documented. **Parse** The :mod:`~exhale.parse` module does **not** currently do the parsing you would think. It currently only parses the file level documentation from the xml documents and gives a best-faith effort to turn this into valid reStructuredText. The future intent is to have the parsing done in the :mod:`~exhale.graph` module get stripped out and placed in this module. **Utils** The :mod:`~exhale.utils` module contains various helper methods for consistent formatting, colorized error reporting, and serialization of the Exhale configurations (Sphinx requires this in order to pickle the environment / identify what has changed, etc). Execution Flow ---------------------------------------------------------------------------------------- There are quite a few different moving parts involved with this project, but when you build your documentation with ``make html``, the Sphinx build process gets launched and the following (simplified, read the Sphinx docs for the full story) occurs: 1. Sphinx reads ``conf.py`` and determines which ``extensions`` to find / load. The user of Exhale should have both ``breathe`` and ``exhale`` in this list. 2. Each extension is ``setup``, whereby the extension declares what variables it is expecting (or can support) for customization that the user will be putting in their ``conf.py``. During this phase is when extensions also request to be signaled when different stages of the Sphinx build process are triggered. - Exhale requests notification of the `builder-inited`__ event, which is the first event where the configuration variables have been populated. __ https://www.sphinx-doc.org/en/master/extdev/appapi.html#event-builder-inited - It would be nice to one-day support incremental builds and a clean target, but at this time I have no idea how to do these. - As far as I understand it, the Exhale **must** complete generating all of the reStructuredText documents *before* the `env-get-outdated`__ event is triggered. .. note:: If you have suggestions of a better way to hook into Sphinx, or ideas on how to only regenerate documents that need to be updated (hard), **please** `raise an issue on GitHub `_. Even if you don't have a solution, it would be great to hear of ideas! __ https://www.sphinx-doc.org/en/master/extdev/appapi.html#event-env-get-outdated 3. Now that the extensions have been setup, the rest of ``conf.py`` is processed. For all intensive purposes, you can assume that as soon as this is complete is when Exhale is getting launched. 4. If requested, Exhale will first execute Doxygen. Afterward, it will generate all of the various reStructuredText documents. 5. Control is passed back to Sphinx and then the source for the documentation is searched for / parsed from the Sphinx source directory (generally wherever your ``conf.py`` is, as well as all nested directories). In picture form: .. graphviz:: :align: center digraph exe_flow { /* Global graph configurations, node and edge styling */ nodesep=1; rankdir="LR"; node [width=2, height=2, style=filled, fontname="Courier"]; /* Rank declarations (e.g. keeps init and ext next to each other) */ build; {rank=same; init; ext} {rank=same; apply_configs; cfg;} {rank=same; explode; rst; sphinx_parse; sphinx_gen} /* Edge declarations */ build -> init; init -> ext [dir=back, style=dashed]; init -> apply_configs; apply_configs -> cfg [dir=back, style=dashed]; apply_configs -> explode; explode -> rst; rst -> sphinx_parse; sphinx_parse -> sphinx_gen; /* Node specific styling */ build [ shape=rectangle, style="rounded,filled", fillcolor="#c4c4c4", label="make html" ]; init [ shape=octagon, fillcolor="#90cc7f", label="exhale/__init__.py\nsetup(app)" ]; ext [ shape=rectangle, style="rounded,filled", fontname="Times New Roman", label="conf.py had 'exhale'\nin the 'extensions' list" ]; apply_configs [ shape=octagon, fillcolor="#a2ddf9", label="exhale/configs.py\napply_sphinx_configurations" ]; cfg [ shape=rectangle, style="rounded,filled", fontname="Times New Roman", label="Exhale reads configs in\n'exhale_args' dictionary from conf.py" ]; explode [ shape=octagon, fillcolor="#f7f389", label="exhale/deploy.py\nexplode" ]; rst [ shape=doubleoctagon, fillcolor="#f7f389", fontname="Times New Roman", label="Exhale generates the\nreStructuredText documents." ]; sphinx_parse [ shape=rectangle, style="rounded,filled", fontname="Times New Roman", label="Sphinx searches for / parses\nreStructuredText found in\nthe source directory." ]; sphinx_gen [ shape=rectangle, style="rounded,filled", fontname="Times New Roman", label="Sphinx generates your website!" ]; } Full Reference Documentation ---------------------------------------------------------------------------------------- .. toctree:: :maxdepth: 5 reference/configs reference/deploy reference/graph reference/parse reference/utils exhale-0.3.1/docs/reference/000077500000000000000000000000001420305250600156445ustar00rootroot00000000000000exhale-0.3.1/docs/reference/configs.rst000066400000000000000000000775461420305250600200510ustar00rootroot00000000000000Exhale Configs Module ======================================================================================== .. contents:: Contents :local: :backlinks: none .. automodule:: exhale.configs .. _required_configs: Required Configuration Arguments ---------------------------------------------------------------------------------------- .. begin_minimum_requirements_breathe_and_exhale Both Breathe and Exhale require that you **already** have Doxygen ``xml`` documentation generated **before** they are launched. See the :ref:`usage_fully_automated` section for more information. There are **6** required arguments you must provide in your ``conf.py``. **2** are for Breathe, and **4** are for Exhale. Required Arguments for Breathe **************************************************************************************** Breathe is setup to allow for multiple projects being controlled by a **single** ``conf.py`` to rule them all. Exhale only supports generating **one** "project" API at a time. The project that Exhale will generate is determined by what you signal to Breathe as your *default* project. The two arguments that must be present in your ``conf.py`` for Breathe are as follows: .. _breathe_project: **Mapping of Project Names to Doxygen XML Output Paths** ``breathe_projects`` (dict) - Keys: strings that are the name of a given project. - Values: strings that are (absolute or relative) paths to where the Doxygen XML output has been generated. So if the Doxygen documentation was generated to the path ``./_doxygen/xml``, and your project was called ``"My Project"``, you would include the following in your ``conf.py``: .. code-block:: py breathe_projects = { "My Project": "./_doxygen/xml" } .. tip:: When specifying relative paths, they are all relative to ``conf.py``. **Selecting the Project to Generate** ``breathe_default_project`` (str) Since Breathe can support multiple projects, specify the default project so that Exhale will know which one to use (when more than one project is available). Following from the example above, where the key in ``breathe_projects`` we want to generate is ``"My Project"``, you would include the following you your ``conf.py``: .. code-block:: py breathe_default_project = "My Project" Required Arguments for Exhale **************************************************************************************** Users must provide the following values to ``exhale_args`` in their ``conf.py``. .. tip:: Recall the variable name conventions from above. If you want to specify the value for ``containmentFolder`` so that :data:`~exhale.configs.containmentFolder` is populated, the name of the *key* is the string ``"containmentFolder"``. Each entry below details what the ``type`` of the *value* of the key should be. So in this case you might have .. code-block:: py exhale_args = { "containmentFolder": "./api", # other required entries here } .. end_minimum_requirements_breathe_and_exhale .. autodata:: exhale.configs.containmentFolder .. autodata:: exhale.configs.rootFileName .. autodata:: exhale.configs.doxygenStripFromPath Optional Configuration Arguments ---------------------------------------------------------------------------------------- Heavily Encouraged Optional Configuration **************************************************************************************** .. autodata:: exhale.configs.rootFileTitle Build Process Logging, Colors, and Debugging **************************************************************************************** .. autodata:: exhale.configs.verboseBuild .. autodata:: exhale.configs.alwaysColorize .. autodata:: exhale.configs.generateBreatheFileDirectives Root API Document Customization **************************************************************************************** .. begin_root_api_document_layout The main library page (at the path given by ``"{containmentFolder}/{rootFileName}"``) that you will link to from your documentation is laid out as follows: +------------+----------------------------------------------------+----------------+ | **1** | {{ rootFileTitle }} | Heading | +============+====================================================+================+ | **2** | {{ afterTitleDescription }} | Section 1 | +------------+----------------------------------------------------+----------------+ | **3** | Page Hierarchy | Section 2 | +------------+----------------------------------------------------+----------------+ | **4** | Class Hierarchy | Section 3 | +------------+----------------------------------------------------+----------------+ | **5** | File Hierarchy | Section 4 | +------------+----------------------------------------------------+----------------+ | **6** | {{ afterHierarchyDescription }} | Section 5 | +------------+----------------------------------------------------+----------------+ | **7** | {{ fullApiSubSectionTitle }} | Section 6 | +------------+----------------------------------------------------+----------------+ | **8** | Unabridged API | Section 7 | +------------+----------------------------------------------------+----------------+ | **9** | {{ afterBodySummary }} | Section 8 | +------------+----------------------------------------------------+----------------+ 1. The title of the document will be the key to ``"rootFileTitle"`` given to ``exhale_args`` in ``conf.py`` **unless** the ``\mainpage`` command is being used on the doxygen side. See :data:`~exhale.configs.rootFileTitle`. 2. If provided, the value of the key ``"afterTitleDescription"`` given to ``exhale_args`` will be included. See :data:`~exhale.configs.afterTitleDescription`. 3. The Page Hierarchy will be included. This section is only included if the project is using the ``\page`` / ``\subpage`` doxygen commands in its documentation. If those commands are not present, this section is not included. By default this is a bulleted list; see the :ref:`usage_creating_the_treeview` section. The default title for this section is ``"Page Hierarchy"``, but can be changed using the key ``"pageHierarchySubSectionTitle"`` in ``exhale_args``. See :data:`~exhale.configs.pageHierarchySubSectionTitle`. .. note:: This is performed by an ``.. include::`` directive. The file for this section is ``"{containmentFolder}/page_view_hierarchy.rst.include"``. .. tip:: Please also see the documentation for :data:`~exhale.configs.rootFileTitle`. 4. Next, the Class Hierarchy is included. If no class-like compounds are documented in the project (e.g., only free-standing functions), this section will not be included. By default this is a bulleted list; see the :ref:`usage_creating_the_treeview` section. .. note:: This is performed by an ``.. include::`` directive. The file for this section is ``"{containmentFolder}/class_view_hierarchy.rst.include"``. 5. Next, the File Hierarchy is included. By default this is a bulleted list; see the :ref:`usage_creating_the_treeview` section. .. note:: This is performed by an ``.. include::`` directive. The file for this section is ``"{containmentFolder}/file_view_hierarchy.rst.include"``. 6. If provided, the value of the key ``"afterHierarchyDescription"`` given to ``exhale_args`` will be included. See :data:`~exhale.configs.afterHierarchyDescription`. 7. After the Class and File Hierarchies, the unabridged API index is generated. The default title for this section is ``"Full API"``, but can be changed using the key ``"fullApiSubSectionTitle"`` in ``exhale_args``. See :data:`~exhale.configs.fullApiSubSectionTitle`. 8. After the title or default value for (6), the full API is included. This includes links to things such as defines, functions, typedefs, etc. that are not included in the hierarchies. .. note:: This is performed by an ``.. include::`` directive. The file for this section is ``"{containmentFolder}/unabridged_api.rst.include"``. .. tip:: The ``unabridged_api.rst`` performs a large number of ``.. toctree::`` directives to link up all of the documents. You can control the number of bullets shown for each section be setting the key ``"fullToctreeMaxDepth"`` (e.g. to a smaller number such as ``2``). See :data:`~exhale.configs.fullToctreeMaxDepth`. .. tip:: Use :data:`~exhale.configs.unabridgedOrphanKinds` to exclude entire sections from the full API listing. 9. If provided, the value of the key ``"afterBodySummary"`` will be included at the bottom of the document. See :data:`~exhale.configs.afterBodySummary`. .. tip:: Where numbers (4), (5), and (8) are concerned, you should be able to happily ignore that an ``.. include::`` is being performed. The URL for the page is strictly determined by what you specified with the *required* arguments ``"containmentFolder"`` and ``"rootFileName"``. However, if things are not working as expected it is useful to know where to look. The hierarchies in particular, though, may be challenging to understand if you do not know HTML (or JavaScript) and you are generating the Tree View. .. end_root_api_document_layout .. autodata:: exhale.configs.afterTitleDescription .. autodata:: exhale.configs.pageHierarchySubSectionTitle .. autodata:: exhale.configs.afterHierarchyDescription .. autodata:: exhale.configs.fullApiSubSectionTitle .. autodata:: exhale.configs.afterBodySummary .. autodata:: exhale.configs.fullToctreeMaxDepth .. autodata:: exhale.configs.listingExclude .. autodata:: exhale.configs.unabridgedOrphanKinds Clickable Hierarchies **************************************************************************************** .. begin_clickable_hierarchies As stated elsewhere, the primary reason for writing Exhale is to revive the Doxygen Class and File hierarchies. **The default behavior of Exhale is to simply insert bulleted lists for these**. This was originally because I had hoped to support other Sphinx writers besides HTML, but that ship has pretty much sailed. Now, the reason is primarily because more information is required by the user depending on their HTML theme. Basically 1. If you are using any theme **other than** the `Sphinx Bootstrap Theme`__, simply add the argument ``"createTreeView": True`` to your ``exhale_args`` dictionary in ``conf.py``. This will use the lightweight and surprisingly compatible collapsibleLists_ library for your clickable hierarchies. __ https://ryan-roemer.github.io/sphinx-bootstrap-theme .. _collapsibleLists: http://code.iamkate.com/javascript/collapsible-lists/ 2. When using either the ``sphinx-bootstrap-theme``, or any other theme that incorporates Bootstrap, you will need to make sure to **also** set ``"treeViewIsBootstrap": True`` in your ``exhale_args`` dictionary in ``conf.py`` **in addition to** ``"createTreeView": True``. Exhale will then use the `Bootstrap Treeview`__ library to generate your clickable hierarchies. __ https://github.com/jonmiles/bootstrap-treeview .. note:: See features available on ``bootstrap-treeview`` that you want access to? Add your thoughts `on the issue `_, explaining which feature you would want to be able to control. The currently available customizations :ref:`begin here `, but it shouldn't be too hard to add more. .. tip:: You will see that many of the color, selection, and search options are **not** available to be customized for ``bootstrap-treeview``. This is by design. See :data:`~exhale.configs.treeViewBootstrapTextSpanClass`, :data:`~exhale.configs.treeViewBootstrapIconMimicColor`, and :data:`~exhale.configs.treeViewBootstrapOnhoverColor` for your color options. The links are defined by your bootstrap theme's ``a`` tag color. See the :ref:`credit` section for information on the licensing of these libraries. Neither library should produce any legal gray areas for you, but I'm not a lawyer. .. todo:: add some pictures of the different hierarchies once this is packaged .. end_clickable_hierarchies .. autodata:: exhale.configs.createTreeView .. autodata:: exhale.configs.minifyTreeView .. autodata:: exhale.configs.treeViewIsBootstrap .. _bootstrap_mods: .. autodata:: exhale.configs.treeViewBootstrapTextSpanClass .. autodata:: exhale.configs.treeViewBootstrapIconMimicColor .. autodata:: exhale.configs.treeViewBootstrapOnhoverColor .. autodata:: exhale.configs.treeViewBootstrapUseBadgeTags .. autodata:: exhale.configs.treeViewBootstrapExpandIcon .. autodata:: exhale.configs.treeViewBootstrapCollapseIcon .. autodata:: exhale.configs.treeViewBootstrapLevels .. autodata:: exhale.configs._class_hierarchy_id .. autodata:: exhale.configs._file_hierarchy_id .. autodata:: exhale.configs._page_hierarchy_id .. autodata:: exhale.configs._bstrap_class_hierarchy_fn_data_name .. autodata:: exhale.configs._bstrap_file_hierarchy_fn_data_name .. autodata:: exhale.configs._bstrap_page_hierarchy_fn_data_name Page Level Customization **************************************************************************************** .. begin_page_level_customization Each page generated for a given "leaf-like" node (classes, structs, functions) will look something like this, where special treatment is given to File pages specifically: +----------------------------------------------------------+-----------+ | {{ pageLevelConfigMeta }} | Meta | +----------------------------------------------------------+-----------+ | {{ Node Title }} | Heading | +=======+==================================================+===========+ | **1** | Definition {{ link to file or program listing }} | Section 1 | +-------+--------------------------------------------------+ | | **2** | {{ contentsDirectives }} | | +-------+--------------------------------------------------+-----------+ | {{ Kind Specific Exhale Links }} | Section 2 | +----------------------------------------------------------+-----------+ | {{ Breathe Directive }} | Section 3 | +----------------------------------------------------------+-----------+ **Meta** The page-level metadata is controlled by :data:`~exhale.configs.pageLevelConfigMeta`. It is only included if provided. **Heading** The internal reStructuredText link and page heading are included. These are determined by the :class:`~exhale.graph.ExhaleNode` object's ``link_name`` and ``title`` members, respectively. **Section 1** **File Pages** 1. If using Exhale to generate Doxygen on STDIN, the ``XML_PROGRAMLISTING`` Doxygen variable is set to ``YES``, and an associated program listing page is generated for each file and linked to here. .. tip:: The value of :data:`~exhale.configs.doxygenStripFromPath` **directly** affects what path is displayed here. .. danger:: If you override ``XML_PROGRAMLISTING = NO`` (or do not explicitly set it to ``YES`` if using alternative Doxygen generation methods), **significantly more** than just whether or not the program listing document is generated is affected. There are numerous graph relationships that Exhale **cannot recover without the xml program listing**. 2. See the :ref:`using_contents_directives` section. For File pages, the ``brief`` description of the File (if provided) is included in Section 1 underneath the title. See :ref:`file_and_namespace_level_documentation_in_exhale` **Other Pages** 1. Assuming Exhale was able to infer which file defined a given node, a link to the file page that defined it is included here. 2. See the :ref:`using_contents_directives` section. For Namespace pages, the ``brief`` description of the Namespace (if provided) is included in Section 1 underneath the title. See :ref:`file_and_namespace_level_documentation_in_exhale`. **Section 2** **File Pages** At the beginning of section 2, the detailed description of the file is included if provided. Afterward, an enumeration of the files that this file ``#include`` s, as well as files that ``#include`` this file, is presented next. Afterward, an enumeration by kind (namespaces, classes, functions, etc) that were defined in this file are included. **Other Pages** For many pages, section 2 will be blank. **Classes and Structs** Links to any nested classes (of this type, or the containing class if this is a nested type) are included. Afterward, links to any base or derived classes are included. If :data:`~exhale.configs.includeTemplateParamOrderList` is ``True``, the template parameter list enumeration is included next. **Namespaces** Namespaces will include an enumeration of everything that was determined to be a member of this namespace, listed by kind. So things like nested namespaces, classes and structs, functions, etc. **Section 3** **File Pages** If Exhale is producing unexpected output for file level documentation, you can set :data:`~exhale.configs.generateBreatheFileDirectives` to ``True`` **as a debugging feature**. Please refer to the :ref:`doxygen_documentaion_specifics` section for potential causes, in particular the subsection describing :ref:`file_and_namespace_level_documentation_in_exhale`. **Namespaces** No Breathe directives for namespaces are used, as they will cause the same problems that the file directives do. Please refer to the :ref:`file_and_namespace_level_documentation_in_exhale` section. **Other Pages** For all other pages (except for directories, which simply link to subdirectories and files in that directory), this is where the Breathe directive is inserted. .. tip:: See the :ref:`usage_customizing_breathe_output` section for how you can modify this section of a given document. .. end_page_level_customization .. autodata:: exhale.configs.includeTemplateParamOrderList .. autodata:: exhale.configs.pageLevelConfigMeta .. autodata:: exhale.configs.repoRedirectURL Using Contents Directives ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ .. begin_page_level_customization_contents_directives .. note:: I put a lot of thought into the defaults for the ``.. contents::`` directive. I believe the chosen defaults are optimal, but this is very much a personal decision. It also depends a lot on what ``html_theme`` you are using in ``conf.py``. For example, the ``sphinx_rtd_theme`` (while classy) leaves a lot to the imagination where page-level navigation is concerned (and the page is long). The `Contents Directive`__ can be used to display the table of contents on an individual reStructuredText page. This is **different** than the ``toctree`` directive in that it is not specifying a list of documents to include next, it is simply informing the reStructuredText parser (Sphinx in our case) that you would like a table of contents displayed where this directive appears. __ http://docutils.sourceforge.net/docs/ref/rst/directives.html#table-of-contents By default, Exhale includes a ``.. contents::`` directive on all File and Namespace pages. This is particularly useful for making the Namespace and File pages navigable by the reader of your documentation when the Namespace / File incorporates many parts of the API. Classes and structs have potential for a contents directive to be warranted (e.g., complex inheritance relationships with nested types, core base types that every class in the API inherits from). If you have particular Classes or Structs that warrant a ``.. contents::`` directive, you can enable this. However, at this time, this is a global setting for Exhale --- either all have it or none have it. That said, the presence of a ``.. contents::`` directive on simple class / struct pages doesn't seem to be *too* distracting. On the other hand, pages generated for things like Directories, Enums, Variables, Defines, Typedefs, etc, are generally only as long as the documentation so they do not receive a ``.. contents::`` directive by default. The way Exhale is setup is to coordinate four variables: 1. :data:`~exhale.configs.contentsDirectives` sets globally whether or not *any* ``.. contents::`` directives are generated. 2. :data:`~exhale.configs.contentsTitle` determines the title of these directives. The default is the reStructuredText default: ``Contents``. 3. :data:`~exhale.configs.contentsSpecifiers` provides the specifications to apply to the ``.. contents::`` directives. For stylistic reasons, the specifiers Exhale defaults to are ``:local:`` and ``:backlinks: none``. 4. :data:`~exhale.configs.kindsWithContentsDirectives` specifies the kinds of compounds that will include a ``.. contents::`` directive. The default is to only generate these for ``namespace`` and ``file``. The implementation, if interested, is in :func:`~exhale.utils.contentsDirectiveOrNone`. Assuming you use all of the Exhale defaults, then every Namespace and File document will include a directive like this: .. code-block:: rst .. contents:: Contents :local: :backlinks: none These defaults basically have two implications: **The Effect of** ``:local:`` **on the Title** The ``:local:`` option states to only include the table of contents for the *remainder* of the page. For Exhale, this means *do not include the title of the page*. When using ``:local:``, the title must be explicitly specified. So if you set :data:`~exhale.configs.contentsTitle` to the **empty string** (keeping all other defaults), the directive generated would be .. code-block:: rst .. contents:: :local: :backlinks: none which results in a table of contents, without the word **Contents** before it. This is likely a personal preference. **The Effect of** ``:backlinks:`` Traditional usage of a ``.. contents::`` directive, when ``:backlinks:`` is not explicitly specified, is to create circular links. When considering writing a long document with many sections and subsections, this is exceptionally convenient. This means that when you click on a link in the generated table of contents, it takes you to the heading for that section. The heading **is also a hyperlink**, which when clicked takes you back to the table of contents. I find this to be awkward for Exhale for two reasons: 1. Section titles as hyperlinks, especially with Bootstrap, are quite unattractive. 2. The circular linking is not exactly intuitive for code documentation. Alas these are things that very much depend on *your personal preferences*, so I've done my best to enable as much flexibility as possible. .. end_page_level_customization_contents_directives .. autodata:: exhale.configs.contentsDirectives .. autodata:: exhale.configs.contentsTitle .. autodata:: exhale.configs.contentsSpecifiers .. autodata:: exhale.configs.kindsWithContentsDirectives Breathe Customization **************************************************************************************** .. begin_customizing_breathe_output The directives for generating the documentation for a given node come from Breathe. Exhale uses the Breathe defaults for all directives, **except** for Classes and Structs. Suppose you are documenting a class ``namespace::ClassName``. Exhale will produce the following directive: .. code-block:: rst .. doxygenclass:: namespace::ClassName :members: :protected-members: :undoc-members: where the defaults being overridden are to include ``:protected-members:`` as well as ``:undoc-members:``. You may, for example, want to also include ``:private-members:`` in your documentation, or override the `default settings for other Breathe directives`__ to control what is displayed. __ http://breathe.readthedocs.io/en/latest/directives.html In order to override these settings, a layer of indirection has to be added. Because Exhale is a Sphinx Extension, it needs to be possible to do something called "Pickle". The short version of what this means is that you cannot give me a function directly to call, because the Python function object cannot be pickled. The solution is to use the wrapper function I have created that takes your input function and stores all possible inputs and outputs in a dictionary. Details aside, it's easier than it sounds. 1. Define your custom specifications function in ``conf.py``. In this example we'll be changing the specifications for the ``class``, ``struct``, and ``enum`` directives, and use the Breathe defaults for everything else: .. code-block:: py # somewhere in `conf.py`, *BERORE* declaring `exhale_args` def specificationsForKind(kind): ''' For a given input ``kind``, return the list of reStructuredText specifications for the associated Breathe directive. ''' # Change the defaults for .. doxygenclass:: and .. doxygenstruct:: if kind == "class" or kind == "struct": return [ ":members:", ":protected-members:", ":private-members:", ":undoc-members:" ] # Change the defaults for .. doxygenenum:: elif kind == "enum": return [":no-link:"] # An empty list signals to Exhale to use the defaults else: return [] .. tip:: The full list of inputs your function will be called with are defined by :data:`~exhale.utils.AVAILABLE_KINDS`. 2. Use Exhale's utility function to create the correct dictionary. Below that function you can now do .. code-block:: py # Use exhale's utility function to transform `specificationsForKind` # defined above into something Exhale can use from exhale import utils exhale_args = { # ... required arguments / other configs ... "customSpecificationsMapping": utils.makeCustomSpecificationsMapping( specificationsForKind ) } .. note:: The parameter to :func:`~exhale.utils.makeCustomSpecificationsMapping` is the **function** itself. .. end_customizing_breathe_output .. autodata:: exhale.configs.customSpecificationsMapping .. autodata:: exhale.configs._closure_map_sanity_check .. _configs_doxygen_execution_and_customization: Doxygen Execution and Customization **************************************************************************************** .. begin_doxygen_execution_and_customization To have Exhale launch Doxygen when you run ``make html``, you will need to set :data:`~exhale.configs.exhaleExecutesDoxygen` to ``True``. After setting that, you will need to choose how Exhale is executing Doxygen. If you already know what you are doing, continue on. If you've *never* used Doxygen before, skim this, but refer to the :ref:`Mastering Doxygen ` for more information on areas that you may get confused by. **Suggested Approach** Provide a (multiline) string to :data:`~exhale.configs.exhaleDoxygenStdin`. In the :ref:`quickstart_guide`, the bare minimum needed to get things off the ground was used: ``INPUT`` must be set to tell Doxygen where to look. .. tip:: If you set :data:`~exhale.configs.verboseBuild` to ``True``, Exhale will print out exactly what it sends to Doxygen. Presumably just specifying ``INPUT`` will not be enough, particularly if the Doxygen preprocessor is not understanding your code. Exhale uses a number of defaults to send to Doxygen as specified by :data:`~exhale.configs.DEFAULT_DOXYGEN_STDIN_BASE`. The way these are used with your argument are as follows: .. code-block:: py # doxy_dir is the parent directory of what you specified in # `breathe_projects[breathe_default_project]` in `conf.py` internal_configs = textwrap.dedent(''' # Tell doxygen to output wherever breathe is expecting things 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)) # The configurations you specified external_configs = textwrap.dedent(configs.exhaleDoxygenStdin) # The full input being sent full_input = "{base}\n{external}\n{internal}\n\n".format( base=configs.DEFAULT_DOXYGEN_STDIN_BASE, external=external_configs, internal=internal_configs ) In words, first the Exhale defaults are sent in. Then your configurations, allowing you to override anything you need. Last, the output directory and strip from path (specified elsewhere) are sent in. The error checking and warning logic seems pretty robust. For example, suppose you need to add to the ``PREDEFINED`` to add a definition. If you did something like .. code-block:: py import textwrap exhale_args = { # ... required args ... "exhaleExecutesDoxygen": True, "exhaleDoxygenStdin": textwrap.dedent(''' INPUT = ../include # Using `=` instead of `+=` overrides PREDEFINED = FOO="12" ''') } This will **override** the ``PREDEFINED`` section in the default configurations. Exhale will produce a warning encouraging you to ``+=``, but still continue. **Using a Doxyfile** If you have your own customized ``Doxyfile``, just make sure it is in the same directory as ``conf.py``. See the documentation for :data:`~exhale.configs.exhaleUseDoxyfile` for items you need to make sure agree with the configurations you have applied elsewhere to Breathe / Exhale. .. end_doxygen_execution_and_customization .. autodata:: exhale.configs._doxygen_xml_output_directory .. autodata:: exhale.configs.exhaleExecutesDoxygen .. autodata:: exhale.configs.exhaleUseDoxyfile .. autodata:: exhale.configs.exhaleDoxygenStdin .. autodata:: exhale.configs.DEFAULT_DOXYGEN_STDIN_BASE .. autodata:: exhale.configs.exhaleSilentDoxygen Programlisting Customization ---------------------------------------------------------------------------------------- .. autodata:: exhale.configs.lexerMapping .. autodata:: exhale.configs._compiled_lexer_mapping Utility Variables ---------------------------------------------------------------------------------------- .. autodata:: exhale.configs.SECTION_HEADING_CHAR .. autodata:: exhale.configs.SUB_SECTION_HEADING_CHAR .. autodata:: exhale.configs.SUB_SUB_SECTION_HEADING_CHAR .. autodata:: exhale.configs.MAXIMUM_FILENAME_LENGTH .. autodata:: exhale.configs.MAXIMUM_WINDOWS_PATH_LENGTH .. autodata:: exhale.configs._the_app .. autodata:: exhale.configs._app_src_dir .. autodata:: exhale.configs._on_rtd Secondary Sphinx Entry Point ---------------------------------------------------------------------------------------- .. autofunction:: exhale.configs.apply_sphinx_configurations exhale-0.3.1/docs/reference/deploy.rst000066400000000000000000000010631420305250600176720ustar00rootroot00000000000000Exhale Deploy Module ======================================================================================== .. automodule:: exhale.deploy Doxygen Execution Functions ---------------------------------------------------------------------------------------- .. autofunction:: exhale.deploy._generate_doxygen .. autofunction:: exhale.deploy._valid_config .. autofunction:: exhale.deploy.generateDoxygenXML Library API Generation ---------------------------------------------------------------------------------------- .. autofunction:: exhale.deploy.explode exhale-0.3.1/docs/reference/graph.rst000066400000000000000000000010301420305250600174710ustar00rootroot00000000000000Exhale Graph Module ======================================================================================== .. automodule:: exhale.graph Helper Class ExhaleNode Reference ---------------------------------------------------------------------------------------- .. autoclass:: exhale.graph.ExhaleNode :members: :special-members: Primary Class ExhaleRoot Reference ---------------------------------------------------------------------------------------- .. autoclass:: exhale.graph.ExhaleRoot :members: :special-members: exhale-0.3.1/docs/reference/parse.rst000066400000000000000000000003021420305250600175030ustar00rootroot00000000000000Exhale Parse Module ======================================================================================== .. automodule:: exhale.parse :members: :special-members: :private-members: exhale-0.3.1/docs/reference/utils.rst000066400000000000000000000030621420305250600175370ustar00rootroot00000000000000Exhale Utils Module ======================================================================================== .. automodule:: exhale.utils .. autodata:: exhale.utils.AVAILABLE_KINDS .. autodata:: exhale.utils.LEAF_LIKE_KINDS .. autofunction:: exhale.utils.contentsDirectiveOrNone Breathe Customization Support ---------------------------------------------------------------------------------------- .. autofunction:: exhale.utils.makeCustomSpecificationsMapping Unsorted Misc ---------------------------------------------------------------------------------------- .. todo:: cleanup / reorder these .. autofunction:: exhale.utils.heading_mark .. autofunction:: exhale.utils.nodeCompoundXMLContents .. autofunction:: exhale.utils.qualifyKind .. autofunction:: exhale.utils.kindAsBreatheDirective .. autofunction:: exhale.utils.specificationsForKind .. autoclass:: exhale.utils.AnsiColors .. autofunction:: exhale.utils.indent .. autofunction:: exhale.utils.prefix .. autofunction:: exhale.utils.exclaim .. autofunction:: exhale.utils.colorize .. autofunction:: exhale.utils._use_color .. autofunction:: exhale.utils.progress .. autofunction:: exhale.utils.info .. autofunction:: exhale.utils.critical .. autofunction:: exhale.utils.verbose_log .. autofunction:: exhale.utils.__fancy .. autofunction:: exhale.utils.fancyErrorString .. autofunction:: exhale.utils.fancyError .. autodata:: exhale.utils.LANG_TO_LEX .. autofunction:: exhale.utils.doxygenLanguageToPygmentsLexer .. autofunction:: exhale.utils.sanitize .. autofunction:: exhale.utils.sanitize_all exhale-0.3.1/docs/requirements.txt000066400000000000000000000002241420305250600171700ustar00rootroot00000000000000sphinx==4.3.1 sphinx_rtd_theme==1.0.0 sphinx-issues # exhale requirements beautifulsoup4 lxml breathe six # test imports pytest pytest-raises>=0.10 exhale-0.3.1/docs/testing.rst000066400000000000000000000124651420305250600161250ustar00rootroot00000000000000Testing Suite ======================================================================================== |coverage| .. include:: ../README.rst :start-after: begin_badges :end-before: end_badges Exhale uses `pytest `_ for its testing suite. To run the tests, Exhale uses `tox `_. .. code-block:: console $ cd /path/to/exhale $ pip install tox $ tox .. note:: See ``tox.ini`` in the root of the repository. You only need to install ``tox`` to be able to run the tests, which will internally download the additional dependencies such as ``pytest``. The tests will be run in a virtual environment. Running Specific Tests ---------------------------------------------------------------------------------------- By default, ``tox`` will run the python tests and linting checks with `flake8 `_. More formally, the default environment list is defined as: .. code-block:: ini [testenv] envlist = py, flake8 This means that the version of python the tests are run with are the interpretor that you installed ``tox`` for. To run a specific test: ``tox -e py`` Run the python unit tests. ``tox -e flake8`` Run the lint tests. ``tox -e docs`` Build the sphinx documentation using the *html* builder (in nitpicky mode). You can view the generated html website with ``open .tox/docs/tmp/html/index.html``. ``tox -e linkcheck`` Build the sphinx documentation using the *linkcheck* builder. .. tip:: If you need to debug a test case to discern why it is failing, the vibrantly colorful `ipdb `_ debugger is already installed in the environment running the tests. It is nearly identical to ``pdb``, and although it is designed for IPython, it works just as well in "regular" code. Just like with ``pdb``, set a trace in the test case you are debugging: .. code-block:: py import ipdb ipdb.set_trace() In order to be able to achieve the break-point, you would launch the tests with .. code-block:: console $ tox -e py -- -s The ``--`` signals that the command line arguments for ``tox`` are complete, and everything afterward should be forwarded to the specified environment (in this case to ``pytest`` since we launched ``-e py``). The ``-s`` switch for ``pytest`` enables the debugger to stop the test, without this flag the test will fail at the break-point. The same goes for other test cases, if you need to specify a flag to ``flake8`` for some reason you would run ``tox -e flake8 -- --ignore XYYY`` where ``XYYY`` is the linter check you want to ignore. Writing Project Test Cases ---------------------------------------------------------------------------------------- ``exhale`` provides tools to build tests that need to generate output files (= ``*.rst`` files) from actual test projects. To create such tests: 1. Create a new test-case folder in ``testing/projects/{new project name}``. 2. Inherit from :class:`testing.base.ExhaleTestCase` in a module you create in the ``testing/tests`` folder, setting the ``test_project`` class-level variable to ``{new project name}`` 3. Add some ``test_{something}`` methods. :class:`testing.base.ExhaleTestCase` will work its magic to apply the necessary pytest fixtures to all these methods so that the configuration is applied, ``sphinx`` is bootstrapped and all ``*.rst`` files generated by ``exhale`` are in the specified output directory at the start of your test. Note that the ``sphinx`` application is made available as ``self.app``. 4. In case you need to run tests with a configuration differing from the default configuration, you can use the :func:`testing.decorators.confoverrides` decorator to tweak the configuration for a test method or the whole test class:: @confoverrides(conf_var1=conv_val1, conf_var2=conf_val2) class MyTestCase(ExhaleTestCase): test_project = 'my_project' or:: @confoverrides(conf_var1=conv_val1, conf_var2=conf_val2) def test_something(self): ... Note that ``@confoverrides`` applied to a test method are combined with ``@confoverrides`` applied to test classes and have precedence. 5. In case you don't want ``exhale`` to generate any files and just need to test the configuration, you can use the :func:`testing.decorators.no_run` on a test method or test class:: @no_run class MyTestCase(ExhaleTestCase): test_project = 'my_project' or:: @no_run def test_something_else(self): ... For more examples, just have a look at the existing tests in the ``testing/tests`` folder. Full Testing Suite Documentation ---------------------------------------------------------------------------------------- .. A **HUGE** thank you to Thomas Khyn for his thorough help figuring out how to coordinate ``pytest``, ``sphinx``, metaclasses, and more -- this testing framework would never have come to fruition without his `epic pull request `_. .. automodule:: testing :members: .. toctree:: :maxdepth: 2 testing/base testing/conftest testing/decorators testing/fixtures testing/hierarchies testing/projects testing/utils testing/tests exhale-0.3.1/docs/testing/000077500000000000000000000000001420305250600153635ustar00rootroot00000000000000exhale-0.3.1/docs/testing/base.rst000066400000000000000000000005711420305250600170320ustar00rootroot00000000000000Testing Base Module ======================================================================================== .. automodule:: testing.base .. autofunction:: testing.base.make_default_config .. autoclass:: testing.base.ExhaleTestCaseMetaclass :members: :special-members: .. autoclass:: testing.base.ExhaleTestCase :members: :special-members: exhale-0.3.1/docs/testing/conftest.rst000066400000000000000000000002401420305250600177360ustar00rootroot00000000000000Testing Conftest Module ======================================================================================== .. automodule:: testing.conftest :members: exhale-0.3.1/docs/testing/decorators.rst000066400000000000000000000012141420305250600202600ustar00rootroot00000000000000Testing Decorators Module ======================================================================================== .. automodule:: testing.decorators Available Decorators ---------------------------------------------------------------------------------------- .. autofunction:: testing.decorators.confoverrides .. autofunction:: testing.decorators.no_cleanup .. autofunction:: testing.decorators.no_run Decorator Helper Functions ---------------------------------------------------------------------------------------- .. autofunction:: testing.decorators._apply_confoverride_to_class .. autofunction:: testing.decorators.default_confoverrides exhale-0.3.1/docs/testing/fixtures.rst000066400000000000000000000003011420305250600177600ustar00rootroot00000000000000Testing Fixtures Module ======================================================================================== .. automodule:: testing.fixtures .. autofunction:: testing.fixtures.no_run exhale-0.3.1/docs/testing/hierarchies.rst000066400000000000000000000210021420305250600203760ustar00rootroot00000000000000Testing Hierarchies Module ======================================================================================== .. automodule:: testing.hierarchies .. contents:: Contents :local: :backlinks: none Representing Hierarchies ---------------------------------------------------------------------------------------- .. graphviz:: :align: center digraph G { rankdir="LR"; ranksep=0.88; edge [dir=none]; subgraph cluster_exhale { style=filled; color="#a2ddf9"; label="Exhale Type"; fontsize=22; node [style=filled, shape=rectangle, fontname="Courier"]; e_fake_root_1 [style=none, shape=none, label=""]; exhale_root [label="exhale.graph.ExhaleRoot"]; e_fake_root_2 [style=none, shape=none, label=""]; } subgraph cluster_testing { style=filled; color="#f7f389"; label="Testing Types"; fontsize=22; node [ style=filled, shape=rectangle, fontname="Courier", width=2, fixedsize=true ]; fake_root_1 [style=none, shape=none, label=""]; hierarchy_root [label="hierarchies.root"]; fake_root_2 [style=none, shape=none, label=""]; file_hierarchy [label="file_hierarchy"]; fake_hierarchy [style=none, shape=none, label=""]; class_hierarchy [label="class_hierarchy"]; hierarchy_root:e -> file_hierarchy:w; hierarchy_root -> fake_hierarchy [style=none, color=none]; hierarchy_root:e -> class_hierarchy:w; {rank=same; fake_root_1; hierarchy_root; fake_root_2} {rank=same; class_hierarchy; fake_hierarchy; file_hierarchy} } exhale_root -> hierarchy_root [style=dashed, label="represents"]; } During normal execution, :class:`exhale.graph.ExhaleRoot` essentially represents ``index.xml`` coming from Doxygen. The ``index.xml`` enumerates **far more** than what the :class:`testing.hierarchies.root` class represents, the primary goal for the class and file hierarchies is to validate generated (from Exhale) and expected (from the test) output with respect to **Name Scope Resolutions** (:class:`testing.hierarchies.class_hierarchy`) Particularly, parent-child relationships constructed in :class:`exhale.graph.ExhaleRoot`. See :ref:`hierarchies_class_hierarchies`. **Parsed Directory Structure** (:class:`testing.hierarchies.file_hierarchy`) Directory and file structure, **and** which file defines which documented construct. See :ref:`hierarchies_file_hierarchies`. When creating a hierarchy to test, the author creates a json-like dictionary encoding the expected relationships. Generally, every helper class maps to a nested dictionary value, with some exceptions: .. graphviz:: :align: center digraph G { rankdir="LR"; ranksep=0.88; edge [dir=none]; subgraph cluster_exhale { style=filled; color="#a2ddf9"; label="Exhale Type"; fontsize=22; node [style=filled, shape=rectangle, fontname="Courier"]; subgraph cluster_hacksize { label=" ";// soooo hacky... fontsize=18; fake_node_1 [style=none, shape=none, label=""]; fake_node_2 [style=none, shape=none, label=""]; fake_node_3 [style=none, shape=none, label=""]; fake_node_4 [style=none, shape=none, label=""]; exhale_node [label="exhale.graph.ExhaleNode"]; fake_node_5 [style=none, shape=none, label=""]; fake_node_6 [style=none, shape=none, label=""]; fake_node_7 [style=none, shape=none, label=""]; fake_node_8 [style=none, shape=none, label=""]; fake_node_9 [style=none, shape=none, label=""]; } } subgraph cluster_testing { style=filled; color="#f7f389"; label="Testing Types"; fontsize=22; node [ style=filled, shape=rectangle, fontname="Courier", width=2, fixedsize=true ]; hierarchy_node [label="hierarchies.node"]; subgraph cluster_keys { style=filled; color=cadetblue; label="Key Type"; fontsize=18; clike; define; directory; enum; file; function; namespace; typedef; union; variable; } subgraph cluster_values { style=filled; color=mediumaquamarine; label="Value Type"; fontsize=18; v_clike [label="dict"]; v_define [label="????"]; v_directory [label="dict"]; v_enum [label="????"]; v_file [label="dict"]; v_function [label="parameters"]; v_namespace [label="dict"]; v_typedef [label="????"]; v_union [label="dict"]; v_variable [label="????"]; } hierarchy_node -> clike:w; hierarchy_node -> define:w; hierarchy_node -> directory:w; hierarchy_node -> enum:w; hierarchy_node -> file:w; hierarchy_node -> function:w; hierarchy_node -> namespace:w; hierarchy_node -> typedef:w; hierarchy_node -> union:w; hierarchy_node -> variable:w; clike -> v_clike [dir=forward]; define -> v_define [dir=forward]; directory -> v_directory [dir=forward]; enum -> v_enum [dir=forward]; file -> v_file [dir=forward]; function -> v_function [dir=forward]; namespace -> v_namespace [dir=forward]; typedef -> v_typedef [dir=forward]; union -> v_union [dir=forward]; variable -> v_variable [dir=forward]; } exhale_node -> hierarchy_node [dir=none]; fake_node_1 -> clike [style=none, color=none]; } .. todo:: ``????`` values indicate items not currently implemented or used in the testing framework. .. autoclass:: testing.hierarchies.root :members: :special-members: :exclude-members: __init__, __weakref__, __dict__ .. _hierarchies_class_hierarchies: Class Hierarchies **************************************************************************************** .. autoclass:: testing.hierarchies.class_hierarchy :members: :special-members: :exclude-members: __init__, __weakref__, __dict__ .. autofunction:: testing.hierarchies.compare_class_hierarchy .. _hierarchies_file_hierarchies: File Hierarchies **************************************************************************************** .. autoclass:: testing.hierarchies.file_hierarchy :members: :special-members: :exclude-members: __init__, __weakref__, __dict__ .. autofunction:: testing.hierarchies.compare_file_hierarchy Utility Classes ---------------------------------------------------------------------------------------- .. autoclass:: testing.hierarchies.node :members: :special-members: :exclude-members: __init__, __weakref__, __dict__ Classes and Structs **************************************************************************************** .. autoclass:: testing.hierarchies.clike :members: :special-members: :exclude-members: __init__, __weakref__, __dict__ Directories **************************************************************************************** .. autoclass:: testing.hierarchies.directory :members: :special-members: :exclude-members: __init__, __weakref__, __dict__ Enums **************************************************************************************** .. autoclass:: testing.hierarchies.enum :members: :special-members: :exclude-members: __init__, __weakref__, __dict__ Files **************************************************************************************** .. autoclass:: testing.hierarchies.file :members: :special-members: :exclude-members: __init__, __weakref__, __dict__ Functions **************************************************************************************** .. autoclass:: testing.hierarchies.function :members: :special-members: :exclude-members: __init__, __weakref__, __dict__ .. autoclass:: testing.hierarchies.parameters :members: :special-members: :exclude-members: __init__, __weakref__, __dict__ Namespaces **************************************************************************************** .. autoclass:: testing.hierarchies.namespace :members: :special-members: :exclude-members: __init__, __weakref__, __dict__ Unions **************************************************************************************** .. autoclass:: testing.hierarchies.union :members: :special-members: :exclude-members: __init__, __weakref__, __dict__ exhale-0.3.1/docs/testing/projects.rst000066400000000000000000000034201420305250600177450ustar00rootroot00000000000000Testing Projects Module ======================================================================================== .. automodule:: testing.projects :members: ``testing.projects.c_maths`` Project ---------------------------------------------------------------------------------------- .. automodule:: testing.projects.c_maths :members: ``testing.projects.cpp with spaces`` Project ---------------------------------------------------------------------------------------- .. module:: testing.projects.cpp_with_spaces This cannot be documented because it has spaces in the name and autodoc cannot complete its import. ``testing.projects.cpp_fortran_mixed`` Project ---------------------------------------------------------------------------------------- .. automodule:: testing.projects.cpp_fortran_mixed :members: ``testing.projects.cpp_func_overloads`` Project ---------------------------------------------------------------------------------------- .. automodule:: testing.projects.cpp_func_overloads :members: ``testing.projects.cpp_long_names`` Project ---------------------------------------------------------------------------------------- .. automodule:: testing.projects.cpp_long_names :members: ``testing.projects.cpp_dir_underscores`` Project ---------------------------------------------------------------------------------------- .. automodule:: testing.projects.cpp_dir_underscores :members: ``testing.projects.cpp_nesting`` Project ---------------------------------------------------------------------------------------- .. automodule:: testing.projects.cpp_nesting :members: ``testing.projects.cpp_pimpl`` Project ---------------------------------------------------------------------------------------- .. automodule:: testing.projects.cpp_pimpl :members: exhale-0.3.1/docs/testing/tests.rst000066400000000000000000000034521420305250600172630ustar00rootroot00000000000000All Tests ======================================================================================== .. automodule:: testing.tests Exhale Core Tests ======================================================================================== ``configs`` ---------------------------------------------------------------------------------------- .. automodule:: testing.tests.configs :members: ``configs_tree_view`` ---------------------------------------------------------------------------------------- .. automodule:: testing.tests.configs_tree_view :members: Project Tests ======================================================================================== ``c_maths`` ---------------------------------------------------------------------------------------- .. automodule:: testing.tests.c_maths :members: ``cpp_fortran_mixed`` ---------------------------------------------------------------------------------------- .. automodule:: testing.tests.cpp_fortran_mixed :members: ``cpp_long_names`` ---------------------------------------------------------------------------------------- .. automodule:: testing.tests.cpp_long_names :members: ``cpp_dir_underscores`` ---------------------------------------------------------------------------------------- .. automodule:: testing.tests.cpp_dir_underscores :members: ``cpp_nesting`` ---------------------------------------------------------------------------------------- .. automodule:: testing.tests.cpp_nesting :members: ``cpp_pimpl`` ---------------------------------------------------------------------------------------- .. automodule:: testing.tests.cpp_pimpl :members: ``cpp with spaces`` ---------------------------------------------------------------------------------------- .. automodule:: testing.tests.cpp_with_spaces :members: exhale-0.3.1/docs/testing/utils.rst000066400000000000000000000002321420305250600172520ustar00rootroot00000000000000Testing Utils Module ======================================================================================== .. automodule:: testing.utils :members: exhale-0.3.1/docs/todo.rst000066400000000000000000000002741420305250600154100ustar00rootroot00000000000000TODO ======================================================================================== See also: `project roadmap `_. .. todolist:: exhale-0.3.1/docs/usage.rst000066400000000000000000000372151420305250600155540ustar00rootroot00000000000000Usage ======================================================================================== Using exhale can be simple or involved, depending on how much you want to change and how familiar you are with things like Sphinx, Breathe, Doxygen, etc. At the top level, what you need is: 1. Your C++ code you want to document, with "proper" Doxygen documentation. Please read the :ref:`doxygen_documentaion_specifics` for common documentation pitfalls, as well as features previously unavailable in standard Doxygen. 2. A sphinx documentation project ready to go. See the `Sphinx Getting Started `_ tutorial for getting that off the ground. .. contents:: Contents :local: :backlinks: none .. tip:: Don't know how to get started? That's ok! There's a lot of tools involved here, and it can be quite confusing to know what's what. Please see the :ref:`quickstart_guide` for getting started with Sphinx, Breathe, and Exhale. .. _usage_advanced_usage: Additional Usage and Customization ---------------------------------------------------------------------------------------- Controlling the Layout of the Generated Root Library Document **************************************************************************************** .. include:: reference/configs.rst :start-after: begin_root_api_document_layout :end-before: end_root_api_document_layout .. _usage_creating_the_treeview: Clickable Hierarchies **************************************************************************************** .. include:: reference/configs.rst :start-after: begin_clickable_hierarchies :end-before: end_clickable_hierarchies .. _usage_external_linkage: Linking to a Generated File **************************************************************************************** Using the linking strategies in this section is primarily for in your website's documentation such as ``index.rst`` or ``usage.rst`` (since those are already reStructuredText documents), or even in the supplemental arguments you supply to ``exhale_args`` such as :data:`~exhale.configs.afterTitleDescription` (since these arguments get "pasted" directly onto a generated reStructuredText document). In the actual code documentation, Breathe is typically able to infer links automatically (which is really great!), as well as you can also use ``\ref`` from Doxygen if that is not working. **Where possible, you should prefer using the Doxygen** ``\ref`` **command**. However, you can also use these in your code documentation **provided** that you enter a verbatim reStructuredText. See the :ref:`Doxygen ALIASES ` section for more information on that. .. _usage_suggested_linking_strategy: Suggested reStructuredText Linking Strategy ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Assuming you have set ``primary_domain = 'cpp'`` (as shown in the :ref:`quickstart_guide`), you should be able to use the linking strategies `provided by Sphinx itself`__ *without* needing to prefix everything with ``cpp:``. Some examples: __ https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#cpp-domain +--------------------------------+-----------------------------------------------------+ | Action | Syntax | +================================+=====================================================+ | Linking to a class | ``:class:`namespace::ClassName``` | +--------------------------------+-----------------------------------------------------+ | Linking to a method of a class | ``:func:`namespace::ClassName::methodName``` | +--------------------------------+-----------------------------------------------------+ | Linking to a member of a class | ``:member:`namespace::ClassName::mMemberName``` | +--------------------------------+-----------------------------------------------------+ | Linking to a function | ``:func:`namespace::funcName``` | +--------------------------------+-----------------------------------------------------+ .. tip:: The value of ``primary_domain`` in ``conf.py`` is **very** important here! If you do not set it, the default is ``py`` (python). This means that instead of ``:class:`namespace::ClassName``` you would need to use ``:cpp:class:`namespace::ClassName``` to use a different *domain*. A much more thorough walk-through of how the different domains can be used together (e.g., how to link to a ``define`` or macro) is provided in the companion website's `Using Intersphinx`__ guide. __ http://my-favorite-documentation-test.readthedocs.io/en/latest/using_intersphinx.html .. _using_exhale_internal_links: Using Exhale's Internal Links ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ If the above are not working, you can try using the links that Exhale generates to perform its own internal linkage. These links are much harder to use directly. **However, there are some documented compounds that can only be linked to using these internal links.** These are either because Exhale does not use the corresponding Breathe directive (e.g., ``namespace``) **or** there does not exist an equivalent link type in the Sphinx C++ domain (``file`` and ``dir``). .. _usage_linking_to_the_root_api_document: Linking to the Root API Document ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. todo:: In Exhale v1.0.0 (when full-fledged projects are supported) each heading on this page will receive a formal reStructuredText label to ease the process of linking to this page. .. danger:: This has an important consequence for **you**! If you use the linking strategy described here, when formal reStructuredText labels are introduced, your links will likely break (because it changes the anchor point from the title to the generated label). The link generated is entirely defined by what you specify to ``exhale_args`` with the keys :data:`~exhale.configs.rootFileTitle`, :data:`~exhale.configs.rootFileName` and :data:`~exhale.configs.containmentFolder`. Consider the values .. code-block:: py exhale_args = { "containmentFolder": "./api", "rootFileName": "library_root.rst", "rootFileTitle": "Library API" # ... other arguments ... } Exhale will generate the file ``{containmentFolder}/{rootFileName}`` (so ``./api/library_root.rst``) with an H1 heading ``{rootFileTitle}`` like so: .. code-block:: rst Library API =========== Other stuff generated by Exhale... To link to the top of this generated document, you can use a reStructuredText link such as .. code-block:: rst .. html, not rst! ----------vv .. vvvv Please see the `full Library API `_ .. |--------------| |-------------------| |---------| | .. 1 2 3 4 1. The hyperlink text generated will be what is **before** the ````. In this example the hyperlink text will be ``full Library API``. 2. Inside of the ```` is the actual hyperlink target. Note that this is a relative path here, so the above link would work for ``index.rst``, but would not work for say ``tutorials/getting_started.rst``. 3. Lastly, the anchor point is derived from what you specified as the root file title. The general scheme for how reStructuredText generates these links is basically ``target.lower().replace(" ", "-")``, but special symbols are treated differently. 4. The trailing ``_`` after ``#library-api>`` is **required**! That's how reStructuredText hyperlinks work. .. _usage_linking_to_special_cases: Linking to "Special Cases": namespaces, files, and directories ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The work for generating these links takes place in :func:`ExhaleRoot.initializeNodeFilenameAndLink `. The relevant section of the code: .. code-block:: py SPECIAL_CASES = ["dir", "file", "namespace"] if node.kind in SPECIAL_CASES: if node.kind == "file": unique_id = node.location else: unique_id = node.name unique_id = unique_id.replace(":", "_").replace(os.sep, "_").replace(" ", "_") # ... later on ... if node.kind in SPECIAL_CASES: node.link_name = "{kind}_{id}".format(kind=node.kind, id=unique_id) Some examples of links you would use: - ``namespace foo::bar::baz``: .. code-block:: rst .. node.kind .. |-------| :ref:`namespace_foo__bar__baz` .. || || .. Two underscores between namespaces! .. from: replace(":", "_") - ``file include/outer_dir/inner/file.hpp``: .. code-block:: rst .. node.kind .. |--| :ref:`file_include_outer_dir_inner_file.hpp` Note that unlike namespaces, ``replace(os.sep, "_")`` will only result in a single underscore between directories. Note that spaces are not allowed in the generated links, so they are also replaced with underscores. - ``dir include/outer_dir/inner``: .. code-block:: rst .. node.kind .. |-| :ref:`dir_include_outer_dir_inner` Treatment is identical to files, with the same considerations for path separator and space replacement. .. _usage_linking_to_any_other_generated_file: Linking to Any Other Generated File ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. warning:: Using these links directly may be an exercise in futility. You should be able to use the links described in :ref:`usage_suggested_linking_strategy`. The work for generating these links also takes place in :func:`ExhaleRoot.initializeNodeFilenameAndLink `. The relevant section for these constructs is: .. code-block:: py unique_id = node.refid # ... later on ... node.link_name = "exhale_{kind}_{id}".format(kind=node.kind, id=unique_id) This is prefixed with ``exhale_`` because there are potential collisions with using the Doxygen ``refid`` (Doxygen's internal "unique identifier scheme", which is mostly equivalent to compiler-like C++ name mangling with some potential hashing and divergences). The collisions come from the fact that Breathe will typically create an anchor point that is exactly the Doxygen ``refid``. The Breathe anchors are where the links such as ``:class:`namespace::ClassName``` **as well as** your standard ``\ref`` calls will direct to. This is ideal. The Exhale links are anchor points to the top of the generated page that has the Breathe directive in its body. These anchor points are used for indexing the tree view. .. tip:: If for whatever reason you want to use the Exhale internal links, you can save yourself a lot of time trying to figure out what they might be and just generate the api once. Open up the generated file for the desired node, and look at the top. .. code-block:: rst .. _exhale_class_somecrazy_thing: Class ``somecrazy_thing`` ========================= Copy-paste the thing at the top starting with ``.. _``, noting that in this particular (hypothetical) example, you would use ``:ref:`exhale_class_somecrazy_thing``` **without** the leading underscore. The leading ``.. _`` is what tells reStructuredText "I want this to be the name of the link". .. note:: The internal links changed in a breaking way with Exhale v0.2.0. Unless an exceptionally compelling reason comes up, I hope to never change the internal link generation again. .. _usage_customizing_file_pages: Page Level Customization **************************************************************************************** .. include:: reference/configs.rst :start-after: begin_page_level_customization :end-before: end_page_level_customization .. _using_contents_directives: Using Contents Directives ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ .. include:: reference/configs.rst :start-after: begin_page_level_customization_contents_directives :end-before: end_page_level_customization_contents_directives .. _usage_customizing_breathe_output: Customizing Breathe Output **************************************************************************************** .. include:: reference/configs.rst :start-after: begin_customizing_breathe_output :end-before: end_customizing_breathe_output .. _usage_fully_automated: Fully Automated Building ---------------------------------------------------------------------------------------- It is preferable to have everything generated at once, e.g. if you wish to host your documentation on Read the Docs. Exhale is configured to enable this directly for you, provided that you have the associated configuration variables setup. .. _usage_exhale_executes_doxygen: Using Exhale to Execute Doxygen **************************************************************************************** .. include:: reference/configs.rst :start-after: begin_doxygen_execution_and_customization :end-before: end_doxygen_execution_and_customization Executing Doxygen Independently **************************************************************************************** This is another option, just make sure that Doxygen is run **before** Exhale is. See the note at the bottom of the :ref:`quickstart_guide`. .. _doxygen_documentaion_specifics: Doxygen Documentation Specifics ---------------------------------------------------------------------------------------- .. include:: reference/configs.rst :start-after: begin_doxygen_execution_and_customization :end-before: end_doxygen_execution_and_customization .. _file_and_namespace_level_documentation_in_exhale: File and Namespace Level Documentation in Exhale **************************************************************************************** Since the Breathe file / namespace directives cannot be used, Exhale implements a "best-faith-effort" documentation parser. It includes support for a few basic block-level elements such as listings, but it is definitively not robust. If the file or namespace level documentation is rendering in unexpected ways, this is because your documentation is "too advanced" for Exhale's mini-parser. .. tip:: See the :func:`~exhale.parse.walk` method for the currently supported Doxygen formatting being parsed. However, the solution is easy: use a verbatim reStructuredText environment in the documentation. See how to do that in the :ref:`Doxygen ALIASES ` section. .. note:: By entering a verbatim RST environment, doxygen commands such as ``\ref`` are **no longer available**. Or rather, they will be parsed as-is without actually generating a link to the desired target. Since you've now entered a verbatim RST environment, you would instead use the Sphinx domain links. So if you were linking to ``class namespace::ClassName`` using ``\ref namespace::ClassName``, this would now change to be ``:class:`namespace::ClassName```. See the `Sphinx Cross Referencing Guide `_ for some more examples. There is also an `Intersphinx Guide `_ available on the companion website with some examples of linking to macros. .. _cross: https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#cross-referencing-syntax .. _intersphinx: http://my-favorite-documentation-test.readthedocs.io/en/latest/using_intersphinx.html exhale-0.3.1/exhale/000077500000000000000000000000001420305250600142245ustar00rootroot00000000000000exhale-0.3.1/exhale/__init__.py000066400000000000000000000036171420305250600163440ustar00rootroot00000000000000# -*- coding: utf8 -*- ######################################################################################## # This file is part of exhale. Copyright (c) 2017-2022, Stephen McDowell. # # Full BSD 3-Clause license available here: # # # # https://github.com/svenevs/exhale/blob/master/LICENSE # ######################################################################################## from __future__ import unicode_literals __version__ = "0.3.1" def environment_ready(app): # Defer importing configs until sphinx is running. from . import configs from . import utils from . import deploy # First, setup the extension and verify all of the configurations. configs.apply_sphinx_configurations(app) ####### Next, perform any cleanup # Generate the full API! try: deploy.explode() except: utils.fancyError("Exhale: could not generate reStructuredText documents :/") # TODO: # This is not the correct event for cleanup of this project, as we want to allow the # users to view the generated reStructuredText / Doxygen xml. What needs to be done is # figure out how to hook an extension into `make clean`. It does not appear to be # possible at this point in time? def cleanup_files(app, env, docname): raise RuntimeError("you made it.") def setup(app): app.setup_extension("breathe") app.add_config_value("exhale_args", {}, "env") app.connect("builder-inited", environment_ready) # app.connect("env-purge-doc", cleanup_files) return { "version": __version__, # Because Exhale hooks into / generates *BEFORE* any reading or writing occurs, # it is parallel safe by default. "parallel_read_safe": True, "parallel_write_safe": True } exhale-0.3.1/exhale/configs.py000066400000000000000000002422651420305250600162410ustar00rootroot00000000000000# -*- coding: utf8 -*- ######################################################################################## # This file is part of exhale. Copyright (c) 2017-2022, Stephen McDowell. # # Full BSD 3-Clause license available here: # # # # https://github.com/svenevs/exhale/blob/master/LICENSE # ######################################################################################## ''' The ``configs`` module exists to contain the Sphinx Application configurations specific to this extension. Almost every ``global`` variable defined in this file can be modified using the ``exhale_args`` in ``conf.py``. The convention for this file is as follows: 1. Things that are **not** supposed to change, because their value is expected to be constant, are declared in ``ALL_CAPS``. See - :data:`~exhale.configs.SECTION_HEADING_CHAR` - :data:`~exhale.configs.SUB_SECTION_HEADING_CHAR` - :data:`~exhale.configs.SUB_SUB_SECTION_HEADING_CHAR` - :data:`~exhale.configs.DEFAULT_DOXYGEN_STDIN_BASE` 2. Internal / private variables that are **not** supposed to changed except for by this extension are declared as ``_lower_case_with_single_leading_underscore`` as is common in Python ;). 3. Every other variable is declared as ``camelCase``, indicating that it can be configured **indirectly** by using it as a key in the arguments to ``exhale_args`` present in your ``conf.py``. For example, one of the *required* arguments for this extension is :data:`~exhale.configs.containmentFolder`. This means that the key ``"containmentFolder"`` is *expected* to be present in ``exhale_args``. .. code-block:: py exhale_args = { "containmentFolder": "./api", # ... } Read the documentation for the various configs present to see what the various options are to modify the behavior of Exhale. ''' from __future__ import unicode_literals import os import six import textwrap from pathlib import Path from sphinx.errors import ConfigError, ExtensionError from sphinx.util import logging from types import FunctionType, ModuleType try: # Python 2 StringIO from cStringIO import StringIO except ImportError: # Python 3 StringIO from io import StringIO logger = logging.getLogger(__name__) """ The |SphinxLoggerAdapter| for communicating with the sphinx build process. .. |SphinxLoggerAdapter| replace:: :class:`sphinx:sphinx.util.SphinxLoggerAdapter` """ ######################################################################################## ## # ## Required configurations, these get set indirectly via the dictionary argument # ## given to exhale in your conf.py. # ## # ######################################################################################## containmentFolder = None ''' **Required** The location where Exhale is going to generate all of the reStructuredText documents. **Value in** ``exhale_args`` (str) The value of key ``"containmentFolder"`` should be a string representing the (relative or absolute) path to the location where Exhale will be creating all of the files. **Relative paths are relative to the Sphinx application source directory**, which is almost always wherever the file ``conf.py`` is. .. note:: To better help you the user know what Exhale is generating (and therefore safe to delete), it is a **hard requirement** that ``containmentFolder`` is a **subdirectory** of the Sphinx Source Directory. AKA the path ``"."`` will be rejected, but the path ``"./api"`` will be accepted. The suggested value for ``"containmentFolder"`` is ``"./api"``, or ``"./source/api"`` if you have separate source and build directories with Sphinx. When the html is eventually generated, this will make for a more human friendly url being generated. ''' rootFileName = None ''' **Required** The name of the file that **you** will be linking to from your reStructuredText documents. Do **not** include the ``containmentFolder`` path in this file name, Exhale will create the file ``"{contaimentFolder}/{rootFileName}"`` for you. **Value in** ``exhale_args`` (str) The value of key ``"rootFileName"`` should be a string representing the name of the file you will be including in your top-level ``toctree`` directive. In order for Sphinx to be happy, you should include a ``.rst`` suffix. All of the generated API uses reStructuredText, and that will not ever change. For example, if you specify - ``"containmentFolder" = "./api"``, and - ``"rootFileName" = "library_root.rst"`` Then exhale will generate the file ``./api/library_root.rst``. You would then include this file in a ``toctree`` directive (say in ``index.rst``) with: .. raw:: html
       .. toctree::
          :maxdepth: 2

          about
          api/library_root
''' 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 :data:`~exhale.configs.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. # ## # ######################################################################################## # Heavily Encouraged Optional Configuration # ######################################################################################## rootFileTitle = None r''' **Optional** The title to be written at the top of ``rootFileName``, which will appear in your file including it in the ``toctree`` directive. **Value in** ``exhale_args`` (str) The value of the key ``"rootFileTitle"`` should be a string that has the title of the main library root document folder Exhale will be generating. For example, if you are including the Exhale generated library root file in your ``index.rst`` top-level ``toctree`` directive, the title you supply here will show up on both your main page, as well as in the navigation menus. An example value could be ``"Library API"``. .. danger:: If you are **not** using doxygen pages (``\mainpage``, ``\page``, and/or ``\subpage`` commands), then you need to include this argument! Exhale does not have the ability to detect whether or not your project needs this. **If** ``\mainpage`` **is used:** The title is set to the ``\mainpage`` title unconditionally. **Otherwise:** The title is set to ``"rootFileTitle"`` (this config). Since :data:`~exhale.configs.rootFileName` is ultimately going to be included in a ``.. toctree::`` directive, this document needs a title in some way. Projects utilizing the ``\mainpage`` command should not be required to duplicate this title, projects **not** using this command **need to supply a title**. ''' ######################################################################################## # Build Process Logging, Colors, and Debugging # ######################################################################################## verboseBuild = False ''' **Optional** If you are having a hard time getting documentation to build, or say hierarchies are not appearing as they should be, set this to ``True``. **Value in** ``exhale_args`` (bool) Set the boolean value to be ``True`` to include colorized printing at various stages of the build process. .. warning:: There is only one level of verbosity: excessively verbose. **All logging is written to** ``sys.stderr``. See :data:`~exhale.configs.alwaysColorize`. .. tip:: Looking at the actual code of Exhale trying to figure out what is going on? All logging sections have a comment ``# << verboseBuild`` just before the logging section. So you can ``grep -r '# << verboseBuild' exhale/`` if you're working with the code locally. ''' alwaysColorize = True ''' **Optional** Exhale prints various messages throughout the build process to both ``sys.stdout`` and ``sys.stderr``. The default behavior is to colorize output always, regardless of if the output is being directed to a file. This is because you can simply use ``cat`` or ``less -R``. By setting this to ``False``, when redirecting output to a file the color will not be included. **Value in** ``exhale_args`` (bool) The default is ``True`` because I find color to be something developers should embrace. Simply use ``less -R`` to view colorized output conveniently. While I have a love of all things color, I understand you may not. So just set this to ``False``. .. note:: There is not and will never be a way to remove the colorized logging from the console. This only controls when ``sys.stdout`` and ``sys.stderr`` are being redirected to a file. ''' generateBreatheFileDirectives = False ''' **Optional** Append the ``.. doxygenfile::`` directive from Breathe for *every* file page generated in the API. **Value in** ``exhale_args`` (bool) If True, then the breathe directive (``doxygenfile``) will be incorporated at the bottom of the file. .. danger:: **This feature is not intended for production release of pages, only debugging.** This feature is "deprecated" in lieu of minimal parsing of the input Doxygen xml for a given documented file. This feature can be used to help determine if Exhale has made a mistake in parsing the file level documentation, but usage of this feature will create **many** duplicate id's and the Sphinx build process will be littered with complaints. **Usage of this feature will completely dismantle the links coordinated in all parts of Exhale**. Because duplicate id's are generated, Sphinx chooses where to link to. It seems to reliably choose the links generated by the Breathe File directive, meaning the majority of the navigational setup of Exhale is pretty much invalidated. ''' ######################################################################################## # Root API Document Customization and Treeview # ######################################################################################## afterTitleDescription = None ''' **Optional** Provide a description to appear just after :data:`~exhale.configs.rootFileTitle`. **Value in** ``exhale_args`` (str) If you want to provide a brief summary of say the layout of the API, or call attention to specific classes, functions, etc, use this. For example, if you had Python bindings but no explicit documentation for the Python side of the API, you could use something like .. code-block:: py exhale_args = { # ... other required arguments... "rootFileTitle": "Library API", "afterTitleDescription": textwrap.dedent(\'\'\' .. note:: The following documentation presents the C++ API. The Python API generally mirrors the C++ API, but some methods may not be available in Python or may perform different actions. \'\'\') } ''' pageHierarchySubSectionTitle = "Page Hierarchy" ''' **Optional** The title for the subsection that comes before the Page hierarchy. **Value in** ``exhale_args`` (str) The default value is simply ``"Page Hierarchy"``. Change this to be something else if you so desire. ''' afterHierarchyDescription = None ''' **Optional** Provide a description that appears after the Class and File hierarchies, but before the full (and usually very long) API listing. **Value in** ``exhale_args`` (str) Similar to :data:`~exhale.configs.afterTitleDescription`, only it is included in the middle of the document. ''' fullApiSubSectionTitle = "Full API" ''' **Optional** The title for the subsection that comes after the Class and File hierarchies, just before the enumeration of the full API. **Value in** ``exhale_args`` (str) The default value is simply ``"Full API"``. Change this to be something else if you so desire. ''' afterBodySummary = None ''' **Optional** Provide a summary to be included at the bottom of the root library file. **Value in** ``exhale_args`` (str) Similar to :data:`~exhale.configs.afterTitleDescription`, only it is included at the bottom of the document. .. note:: The root library document generated can be quite long, depending on your framework. Important notes to developers should be included at the top of the file using :data:`~exhale.configs.afterTitleDescription`, or after the hierarchies using :data:`~exhale.configs.afterHierarchyDescription`. ''' fullToctreeMaxDepth = 5 ''' **Optional** The generated library root document performs ``.. include:: unabridged_api.rst`` at the bottom, after the Class and File hierarchies. Inside ``unabridged_api.rst``, every generated file is included using a ``toctree`` directive to prevent Sphinx from getting upset about documents not being included. This value controls the ``:maxdepth:`` for all of these ``toctree`` directives. **Value in** ``exhale_args`` (int) The default value is ``5``, but you may want to give a smaller value depending on the framework being documented. .. warning:: This value must be greater than or equal to ``1``. You are advised not to use a value greater than ``5``. ''' listingExclude = [] ''' **Optional** A list of regular expressions to exclude from both the class hierarchy and namespace page enumerations. This can be useful when you want to keep the listings for the hierarchy / namespace pages more concise, but **do** ultimately want the excluded items documented somewhere. Nodes whose ``name`` (fully qualified, e.g., ``namespace::ClassName``) matches any regular expression supplied here will: 1. Exclude this item from the class view hierarchy listing. 2. Exclude this item from the defining namespace's listing (where applicable). 3. The "excluded" item will still have it's own documentation **and** be linked in the "full API listing", as well as from the file page that defined the compound (if recovered). Otherwise Sphinx will explode with warnings about documents not being included in any ``toctree`` directives. This configuration variable is **one size fits all**. It was created as a band-aid fix for PIMPL frameworks. .. todo:: More fine-grained control will be available in the pickleable writer API sometime in Exhale 1.x. .. note:: If you want to skip documentation of a compound in your framework *entirely*, this configuration variable is **not** where you do it. See :ref:`Doxygen PREDEFINED ` for information on excluding compounds entirely using the doxygen preprocessor. **Value in** ``exhale_args`` (list) The list can be of variable types, but each item will be compiled into an internal list using :func:`python:re.compile`. The arguments for ``re.compile(pattern, flags=0)`` should be specified in order, but for convenience if no ``flags`` are needed for your use case you can just specify a string. For example: .. code-block:: py exhale_args = { # These two patterns should be equitable for excluding PIMPL # objects in a framework that uses the ``XxxImpl`` naming scheme. "listingExclude": [r".*Impl$", (r".*impl$", re.IGNORECASE)] } Each item in ``listingExclude`` may either be a string (the regular expression pattern), or it may be a length two iterable ``(string pattern, int flags)``. ''' # Compiled regular expressions from listingExclude # TODO: moves into config object _compiled_listing_exclude = [] unabridgedOrphanKinds = {"dir", "file", "page"} """ **Optional** The list of node kinds to **exclude** from the unabridged API listing beneath the class and file hierarchies. **Value in** ``exhale_args`` (list or set of strings) The list of kinds (see :data:`~exhale.utils.AVAILABLE_KINDS`) that will **not** be included in the unabridged API listing. The default is to exclude pages (which are already in the page hierarhcy), directories and files (which are already in the file hierarchy). Note that if this variable is provided, it will overwrite the default ``{"dir", "file", "page"}``, meaning if you want to exclude something in addition you need to include ``"page"``, ``"dir"``, and ``"file"``: .. code-block:: py # In conf.py exhale_args = { # Case 1: _only_ exclude union "unabridgedOrphanKinds": {"union"} # Case 2: exclude union in addition to dir / file / page. "unabridgedOrphanKinds": {"dir", "file", "page", union"} } .. tip:: See :data:`~exhale.configs.fullToctreeMaxDepth`, users seeking to reduce the length of the unabridged API should set this value to ``1``. .. warning:: If **either** ``"class"`` **or** ``"struct"`` appear in ``unabridgedOrphanKinds`` then **both** will be excluded. The unabridged API will present classes and structs together. """ ######################################################################################## # Clickable Hierarchies <3 # ######################################################################################## createTreeView = False ''' **Optional** When set to ``True``, clickable hierarchies for the Class and File views will be generated. **Set this variable to** ``True`` **if you are generating html** output for much more attractive websites! **Value in** ``exhale_args`` (bool) When set to ``False``, the Class and File hierarches are just reStructuredText bullet lists. This is rather unattractive, but the default of ``False`` is to hopefully enable non-html writers to still be able to use ``exhale``. .. tip:: Using ``html_theme = "bootstrap"`` (the `Sphinx Bootstrap Theme`__)? Make sure you set :data:`~exhale.configs.treeViewIsBootstrap` to ``True``! __ https://ryan-roemer.github.io/sphinx-bootstrap-theme/ ''' minifyTreeView = True ''' **Optional** When set to ``True``, the generated html and/or json for the class and file hierarchy trees will be minified. **Value in** ``exhale_args`` (bool) The default value is ``True``, which should help page load times for larger APIs. Setting to ``False`` should only really be necessary if there is a problem -- the minified version will be hard to parse as a human. ''' treeViewIsBootstrap = False ''' **Optional** If the generated html website is using ``bootstrap``, make sure to set this to ``True``. The `Bootstrap Treeview`__ library will be used. __ http://jonmiles.github.io/bootstrap-treeview/ **Value in** ``exhale_args`` (bool) When set to ``True``, the clickable hierarchies will be generated using a Bootstrap friendly library. ''' treeViewBootstrapTextSpanClass = "text-muted" ''' **Optional** What **span** class to use for the *qualifying* text after the icon, but before the hyperlink to the actual documentation page. For example, ``Struct Foo`` in the hierarchy would have ``Struct`` as the *qualifying* text (controlled by this variable), and ``Foo`` will be a hyperlink to ``Foo``'s actual documentation. **Value in** ``exhale_args`` (str) A valid class to apply to a ``span``. The actual HTML being generated is something like: .. code-block:: html {qualifier} {hyperlink text} So if the value of this input was ``"text-muted"``, and it was the hierarchy element for ``Struct Foo``, it would be .. code-block:: html Struct Foo The ``Foo`` portion will receive the hyperlink styling elsewhere. .. tip:: Easy choices to consider are the `contextual classes`__ provided by your bootstrap theme. Alternatively, add your own custom stylesheet to Sphinx directly and create a class with the color you want there. __ https://getbootstrap.com/docs/3.3/css/#helper-classes-colors .. danger:: No validity checks are performed. If you supply a class that cannot be used, there is no telling what will happen. ''' treeViewBootstrapIconMimicColor = "text-muted" ''' **Optional** The **paragraph** CSS class to *mimic* for the icon color in the tree view. **Value in** ``exhale_args`` (str) This value must be a valid CSS class for a **paragraph**. The way that it is used is in JavaScript, on page-load, a "fake paragraph" is inserted with the class specified by this variable. The color is extracted, and then a force-override is applied to the page's stylesheet. This was necessary to override some aspects of what the ``bootstrap-treeview`` library does. It's full usage looks like this: .. code-block:: js /* Inspired by very informative answer to get color of links: https://stackoverflow.com/a/2707837/3814202 */ /* vvvvvvvvvv what you give */ var $fake_p = $('

').hide().appendTo("body"); /* ^^^^^^^^^^ */ var iconColor = $fake_p.css("color"); $fake_p.remove(); /* later on */ // Part 2: override the style of the glyphicons by injecting some CSS $('').appendTo('head'); .. tip:: Easy choices to consider are the `contextual classes`__ provided by your bootstrap theme. Alternatively, add your own custom stylesheet to Sphinx directly and create a class with the color you want there. __ https://getbootstrap.com/docs/3.3/css/#helper-classes-colors .. danger:: No validity checks are performed. If you supply a class that cannot be used, there is no telling what will happen. ''' treeViewBootstrapOnhoverColor = "#F5F5F5" ''' **Optional** The hover color for elements in the hierarchy trees. Default color is a light-grey, as specified by default value of ``bootstrap-treeview``'s `onhoverColor`_. *Value in** ``exhale_args`` (str) Any valid color. See `onhoverColor`_ for information. .. _onhoverColor: https://github.com/jonmiles/bootstrap-treeview#onhovercolor ''' treeViewBootstrapUseBadgeTags = True ''' **Optional** When set to ``True`` (default), a Badge indicating the number of nested children will be included **when 1 or more children are present**. When enabled, each node in the json data generated has it's `tags`_ set, and the global `showTags`_ option is set to ``true``. .. _tags: https://github.com/jonmiles/bootstrap-treeview#tags .. _showTags: https://github.com/jonmiles/bootstrap-treeview#showtags **Value in** ``exhale_args`` (bool) Set to ``False`` to exclude the badges. Search for ``Tags as Badges`` on the `example bootstrap treeview page`__, noting that if a given node does not have any children, no badge will be added. This is simply because a ``0`` badge is likely more confusing than helpful. __ http://jonmiles.github.io/bootstrap-treeview/ ''' treeViewBootstrapExpandIcon = "glyphicon glyphicon-plus" ''' **Optional** Global setting for what the "expand" icon is for the bootstrap treeview. The default value here is the default of the ``bootstrap-treeview`` library. **Value in** ``exhale_args`` (str) See the `expandIcon`_ description of ``bootstrap-treeview`` for more information. .. _expandIcon: https://github.com/jonmiles/bootstrap-treeview#expandicon .. note:: Exhale handles wrapping this in quotes, you just need to specify the class (making sure that it has spaces where it should). Exhale does **not** perform any validity checks on the value of this variable. For example, you could use something like: .. code-block:: py exhale_args = { # ... required / other optional args ... # you can set one, both, or neither. just showing both in same example # set the icon to show it can be expanded "treeViewBootstrapExpandIcon": "glyphicon glyphicon-chevron-right", # set the icon to show it can be collapsed "treeViewBootstrapCollapseIcon": "glyphicon glyphicon-chevron-down" } ''' treeViewBootstrapCollapseIcon = "glyphicon glyphicon-minus" ''' **Optional** Global setting for what the "collapse" icon is for the bootstrap treeview. The default value here is the default of the ``bootstrap-treeview`` library. **Value in** ``exhale_args`` (str) See the `collapseIcon`_ description of ``bootstrap-treeview`` for more information. See :data:`~exhale.configs.treeViewBootstrapExpandIcon` for how to specify this CSS class value. .. _collapseIcon: https://github.com/jonmiles/bootstrap-treeview#collapseicon ''' treeViewBootstrapLevels = 1 ''' **Optional** The default number of levels to expand on page load. Note that the ``bootstrap-treeview`` default `levels`_ value is ``2``. ``1`` seems like a safer default for Exhale since the value you choose here largely depends on how you have structured your code. .. _levels: https://github.com/jonmiles/bootstrap-treeview#levels **Value in** ``exhale_args`` (int) An integer representing the number of levels to expand for **both** the Class and File hierarchies. **This value should be greater than or equal to** ``1``, but **no validity checks are performed** on your input. Buyer beware. ''' _class_hierarchy_id = "class-treeView" ''' The ``id`` attribute of the HTML element associated with the **Class** Hierarchy when :data:`~exhale.configs.createTreeView` is ``True``. 1. When :data:`~exhale.configs.treeViewIsBootstrap` is ``False``, this ``id`` is attached to the outer-most ``ul``. 2. For bootstrap, an empty ``div`` is inserted with this ``id``, which will be the anchor point for the ``bootstrap-treeview`` library. ''' _file_hierarchy_id = "file-treeView" ''' The ``id`` attribute of the HTML element associated with the **Class** Hierarchy when :data:`~exhale.configs.createTreeView` is ``True``. 1. When :data:`~exhale.configs.treeViewIsBootstrap` is ``False``, this ``id`` is attached to the outer-most ``ul``. 2. For bootstrap, an empty ``div`` is inserted with this ``id``, which will be the anchor point for the ``bootstrap-treeview`` library. ''' _page_hierarchy_id = "page-treeView" ''' The ``id`` attribute of the HTML element associated with the **Page** Hierarchy when :data:`~exhale.configs.createTreeView` is ``True``. 1. When :data:`~exhale.configs.treeViewIsBootstrap` is ``False``, this ``id`` is attached to the outer-most ``ul``. 2. For bootstrap, an empty ``div`` is inserted with this ``id``, which will be the anchor point for the ``bootstrap-treeview`` library. ''' _bstrap_class_hierarchy_fn_data_name = "getClassHierarchyTree" ''' The name of the JavaScript function that returns the ``json`` data associated with the **Class** Hierarchy when :data:`~exhale.configs.createTreeView` is ``True`` **and** :data:`~exhale.configs.treeViewIsBootstrap` is ``True``. ''' _bstrap_file_hierarchy_fn_data_name = "getFileHierarchyTree" ''' The name of the JavaScript function that returns the ``json`` data associated with the **File** Hierarchy when :data:`~exhale.configs.createTreeView` is ``True`` **and** :data:`~exhale.configs.treeViewIsBootstrap` is ``True``. ''' _bstrap_page_hierarchy_fn_data_name = "getPageHierarchyTree" ''' The name of the JavaScript function that returns the ``json`` data associated with the **Page** Hierarchy when :data:`~exhale.configs.createTreeView` is ``True`` **and** :data:`~exhale.configs.treeViewIsBootstrap` is ``True``. ''' ######################################################################################## # Page Level Customization # ######################################################################################## includeTemplateParamOrderList = False ''' **Optional** For Classes and Structs (only), Exhale can provide a numbered list enumeration displaying the template parameters in the order they should be specified. **Value in** ``exhale_args`` (bool) This feature can be useful when you have template classes that have **many** template parameters. The Breathe directives **will** include the parameters in the order they should be given. However, if you have a template class with more than say 5 parameters, it can become a little hard to read. .. note:: This configuration is all or nothing, and applies to every template Class / Struct. Additionally, **no** ``tparam`` documentation is displayed with this listing. Just the types / names they are declared as (and default values if provided). This feature really only exists as a historical accident. .. warning:: As a consequence of the (hacky) implementation, if you use this feature you commit to HTML output only. Where applicable, template parameters that generate links to other items being documented **only** work in HTML. ''' pageLevelConfigMeta = None ''' **Optional** reStructuredText allows you to employ page-level configurations. These are included at the top of the page, before the title. **Value in** ``exhale_args`` (str) An example of one such feature would be ``":tocdepth: 5"``. To be honest, I'm not sure why you would need this feature. But it's easy to implement, you just need to make sure that you provide valid reStructuredText or *every* page will produce errors. See the `Field Lists`__ guide for more information. __ https://www.sphinx-doc.org/en/master/usage/restructuredtext/field-lists.html ''' 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 ''' **Optional** Include a ``.. contents::`` directive beneath the title on pages that have potential to link to a decent number of documents. **Value in** ``exhale_args`` (bool) By default, Exhale will include a ``.. contents::`` directive on the individual generated pages for the types specified by :data:`~exhale.configs.kindsWithContentsDirectives`. Set this to ``False`` to disable globally. See the :ref:`using_contents_directives` section for all pieces of the puzzle. ''' contentsTitle = "Contents" ''' **Optional** The title of the ``.. contents::`` directive for an individual file page, when it's ``kind`` is in the list specified by :data:`~exhale.configs.kindsWithContentsDirectives` **and** :data:`~exhale.configs.contentsDirectives` is ``True``. **Value in** ``exhale_args`` (str) The default (for both Exhale and reStructuredText) is to label this as ``Contents``. You can choose whatever value you like. If you prefer to have **no title** for the ``.. contents::`` directives, **specify the empty string**. .. note:: Specifying the empty string only removes the title **when** ``":local:"`` **is present in** :data:`~exhale.configs.contentsSpecifiers`. See the :ref:`using_contents_directives` section for more information. ''' contentsSpecifiers = [":local:", ":backlinks: none"] ''' **Optional** The specifications to apply to ``.. contents::`` directives for the individual file pages when it's ``kind`` is in the list specified by :data:`~exhale.configs.kindsWithContentsDirectives` **and** :data:`~exhale.configs.contentsDirectives` is ``True``. **Value in** ``exhale_args`` (list) A (one-dimensional) list of strings that will be applied to any ``.. contents::`` directives generated. Provide the **empty list** if you wish to have no specifiers added to these directives. See the :ref:`using_contents_directives` section for more information. ''' kindsWithContentsDirectives = ["file", "namespace"] ''' **Optional** The kinds of compounds that will include a ``.. contents::`` directive on their individual library page. The default is to generate one for Files and Namespaces. Only takes meaning when :data:`~exhale.configs.contentsDirectives` is ``True``. **Value in** ``exhale_args`` (list) Provide a (one-dimensional) ``list`` or ``tuple`` of strings of the kinds of compounds that should include a ``.. contents::`` directive. Each kind given must one of the entries in :data:`~exhale.utils.AVAILABLE_KINDS`. For example, if you wanted to enable Structs and Classes as well you would do something like: .. code-block:: py # in conf.py exhale_args = { # ... required / optional args ... "kindsWithContentsDirectives": ["file", "namespace", "class", "struct"] } .. note:: This is a "full override". So if you want to still keep the defaults of ``"file"`` and ``"namespace"``, **you** must include them yourself. ''' ######################################################################################## # Breathe Customization # ######################################################################################## customSpecificationsMapping = None ''' **Optional** See the :ref:`usage_customizing_breathe_output` section for how to use this. **Value in** ``exhale_args`` (dict) The dictionary produced by calling :func:`~exhale.utils.makeCustomSpecificationsMapping` with your custom function. ''' _closure_map_sanity_check = "blargh_BLARGH_blargh" ''' See :func:`~exhale.utils.makeCustomSpecificationsMapping` implementation, this is inserted to help enforce that Exhale made the dictionary going into :data:`~exhale.configs.customSpecificationsMapping`. ''' ######################################################################################## # Doxygen Execution and Customization # ######################################################################################## _doxygen_xml_output_directory = None ''' The absolute path the the root level of the doxygen xml output. If the path to the ``index.xml`` file created by doxygen was ``./_doxygen/xml/index.xml``, then this would simply be ``./_doxygen/xml``. .. note:: This is the exact same path as ``breathe_projects[breathe_default_project]``, only it is an absolute path. ''' exhaleExecutesDoxygen = False ''' **Optional** Have Exhale launch Doxygen when you execute ``make html``. **Value in** ``exhale_args`` (bool) Set to ``True`` to enable launching Doxygen. You must set either :data:`~exhale.configs.exhaleUseDoxyfile` or :data:`~exhale.configs.exhaleDoxygenStdin`. ''' exhaleUseDoxyfile = False ''' **Optional** If :data:`~exhale.configs.exhaleExecutesDoxygen` is ``True``, this tells Exhale to use your own ``Doxyfile``. The encouraged approach is to use :data:`~exhale.configs.exhaleDoxygenStdin`. **Value in** ``exhale_args`` (bool) Set to ``True`` to have Exhale use your ``Doxyfile``. .. note:: The ``Doxyfile`` must be in the **same** directory as ``conf.py``. Exhale will change directories to here before launching Doxygen when you have separate source and build directories for Sphinx configured. .. warning:: No sanity checks on the ``Doxyfile`` are performed. If you are using this option you need to verify two parameters in particular: 1. ``OUTPUT_DIRECTORY`` is configured so that ``breathe_projects[breathe_default_project]`` agrees. See the :ref:`Mapping of Project Names to Doxygen XML Output Paths ` section. 2. ``STRIP_FROM_PATH`` is configured to be identical to what is specified with :data:`~exhale.configs.doxygenStripFromPath`. I have no idea what happens when these conflict, but it likely will never result in valid documentation. ''' exhaleDoxygenStdin = None ''' **Optional** If :data:`~exhale.configs.exhaleExecutesDoxygen` is ``True``, this tells Exhale to use the (multiline string) value specified in this argument *in addition to* the :data:`~exhale.configs.DEFAULT_DOXYGEN_STDIN_BASE`. **Value in** ``exhale_args`` (str) This string describes your project's specific Doxygen configurations. At the very least, it must provide ``INPUT``. See the :ref:`usage_exhale_executes_doxygen` section for how to use this in conjunction with the default configurations, as well as how to override them. ''' DEFAULT_DOXYGEN_STDIN_BASE = textwrap.dedent(r''' # If you need this to be YES, exhale will probably break. CREATE_SUBDIRS = NO # So that only Doxygen does not trim paths, which affects the File hierarchy FULL_PATH_NAMES = YES # Nested folders will be ignored without this. You may not need it. RECURSIVE = YES # Set to YES if you are debugging or want to compare. GENERATE_HTML = NO # Unless you want it... GENERATE_LATEX = NO # Both breathe and exhale need the xml. GENERATE_XML = YES # Set to NO if you do not want the Doxygen program listing included. XML_PROGRAMLISTING = YES # Allow for rst directives and advanced functions e.g. grid tables ALIASES = "rst=\verbatim embed:rst:leading-asterisk" ALIASES += "endrst=\endverbatim" # Enable preprocessing and related preprocessor necessities ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = NO SKIP_FUNCTION_MACROS = NO # extra defs for to help with building the _right_ version of the docs PREDEFINED = DOXYGEN_DOCUMENTATION_BUILD PREDEFINED += DOXYGEN_SHOULD_SKIP_THIS ''') ''' These are the default values sent to Doxygen along stdin when :data:`~exhale.configs.exhaleExecutesDoxygen` is ``True``. This is sent to Doxygen immediately **before** the :data:`~exhale.configs.exhaleDoxygenStdin` provided to ``exhale_args`` in your ``conf.py``. In this way, you can override any of the specific defaults shown here. .. tip:: See the documentation for :data:`~exhale.configs.exhaleDoxygenStdin`, as well as :data:`~exhale.configs.exhaleUseDoxyfile`. Only **one** may be provided to the ``exhale_args`` in your ``conf.py``. .. include:: ../DEFAULT_DOXYGEN_STDIN_BASE_value.rst ''' exhaleSilentDoxygen = False ''' **Optional** When set to ``True``, the Doxygen output is omitted from the build. **Value in** ``exhale_args`` (bool) Documentation generation can be quite verbose, especially when running both Sphinx and Doxygen in the same process. Use this to silence Doxygen. .. danger:: You are **heavily** discouraged from setting this to ``True``. Many problems that may arise through either Exhale or Breathe are because the Doxygen documentation itself has errors. It will be much more difficult to find these when you squelch the Doxygen output. The reason you would do this is for actual limitations on your specific ``stdout`` (e.g. you are getting a buffer maxed out). The likelihood of this being a problem for you is exceptionally small. ''' ######################################################################################## # Programlisting Customization # ######################################################################################## lexerMapping = {} r''' **Optional** When specified, and ``XML_PROGRAMLISTING`` is set to ``YES`` in Doxygen (either via your ``Doxyfile`` or :data:`exhaleDoxygenStdin `), this mapping can be used to customize / correct the Pygments lexer used for the program listing page generated for files. Most projects will **not** need to use this setting. **Value in** ``exhale_args`` (dict) The keys and values are both strings. Each key is a regular expression that will be used to check with :func:`python:re.match`, noting that the primary difference between :func:`python:re.match` and :func:`python:re.search` that you should be aware of is that ``match`` searches from the **beginning** of the string. Each value should be a **valid** `Pygments lexer `_. Example usage: .. code-block:: py exhale_args { # ... "lexerMapping": { r".*\.cuh": "cuda", r"path/to/exact_filename\.ext": "c" } } .. note:: The pattern is used to search the full path of a file, **as represented in Doxygen**. This is so that duplicate file names in separate folders can be distinguished if needed. The file path as represented in Doxygen is defined by the path to the file, with some prefix stripped out. The prefix stripped out depends entirely on what you provided to :data:`doxygenStripFromPath `. .. tip:: This mapping is used in :func:`utils.doxygenLanguageToPygmentsLexer `, when provided it is queried first. If you are trying to get program listings for a file that is otherwise not supported directly by Doxygen, you typically want to tell Doxygen to interpret the file as a different language. Take the CUDA case. In my input to :data:`exhaleDoxygenStdin `, I will want to set both ``FILE_PATTERNS`` and append to ``EXTENSION_MAPPING``: .. code-block:: make FILE_PATTERNS = *.hpp *.cuh EXTENSION_MAPPING += cuh=c++ By setting ``FILE_PATTERNS``, Doxygen will now try and process ``*.cuh`` files. By *appending* to ``EXTENSION_MAPPING``, it will treat ``*.cuh`` as C++ files. For CUDA, this is a reasonable choice because Doxygen is generally able to parse the file as C++ and get everything right in terms of member definitions, docstrings, etc. **However**, now the XML generated by doxygen looks like this: .. code-block:: xml So Exhale would be default put the program listing in a ``.. code-block:: cpp``. By setting this variable in ``exhale_args``, you can bypass this and get the desired lexer of your choice. Some important notes for those not particularly comfortable or familiar with regular expressions in python: 1. Note that each key defines a *raw* string (prefix with ``r``): ``r"pattern"``. This is not entirely necessary for this case, but using raw strings makes it so that you do not have to escape as many things. It's a good practice to adopt, but for these purposes should not matter all that much. 2. Note the escaped ``.`` character. This means find the literal ``.``, rather than the regular expression wildcard for *any character*. Observe the difference with and without: .. code-block:: pycon >>> import re >>> if re.match(r".*.cuh", "some_filecuh.hpp"): print("Oops!") ... Oops! >>> if re.match(r".*\.cuh", "some_filecuh.hpp"): print("Oops!") ... >>> Without ``\.``, the ``.cuh`` matches ``ecuh`` since ``.`` is a wildcard for *any* character. You may also want to use ``$`` at the end of the expression if there are multiple file extensions involved: ``r".*\.cuh$"``. The ``$`` states "end-of-pattern", which in the usage of Exhale means end of line (the compiled regular expressions are not compiled with :data:`python:re.MULTILINE`). 3. Take special care at the beginning of your regular expression. The pattern ``r"*\.cuh"`` does **not** compile! You need to use ``r".*\.cuh"``, with the leading ``.`` being required. ''' _compiled_lexer_mapping = {} ''' Internal mapping of compiled regular expression objects to Pygments lexer strings. This dictionary is created by compiling every key in :data:`lexerMapping `. See implementation of :func:`utils.doxygenLanguageToPygmentsLexer ` for usage. ''' ######################################################################################## ## # ## Utility variables. # ## # ######################################################################################## SECTION_HEADING_CHAR = "=" ''' The restructured text H1 heading character used to underline sections. ''' SUB_SECTION_HEADING_CHAR = "-" ''' The restructured text H2 heading character used to underline subsections. ''' SUB_SUB_SECTION_HEADING_CHAR = "*" ''' The restructured text H3 heading character used to underline sub-subsections. ''' MAXIMUM_FILENAME_LENGTH = 255 ''' When a potential filename is longer than ``255``, a sha1 sum is used to shorten. Note that there is no ubiquitous and reliable way to query this information, as it depends on both the operating system, filesystem, **and** even the location (directory path) the file would be generated to (depending on the filesystem). As such, a conservative value of ``255`` should guarantee that the desired filename can always be created. ''' MAXIMUM_WINDOWS_PATH_LENGTH = 260 r''' The file path length on Windows cannot be greater than or equal to ``260`` characters. Since Windows' pathetically antiquated filesystem cannot handle this, they have enabled a "magic" prefix they call an *extended-length path*. This is achieved by inserting the prefix ``\\?\`` which allows you to go up to a maximum path of ``32,767`` characters **but you may only do this for absolute paths**. See `Maximum Path Length Limitation`__ for more information. Dear Windows, did you know it is the 21st century? __ https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file#maximum-path-length-limitation ''' _the_app = None ''' The Sphinx ``app`` object. Currently unused, saved for availability in future. ''' _app_src_dir = None ''' **Do not modify**. The location of ``app.srcdir`` of the Sphinx application, once the build process has begun to execute. Saved to be able to run a few different sanity checks in different places. ''' _on_rtd = os.environ.get('READTHEDOCS', None) == 'True' ''' **Do not modify**. Signals whether or not the build is taking place on ReadTheDocs. If it is, then colorization of output is disabled, as well as the Doxygen output (where applicable) is directed to ``/dev/null`` as capturing it can cause the ``subprocess`` buffers to overflow. ''' ######################################################################################## ## # ## Secondary Sphinx Entry Point # ## Called from exhale/__init__.py:environment_ready during the sphinx build process. # ## # ######################################################################################## def apply_sphinx_configurations(app): ''' This method applies the various configurations users place in their ``conf.py``, in the dictionary ``exhale_args``. The error checking seems to be robust, and borderline obsessive, but there may very well be some glaring flaws. When the user requests for the ``treeView`` to be created, this method is also responsible for adding the various CSS / JavaScript to the Sphinx Application to support the hierarchical views. .. danger:: This method is **not** supposed to be called directly. See ``exhale/__init__.py`` for how this function is called indirectly via the Sphinx API. **Parameters** ``app`` (:class:`sphinx.application.Sphinx`) The Sphinx Application running the documentation build. ''' # Import local to function to prevent circular imports elsewhere in the framework. from . import deploy from . import utils #################################################################################### # Make sure they have the `breathe` configs setup in a way that we can use them. # #################################################################################### # Breathe allows users to have multiple projects to configure in one `conf.py` # A dictionary of keys := project names, values := path to Doxygen xml output dir breathe_projects = app.config.breathe_projects if not breathe_projects: raise ConfigError("You must set the `breathe_projects` in `conf.py`.") elif type(breathe_projects) is not dict: raise ConfigError("The type of `breathe_projects` in `conf.py` must be a dictionary.") # The breathe_default_project is required by `exhale` to determine where to look for # the doxygen xml. # # TODO: figure out how to allow multiple breathe projects? breathe_default_project = app.config.breathe_default_project if not breathe_default_project: raise ConfigError("You must set the `breathe_default_project` in `conf.py`.") elif not isinstance(breathe_default_project, six.string_types): raise ConfigError("The type of `breathe_default_project` must be a string.") if breathe_default_project not in breathe_projects: raise ConfigError( "The given breathe_default_project='{0}' was not a valid key in `breathe_projects`:\n{1}".format( breathe_default_project, breathe_projects ) ) # Grab where the Doxygen xml output is supposed to go, make sure it is a string, # defer validation of existence until after potentially running Doxygen based on # the configs given to exhale doxy_xml_dir = breathe_projects[breathe_default_project] if not isinstance(doxy_xml_dir, six.string_types): raise ConfigError( "The type of `breathe_projects[breathe_default_project]` from `conf.py` was not a string." ) # Make doxy_xml_dir relative to confdir (where conf.py is) if not os.path.isabs(doxy_xml_dir): doxy_xml_dir = os.path.abspath(os.path.join(app.confdir, doxy_xml_dir)) #################################################################################### # Initial sanity-check that we have the arguments needed. # #################################################################################### exhale_args = app.config.exhale_args if not exhale_args: raise ConfigError("You must set the `exhale_args` dictionary in `conf.py`.") elif type(exhale_args) is not dict: raise ConfigError("The type of `exhale_args` in `conf.py` must be a dictionary.") #################################################################################### # In order to be able to loop through things below, we want to grab the globals # # dictionary (rather than needing to do `global containmentFolder` etc for every # # setting that is being changed). # #################################################################################### configs_globals = globals() # Used for internal verification of available keys keys_available = [] # At the end of input processing, fail out if unrecognized keys were found. keys_processed = [] #################################################################################### # Gather the mandatory input for exhale. # #################################################################################### key_error = "Did not find required key `{key}` in `exhale_args`." val_error = "The type of the value for key `{key}` must be `{exp}`, but was `{got}`." req_kv = [ ("containmentFolder", six.string_types, True), ("rootFileName", six.string_types, False), ("doxygenStripFromPath", six.string_types, True) ] for key, expected_type, make_absolute in req_kv: # Used in error checking later keys_available.append(key) # Make sure we have the key if key not in exhale_args: raise ConfigError(key_error.format(key=key)) # Make sure the value is at the very least the correct type val = exhale_args[key] if not isinstance(val, expected_type): val_t = type(val) raise ConfigError(val_error.format(key=key, exp=expected_type, got=val_t)) # Make sure that a value was provided (e.g. no empty strings) if not val: raise ConfigError("Non-empty value for key [{0}] required.".format(key)) # If the string represents a path, make it absolute if make_absolute: # Directories are made absolute relative to app.confdir (where conf.py is) if not os.path.isabs(val): val = os.path.abspath(os.path.join(os.path.abspath(app.confdir), val)) # Set the config for use later try: configs_globals[key] = val keys_processed.append(key) except Exception as e: raise ExtensionError( "Critical error: unable to set `global {0}` to `{1}` in exhale.configs:\n{2}".format( key, val, e ) ) #################################################################################### # Validate what can be checked from the required arguments at this time. # #################################################################################### global _the_app _the_app = app # Require that containmentFolder is a subpath of the sphinx application source # directory (otherwise Sphinx will not process the generated documents). containment_folder_parent = Path(containmentFolder).absolute() app_srcdir = Path(app.srcdir).absolute() try: # relative_to will raise if it is not a subchild containment_folder_parent.relative_to(app_srcdir) # but if it is the same path (docs/ directory) relative_to succeeds if containment_folder_parent == app_srcdir: raise ValueError except: raise ConfigError( "The given `containmentFolder` [{0}] must be a *SUBDIRECTORY* of [{1}].".format( containmentFolder, app.srcdir ) ) global _app_src_dir _app_src_dir = os.path.abspath(app.srcdir) # We *ONLY* generate reStructuredText, make sure Sphinx is expecting this as well as # the to-be-generated library root file is correctly suffixed. if not rootFileName.endswith(".rst"): raise ConfigError( "The given `rootFileName` ({0}) did not end with '.rst'; Exhale is reStructuredText only.".format( rootFileName ) ) if ".rst" not in app.config.source_suffix: raise ConfigError( "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. # #################################################################################### # TODO: `list` -> `(list, tuple)`, update docs too. opt_kv = [ ("rootFileTitle", six.string_types), # Build Process Logging, Colors, and Debugging ("verboseBuild", bool), ("alwaysColorize", bool), ("generateBreatheFileDirectives", bool), # Root API Document Customization and Treeview ("afterTitleDescription", six.string_types), ("pageHierarchySubSectionTitle", six.string_types), ("afterHierarchyDescription", six.string_types), ("fullApiSubSectionTitle", six.string_types), ("afterBodySummary", six.string_types), ("fullToctreeMaxDepth", int), ("listingExclude", list), ("unabridgedOrphanKinds", (list, set)), # Clickable Hierarchies <3 ("createTreeView", bool), ("minifyTreeView", bool), ("treeViewIsBootstrap", bool), ("treeViewBootstrapTextSpanClass", six.string_types), ("treeViewBootstrapIconMimicColor", six.string_types), ("treeViewBootstrapOnhoverColor", six.string_types), ("treeViewBootstrapUseBadgeTags", bool), ("treeViewBootstrapExpandIcon", six.string_types), ("treeViewBootstrapCollapseIcon", six.string_types), ("treeViewBootstrapLevels", int), # Page Level Customization ("includeTemplateParamOrderList", bool), ("pageLevelConfigMeta", six.string_types), ("repoRedirectURL", six.string_types), ("contentsDirectives", bool), ("contentsTitle", six.string_types), ("contentsSpecifiers", list), ("kindsWithContentsDirectives", list), # Breathe Customization ("customSpecificationsMapping", dict), # Doxygen Execution and Customization ("exhaleExecutesDoxygen", bool), ("exhaleUseDoxyfile", bool), ("exhaleDoxygenStdin", six.string_types), ("exhaleSilentDoxygen", bool), # Programlisting Customization ("lexerMapping", dict) ] for key, expected_type in opt_kv: # Used in error checking later keys_available.append(key) # Override the default settings if the key was provided if key in exhale_args: # Make sure the value is at the very least the correct type val = exhale_args[key] if not isinstance(val, expected_type): val_t = type(val) raise ConfigError(val_error.format(key=key, exp=expected_type, got=val_t)) # Set the config for use later try: configs_globals[key] = val keys_processed.append(key) except Exception as e: raise ExtensionError( "Critical error: unable to set `global {0}` to `{1}` in exhale.configs:\n{2}".format( key, val, e ) ) # These two need to be lists of strings, check to make sure def _list_of_strings(lst, title): for spec in lst: if not isinstance(spec, six.string_types): raise ConfigError( "`{title}` must be a list of strings. `{spec}` was of type `{spec_t}`".format( title=title, spec=spec, spec_t=type(spec) ) ) _list_of_strings( contentsSpecifiers, "contentsSpecifiers") _list_of_strings(kindsWithContentsDirectives, "kindsWithContentsDirectives") _list_of_strings( unabridgedOrphanKinds, "unabridgedOrphanKinds") # Make sure the kinds they specified are valid unknown = "Unknown kind `{kind}` given in `{config}`. See utils.AVAILABLE_KINDS." for kind in kindsWithContentsDirectives: if kind not in utils.AVAILABLE_KINDS: raise ConfigError( unknown.format(kind=kind, config="kindsWithContentsDirectives") ) for kind in unabridgedOrphanKinds: if kind not in utils.AVAILABLE_KINDS: raise ConfigError( unknown.format(kind=kind, config="unabridgedOrphanKinds") ) # Make sure the listingExlcude is usable if "listingExclude" in exhale_args: import re # TODO: remove this once config objects are in. Reset needed for testing suite. configs_globals["_compiled_listing_exclude"] = [] # used for error printing, tries to create string out of item otherwise # returns 'at index {idx}' def item_or_index(item, idx): try: return "`{item}`".format(item=item) except: return "at index {idx}".format(idx=idx) exclusions = exhale_args["listingExclude"] for idx in range(len(exclusions)): # Gather the `pattern` and `flags` parameters for `re.compile` item = exclusions[idx] if isinstance(item, six.string_types): pattern = item flags = 0 else: try: pattern, flags = item except Exception as e: raise ConfigError( "listingExclude item {0} cannot be unpacked as `pattern, flags = item`:\n{1}".format( item_or_index(item, idx), e ) ) # Compile the regular expression object. try: regex = re.compile(pattern, flags) except Exception as e: raise ConfigError( "Unable to compile specified listingExclude {0}:\n{1}".format( item_or_index(item, idx), e ) ) configs_globals["_compiled_listing_exclude"].append(regex) # Make sure the lexerMapping is usable if "lexerMapping" in exhale_args: from pygments import lexers import re # TODO: remove this once config objects are in. Reset needed for testing suite. configs_globals["_compiled_lexer_mapping"] = {} lexer_mapping = exhale_args["lexerMapping"] for key in lexer_mapping: val = lexer_mapping[key] # Make sure both are strings if not isinstance(key, six.string_types) or not isinstance(val, six.string_types): raise ConfigError("All keys and values in `lexerMapping` must be strings.") # Make sure the key is a valid regular expression try: regex = re.compile(key) except Exception as e: raise ConfigError( "The `lexerMapping` key [{0}] is not a valid regular expression: {1}".format(key, e) ) # Make sure the provided lexer is available try: lex = lexers.find_lexer_class_by_name(val) except Exception as e: raise ConfigError( "The `lexerMapping` value of [{0}] for key [{1}] is not a valid Pygments lexer.".format( val, key ) ) # Everything works, stash for later processing configs_globals["_compiled_lexer_mapping"][regex] = val #################################################################################### # Internal consistency check to make sure available keys are accurate. # #################################################################################### # See naming conventions described at top of file for why this is ok! keys_expected = [] for key in configs_globals.keys(): val = configs_globals[key] # Ignore modules and functions if not isinstance(val, FunctionType) and not isinstance(val, ModuleType): if key != "logger": # band-aid for logging api with Sphinx prior to config objects # Ignore specials like __name__ and internal variables like _the_app if "_" not in key and len(key) > 0: # don't think there can be zero length ones... first = key[0] if first.isalpha() and first.islower(): keys_expected.append(key) keys_expected = set(keys_expected) keys_available = set(keys_available) if keys_expected != keys_available: err = StringIO() err.write(textwrap.dedent(''' CRITICAL: Exhale encountered an internal error, please raise an Issue on GitHub: https://github.com/svenevs/exhale/issues Please paste the following in the issue report: Expected keys: ''')) for key in keys_expected: err.write("- {0}\n".format(key)) err.write(textwrap.dedent(''' Available keys: ''')) for key in keys_available: err.write("- {0}\n".format(key)) err.write(textwrap.dedent(''' The Mismatch(es): ''')) for key in (keys_available ^ keys_expected): err.write("- {0}\n".format(key)) err_msg = err.getvalue() err.close() raise ExtensionError(err_msg) #################################################################################### # See if unexpected keys were presented. # #################################################################################### all_keys = set(exhale_args.keys()) keys_processed = set(keys_processed) if all_keys != keys_processed: # Much love: https://stackoverflow.com/a/17388505/3814202 from difflib import SequenceMatcher def similar(a, b): return SequenceMatcher(None, a, b).ratio() * 100.0 # If there are keys left over after taking the differences of keys_processed # (which is all keys Exhale expects to see), inform the user of keys they might # have been trying to provide. # # Convert everything to lower case for better matching success potential_keys = keys_available - keys_processed potential_keys_lower = {key.lower(): key for key in potential_keys} extras = all_keys - keys_processed extra_error = StringIO() extra_error.write("Exhale found unexpected keys in `exhale_args`:\n") for key in extras: extra_error.write(" - Extra key: {0}\n".format(key)) potentials = [] for mate in potential_keys_lower: similarity = similar(key, mate) if similarity > 50.0: # Output results with the non-lower version they should put in exhale_args potentials.append((similarity, potential_keys_lower[mate])) if potentials: potentials = reversed(sorted(potentials)) for rank, mate in potentials: extra_error.write(" - {0:2.2f}% match with: {1}\n".format(rank, mate)) extra_error_str = extra_error.getvalue() extra_error.close() raise ConfigError(extra_error_str) #################################################################################### # Verify some potentially inconsistent or ignored settings. # #################################################################################### # treeViewIsBootstrap only takes meaning when createTreeView is True if not createTreeView and treeViewIsBootstrap: logger.warning("Exhale: `treeViewIsBootstrap=True` ignored since `createTreeView=False`") # fullToctreeMaxDepth > 5 may produce other sphinx issues unrelated to exhale if fullToctreeMaxDepth > 5: logger.warning( "Exhale: `fullToctreeMaxDepth={0}` is greater than 5 and may build errors for non-html.".format( fullToctreeMaxDepth ) ) # Make sure that we received a valid mapping created by utils.makeCustomSpecificationsMapping sanity = _closure_map_sanity_check insane = "`customSpecificationsMapping` *MUST* be made using exhale.utils.makeCustomSpecificationsMapping" if customSpecificationsMapping: # Sanity check to make sure exhale made this mapping if sanity not in customSpecificationsMapping: raise ConfigError(insane) elif customSpecificationsMapping[sanity] != sanity: # LOL raise ConfigError(insane) # Sanity check #2: enforce no new additions were made expected_keys = set([sanity]) | set(utils.AVAILABLE_KINDS) provided_keys = set(customSpecificationsMapping.keys()) diff = provided_keys - expected_keys if diff: raise ConfigError("Found extra keys in `customSpecificationsMapping`: {0}".format(diff)) # Sanity check #3: make sure the return values are all strings for key in customSpecificationsMapping: val_t = type(customSpecificationsMapping[key]) if not isinstance(key, six.string_types): raise ConfigError( "`customSpecificationsMapping` key `{key}` gave value type `{val_t}` (need `str`).".format( key=key, val_t=val_t ) ) # Specify where the doxygen output should be going global _doxygen_xml_output_directory _doxygen_xml_output_directory = doxy_xml_dir # If requested, the time is nigh for executing doxygen. The strategy: # 1. Execute doxygen if requested # 2. Verify that the expected doxy_xml_dir (specified to `breathe`) was created # 3. Assuming everything went to plan, let exhale take over and create all of the .rst docs if exhaleExecutesDoxygen: # Cannot use both, only one or the other if exhaleUseDoxyfile and (exhaleDoxygenStdin is not None): raise ConfigError("You must choose one of `exhaleUseDoxyfile` or `exhaleDoxygenStdin`, not both.") # 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(app.confdir, "Doxyfile")) if not os.path.exists(doxyfile_path): raise ConfigError("The file [{0}] does not exist".format(doxyfile_path)) here = os.path.abspath(os.curdir) if here == app.confdir: returnPath = None else: returnPath = here # All necessary information ready, go to where the Doxyfile is, run Doxygen # and then return back (where applicable) so sphinx can continue start = utils.get_time() if returnPath: logger.info(utils.info( "Exhale: changing directories to [{0}] to execute Doxygen.".format(app.confdir) )) os.chdir(app.confdir) logger.info(utils.info("Exhale: executing doxygen.")) status = deploy.generateDoxygenXML() # Being overly-careful to put sphinx back where it was before potentially erroring out if returnPath: logger.info(utils.info( "Exhale: changing directories back to [{0}] after Doxygen.".format(returnPath) )) os.chdir(returnPath) if status: raise ExtensionError(status) else: end = utils.get_time() logger.info(utils.progress( "Exhale: doxygen ran successfully in {0}.".format(utils.time_string(start, end)) )) else: if exhaleUseDoxyfile: logger.warning("Exhale: `exhaleUseDoxyfile` ignored since `exhaleExecutesDoxygen=False`") if exhaleDoxygenStdin is not None: logger.warning("Exhale: `exhaleDoxygenStdin` ignored since `exhaleExecutesDoxygen=False`") if exhaleSilentDoxygen: logger.warning("Exhale: `exhaleSilentDoxygen=True` ignored since `exhaleExecutesDoxygen=False`") # Either Doxygen was run prior to this being called, or we just finished running it. # Make sure that the files we need are actually there. if not os.path.isdir(doxy_xml_dir): raise ConfigError( "Exhale: the specified folder [{0}] does not exist. Has Doxygen been run?".format(doxy_xml_dir) ) index = os.path.join(doxy_xml_dir, "index.xml") if not os.path.isfile(index): raise ConfigError("Exhale: the file [{0}] does not exist. Has Doxygen been run?".format(index)) # Legacy / debugging feature, warn of its purpose if generateBreatheFileDirectives: logger.warning("Exhale: `generateBreatheFileDirectives` is a debugging feature not intended for production.") #################################################################################### # If using a fancy treeView, add the necessary frontend files. # #################################################################################### if createTreeView: if treeViewIsBootstrap: tree_data_static_base = "treeView-bootstrap" tree_data_css = [os.path.join("bootstrap-treeview", "bootstrap-treeview.min.css")] tree_data_js = [ os.path.join("bootstrap-treeview", "bootstrap-treeview.min.js"), # os.path.join("bootstrap-treeview", "apply-bootstrap-treview.js") ] tree_data_ext = [] else: tree_data_static_base = "treeView" tree_data_css = [os.path.join("collapsible-lists", "css", "tree_view.css")] tree_data_js = [ os.path.join("collapsible-lists", "js", "CollapsibleLists.compressed.js"), os.path.join("collapsible-lists", "js", "apply-collapsible-lists.js") ] # The tree_view.css file uses these tree_data_ext = [ os.path.join("collapsible-lists", "css", "button-closed.png"), os.path.join("collapsible-lists", "css", "button-open.png"), os.path.join("collapsible-lists", "css", "button.png"), os.path.join("collapsible-lists", "css", "list-item-contents.png"), os.path.join("collapsible-lists", "css", "list-item-last-open.png"), os.path.join("collapsible-lists", "css", "list-item-last.png"), os.path.join("collapsible-lists", "css", "list-item-open.png"), os.path.join("collapsible-lists", "css", "list-item.png"), os.path.join("collapsible-lists", "css", "list-item-root.png"), ] # Make sure we have everything we need collapse_data = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data", tree_data_static_base) if not os.path.isdir(collapse_data): raise ExtensionError( "Exhale: the path to [{0}] was not found, possible installation error.".format(collapse_data) ) else: all_files = tree_data_css + tree_data_js + tree_data_ext missing = [] for file in all_files: path = os.path.join(collapse_data, file) if not os.path.isfile(path): missing.append(path) if missing: raise ExtensionError( "Exhale: the path(s) {0} were not found, possible installation error.".format(missing) ) # We have all the files we need, the extra files will be copied automatically by # sphinx to the correct _static/ location, but stylesheets and javascript need # to be added explicitly logger.info(utils.info("Exhale: adding tree view css / javascript.")) # TODO: hack for multiproj if collapse_data not in app.config.html_static_path: app.config.html_static_path.append(collapse_data) # TODO: dubious hack on multiproj monkeypatch calling this method multiple times # resulting in the css / js files being added multiple times (which is a problem # so we have to bypass). Probably it is an upstream bug that adding the same # file multiple times is allowed, but it's also definitely operator error. # # app.add_css_files -> look at the source, it registers first and then it adds # to the html builder, so we want to check if it is already there first before # trying to add it again. for css in tree_data_css: already_there = False for filename, attributes in app.registry.css_files: if css == filename: already_there = True break if not already_there: app.add_css_file(css) for js in tree_data_js: already_there = False for filename, attributes in app.registry.js_files: if js == filename: already_there = True break if not already_there: app.add_js_file(js) logger.info(utils.progress("Exhale: added tree view css / javascript.")) exhale-0.3.1/exhale/data/000077500000000000000000000000001420305250600151355ustar00rootroot00000000000000exhale-0.3.1/exhale/data/treeView-bootstrap/000077500000000000000000000000001420305250600207425ustar00rootroot00000000000000exhale-0.3.1/exhale/data/treeView-bootstrap/bootstrap-treeview/000077500000000000000000000000001420305250600246075ustar00rootroot00000000000000exhale-0.3.1/exhale/data/treeView-bootstrap/bootstrap-treeview/LICENSE000066400000000000000000000260611420305250600256210ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2017 Jonathan Miles Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. exhale-0.3.1/exhale/data/treeView-bootstrap/bootstrap-treeview/apply-bootstrap-treview.js000066400000000000000000000005141420305250600317700ustar00rootroot00000000000000$(document).ready(function() { // apply the class view hierarchy $("#class-treeView").treeview({ data: getClassViewTree(), enableLinks: true }); // apply the directory view hierarchy $("#directory-treeView").treeview({ data: getDirectoryViewTree(), enableLinks: true }); }); exhale-0.3.1/exhale/data/treeView-bootstrap/bootstrap-treeview/bootstrap-treeview.min.css000066400000000000000000000003141420305250600317460ustar00rootroot00000000000000.treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed}exhale-0.3.1/exhale/data/treeView-bootstrap/bootstrap-treeview/bootstrap-treeview.min.js000066400000000000000000000405371420305250600316050ustar00rootroot00000000000000!function(a,b,c,d){"use strict";var e="treeview",f={};f.settings={injectStyle:!0,levels:2,expandIcon:"glyphicon glyphicon-plus",collapseIcon:"glyphicon glyphicon-minus",emptyIcon:"glyphicon",nodeIcon:"",selectedIcon:"",checkedIcon:"glyphicon glyphicon-check",uncheckedIcon:"glyphicon glyphicon-unchecked",color:d,backColor:d,borderColor:d,onhoverColor:"#F5F5F5",selectedColor:"#FFFFFF",selectedBackColor:"#428bca",searchResultColor:"#D9534F",searchResultBackColor:d,enableLinks:!1,highlightSelected:!0,highlightSearchResults:!0,showBorder:!0,showIcon:!0,showCheckbox:!1,showTags:!1,multiSelect:!1,onNodeChecked:d,onNodeCollapsed:d,onNodeDisabled:d,onNodeEnabled:d,onNodeExpanded:d,onNodeSelected:d,onNodeUnchecked:d,onNodeUnselected:d,onSearchComplete:d,onSearchCleared:d},f.options={silent:!1,ignoreChildren:!1},f.searchOptions={ignoreCase:!0,exactMatch:!1,revealResults:!0};var g=function(b,c){return this.$element=a(b),this.elementId=b.id,this.styleId=this.elementId+"-style",this.init(c),{options:this.options,init:a.proxy(this.init,this),remove:a.proxy(this.remove,this),getNode:a.proxy(this.getNode,this),getParent:a.proxy(this.getParent,this),getSiblings:a.proxy(this.getSiblings,this),getSelected:a.proxy(this.getSelected,this),getUnselected:a.proxy(this.getUnselected,this),getExpanded:a.proxy(this.getExpanded,this),getCollapsed:a.proxy(this.getCollapsed,this),getChecked:a.proxy(this.getChecked,this),getUnchecked:a.proxy(this.getUnchecked,this),getDisabled:a.proxy(this.getDisabled,this),getEnabled:a.proxy(this.getEnabled,this),selectNode:a.proxy(this.selectNode,this),unselectNode:a.proxy(this.unselectNode,this),toggleNodeSelected:a.proxy(this.toggleNodeSelected,this),collapseAll:a.proxy(this.collapseAll,this),collapseNode:a.proxy(this.collapseNode,this),expandAll:a.proxy(this.expandAll,this),expandNode:a.proxy(this.expandNode,this),toggleNodeExpanded:a.proxy(this.toggleNodeExpanded,this),revealNode:a.proxy(this.revealNode,this),checkAll:a.proxy(this.checkAll,this),checkNode:a.proxy(this.checkNode,this),uncheckAll:a.proxy(this.uncheckAll,this),uncheckNode:a.proxy(this.uncheckNode,this),toggleNodeChecked:a.proxy(this.toggleNodeChecked,this),disableAll:a.proxy(this.disableAll,this),disableNode:a.proxy(this.disableNode,this),enableAll:a.proxy(this.enableAll,this),enableNode:a.proxy(this.enableNode,this),toggleNodeDisabled:a.proxy(this.toggleNodeDisabled,this),search:a.proxy(this.search,this),clearSearch:a.proxy(this.clearSearch,this)}};g.prototype.init=function(b){this.tree=[],this.nodes=[],b.data&&("string"==typeof b.data&&(b.data=a.parseJSON(b.data)),this.tree=a.extend(!0,[],b.data),delete b.data),this.options=a.extend({},f.settings,b),this.destroy(),this.subscribeEvents(),this.setInitialStates({nodes:this.tree},0),this.render()},g.prototype.remove=function(){this.destroy(),a.removeData(this,e),a("#"+this.styleId).remove()},g.prototype.destroy=function(){this.initialized&&(this.$wrapper.remove(),this.$wrapper=null,this.unsubscribeEvents(),this.initialized=!1)},g.prototype.unsubscribeEvents=function(){this.$element.off("click"),this.$element.off("nodeChecked"),this.$element.off("nodeCollapsed"),this.$element.off("nodeDisabled"),this.$element.off("nodeEnabled"),this.$element.off("nodeExpanded"),this.$element.off("nodeSelected"),this.$element.off("nodeUnchecked"),this.$element.off("nodeUnselected"),this.$element.off("searchComplete"),this.$element.off("searchCleared")},g.prototype.subscribeEvents=function(){this.unsubscribeEvents(),this.$element.on("click",a.proxy(this.clickHandler,this)),"function"==typeof this.options.onNodeChecked&&this.$element.on("nodeChecked",this.options.onNodeChecked),"function"==typeof this.options.onNodeCollapsed&&this.$element.on("nodeCollapsed",this.options.onNodeCollapsed),"function"==typeof this.options.onNodeDisabled&&this.$element.on("nodeDisabled",this.options.onNodeDisabled),"function"==typeof this.options.onNodeEnabled&&this.$element.on("nodeEnabled",this.options.onNodeEnabled),"function"==typeof this.options.onNodeExpanded&&this.$element.on("nodeExpanded",this.options.onNodeExpanded),"function"==typeof this.options.onNodeSelected&&this.$element.on("nodeSelected",this.options.onNodeSelected),"function"==typeof this.options.onNodeUnchecked&&this.$element.on("nodeUnchecked",this.options.onNodeUnchecked),"function"==typeof this.options.onNodeUnselected&&this.$element.on("nodeUnselected",this.options.onNodeUnselected),"function"==typeof this.options.onSearchComplete&&this.$element.on("searchComplete",this.options.onSearchComplete),"function"==typeof this.options.onSearchCleared&&this.$element.on("searchCleared",this.options.onSearchCleared)},g.prototype.setInitialStates=function(b,c){if(b.nodes){c+=1;var d=b,e=this;a.each(b.nodes,function(a,b){b.nodeId=e.nodes.length,b.parentId=d.nodeId,b.hasOwnProperty("selectable")||(b.selectable=!0),b.state=b.state||{},b.state.hasOwnProperty("checked")||(b.state.checked=!1),b.state.hasOwnProperty("disabled")||(b.state.disabled=!1),b.state.hasOwnProperty("expanded")||(!b.state.disabled&&c0?b.state.expanded=!0:b.state.expanded=!1),b.state.hasOwnProperty("selected")||(b.state.selected=!1),e.nodes.push(b),b.nodes&&e.setInitialStates(b,c)})}},g.prototype.clickHandler=function(b){this.options.enableLinks||b.preventDefault();var c=a(b.target),d=this.findNode(c);if(d&&!d.state.disabled){var e=c.attr("class")?c.attr("class").split(" "):[];-1!==e.indexOf("expand-icon")?(this.toggleExpandedState(d,f.options),this.render()):-1!==e.indexOf("check-icon")?(this.toggleCheckedState(d,f.options),this.render()):(d.selectable?this.toggleSelectedState(d,f.options):this.toggleExpandedState(d,f.options),this.render())}},g.prototype.findNode=function(a){var b=a.closest("li.list-group-item").attr("data-nodeid"),c=this.nodes[b];return c||console.log("Error: node does not exist"),c},g.prototype.toggleExpandedState=function(a,b){a&&this.setExpandedState(a,!a.state.expanded,b)},g.prototype.setExpandedState=function(b,c,d){c!==b.state.expanded&&(c&&b.nodes?(b.state.expanded=!0,d.silent||this.$element.trigger("nodeExpanded",a.extend(!0,{},b))):c||(b.state.expanded=!1,d.silent||this.$element.trigger("nodeCollapsed",a.extend(!0,{},b)),b.nodes&&!d.ignoreChildren&&a.each(b.nodes,a.proxy(function(a,b){this.setExpandedState(b,!1,d)},this))))},g.prototype.toggleSelectedState=function(a,b){a&&this.setSelectedState(a,!a.state.selected,b)},g.prototype.setSelectedState=function(b,c,d){c!==b.state.selected&&(c?(this.options.multiSelect||a.each(this.findNodes("true","g","state.selected"),a.proxy(function(a,b){this.setSelectedState(b,!1,d)},this)),b.state.selected=!0,d.silent||this.$element.trigger("nodeSelected",a.extend(!0,{},b))):(b.state.selected=!1,d.silent||this.$element.trigger("nodeUnselected",a.extend(!0,{},b))))},g.prototype.toggleCheckedState=function(a,b){a&&this.setCheckedState(a,!a.state.checked,b)},g.prototype.setCheckedState=function(b,c,d){c!==b.state.checked&&(c?(b.state.checked=!0,d.silent||this.$element.trigger("nodeChecked",a.extend(!0,{},b))):(b.state.checked=!1,d.silent||this.$element.trigger("nodeUnchecked",a.extend(!0,{},b))))},g.prototype.setDisabledState=function(b,c,d){c!==b.state.disabled&&(c?(b.state.disabled=!0,this.setExpandedState(b,!1,d),this.setSelectedState(b,!1,d),this.setCheckedState(b,!1,d),d.silent||this.$element.trigger("nodeDisabled",a.extend(!0,{},b))):(b.state.disabled=!1,d.silent||this.$element.trigger("nodeEnabled",a.extend(!0,{},b))))},g.prototype.render=function(){this.initialized||(this.$element.addClass(e),this.$wrapper=a(this.template.list),this.injectStyle(),this.initialized=!0),this.$element.empty().append(this.$wrapper.empty()),this.buildTree(this.tree,0)},g.prototype.buildTree=function(b,c){if(b){c+=1;var d=this;a.each(b,function(b,e){for(var f=a(d.template.item).addClass("node-"+d.elementId).addClass(e.state.checked?"node-checked":"").addClass(e.state.disabled?"node-disabled":"").addClass(e.state.selected?"node-selected":"").addClass(e.searchResult?"search-result":"").attr("data-nodeid",e.nodeId).attr("style",d.buildStyleOverride(e)),g=0;c-1>g;g++)f.append(d.template.indent);var h=[];if(e.nodes?(h.push("expand-icon"),h.push(e.state.expanded?d.options.collapseIcon:d.options.expandIcon)):h.push(d.options.emptyIcon),f.append(a(d.template.icon).addClass(h.join(" "))),d.options.showIcon){var h=["node-icon"];h.push(e.icon||d.options.nodeIcon),e.state.selected&&(h.pop(),h.push(e.selectedIcon||d.options.selectedIcon||e.icon||d.options.nodeIcon)),f.append(a(d.template.icon).addClass(h.join(" ")))}if(d.options.showCheckbox){var h=["check-icon"];h.push(e.state.checked?d.options.checkedIcon:d.options.uncheckedIcon),f.append(a(d.template.icon).addClass(h.join(" ")))}return f.append(d.options.enableLinks?a(d.template.link).attr("href",e.href).append(e.text):e.text),d.options.showTags&&e.tags&&a.each(e.tags,function(b,c){f.append(a(d.template.badge).append(c))}),d.$wrapper.append(f),e.nodes&&e.state.expanded&&!e.state.disabled?d.buildTree(e.nodes,c):void 0})}},g.prototype.buildStyleOverride=function(a){if(a.state.disabled)return"";var b=a.color,c=a.backColor;return this.options.highlightSelected&&a.state.selected&&(this.options.selectedColor&&(b=this.options.selectedColor),this.options.selectedBackColor&&(c=this.options.selectedBackColor)),this.options.highlightSearchResults&&a.searchResult&&!a.state.disabled&&(this.options.searchResultColor&&(b=this.options.searchResultColor),this.options.searchResultBackColor&&(c=this.options.searchResultBackColor)),"color:"+b+";background-color:"+c+";"},g.prototype.injectStyle=function(){this.options.injectStyle&&!c.getElementById(this.styleId)&&a('").appendTo("head")},g.prototype.buildStyle=function(){var a=".node-"+this.elementId+"{";return this.options.color&&(a+="color:"+this.options.color+";"),this.options.backColor&&(a+="background-color:"+this.options.backColor+";"),this.options.showBorder?this.options.borderColor&&(a+="border:1px solid "+this.options.borderColor+";"):a+="border:none;",a+="}",this.options.onhoverColor&&(a+=".node-"+this.elementId+":not(.node-disabled):hover{background-color:"+this.options.onhoverColor+";}"),this.css+a},g.prototype.template={list:'
    ',item:'
  • ',indent:'',icon:'',link:'',badge:''},g.prototype.css=".treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed}",g.prototype.getNode=function(a){return this.nodes[a]},g.prototype.getParent=function(a){var b=this.identifyNode(a);return this.nodes[b.parentId]},g.prototype.getSiblings=function(a){var b=this.identifyNode(a),c=this.getParent(b),d=c?c.nodes:this.tree;return d.filter(function(a){return a.nodeId!==b.nodeId})},g.prototype.getSelected=function(){return this.findNodes("true","g","state.selected")},g.prototype.getUnselected=function(){return this.findNodes("false","g","state.selected")},g.prototype.getExpanded=function(){return this.findNodes("true","g","state.expanded")},g.prototype.getCollapsed=function(){return this.findNodes("false","g","state.expanded")},g.prototype.getChecked=function(){return this.findNodes("true","g","state.checked")},g.prototype.getUnchecked=function(){return this.findNodes("false","g","state.checked")},g.prototype.getDisabled=function(){return this.findNodes("true","g","state.disabled")},g.prototype.getEnabled=function(){return this.findNodes("false","g","state.disabled")},g.prototype.selectNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setSelectedState(a,!0,b)},this)),this.render()},g.prototype.unselectNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setSelectedState(a,!1,b)},this)),this.render()},g.prototype.toggleNodeSelected=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.toggleSelectedState(a,b)},this)),this.render()},g.prototype.collapseAll=function(b){var c=this.findNodes("true","g","state.expanded");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setExpandedState(a,!1,b)},this)),this.render()},g.prototype.collapseNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setExpandedState(a,!1,b)},this)),this.render()},g.prototype.expandAll=function(b){if(b=a.extend({},f.options,b),b&&b.levels)this.expandLevels(this.tree,b.levels,b);else{var c=this.findNodes("false","g","state.expanded");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setExpandedState(a,!0,b)},this))}this.render()},g.prototype.expandNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setExpandedState(a,!0,b),a.nodes&&b&&b.levels&&this.expandLevels(a.nodes,b.levels-1,b)},this)),this.render()},g.prototype.expandLevels=function(b,c,d){d=a.extend({},f.options,d),a.each(b,a.proxy(function(a,b){this.setExpandedState(b,c>0?!0:!1,d),b.nodes&&this.expandLevels(b.nodes,c-1,d)},this))},g.prototype.revealNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){for(var c=this.getParent(a);c;)this.setExpandedState(c,!0,b),c=this.getParent(c)},this)),this.render()},g.prototype.toggleNodeExpanded=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.toggleExpandedState(a,b)},this)),this.render()},g.prototype.checkAll=function(b){var c=this.findNodes("false","g","state.checked");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setCheckedState(a,!0,b)},this)),this.render()},g.prototype.checkNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setCheckedState(a,!0,b)},this)),this.render()},g.prototype.uncheckAll=function(b){var c=this.findNodes("true","g","state.checked");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setCheckedState(a,!1,b)},this)),this.render()},g.prototype.uncheckNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setCheckedState(a,!1,b)},this)),this.render()},g.prototype.toggleNodeChecked=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.toggleCheckedState(a,b)},this)),this.render()},g.prototype.disableAll=function(b){var c=this.findNodes("false","g","state.disabled");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setDisabledState(a,!0,b)},this)),this.render()},g.prototype.disableNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setDisabledState(a,!0,b)},this)),this.render()},g.prototype.enableAll=function(b){var c=this.findNodes("true","g","state.disabled");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setDisabledState(a,!1,b)},this)),this.render()},g.prototype.enableNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setDisabledState(a,!1,b)},this)),this.render()},g.prototype.toggleNodeDisabled=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setDisabledState(a,!a.state.disabled,b)},this)),this.render()},g.prototype.forEachIdentifier=function(b,c,d){c=a.extend({},f.options,c),b instanceof Array||(b=[b]),a.each(b,a.proxy(function(a,b){d(this.identifyNode(b),c)},this))},g.prototype.identifyNode=function(a){return"number"==typeof a?this.nodes[a]:a},g.prototype.search=function(b,c){c=a.extend({},f.searchOptions,c),this.clearSearch({render:!1});var d=[];if(b&&b.length>0){c.exactMatch&&(b="^"+b+"$");var e="g";c.ignoreCase&&(e+="i"),d=this.findNodes(b,e),a.each(d,function(a,b){b.searchResult=!0})}return c.revealResults?this.revealNode(d):this.render(),this.$element.trigger("searchComplete",a.extend(!0,{},d)),d},g.prototype.clearSearch=function(b){b=a.extend({},{render:!0},b);var c=a.each(this.findNodes("true","g","searchResult"),function(a,b){b.searchResult=!1});b.render&&this.render(),this.$element.trigger("searchCleared",a.extend(!0,{},c))},g.prototype.findNodes=function(b,c,d){c=c||"g",d=d||"text";var e=this;return a.grep(this.nodes,function(a){var f=e.getNodeValue(a,d);return"string"==typeof f?f.match(new RegExp(b,c)):void 0})},g.prototype.getNodeValue=function(a,b){var c=b.indexOf(".");if(c>0){var e=a[b.substring(0,c)],f=b.substring(c+1,b.length);return this.getNodeValue(e,f)}return a.hasOwnProperty(b)?a[b].toString():d};var h=function(a){b.console&&b.console.error(a)};a.fn[e]=function(b,c){var d;return this.each(function(){var f=a.data(this,e);"string"==typeof b?f?a.isFunction(f[b])&&"_"!==b.charAt(0)?(c instanceof Array||(c=[c]),d=f[b].apply(f,c)):h("No such method : "+b):h("Not initialized, can not call method : "+b):"boolean"==typeof b?d=f:a.data(this,e,new g(this,a.extend(!0,{},b)))}),d||this}}(jQuery,window,document);exhale-0.3.1/exhale/data/treeView/000077500000000000000000000000001420305250600167275ustar00rootroot00000000000000exhale-0.3.1/exhale/data/treeView/collapsible-lists/000077500000000000000000000000001420305250600223545ustar00rootroot00000000000000exhale-0.3.1/exhale/data/treeView/collapsible-lists/LICENSE.md000066400000000000000000000003351420305250600237610ustar00rootroot00000000000000This code is the fruit of Kate Morley's labor, taken from here: - http://code.iamkate.com/javascript/collapsible-lists/ She includes a generous CC0 1.0 license for all materials on her site: - http://code.iamkate.com/ exhale-0.3.1/exhale/data/treeView/collapsible-lists/css/000077500000000000000000000000001420305250600231445ustar00rootroot00000000000000exhale-0.3.1/exhale/data/treeView/collapsible-lists/css/button-closed.png000066400000000000000000000004001420305250600264260ustar00rootroot00000000000000PNG  IHDR w&tEXtSoftwareAdobe ImageReadyqe<IDATxڄ Gу v")(%lAn{ .;}ȼ/"z7&Ns1cYk1=per1 }#(n~#_3PiƶmטsZf~ta g4SJ7 x>2hx0[c,mIENDB`exhale-0.3.1/exhale/data/treeView/collapsible-lists/css/button-open.png000066400000000000000000000003601420305250600261230ustar00rootroot00000000000000PNG  IHDR w&tEXtSoftwareAdobe ImageReadyqe<IDATxڔQ ! Dc//!ٛhFfZ?JCہ Cz#R&+Qe朷9Lrɽl*.-("M:(hc 4N!Xϯ ul{ background-image:none ! important; } .treeView li{ margin:0 ! important; padding:0 ! important; background:url('list-item-root.png') no-repeat top left ! important; list-style-position:inside ! important; list-style-image:url('button.png') ! important; cursor:auto; } .treeView li.collapsibleListOpen{ list-style-image:url('button-open.png') ! important; cursor:pointer; } .treeView li.collapsibleListClosed{ list-style-image:url('button-closed.png') ! important; cursor:pointer; } .treeView li li{ background-image:url('list-item.png') ! important; padding-left:1.5em ! important; } .treeView li.lastChild{ background-image:url('list-item-last.png') ! important; } .treeView li.collapsibleListOpen{ background-image:url('list-item-open.png') ! important; } .treeView li.collapsibleListOpen.lastChild{ background-image:url('list-item-last-open.png') ! important; } exhale-0.3.1/exhale/data/treeView/collapsible-lists/js/000077500000000000000000000000001420305250600227705ustar00rootroot00000000000000exhale-0.3.1/exhale/data/treeView/collapsible-lists/js/CollapsibleLists.compressed.js000066400000000000000000000032711420305250600307440ustar00rootroot00000000000000/* CollapsibleLists.js An object allowing lists to dynamically expand and collapse Created by Kate Morley - http://code.iamkate.com/ - and released under the terms of the CC0 1.0 Universal legal code: http://creativecommons.org/publicdomain/zero/1.0/legalcode */ var CollapsibleLists=new function(){ this.apply=function(_1){ var _2=document.getElementsByTagName("ul"); for(var _3=0;_3<_2.length;_3++){ if(_2[_3].className.match(/(^| )collapsibleList( |$)/)){ this.applyTo(_2[_3],true); if(!_1){ var _4=_2[_3].getElementsByTagName("ul"); for(var _5=0;_5<_4.length;_5++){ _4[_5].className+=" collapsibleList"; } } } } }; this.applyTo=function(_6,_7){ var _8=_6.getElementsByTagName("li"); for(var _9=0;_9<_8.length;_9++){ if(!_7||_6==_8[_9].parentNode){ if(_8[_9].addEventListener){ _8[_9].addEventListener("mousedown",function(e){ e.preventDefault(); },false); }else{ _8[_9].attachEvent("onselectstart",function(){ event.returnValue=false; }); } if(_8[_9].addEventListener){ _8[_9].addEventListener("click",_a(_8[_9]),false); }else{ _8[_9].attachEvent("onclick",_a(_8[_9])); } _b(_8[_9]); } } }; function _a(_c){ return function(e){ if(!e){ e=window.event; } var _d=(e.target?e.target:e.srcElement); while(_d.nodeName!="LI"){ _d=_d.parentNode; } if(_d==_c){ _b(_c); } }; }; function _b(_e){ var _f=_e.className.match(/(^| )collapsibleListClosed( |$)/); var uls=_e.getElementsByTagName("ul"); for(var _10=0;_100){ _e.className+=" collapsibleList"+(_f?"Open":"Closed"); } }; }(); exhale-0.3.1/exhale/data/treeView/collapsible-lists/js/apply-collapsible-lists.js000066400000000000000000000001011420305250600300660ustar00rootroot00000000000000$(document).ready(function() { CollapsibleLists.apply(); }); exhale-0.3.1/exhale/deploy.py000066400000000000000000000432011420305250600160720ustar00rootroot00000000000000# -*- coding: utf8 -*- ######################################################################################## # This file is part of exhale. Copyright (c) 2017-2022, Stephen McDowell. # # Full BSD 3-Clause license available here: # # # # https://github.com/svenevs/exhale/blob/master/LICENSE # ######################################################################################## ''' The deploy module is responsible for two primary actions: 1. Executing Doxygen (if requested in ``exhale_args``). 2. Launching the full API generation via the :func:`~exhale.deploy.explode` function. ''' from __future__ import unicode_literals from . import configs from . import utils from .graph import ExhaleRoot import os import sys import six import re import codecs import tempfile import textwrap from subprocess import PIPE, Popen, STDOUT def _generate_doxygen(doxygen_input): ''' This method executes doxygen based off of the specified input. By the time this method is executed, it is assumed that Doxygen is intended to be run in the **current working directory**. Search for ``returnPath`` in the implementation of :func:`~exhale.configs.apply_sphinx_configurations` for handling of this aspect. This method is intended to be called by :func:`~exhale.deploy.generateDoxygenXML`, which is in turn called by :func:`~exhale.configs.apply_sphinx_configurations`. Two versions of the doxygen command can be executed: 1. If ``doxygen_input`` is exactly ``"Doxyfile"``, then it is assumed that a ``Doxyfile`` exists in the **current working directory**. Meaning the command being executed is simply ``doxygen``. 2. For all other values, ``doxygen_input`` represents the arguments as to be specified on ``stdin`` to the process. **Parameters** ``doxygen_input`` (str) Either the string ``"Doxyfile"`` to run vanilla ``doxygen``, or the selection of doxygen inputs (that would ordinarily be in a ``Doxyfile``) that will be ``communicate``d to the ``doxygen`` process on ``stdin``. .. note:: If using Python **3**, the input **must** still be a ``str``. This method will convert the input to ``bytes`` as follows: .. code-block:: py if sys.version[0] == "3": doxygen_input = bytes(doxygen_input, "utf-8") **Return** ``str`` or ``None`` If an error occurs, a string describing the error is returned with the intention of the caller raising the exception. If ``None`` is returned, then the process executed without error. Example usage: .. code-block:: py status = _generate_doxygen("Doxygen") if status: raise RuntimeError(status) Though a little awkward, this is done to enable the intended caller of this method to restore some state before exiting the program (namely, the working directory before propagating an exception to ``sphinx-build``). ''' if not isinstance(doxygen_input, six.string_types): return "Error: the `doxygen_input` variable must be of type `str`." doxyfile = doxygen_input == "Doxyfile" try: # Setup the arguments to launch doxygen if doxyfile: args = ["doxygen"] kwargs = {} else: args = ["doxygen", "-"] kwargs = {"stdin": PIPE} if configs._on_rtd: # On RTD, any capturing of Doxygen output can cause buffer overflows for # even medium sized projects. So it is disregarded entirely to ensure the # build will complete (otherwise, it silently fails after `cat conf.py`) devnull_file = open(os.devnull, "w") kwargs["stdout"] = devnull_file kwargs["stderr"] = STDOUT else: # TL;DR: strictly enforce that (verbose) doxygen output doesn't cause the # `communicate` to hang due to buffer overflows. # # See excellent synopsis: # https://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/ if six.PY2: tempfile_kwargs = {} else: # encoding argument introduced in python 3 tempfile_kwargs = {"encoding": "utf-8"} tempfile_kwargs["mode"] = "r+" tmp_out_file = tempfile.TemporaryFile( prefix="doxygen_stdout_buff", **tempfile_kwargs ) tmp_err_file = tempfile.TemporaryFile( prefix="doxygen_stderr_buff", **tempfile_kwargs ) # Write to the tempfiles over PIPE to avoid buffer overflowing kwargs["stdout"] = tmp_out_file kwargs["stderr"] = tmp_err_file # Note: overload of args / kwargs, Popen is expecting a list as the first # parameter (aka no *args, just args)! doxygen_proc = Popen(args, **kwargs) # Communicate can only be called once, arrange whether or not stdin has value if not doxyfile: # In Py3, make sure we are communicating a bytes-like object which is no # longer interchangeable with strings (as was the case in Py2). if sys.version[0] == "3": doxygen_input = bytes(doxygen_input, "utf-8") comm_kwargs = {"input": doxygen_input} else: comm_kwargs = {} # Waits until doxygen has completed doxygen_proc.communicate(**comm_kwargs) # Print out what was written to the tmpfiles by doxygen if not configs._on_rtd and not configs.exhaleSilentDoxygen: # Doxygen output (some useful information, mostly just enumeration of the # configurations you gave it {useful for debugging...}) if tmp_out_file.tell() > 0: tmp_out_file.seek(0) print(tmp_out_file.read()) # Doxygen error (e.g. any warnings, or invalid input) if tmp_err_file.tell() > 0: # Making them stick out, ideally users would reduce this output to 0 ;) # This will print a yellow [~] before every line, but not make the # entire line yellow because it's definitively not helpful prefix = utils._use_color( utils.prefix("[~]", " "), utils.AnsiColors.BOLD_YELLOW, sys.stderr ) tmp_err_file.seek(0) sys.stderr.write(utils.prefix(prefix, tmp_err_file.read())) # Close the file handles opened for communication with subprocess if configs._on_rtd: devnull_file.close() else: # Delete the tmpfiles tmp_out_file.close() tmp_err_file.close() # Make sure we had a valid execution of doxygen exit_code = doxygen_proc.returncode if exit_code != 0: raise RuntimeError("Non-zero return code of [{0}] from 'doxygen'...".format(exit_code)) except Exception as e: return "Unable to execute 'doxygen': {0}".format(e) # returning None signals _success_ return None def _valid_config(config, required): ''' .. todo:: add documentation of this method ``config``: doxygen input we're looking for ``required``: if ``True``, must be present. if ``False``, NOT ALLOWED to be present ''' re_template = r"\s*{config}\s*=.*".format(config=config) found = re.search(re_template, configs.exhaleDoxygenStdin) if required: return found is not None else: return found is None def generateDoxygenXML(): # If this happens, we really shouldn't be here... if not configs.exhaleExecutesDoxygen: return textwrap.dedent(''' `generateDoxygenXML` should *ONLY* be called internally. You should set `exhaleExecutesDoxygen=True` in `exhale_args` in `conf.py`. ''') # Case 1: the user has their own `Doxyfile`. if configs.exhaleUseDoxyfile: return _generate_doxygen("Doxyfile") # Case 2: use stdin, with some defaults and potentially additional specs from user else: # 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`) # # There is one doxygen spec that is REQUIRED to be given: # # 1. INPUT (where doxygen should parse). # # The below is a modest attempt to validate that these were / were not given. if not isinstance(configs.exhaleDoxygenStdin, six.string_types): return "`exhaleDoxygenStdin` config must be a string!" if not _valid_config("OUTPUT_DIRECTORY", False): # If we are hitting this code, these should both exist and be configured # since this method is called **AFTER** the configuration verification code # performed in configs.apply_sphinx_configurations breathe_projects = configs._the_app.config.breathe_projects breathe_default_project = configs._the_app.config.breathe_default_project return textwrap.dedent(''' `exhaleDoxygenStdin` may *NOT* specify `OUTPUT_DIRECTORY`. Exhale does this internally by reading what you provided to `breathe_projects` in your `conf.py`. Based on what you had in `conf.py`, Exhale will be using - The `breathe_default_project`: {default} - The output path specfied (`breathe_projects[breathe_default_project]`): {path} NOTE: the above path has the `xml` portion removed from what you provided. This path is what is sent to Doxygen, Breathe requires you include the `xml` directory path; so Exhale simply re-uses this variable and adapts the value for our needs. '''.format( default=breathe_default_project, path=breathe_projects[breathe_default_project].rsplit("{sep}xml".format(sep=os.sep), 1)[0] )) if not _valid_config("STRIP_FROM_PATH", False): return textwrap.dedent(''' `exhaleDoxygenStdin` 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 )) if not _valid_config("INPUT", True): return textwrap.dedent(''' `exhaleDoxygenStdin` *MUST* specify the `INPUT` doxygen config variable. The INPUT variable is what tells Doxygen where to look for code to extract documentation from. For example, if you had a directory layout project_root/ docs/ conf.py Makefile ... etc ... include/ my_header.hpp src/ my_header.cpp Then you would include the line INPUT = ../include in the string provided to `exhale_args["exhaleDoxygenStdin"]`. ''') # For these, we just want to warn them of the impact but still allow an override re_template = r"\s*{config}\s*=\s*(.*)" for cfg in ("ALIASES", "PREDEFINED"): found = re.search(re_template.format(config=cfg), configs.exhaleDoxygenStdin) if found: sys.stderr.write(utils.info(textwrap.dedent(''' You have supplied to `exhaleDoxygenStdin` a configuration of: {cfg} = {theirs} This has an important impact, as it overrides a default setting that Exhale is using. 1. If you are intentionally overriding this configuration, simply ignore this message --- what you intended will happen. 2. If you meant to _continue_ adding to the defaults Exhale provides, you need to use a `+=` instead of a raw `=`. So do instead {cfg} += {theirs} '''.format(cfg=cfg, theirs=found.groups()[0])), utils.AnsiColors.BOLD_YELLOW)) # Include their custom doxygen definitions after the defaults so that they can # override anything they want to. Populate the necessary output dir and strip path. doxy_dir = configs._doxygen_xml_output_directory.rsplit("{sep}xml".format(sep=os.sep), 1)[0] internal_configs = textwrap.dedent(''' # Tell doxygen to output wherever breathe is expecting things 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)) external_configs = textwrap.dedent(configs.exhaleDoxygenStdin) # Place external configs last so that if the _valid_config method isn't actually # catching what it should be, the internal configs will override theirs full_input = "{base}\n{external}\n{internal}\n\n".format(base=configs.DEFAULT_DOXYGEN_STDIN_BASE, external=external_configs, internal=internal_configs) # << verboseBuild if configs.verboseBuild: msg = "[*] The following input will be sent to Doxygen:\n" if not configs.alwaysColorize and not sys.stderr.isatty(): sys.stderr.write(msg) sys.stderr.write(full_input) else: sys.stderr.write(utils.colorize(msg, utils.AnsiColors.BOLD_CYAN)) sys.stderr.write(utils.__fancy(full_input, "make", "console")) return _generate_doxygen(full_input) ######################################################################################## # ## ### #### ##### Primary entry point. #### ### ## # ######################################################################################## def explode(): ''' This method **assumes** that :func:`~exhale.configs.apply_sphinx_configurations` has already been applied. It performs minimal sanity checking, and then performs in order 1. Creates a :class:`~exhale.graph.ExhaleRoot` object. 2. Executes :func:`~exhale.graph.ExhaleRoot.parse` for this object. 3. Executes :func:`~exhale.graph.ExhaleRoot.generateFullAPI` for this object. 4. Executes :func:`~exhale.graph.ExhaleRoot.toConsole` for this object (which will only produce output when :data:`~exhale.configs.verboseBuild` is ``True``). This results in the full API being generated, and control is subsequently passed back to Sphinx to now read in the source documents (many of which were just generated in :data:`~exhale.configs.containmentFolder`), and proceed to writing the final output. ''' # Quick sanity check to make sure the bare minimum have been set in the configs err_msg = "`configs.{config}` was `None`. Do not call `deploy.explode` directly." if configs.containmentFolder is None: raise RuntimeError(err_msg.format(config="containmentFolder")) if configs.rootFileName is None: raise RuntimeError(err_msg.format(config="rootFileName")) if configs.doxygenStripFromPath is None: raise RuntimeError(err_msg.format(config="doxygenStripFromPath")) # From here on, we assume that everything else has been checked / configured. try: textRoot = ExhaleRoot() except: utils.fancyError("Unable to create an `ExhaleRoot` object:") try: sys.stdout.write("{0}\n".format(utils.info("Exhale: parsing Doxygen XML."))) start = utils.get_time() textRoot.parse() end = utils.get_time() sys.stdout.write("{0}\n".format( utils.progress("Exhale: finished parsing Doxygen XML in {0}.".format( utils.time_string(start, end) )) )) except: utils.fancyError("Exception caught while parsing:") try: sys.stdout.write("{0}\n".format( utils.info("Exhale: generating reStructuredText documents.") )) start = utils.get_time() textRoot.generateFullAPI() end = utils.get_time() sys.stdout.write("{0}\n".format( utils.progress("Exhale: generated reStructuredText documents in {0}.".format( utils.time_string(start, end) )) )) except: utils.fancyError("Exception caught while generating:") # << verboseBuild # toConsole only prints if verbose mode is enabled textRoot.toConsole() # allow access to the result after-the-fact configs._the_app.exhale_root = textRoot exhale-0.3.1/exhale/graph.py000066400000000000000000005735521420305250600157200ustar00rootroot00000000000000# -*- coding: utf8 -*- ######################################################################################## # This file is part of exhale. Copyright (c) 2017-2022, Stephen McDowell. # # Full BSD 3-Clause license available here: # # # # https://github.com/svenevs/exhale/blob/master/LICENSE # ######################################################################################## from __future__ import unicode_literals from . import configs from . import parse from . import utils import re import os import sys import codecs import hashlib import itertools from pathlib import Path import platform import textwrap from bs4 import BeautifulSoup try: # Python 2 StringIO from cStringIO import StringIO except ImportError: # Python 3 StringIO from io import StringIO __all__ = ["ExhaleRoot", "ExhaleNode"] ######################################################################################## # ## ### #### ##### Graph representation. #### ### ## # ######################################################################################## class ExhaleNode(object): ''' A wrapper class to track parental relationships, filenames, etc. **Parameters** ``name`` (str) The name of the compound. ``kind`` (str) The kind of the compound (see :data:`~exhale.utils.AVAILABLE_KINDS`). ``refid`` (str) The reference ID that Doxygen has associated with this compound. **Attributes** ``kind`` (str) The value of the ``kind`` parameter. ``name`` (str) The value of the ``name`` parameter. ``refid`` (str) The value of the ``refid`` parameter. ``children`` (list) A potentially empty list of ``ExhaleNode`` object references that are considered a child of this Node. Please note that a child reference in any ``children`` list may be stored in **many** other lists. Mutating a given child will mutate the object, and therefore affect other parents of this child. Lastly, a node of kind ``enum`` will never have its ``enumvalue`` children as it is impossible to rebuild that relationship without more Doxygen xml parsing. ``parent`` (:class:`~exhale.graph.ExhaleNode`) If an ExhaleNode is determined to be a child of another ExhaleNode, this node will be added to its parent's ``children`` list, and a reference to the parent will be in this field. Initialized to ``None``, make sure you check that it is an object first. .. warning:: Do not ever set the ``parent`` of a given node if the would-be parent's kind is ``"file"``. Doing so will break many important relationships, such as nested class definitions. Effectively, **every** node will be added as a child to a file node at some point. The file node will track this, but the child should not. The following three member variables are stored internally, but managed externally by the :class:`~exhale.graph.ExhaleRoot` class: ``file_name`` (str) The name of the file to create. Set to ``None`` on creation, refer to :func:`~exhale.graph.ExhaleRoot.initializeNodeFilenameAndLink`. ``link_name`` (str) The name of the reStructuredText link that will be at the top of the file. Set to ``None`` on creation, refer to :func:`~exhale.graph.ExhaleRoot.initializeNodeFilenameAndLink`. ``title`` (str) The title that will appear at the top of the reStructuredText file ``file_name``. When the reStructuredText document for this node is being written, the root object will set this field. The following two fields are used for tracking what has or has not already been included in the hierarchy views. Things like classes or structs in the global namespace will not be found by :func:`~exhale.graph.ExhaleNode.inClassHierarchy`, and the ExhaleRoot object will need to track which ones were missed. ``in_class_hierarchy`` (bool) Whether or not this node has already been incorporated in the class view. ``in_file_hierarchy`` (bool) Whether or not this node has already been incorporated in the file view. This class wields duck typing. If ``self.kind == "file"``, then the additional member variables below exist: ``namespaces_used`` (list) A list of namespace nodes that are either defined or used in this file. ``includes`` (list) A list of strings that are parsed from the Doxygen xml for this file as include directives. ``included_by`` (list) A list of (refid, name) string tuples that are parsed from the Doxygen xml for this file presenting all of the other files that include this file. They are stored this way so that the root class can later link to that file by its refid. ``location`` (str) A string parsed from the Doxygen xml for this file stating where this file is physically in relation to the *Doxygen* root. ``program_listing`` (list) A list of strings that is the Doxygen xml , without the opening or closing tags. ``program_file`` (list) Managed externally by the root similar to ``file_name`` etc, this is the name of the file that will be created to display the program listing if it exists. Set to ``None`` on creation, refer to :func:`~exhale.graph.ExhaleRoot.initializeNodeFilenameAndLink`. ``program_link_name`` (str) Managed externally by the root similar to ``file_name`` etc, this is the reStructuredText link that will be declared at the top of the ``program_file``. Set to ``None`` on creation, refer to :func:`~exhale.graph.ExhaleRoot.initializeNodeFilenameAndLink`. ''' def __init__(self, name, kind, refid): self.name = os.path.normpath(name) if kind == 'dir' else name self.kind = kind self.refid = refid self.root_owner = None # the ExhaleRoot owner self.template_params = [] # only populated if found # for inheritance self.base_compounds = [] self.derived_compounds = [] # used for establishing a link to the file something was done in for leaf-like # nodes conveniently, files also have this defined as their name making # comparison easy :) self.def_in_file = None # la familia self.children = [] # ExhaleNodes self.parent = None # if reparented, will be an ExhaleNode # managed externally self.file_name = None self.link_name = None self.title = None # representation of hierarchies self.in_page_hierarchy = False self.in_class_hierarchy = False self.in_file_hierarchy = False # kind-specific additional information if self.kind == "file": self.namespaces_used = [] # ExhaleNodes self.includes = [] # strings self.included_by = [] # (refid, name) tuples self.language = "" self.location = "" self.program_listing = [] # strings self.program_file = "" self.program_link_name = "" if self.kind == "function": self.return_type = None # string (void, int, etc) self.parameters = [] # list of strings: ["int", "int"] for foo(int x, int y) self.template = None # list of strings def __lt__(self, other): ''' The ``ExhaleRoot`` class stores a bunch of lists of ``ExhaleNode`` objects. When these lists are sorted, this method will be called to perform the sorting. :Parameters: ``other`` (ExhaleNode) The node we are comparing whether ``self`` is less than or not. :Return (bool): True if ``self`` is less than ``other``, False otherwise. ''' # allows alphabetical sorting within types if self.kind == other.kind: if self.kind != "page": return self.name.lower() < other.name.lower() else: # Arbitrarily stuff "indexpage" refid to the front. As doxygen presents # things, it shows up last, but it does not matter since the sort we # really care about will be with lists that do *NOT* have indexpage in # them (for creating the page view hierarchy). if self.refid == "indexpage": return True elif other.refid == "indexpage": return False # NOTE: kind of wasteful, but ordered_refs has ALL pages # but realistically, there wont be *that* many pages. right? ;) ordered_refs = [ p.refid for p in self.root_owner.index_xml_page_ordering ] return ordered_refs.index(self.refid) < ordered_refs.index(other.refid) # treat structs and classes as the same type elif self.kind == "struct" or self.kind == "class": if other.kind != "struct" and other.kind != "class": return True else: if self.kind == "struct" and other.kind == "class": return True elif self.kind == "class" and other.kind == "struct": return False else: return self.name.lower() < other.name.lower() # otherwise, sort based off the kind else: return self.kind < other.kind def set_owner(self, root): """Sets the :class:`~exhale.graph.ExhaleRoot` owner ``self.root_owner``.""" # needed to be able to track the page orderings as presented in index.xml self.root_owner = root def breathe_identifier(self): """ The unique identifier for breathe directives. .. note:: This method is currently assumed to only be called for nodes that are in :data:`exhale.utils.LEAF_LIKE_KINDS` (see also :func:`exhale.graph.ExhaleRoot.generateSingleNodeRST` where it is used). **Return** :class:`python:str` Usually, this will just be ``self.name``. However, for functions in particular the signature must be included to distinguish overloads. """ if self.kind == "function": # TODO: breathe bug with templates and overloads, don't know what to do... return "{name}({parameters})".format( name=self.name, parameters=", ".join(self.parameters) ) return self.name def full_signature(self): """ The full signature of a ``"function"`` node. **Return** :class:`python:str` The full signature of the function, including template, return type, name, and parameter types. **Raises** :class:`python:RuntimeError` If ``self.kind != "function"``. """ if self.kind == "function": return "{template}{return_type} {name}({parameters})".format( template="template <{0}> ".format(", ".join(self.template)) if self.template is not None else "", return_type=self.return_type, name=self.name, parameters=", ".join(self.parameters) ) raise RuntimeError( "full_signature may only be called for a 'function', but {name} is a '{kind}' node.".format( name=self.name, kind=self.kind ) ) def templateParametersStringAsRestList(self, nodeByRefid): ''' .. todo:: document this, create another method for creating this without the need for generating links, to be used in making the node titles and labels ''' if not self.template_params: return None else: param_stream = StringIO() for param_t, decl_n, def_n in self.template_params: refid, typeid = param_t # Say you wanted a custom link text 'custom', and somewhere # else you had an internal link '.. _some_link:'. Then you do # `custom `_ # LOL. RST is confusing if refid: # Easy case: the refid is something Exhale is explicitly documenting if refid in nodeByRefid: link = "{0}_".format(nodeByRefid[refid].link_name) else: # It's going to get generated by Breathe down the line, we need # to reference the page the directive will appear on. parent_refid = "" for key in nodeByRefid: if len(key) > len(parent_refid) and key in refid: parent_refid = key parent = nodeByRefid[parent_refid] parent_page = os.path.basename(parent.file_name.replace(".rst", ".html")) link = "{page}#{refid}".format(page=parent_page, refid=refid) param_stream.write( "#. `{typeid} <{link}>`_".format( typeid=typeid, # Not necessarily an ExhaleNode link, should be a link by # the time Breathe is finished? link=link ) ) close_please = False else: param_stream.write("#. ``{typeid}".format(typeid=typeid)) close_please = True # The type is in there, but when parsed it may have given something like # `class X` for the typeid (meaning nothing else to write). For others, # the decl_n is the declared name of the template parameter. E.g. it # was parsed as `typeid <- class` and `decl_n <- X`. if decl_n: param_stream.write(" ") if not close_please: param_stream.write("``") param_stream.write("{decl_n}".format(decl_n=decl_n)) close_please = True # When templates provide a default value, `def_n` is it. When parsed, # if the `decl_n` and `def_n` are the same, `def_n` is explicitly set # to be None. if def_n: param_stream.write(" ") if not close_please: param_stream.write("``") param_stream.write("= {def_n}``".format(def_n=def_n)) close_please = True if close_please: param_stream.write("``") param_stream.write("\n") param_stream.write("\n") param_value = param_stream.getvalue() param_stream.close() return param_value def baseOrDerivedListString(self, lst, nodeByRefid): ''' .. todo:: long time from now: intersphinx should be possible here ''' # lst should either be self.base_compounds or self.derived_compounds if not lst: return None bod_stream = StringIO() for prot, refid, string in lst: bod_stream.write("- ") # Include the prototype if prot: bod_stream.write("``{0}".format(prot)) please_close = True else: please_close = False # Create the link, if possible # TODO: how to do intersphinx links here? # NOTE: refid is *NOT* guaranteed to be in nodeByRefid # https://github.com/svenevs/exhale/pull/103 if refid and refid in nodeByRefid: # TODO: why are these links not working???????????????????????????????? ###########flake8breaks :/ :/ :/ :/ :/ :/ :/ :/ :/ :/ :/ :/ :/ :/ :/ :/ # if please_close: # bod_stream.write("`` ") # close prototype # bod_stream.write("`{name} <{link}_>`_".format( # # name=string.replace("<", ">").replace(">", "<"), # name=string.replace("<", "").replace(">", ""), # link=nodeByRefid[refid].link_name # )) if not please_close: bod_stream.write("``") else: bod_stream.write(" ") bod_stream.write("{string}`` (:ref:`{link}`)".format( string=string, link=nodeByRefid[refid].link_name )) else: if not please_close: bod_stream.write("``") else: bod_stream.write(" ") bod_stream.write("{0}``".format(string)) bod_stream.write("\n") bod_value = bod_stream.getvalue() bod_stream.close() return bod_value def findNestedNamespaces(self, lst): ''' Recursive helper function for finding nested namespaces. If this node is a namespace node, it is appended to ``lst``. Each node also calls each of its child ``findNestedNamespaces`` with the same list. :Parameters: ``lst`` (list) The list each namespace node is to be appended to. ''' if self.kind == "namespace": lst.append(self) for c in self.children: c.findNestedNamespaces(lst) def findNestedDirectories(self, lst): ''' Recursive helper function for finding nested directories. If this node is a directory node, it is appended to ``lst``. Each node also calls each of its child ``findNestedDirectories`` with the same list. :Parameters: ``lst`` (list) The list each directory node is to be appended to. ''' if self.kind == "dir": lst.append(self) for c in self.children: c.findNestedDirectories(lst) def findNestedClassLike(self, lst): ''' Recursive helper function for finding nested classes and structs. If this node is a class or struct, it is appended to ``lst``. Each node also calls each of its child ``findNestedClassLike`` with the same list. :Parameters: ``lst`` (list) The list each class or struct node is to be appended to. ''' if self.kind == "class" or self.kind == "struct": lst.append(self) for c in self.children: c.findNestedClassLike(lst) def findNestedEnums(self, lst): ''' Recursive helper function for finding nested enums. If this node is a class or struct it may have had an enum added to its child list. When this occurred, the enum was removed from ``self.enums`` in the :class:`~exhale.graph.ExhaleRoot` class and needs to be rediscovered by calling this method on all of its children. If this node is an enum, it is because a parent class or struct called this method, in which case it is added to ``lst``. **Note**: this is used slightly differently than nested directories, namespaces, and classes will be. Refer to :func:`~exhale.graph.ExhaleRoot.generateNodeDocuments`. :Parameters: ``lst`` (list) The list each enum is to be appended to. ''' if self.kind == "enum": lst.append(self) for c in self.children: c.findNestedEnums(lst) def findNestedUnions(self, lst): ''' Recursive helper function for finding nested unions. If this node is a class or struct it may have had a union added to its child list. When this occurred, the union was removed from ``self.unions`` in the :class:`~exhale.graph.ExhaleRoot` class and needs to be rediscovered by calling this method on all of its children. If this node is a union, it is because a parent class or struct called this method, in which case it is added to ``lst``. **Note**: this is used slightly differently than nested directories, namespaces, and classes will be. Refer to :func:`~exhale.graph.ExhaleRoot.generateNodeDocuments`. :Parameters: ``lst`` (list) The list each union is to be appended to. ''' if self.kind == "union": lst.append(self) for c in self.children: c.findNestedUnions(lst) def toConsole(self, level, fmt_spec, printChildren=True): ''' Debugging tool for printing hierarchies / ownership to the console. Recursively calls children ``toConsole`` if this node is not a directory or a file, and ``printChildren == True``. .. todo:: fmt_spec docs needed. keys are ``kind`` and values are color spec :Parameters: ``level`` (int) The indentation level to be used, should be greater than or equal to 0. ``printChildren`` (bool) Whether or not the ``toConsole`` method for the children found in ``self.children`` should be called with ``level+1``. Default is True, set to False for directories and files. ''' indent = " " * level utils.verbose_log("{indent}- [{kind}]: {name}".format( indent=indent, kind=utils._use_color(self.kind, fmt_spec[self.kind], sys.stderr), name=self.name )) # files are children of directories, the file section will print those children if self.kind == "dir": for c in self.children: c.toConsole(level + 1, fmt_spec, printChildren=False) elif printChildren: if self.kind == "file": next_indent = " " * (level + 1) utils.verbose_log("{next_indent}[[[ location=\"{loc}\" ]]]".format( next_indent=next_indent, loc=self.location )) for incl in self.includes: utils.verbose_log("{next_indent}- #include <{incl}>".format( next_indent=next_indent, incl=incl )) for ref, name in self.included_by: utils.verbose_log("{next_indent}- included by: [{name}]".format( next_indent=next_indent, name=name )) for n in self.namespaces_used: n.toConsole(level + 1, fmt_spec, printChildren=False) for c in self.children: c.toConsole(level + 1, fmt_spec) elif self.kind == "class" or self.kind == "struct": relevant_children = [] for c in self.children: if c.kind == "class" or c.kind == "struct" or \ c.kind == "enum" or c.kind == "union": relevant_children.append(c) for rc in sorted(relevant_children): rc.toConsole(level + 1, fmt_spec) elif self.kind != "union": for c in self.children: c.toConsole(level + 1, fmt_spec) def typeSort(self): ''' Sorts ``self.children`` in place, and has each child sort its own children. Refer to :func:`~exhale.graph.ExhaleRoot.deepSortList` for more information on when this is necessary. ''' self.children.sort() for c in self.children: c.typeSort() def inPageHierarchy(self): ''' Whether or not this node should be included in the page view hierarchy. Helper method for :func:`~exhale.graph.ExhaleNode.toHierarchy`. Sets the member variable ``self.in_page_hierarchy`` to True if appropriate. :Return (bool): True if this node should be included in the page view --- if it is a node of kind ``page``. Returns False otherwise. ''' self.in_page_hierarchy = self.kind == "page" return self.in_page_hierarchy def inClassHierarchy(self): ''' Whether or not this node should be included in the class view hierarchy. Helper method for :func:`~exhale.graph.ExhaleNode.toHierarchy`. Sets the member variable ``self.in_class_hierarchy`` to True if appropriate. :Return (bool): True if this node should be included in the class view --- either it is a node of kind ``struct``, ``class``, ``enum``, ``union``, or it is a ``namespace`` that one or more if its descendants was one of the previous four kinds. Returns False otherwise. ''' if self.kind == "namespace": for c in self.children: if c.inClassHierarchy(): return True return False else: # flag that this node is already in the class view so we can find the # missing top level nodes at the end self.in_class_hierarchy = True # Skip children whose names were requested to be explicitly ignored. for exclude in configs._compiled_listing_exclude: if exclude.match(self.name): return False return self.kind in {"struct", "class", "enum", "union"} def inFileHierarchy(self): ''' Whether or not this node should be included in the file view hierarchy. Helper method for :func:`~exhale.graph.ExhaleNode.toHierarchy`. Sets the member variable ``self.in_file_hierarchy`` to True if appropriate. :Return (bool): True if this node should be included in the file view --- either it is a node of kind ``file``, or it is a ``dir`` that one or more if its descendants was a ``file``. Returns False otherwise. ''' if self.kind == "file": # flag that this file is already in the directory view so that potential # missing files can be found later. self.in_file_hierarchy = True return True elif self.kind == "dir": for c in self.children: if c.inFileHierarchy(): return True return False def inHierarchy(self, hierarchyType): if hierarchyType == "page": return self.inPageHierarchy() elif hierarchyType == "class": return self.inClassHierarchy() elif hierarchyType == "file": return self.inFileHierarchy() else: raise RuntimeError("'{}' is not a valid hierarchy type".format(hierarchyType)) def hierarchySortedDirectDescendants(self, hierarchyType): if hierarchyType == "page": if self.kind != "page": raise RuntimeError( "Page hierarchies do not apply to '{}' nodes".format(self.kind) ) return sorted(self.children) elif hierarchyType == "class": # search for nested children to display as sub-items in the tree view if self.kind == "class" or self.kind == "struct": # first find all of the relevant children nested_class_like = [] nested_enums = [] nested_unions = [] # important: only scan self.children, do not use recursive findNested* methods for c in self.children: if c.kind == "struct" or c.kind == "class": nested_class_like.append(c) elif c.kind == "enum": nested_enums.append(c) elif c.kind == "union": nested_unions.append(c) # sort the lists we just found nested_class_like.sort() nested_enums.sort() nested_unions.sort() # return a flattened listing with everything in the order it should be return [ child for child in itertools.chain(nested_class_like, nested_enums, nested_unions) ] # namespaces include nested namespaces, and any top-level class_like, enums, # and unions. include nested namespaces first elif self.kind == "namespace": # pre-process and find everything that is relevant nested_nspaces = [] nested_kids = [] for c in self.children: if c.inHierarchy(hierarchyType): if c.kind == "namespace": nested_nspaces.append(c) else: nested_kids.append(c) # sort the lists nested_nspaces.sort() nested_kids.sort() # return a flattened listing with everything in the order it should be return [ child for child in itertools.chain(nested_nspaces, nested_kids) ] else: # everything else is a terminal node return [] elif hierarchyType == "file": if self.kind == "dir": # find the nested children of interest nested_dirs = [] nested_kids = [] for c in self.children: if c.inHierarchy(hierarchyType): if c.kind == "dir": nested_dirs.append(c) elif c.kind == "file": nested_kids.append(c) # sort the lists nested_dirs.sort() nested_kids.sort() # return a flattened listing with everything in the order it should be return [ child for child in itertools.chain(nested_dirs, nested_kids) ] else: # files are terminal nodes in this hierarchy view return [] else: raise RuntimeError("{} is not a valid hierarchy type".format(hierarchyType)) def toHierarchy(self, hierarchyType, level, stream, lastChild=False): ''' **Parameters** ``hierarchyType`` (str) ``"page"`` if generating the Page Hierarchy, ``"class"`` if generating the Class Hierarchy, ``"file"`` if generating the File Hierarchy. ``level`` (int) Recursion level used to determine indentation. ``stream`` (StringIO) The stream to write the contents to. ``lastChild`` (bool) When :data:`~exhale.configs.createTreeView` is ``True`` and :data:`~exhale.configs.treeViewIsBootstrap` is ``False``, the generated HTML ``li`` elements need to add a ``class="lastChild"`` to use the appropriate styling. .. todo:: add thorough documentation of this ''' # NOTE: indexpage needs to be treated specially, you need to include the # children at the *same* level, and not actually include indexpage. if hierarchyType == "page" and self.refid == "indexpage": nested_children = self.hierarchySortedDirectDescendants(hierarchyType) last_child_index = len(nested_children) - 1 child_idx = 0 for child in nested_children: child.toHierarchy( hierarchyType, level, stream, child_idx == last_child_index) child_idx += 1 return if self.inHierarchy(hierarchyType): # For the Tree Views, we need to know if there are nested children before # writing anything. If there are, we need to open a new list nested_children = self.hierarchySortedDirectDescendants(hierarchyType) ############################################################################ # Write out this node. # ############################################################################ # Easy case: just write another bullet point if not configs.createTreeView: stream.write("{indent}- :ref:`{link}`\n".format( indent=' ' * level, link=self.link_name )) # Otherwise, we're generating some raw HTML and/or JavaScript depending on # whether we are using bootstrap or not else: # Declare the relevant links needed for the Tree Views indent = " " * (level * 2) next_indent = " {0}".format(indent) # turn double underscores into underscores, then underscores into hyphens html_link = self.link_name.replace("__", "_").replace("_", "-") href = "{file}.html#{anchor}".format( file=self.file_name.rsplit(".rst", 1)[0], anchor=html_link ) if self.kind != "page": # should always have at least two parts (templates will have more) title_as_link_parts = self.title.split(" ") if self.template_params: # E.g. 'Template Class Foo' q_start = 0 q_end = 2 else: # E.g. 'Class Foo' q_start = 0 q_end = 1 # the qualifier will not be part of the hyperlink (for clarity of # navigation), the link_title will be qualifier = " ".join(title_as_link_parts[q_start:q_end]) link_title = " ".join(title_as_link_parts[q_end:]) else: # E.g. 'Foo' qualifier = "" link_title = self.title link_title = link_title.replace("&", "&").replace("<", "<").replace(">", ">") # the actual text / link inside of the list item li_text = '{qualifier} {link_title}'.format( qualifier=qualifier, href=href, link_title=link_title ) if configs.treeViewIsBootstrap: text = "text: \"{qualifier} {link_title}\"".format( span_cls=configs.treeViewBootstrapTextSpanClass, qualifier=qualifier, link_title=link_title ) link = "href: \"{href}\"".format(href=href) # write some json data, something like # { # text: " some text", # href: "link to actual item", # selectable: false, stream.write("{indent}{{\n{next_indent}{text},\n".format( indent=indent, next_indent=next_indent, text=text )) stream.write("{next_indent}{link},\n{next_indent}selectable: false,\n".format( next_indent=next_indent, link=link )) # if requested, add the badge indicating how many children there are # only add this if there are children if configs.treeViewBootstrapUseBadgeTags and nested_children: stream.write("{next_indent}tags: ['{num_children}'],\n".format( next_indent=next_indent, num_children=len(nested_children) )) if nested_children: # If there are children then `nodes: [ ... ]` will be next stream.write("\n{next_indent}nodes: [\n".format(next_indent=next_indent)) else: # Otherwise, this element is ending. JavaScript doesn't care # about trailing commas :) stream.write("{indent}}},\n".format(indent=indent)) else: if lastChild: opening_li = '
  • ' else: opening_li = "
  • " if nested_children: # write this list element and begin the next list # writes something like #
  • # some text with an href #