diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 2ffd4262..cfa16321 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,6 +1,7 @@ name: documentation on: + workflow_dispatch: # Trigger the workflow on push or pull request, # but only for the master branch push: @@ -26,17 +27,41 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + with: + fetch-depth: 0 - uses: actions/setup-python@v2 with: python-version: 3.7 - name: Install dependencies run: | - pip install Sphinx fluent.pygments + pip install -r docs/requirements.txt + pip install ./fluent.docs - name: sphinx-build run: | - ./scripts/build-docs --to-publish python-fluent + ./scripts/build-docs python-fluent - name: artifact uses: actions/upload-artifact@v2 with: name: html - path: _build/python-fluent + path: | + _build/python-fluent + !_build/**/.buildinfo + + publish: + name: publish + needs: [build] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Download artifact + uses: actions/download-artifact@v2 + with: + name: html + path: _build + - name: Deploy 🚀 + uses: JamesIves/github-pages-deploy-action@4.0.0 + with: + branch: gh-pages # The branch the action should deploy to. + folder: _build # The folder the action should deploy. + clean: true # Automatically remove deleted files from the deploy branch + dry-run: ${{ github.event_name == 'pull_request' }} diff --git a/.gitignore b/.gitignore index 46040d3b..01deed64 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ _build build dist +/fluent.*/docs/_templates/versions.html diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d4bb2cbb..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -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) diff --git a/docs/_static/project-fluent.css b/docs/_static/project-fluent.css index 5ea2ef0b..84e242c3 100644 --- a/docs/_static/project-fluent.css +++ b/docs/_static/project-fluent.css @@ -1,3 +1,24 @@ div.related { background-color: #356eb7; } + +#versions { + position: relative; + color: #444; +} + +#versions > span.version { + cursor: pointer; +} + +#versions > span.version::after { + content: " ⮞"; +} + +#versions.opened > span.version { + display: none; +} + +#versions:not(.opened) > span.links { + display: none; +} diff --git a/docs/_static/versions.js b/docs/_static/versions.js new file mode 100644 index 00000000..4cb1474f --- /dev/null +++ b/docs/_static/versions.js @@ -0,0 +1,12 @@ +/* + * Show/hide versions when clicking on the versions paragraph + */ + +$(function() { + let versions = document.getElementById('versions'); + if (versions) { + versions.onclick = function(ev) { + this.classList.toggle('opened'); + } + } +}) diff --git a/docs/_templates/versions.html b/docs/_templates/versions.html new file mode 100644 index 00000000..b12a6d88 --- /dev/null +++ b/docs/_templates/versions.html @@ -0,0 +1,4 @@ +{# +Intentionally left blank. +Project documtations fill in a sidebar control as part of the build process. +#} diff --git a/docs/conf.py b/docs/conf.py index 3b573da2..49f112f4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,6 +34,13 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] +# Add src_dir/docs/_templates in a hook as we only have the src_dir then. +def setup(app): + app.connect('config-inited', add_templates) + +def add_templates(app, config): + config.templates_path.insert(0, f'{app.srcdir}/_templates') + # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. @@ -52,9 +59,10 @@ # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] html_css_files = ['project-fluent.css'] +html_js_files = ['versions.js'] html_sidebars = { - '**': ['globaltoc.html', 'searchbox.html'], + '**': ['globaltoc.html', 'versions.html', 'searchbox.html'], } html_theme_options = { } diff --git a/docs/requirements.in b/docs/requirements.in new file mode 100644 index 00000000..b49fca3d --- /dev/null +++ b/docs/requirements.in @@ -0,0 +1,3 @@ +Sphinx==3.5.1 +fluent.pygments==1.0 +wheel diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..6c5a0815 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,168 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile --generate-hashes docs/requirements.in +# +alabaster==0.7.12 \ + --hash=sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359 \ + --hash=sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02 \ + # via sphinx +babel==2.9.0 \ + --hash=sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5 \ + --hash=sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05 \ + # via sphinx +certifi==2020.12.5 \ + --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c \ + --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 \ + # via requests +chardet==4.0.0 \ + --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \ + --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 \ + # via requests +docutils==0.16 \ + --hash=sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af \ + --hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc \ + # via sphinx +fluent.pygments==1.0 \ + --hash=sha256:625c87a8a2362ef304146b161d359dcf652bed2a1ae4869b5607b8e06d117d97 \ + --hash=sha256:b44758f74f87e1aa9d78d8f53363962639c5bf99d88cf3e407d046b5249ec27f \ + # via -r docs/requirements.in +fluent.syntax==0.18.1 \ + --hash=sha256:0e63679fa4f1b3042565220a5127b4bab842424f07d6a13c12299e3b3835486a \ + --hash=sha256:3a55f5e605d1b029a65cc8b6492c86ec4608e15447e73db1495de11fd46c104f \ + # via fluent.pygments +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 \ + # via requests +imagesize==1.2.0 \ + --hash=sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1 \ + --hash=sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1 \ + # via sphinx +jinja2==2.11.3 \ + --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ + --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 \ + # via sphinx +markupsafe==1.1.1 \ + --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ + --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ + --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ + --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ + --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ + --hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \ + --hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \ + --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ + --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ + --hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \ + --hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \ + --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ + --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ + --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ + --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ + --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ + --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ + --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ + --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ + --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \ + --hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \ + --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ + --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ + --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ + --hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \ + --hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \ + --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ + --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ + --hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \ + --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ + --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ + --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ + --hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \ + --hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \ + --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ + --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ + --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ + --hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \ + --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ + --hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \ + --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ + --hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \ + --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ + --hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \ + --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ + --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ + --hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \ + --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \ + --hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \ + --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ + --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ + --hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 \ + # via jinja2 +packaging==20.9 \ + --hash=sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5 \ + --hash=sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a \ + # via sphinx +pygments==2.8.0 \ + --hash=sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0 \ + --hash=sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88 \ + # via fluent.pygments, sphinx +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b \ + # via packaging +pytz==2021.1 \ + --hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da \ + --hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798 \ + # via babel +requests==2.25.1 \ + --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \ + --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e \ + # via sphinx +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced \ + # via fluent.pygments +snowballstemmer==2.1.0 \ + --hash=sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2 \ + --hash=sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914 \ + # via sphinx +sphinx==3.5.1 \ + --hash=sha256:11d521e787d9372c289472513d807277caafb1684b33eb4f08f7574c405893a9 \ + --hash=sha256:e90161222e4d80ce5fc811ace7c6787a226b4f5951545f7f42acf97277bfc35c \ + # via -r docs/requirements.in +sphinxcontrib-applehelp==1.0.2 \ + --hash=sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a \ + --hash=sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58 \ + # via sphinx +sphinxcontrib-devhelp==1.0.2 \ + --hash=sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e \ + --hash=sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4 \ + # via sphinx +sphinxcontrib-htmlhelp==1.0.3 \ + --hash=sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f \ + --hash=sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b \ + # via sphinx +sphinxcontrib-jsmath==1.0.1 \ + --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \ + --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8 \ + # via sphinx +sphinxcontrib-qthelp==1.0.3 \ + --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 \ + --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6 \ + # via sphinx +sphinxcontrib-serializinghtml==1.1.4 \ + --hash=sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc \ + --hash=sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a \ + # via sphinx +urllib3==1.26.3 \ + --hash=sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80 \ + --hash=sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73 \ + # via requests +wheel==0.36.2 \ + --hash=sha256:78b5b185f0e5763c26ca1e324373aadd49182ca90e825f7853f4b2509215dc0e \ + --hash=sha256:e11eefd162658ea59a60a0f6c7d493a7190ea4b9a85e335b33489d9f17e0245e \ + # via -r docs/requirements.in + +# WARNING: The following packages were not pinned, but pip requires them to be +# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag. +# setuptools diff --git a/fluent.docs/README.rst b/fluent.docs/README.rst new file mode 100644 index 00000000..b22e458d --- /dev/null +++ b/fluent.docs/README.rst @@ -0,0 +1,22 @@ +``fluent.docs`` +=============== + +Python utilities used by the ``python-fluent`` documentation build +process. The entry point is ``scripts/build-docs``. + +The generated documentation is in ``_build/python-fluent``, and you +can surf it locally via ``python3 -m http.server `` in ``_build``. + +The documentation is created for each tagged version after May 2020, +at which point we had good docs. The current branch (PR tips or +master) is versioned as *dev*, and *stable* is a symlink to the latest +release. The releases are in a dir with their corresponding version number. + +When cutting a new release, manually run the documentation workflow +to pick that up. + +The rationale for regenerating all historic documentation releases is +to provide an easy process to change style and renderering across +the full documentation. For that, the build setup is taken from +the current checkout, and release docs are generated from sources in +a git worktree. diff --git a/fluent.docs/fluent/__init__.py b/fluent.docs/fluent/__init__.py new file mode 100644 index 00000000..69e3be50 --- /dev/null +++ b/fluent.docs/fluent/__init__.py @@ -0,0 +1 @@ +__path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/fluent.docs/fluent/docs/__init__.py b/fluent.docs/fluent/docs/__init__.py new file mode 100755 index 00000000..e346cd58 --- /dev/null +++ b/fluent.docs/fluent/docs/__init__.py @@ -0,0 +1,18 @@ +from pathlib import Path +from .build import DocBuilder + + +def finalize_builddir(repo_name): + 'Bookkeeping on the docs build directory' + root = Path('_build') / repo_name + with open(root / '.nojekyll', 'w') as fh: + fh.write('') + + +def build_root(repo_name): + '''Build the top-level documentation. + + See :py:mod:`.build` on building sub-projects. + ''' + with DocBuilder(repo_name, '.') as builder: + builder.build() diff --git a/fluent.docs/fluent/docs/build.py b/fluent.docs/fluent/docs/build.py new file mode 100644 index 00000000..6ff1a3a8 --- /dev/null +++ b/fluent.docs/fluent/docs/build.py @@ -0,0 +1,193 @@ +from collections import defaultdict +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +from .tags import get_tag_infos + + +def build(repo_name, projects, releases_after=None): + '''Build documentation for projects. + + Build the given projects in _build/repo_name. + Create versioned documentations for tags after ``releases_after``, + if given. + ''' + tagged_versions = [] + if releases_after: + tagged_versions = [ + tag for tag in get_tag_infos(releases_after) + if tag.project in projects + ] + # List of versions we have for each project + versions_4_project = defaultdict(list) + for tag in tagged_versions: + versions_4_project[tag.project].append(tag.version) + last_vers = {} + for project in projects: + if project in versions_4_project: + last_vers[project] = versions_4_project[project][0] + versions_4_project[project][:0] = ['dev', 'stable'] + else: + # No releases yet, just dev + last_vers[project] = 'dev' + versions_4_project[project].append('dev') + # Build current dev version for each project + for project in projects: + src_dir = project + builder = ProjectBuilder( + repo_name, src_dir, project, versions_4_project[project], 'dev' + ) + with builder: + builder.build() + # Create redirect page from project to stable release or dev + index = Path(f'_build/{repo_name}/{project}/index.html') + target = 'stable' if last_vers[project] != 'dev' else 'dev' + index.write_text( + f'\n' + ) + worktree = None + for tag in tagged_versions: + if worktree is None: + worktree = tempfile.mkdtemp() + subprocess.run([ + 'git', + 'worktree', 'add', + '--detach', + worktree + ]) + subprocess.run([ + 'git', + 'checkout', + f'{tag.project}@{tag.version}' + ], cwd=worktree) + with ProjectBuilder( + repo_name, + os.path.join(worktree, tag.project), + tag.project, + versions_4_project[tag.project], + tag.version + ) as builder: + builder.build() + if worktree is not None: + shutil.rmtree(worktree) + for project in projects: + if last_vers[project] == 'dev': + continue + stable = Path(f'_build/{repo_name}/{project}/stable') + if stable.is_symlink(): + stable.unlink() + if stable.exists(): + shutil.rmtree(stable) + stable.symlink_to(last_vers[project], target_is_directory=True) + + +class DocBuilder: + '''Builder for the top-level documentation. + ''' + + def __init__(self, repo_name, src_dir): + self.repo_name = repo_name + self.src_dir = src_dir + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + return False + + def build(self): + cmd = self.command() + env = self.environ() + subprocess.check_call(cmd, env=env) + + def command(self): + return self.cmd_prefix + self.cmd_opts + [ + f'{self.src_dir}/docs', + self.dest_dir, + ] + + def environ(self): + return os.environ.copy() + + @property + def cmd_prefix(self): + return [ + 'sphinx-build', + '-c', 'docs', + '-a', '-E', '-W', + '-A', 'root_url=/' + self.repo_name, + '-d', self.doc_tree, + ] + + @property + def cmd_opts(self): + return [] + + @property + def dest_dir(self): + return f'_build/{self.repo_name}' + + @property + def doc_tree(self): + return '_build/doctrees' + + +class ProjectBuilder(DocBuilder): + '''Builder for individual projects, with project name and version. + ''' + def __init__(self, repo_name, src_dir, project_name, versions, version): + super().__init__(repo_name, src_dir) + self.project_name = project_name + self.versions = versions + self.version = version + + def __exit__(self, exc_type, exc_val, exc_tb): + # Remove static theme files from project static, they're + # used from the top-level _static. + for staticfile in Path(self.dest_dir).glob('_static/*'): + # The options are project-specific. + if staticfile.name != 'documentation_options.js': + staticfile.unlink() + return False + + def build(self): + self.create_versions_doc() + super().build() + + def environ(self): + env = super().environ() + env['PYTHONPATH'] = self.src_dir + return env + + @property + def cmd_opts(self): + opts = [ + '-D', 'project=' + self.project_name, + ] + if self.version != 'dev': + opts += [ + '-D', f'release={self.version}', + ] + return opts + + @property + def dest_dir(self): + return f'_build/{self.repo_name}/{self.project_name}/{self.version}' + + def create_versions_doc(self): + target_path = Path(self.src_dir) / 'docs' / '_templates' + target_path.mkdir(exist_ok=True) + target_path = target_path / 'versions.html' + links = ' '.join( + f'{v}' + for v in self.versions + ) + content = f'''
+ {self.version} + {links} +
+''' + target_path.write_text(content) diff --git a/fluent.docs/fluent/docs/tags.py b/fluent.docs/fluent/docs/tags.py new file mode 100644 index 00000000..8da0fcbe --- /dev/null +++ b/fluent.docs/fluent/docs/tags.py @@ -0,0 +1,36 @@ +from datetime import date +import subprocess + + +def get_tag_infos(cut_off_date): + '''Get fluent.* tags newer than cut_off_date. + TagInfo objects are ordered by committer date, newest first. + ''' + taglines = subprocess.run( + [ + 'git', 'tag', '--list', 'fluent.*', + '--sort=-committerdate', + '--format=%(refname:lstrip=2) %(committerdate:short)', + ], + encoding='utf-8', + stdout=subprocess.PIPE, + check=True + ).stdout.splitlines() + return [ + ti for ti in (TagInfo(line) for line in taglines) + if ti.date > cut_off_date + ] + + +class TagInfo: + def __init__(self, tagline): + tag, date_string = tagline.split(' ') + self.project, self.version = tag.split('@') + self.date = date.fromisoformat(date_string) + + @property + def tag(self): + return f'{self.project}@{self.version}' + + def __repr__(self): + return f'{self.tag} ({self.date})' diff --git a/fluent.docs/setup.cfg b/fluent.docs/setup.cfg new file mode 100644 index 00000000..aa079ec5 --- /dev/null +++ b/fluent.docs/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length=120 diff --git a/fluent.docs/setup.py b/fluent.docs/setup.py new file mode 100644 index 00000000..6a2dea31 --- /dev/null +++ b/fluent.docs/setup.py @@ -0,0 +1,6 @@ +from setuptools import setup + +setup( + name='fluent.docs', + packages=['fluent', 'fluent.docs'], +) diff --git a/fluent.runtime/docs/Makefile b/fluent.runtime/docs/Makefile deleted file mode 100644 index 51285967..00000000 --- a/fluent.runtime/docs/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -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) diff --git a/fluent.runtime/docs/conf.py b/fluent.runtime/docs/conf.py deleted file mode 100644 index 4cb314c3..00000000 --- a/fluent.runtime/docs/conf.py +++ /dev/null @@ -1,143 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys -sys.path.insert(0, os.path.abspath('..')) - - -# -- Project information ----------------------------------------------------- - -project = 'fluent.runtime' -copyright = '2020' - -# The short X.Y version -version = '0.3' -# The full version, including alpha/beta/rc tags -release = '0.3' - - -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.intersphinx', - 'sphinx.ext.viewcode', - 'sphinx.ext.autodoc', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = None - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'fluentruntimedoc' - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, - -# -- Extension configuration ------------------------------------------------- - -# -- Options for intersphinx extension --------------------------------------- - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} - -# -- Options for autodoc extension -------------------------------------------- - -autodoc_mock_imports = [ - 'attr', -] diff --git a/fluent.syntax/docs/Makefile b/fluent.syntax/docs/Makefile deleted file mode 100644 index d4bb2cbb..00000000 --- a/fluent.syntax/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -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) diff --git a/fluent.syntax/docs/conf.py b/fluent.syntax/docs/conf.py deleted file mode 100644 index 57e03b94..00000000 --- a/fluent.syntax/docs/conf.py +++ /dev/null @@ -1,65 +0,0 @@ -# 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 -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. - -import os -import sys -sys.path.insert(0, os.path.abspath('..')) - - -# -- Project information ----------------------------------------------------- - -project = 'Fluent Syntax' -copyright = '2020' - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'nature' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - - -# -- Extension configuration ------------------------------------------------- - -# -- Options for intersphinx extension --------------------------------------- - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/3/': None} - -# -- Options for autodoc extension ------------------------------------------- - -autodoc_member_order = 'bysource' diff --git a/scripts/build-docs b/scripts/build-docs index a1b39533..388c21df 100755 --- a/scripts/build-docs +++ b/scripts/build-docs @@ -1,62 +1,10 @@ #!/usr/bin/env python3 import argparse -from configparser import ConfigParser -import os -from pathlib import Path -import subprocess +from datetime import date - -def get_version(root): - if root == '.': - return - cp = ConfigParser() - cp.read(Path(root) / 'setup.cfg') - return cp.get('metadata', 'version') - - -def build(repo_name, doc_roots): - for doc_root in doc_roots: - env = os.environ.copy() - version = get_version(doc_root) - cmd = [ - 'sphinx-build', - '-c', 'docs', - '-a', '-E', '-W', - '-A', 'root_url=/' + repo_name, - '-d', '_build/doctrees/' + doc_root, - ] - if version: - env['PYTHONPATH'] = doc_root - cmd += [ - '-D', 'release=' + version, - '-D', 'project=' + doc_root, - ] - build_dir = '_build/' + repo_name + '/' + doc_root + '/stable' - else: - build_dir = '_build/' + repo_name - cmd += [ - doc_root + '/docs', - build_dir, - ] - subprocess.check_call(' '.join(cmd), env=env, shell=True) - if build_dir.endswith('/stable'): - with open(build_dir.replace('/stable', '/index.html'), 'w') as index: - index.write('\n') - - -def pre_pub(repo_name): - """Prepare staging area for publishing on Github pages.""" - root = Path('_build') / repo_name - # Ensure `.nojekyll` - with open(root / '.nojekyll', 'w') as fh: - fh.write('') - # Remove static files from subprojects, but leave - # `documentation_options.js` alone. That's per project, probably. - # We built to root/fluent.*/stable, so glob that. - for staticfile in root.glob('fluent.*/stable/_static/*'): - if staticfile.name != 'documentation_options.js': - staticfile.unlink() +from fluent.docs import build_root, finalize_builddir +from fluent.docs.build import build if __name__ == "__main__": @@ -65,18 +13,24 @@ if __name__ == "__main__": 'repo_name', help='URL prefix on the web. Repo name on gh-pages' ) - parser.add_argument( - '--to-publish', dest='pre_pub', action='store_true', - help='Use this to publish to Github. Builds faster without.' - ) default_docs = ['.', 'fluent.syntax', 'fluent.pygments', 'fluent.runtime'] parser.add_argument( '--doc', action='append', choices=default_docs, help='Only build select documentation roots.' ) + parser.add_argument( + '--dev', action='store_false', dest='show_releases', + help='Only build development versions of projects' + ) args = parser.parse_args() if args.doc is None: - args.doc = default_docs - build(args.repo_name, args.doc) - if args.pre_pub: - pre_pub(args.repo_name) + args.doc = default_docs[:] + if '.' in args.doc: + build_root(args.repo_name) + args.doc.remove('.') + releases_after = None + if args.show_releases: + # python-fluent had descent docs since May 2020 + releases_after = date(2020, 5, 1) + build(args.repo_name, args.doc, releases_after) + finalize_builddir(args.repo_name)