-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue #1368 Add Multidoc utiity for creating versioned documentation #1369
Open
Manangka
wants to merge
2
commits into
master
Choose a base branch
from
issue_#1368_create_multi_version_documentation
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -145,3 +145,4 @@ examples/data | |
/imod/tests/mydask.png | ||
/imod/tests/*_report.xml | ||
docs/sg_execution_times.rst | ||
docs/workdir/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,328 @@ | ||
import argparse | ||
import os | ||
import shutil | ||
import subprocess | ||
import sys | ||
from pathlib import Path | ||
|
||
import git | ||
import jinja2 | ||
import packaging.version | ||
|
||
|
||
class MultiDoc: | ||
def __init__(self): | ||
# Define useful paths | ||
self.current_dir = Path(os.path.dirname(os.path.abspath(__file__))) | ||
|
||
self.root_dir = self.current_dir.parent | ||
self.patch_file = self.current_dir / "version-switcher-patch.diff" | ||
|
||
# Attach to existing repo | ||
self.repo = git.Repo.init(self.root_dir) | ||
|
||
# Parse arguments | ||
root_parser = self.setup_arguments() | ||
args = root_parser.parse_args() | ||
self.config = vars(args) | ||
|
||
# Set additional paths | ||
self.work_dir = ( | ||
Path(self.config["build_folder"]) | ||
if os.path.isabs(self.config["build_folder"]) | ||
else self.current_dir / self.config["build_folder"] | ||
) | ||
self.doc_dir = ( | ||
Path(self.config["doc_folder"]) | ||
if os.path.isabs(self.config["doc_folder"]) | ||
else self.current_dir / self.config["doc_folder"] | ||
) | ||
|
||
# Switch between online or offline url | ||
self.baseurl = ( | ||
"https://deltares.github.io/imod-python" | ||
if not self.config["local_build"] | ||
else self.doc_dir.as_uri() | ||
) | ||
self.json_location = "_static/switcher.json" | ||
|
||
# Execute command | ||
if self.config["command"] is None: | ||
root_parser.print_help() | ||
sys.exit(0) | ||
|
||
getattr(self, self.config["command"].replace("-", "_"))() | ||
|
||
def setup_arguments(self): | ||
root_parser = argparse.ArgumentParser( | ||
description="A simple multi version sphinx doc builder.", | ||
formatter_class=argparse.ArgumentDefaultsHelpFormatter, | ||
exit_on_error=True, | ||
) | ||
|
||
# Common arguments shared by all commands | ||
root_parser.add_argument( | ||
"--doc-folder", | ||
action="store", | ||
default=self.current_dir / "_build" / "html", | ||
help="Folder that contains the existing documentation and where new documentation will be added", | ||
) | ||
root_parser.add_argument( | ||
"--build-folder", | ||
action="store", | ||
default=self.current_dir / "workdir", | ||
help="Folder in which the version is checked out and build.", | ||
) | ||
root_parser.add_argument( | ||
"--local-build", | ||
action="store_true", | ||
help="Changes the hardcoded url of the switcher.json to a local file url. This makes it possible to use the version switcher locally.", | ||
) | ||
subparsers = root_parser.add_subparsers(dest="command") | ||
|
||
# Parser for "add-version" | ||
add_version_parser = subparsers.add_parser( | ||
"add-version", | ||
help="Build and add a version to the documentation.", | ||
exit_on_error=True, | ||
) | ||
add_version_parser.add_argument("version", help="Version to add.") | ||
|
||
# Parser for "update-version" | ||
update_version_parser = subparsers.add_parser( | ||
"update-version", | ||
help="Build and override a version of the documentation.", | ||
exit_on_error=True, | ||
) | ||
update_version_parser.add_argument("version", help="Version to update.") | ||
|
||
# Parser for "remove-version" | ||
remove_version_parser = subparsers.add_parser( | ||
"remove-version", | ||
help="Remove a version from the documentation.", | ||
exit_on_error=True, | ||
) | ||
remove_version_parser.add_argument("version", help="Version to remove.") | ||
|
||
# Parser for "list-versions" | ||
_ = subparsers.add_parser( | ||
"list-versions", help="List present versions in the documentation" | ||
) | ||
_ = subparsers.add_parser( | ||
"create-switcher", help="List present versions in the documentation" | ||
) | ||
|
||
return root_parser | ||
|
||
def add_version(self): | ||
version = self.config["version"] | ||
print(f"add-version: {version}") | ||
|
||
self._build_version(version) | ||
self._build_switcher() | ||
|
||
def update_version(self): | ||
version = self.config["version"] | ||
print(f"update-version: {version}") | ||
|
||
self._build_version(version) | ||
|
||
def remove_version(self): | ||
version = self.config["version"] | ||
print(f"remove-version: {version}") | ||
|
||
shutil.rmtree(self.doc_dir / version) | ||
self._build_switcher() | ||
|
||
def _build_version(self, version): | ||
with GitWorktreeManager(self.repo, self.work_dir, version): | ||
# Define the branch documentation source folder and build folder | ||
local_source_dir = self.work_dir / "docs" | ||
local_build_dir = self.work_dir / "builddir" | ||
|
||
# Apply patch to older version. Once it is known in which version(branch/tag) this file will be added | ||
# we can add a check to apply this patch only to older versions | ||
print("Applying patch") | ||
subprocess.run( | ||
["git", "apply", self.patch_file], | ||
cwd=self.work_dir, | ||
check=True, | ||
) | ||
|
||
# Clean existing Pixi enviroment settings | ||
print( | ||
"Clearing pixi enviroment settings. This is needed for a clean build." | ||
) | ||
env = os.environ | ||
|
||
## Remove any path locations to dependencies inside the local .pixi folder | ||
path_items = os.environ["PATH"].split(os.pathsep) | ||
filtered_path = [ | ||
path | ||
for path in path_items | ||
if not os.path.abspath(path).startswith(str(self.root_dir / ".pixi")) | ||
] | ||
env["Path"] = os.pathsep.join(filtered_path) | ||
|
||
## Remove environment variables added by pixi | ||
pixi_env_vars = [ | ||
"PIXI_PROJECT_ROOT", | ||
"PIXI_PROJECT_NAME", | ||
"PIXI_PROJECT_MANIFEST", | ||
"PIXI_PROJECT_VERSION", | ||
"PIXI_PROMPT", | ||
"PIXI_ENVIRONMENT_NAME", | ||
"PIXI_ENVIRONMENT_PLATFORMS", | ||
"CONDA_PREFIX", | ||
"CONDA_DEFAULT_ENV", | ||
"INIT_CWD", | ||
] | ||
|
||
for pixi_var in pixi_env_vars: | ||
if pixi_var in env: | ||
del env[pixi_var] | ||
|
||
# Add json url to the environment. This will be used in the conf.py file | ||
env["JSON_URL"] = f"{self.baseurl}/{self.json_location}" | ||
|
||
# Build the documentation of the branch | ||
print("Start sphinx-build.") | ||
subprocess.run( | ||
["pixi", "run", "--frozen", "install"], | ||
cwd=self.work_dir, | ||
env=env, | ||
check=True, | ||
) | ||
Comment on lines
+190
to
+195
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't this be a dependency of the "sphinx-build" task? If not, please add a comment why |
||
subprocess.run( | ||
[ | ||
"pixi", | ||
"run", | ||
"--frozen", | ||
"sphinx-build", | ||
"-M", | ||
"html", | ||
local_source_dir, | ||
local_build_dir, | ||
], | ||
cwd=self.work_dir, | ||
env=env, | ||
check=True, | ||
) | ||
|
||
# Collect the branch documentation and add it to the | ||
print("Move documentation to correct location.") | ||
branch_html_dir = local_build_dir / "html" | ||
shutil.rmtree(self.doc_dir / version, ignore_errors=True) | ||
shutil.copytree(branch_html_dir, self.doc_dir / version) | ||
|
||
def list_versions(self): | ||
print(self._get_existing_versions()) | ||
|
||
def create_switcher(self): | ||
self._build_switcher() | ||
|
||
def _build_switcher(self): | ||
switcher = SwitcherBuilder(self._get_existing_versions(), self.baseurl) | ||
version_info = switcher.build() | ||
|
||
template = jinja2.Template("""{{ version_info | tojson(indent=4) }}""") | ||
rendered_document = template.render(version_info=version_info) | ||
|
||
json_path = self.doc_dir / self.json_location | ||
os.makedirs(os.path.dirname(json_path), exist_ok=True) | ||
with open(json_path, "w") as fh: | ||
fh.write(rendered_document) | ||
|
||
def _get_existing_versions(self): | ||
ignore = ["_static"] | ||
versions = [ | ||
name | ||
for name in os.listdir(self.doc_dir) | ||
if os.path.isdir(self.doc_dir / name) and name not in ignore | ||
] | ||
return versions | ||
|
||
|
||
class GitWorktreeManager: | ||
"""Context for working with git worktree | ||
|
||
When combined with a `with` statement this class automatically | ||
creates a worktree and removes it at exit | ||
|
||
""" | ||
|
||
def __init__(self, repo, work_dir, branch_or_tag): | ||
self.repo = repo | ||
self.work_dir = work_dir | ||
self.branch_or_tag = branch_or_tag | ||
|
||
def __enter__(self): | ||
self.repo.git.execute( | ||
["git", "worktree", "add", f"{self.work_dir}", self.branch_or_tag] | ||
) | ||
|
||
def __exit__(self, exc_type, exc_value, traceback): | ||
try: | ||
self.repo.git.execute( | ||
["git", "worktree", "remove", f"{self.work_dir}", "--force"] | ||
) | ||
except Exception: | ||
print("Warning: could not remove the worktree") | ||
|
||
|
||
class SwitcherBuilder: | ||
"""Helper class for building the switcher.json file""" | ||
|
||
def __init__(self, versions, baseurl): | ||
self._versions = versions | ||
self._versions.sort(reverse=True) | ||
self.baseurl = baseurl | ||
|
||
@property | ||
def latest_stable_version(self): | ||
dev_branch = ["master"] | ||
filtered_versions = [ | ||
version | ||
for version in self._versions | ||
if version not in dev_branch | ||
and not packaging.version.Version(version).is_prerelease | ||
] | ||
latest_version = ( | ||
max(filtered_versions, key=packaging.version.parse) | ||
if filtered_versions | ||
else None | ||
) | ||
|
||
return latest_version | ||
|
||
@property | ||
def versions(self): | ||
return self._versions | ||
|
||
def build(self): | ||
version_info = [] | ||
for version in self.versions: | ||
version_info += [ | ||
{ | ||
"name": self._version_to_name(version), | ||
"version": version, | ||
"url": f"{self.baseurl}/{version}", | ||
"preferred": version == self.latest_stable_version, | ||
} | ||
] | ||
|
||
return version_info | ||
|
||
def _version_to_name(self, version): | ||
name_postfix = "" | ||
if version == "master": | ||
name_postfix = "(latest)" | ||
if version == self.latest_stable_version: | ||
name_postfix = "(stable)" | ||
|
||
name = " ".join([version, name_postfix]) | ||
return name | ||
|
||
|
||
if __name__ == "__main__": | ||
MultiDoc() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment when JSON_URL is added to the environment when running multidoc.py