docput 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- docput-0.1.0/.forgejo/workflows/push.yml +115 -0
- docput-0.1.0/.gitignore +8 -0
- docput-0.1.0/CHANGELOG.md +7 -0
- docput-0.1.0/Justfile +30 -0
- docput-0.1.0/LICENSE.md +27 -0
- docput-0.1.0/PKG-INFO +45 -0
- docput-0.1.0/README.md +19 -0
- docput-0.1.0/docput/__init__.py +6 -0
- docput-0.1.0/docput/__main__.py +3 -0
- docput-0.1.0/docput/cli/__init__.py +5 -0
- docput-0.1.0/docput/cli/main.py +123 -0
- docput-0.1.0/docput/cli/manifest.py +58 -0
- docput-0.1.0/docput/cli/version.py +183 -0
- docput-0.1.0/docput/config.py +129 -0
- docput-0.1.0/docput/manifest.py +110 -0
- docput-0.1.0/docput/path.py +88 -0
- docput-0.1.0/docput/remote/__init__.py +43 -0
- docput-0.1.0/docput/remote/base.py +134 -0
- docput-0.1.0/docput/remote/filesystem.py +53 -0
- docput-0.1.0/docput/remote/git_pages.py +221 -0
- docput-0.1.0/docput/sphinx_ext/__init__.py +22 -0
- docput-0.1.0/docput/sphinx_ext/_static/docput.js +67 -0
- docput-0.1.0/docput/sphinx_ext/_templates/sidebar/version-switch.html +10 -0
- docput-0.1.0/docput/typing.py +13 -0
- docput-0.1.0/docput.egg-info/PKG-INFO +45 -0
- docput-0.1.0/docput.egg-info/SOURCES.txt +52 -0
- docput-0.1.0/docput.egg-info/dependency_links.txt +1 -0
- docput-0.1.0/docput.egg-info/entry_points.txt +2 -0
- docput-0.1.0/docput.egg-info/requires.txt +5 -0
- docput-0.1.0/docput.egg-info/scm_file_list.json +48 -0
- docput-0.1.0/docput.egg-info/scm_version.json +8 -0
- docput-0.1.0/docput.egg-info/top_level.txt +1 -0
- docput-0.1.0/docs/.gitignore +1 -0
- docput-0.1.0/docs/Justfile +25 -0
- docput-0.1.0/docs/_include/forgejo-hover.svg +41 -0
- docput-0.1.0/docs/_include/forgejo-monochrome.svg +35 -0
- docput-0.1.0/docs/_include/pypi-hover.svg +56 -0
- docput-0.1.0/docs/_include/pypi-monochrome.svg +13 -0
- docput-0.1.0/docs/_root/_headers +4 -0
- docput-0.1.0/docs/_root/_redirects +1 -0
- docput-0.1.0/docs/_static/css/custom.css +27 -0
- docput-0.1.0/docs/_templates/page.html +6 -0
- docput-0.1.0/docs/conf.py +85 -0
- docput-0.1.0/docs/getting_started.md +72 -0
- docput-0.1.0/docs/handbook.md +112 -0
- docput-0.1.0/docs/index.md +17 -0
- docput-0.1.0/docs/reference/commands/index.md +17 -0
- docput-0.1.0/docs/reference/commands/manifest.md +5 -0
- docput-0.1.0/docs/reference/commands/version.md +5 -0
- docput-0.1.0/docs/reference/index.md +9 -0
- docput-0.1.0/docs/versions.json +12 -0
- docput-0.1.0/pyproject.toml +76 -0
- docput-0.1.0/setup.cfg +4 -0
- docput-0.1.0/tests/test_remotes.py +95 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
name: build
|
|
2
|
+
on: [ push, pull_request ]
|
|
3
|
+
|
|
4
|
+
jobs:
|
|
5
|
+
build:
|
|
6
|
+
name: Test & Build
|
|
7
|
+
runs-on: ubuntu-24.04
|
|
8
|
+
services:
|
|
9
|
+
gitpages:
|
|
10
|
+
image: codeberg.org/git-pages/git-pages:0.9.1
|
|
11
|
+
env:
|
|
12
|
+
PAGES_INSECURE: 1
|
|
13
|
+
env:
|
|
14
|
+
DOCPUT_TEST_GIT_PAGES_URL: http://gitpages:3000
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: https://code.forgejo.org/actions/checkout@v5
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
|
|
22
|
+
- name: Set up Project
|
|
23
|
+
uses: https://git.offworld.city/actions/setup-python-project@87a134c2c5e6c03139c4215575f0f1ebdfcb41a2
|
|
24
|
+
with:
|
|
25
|
+
python-version-file: pyproject.toml
|
|
26
|
+
pip-install: -e .[sphinx] --group dev
|
|
27
|
+
|
|
28
|
+
- name: Lint
|
|
29
|
+
run: just lint
|
|
30
|
+
|
|
31
|
+
- name: Test
|
|
32
|
+
run: just test
|
|
33
|
+
|
|
34
|
+
- name: Build
|
|
35
|
+
run: just build
|
|
36
|
+
|
|
37
|
+
- name: Upload dist
|
|
38
|
+
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
|
|
39
|
+
with:
|
|
40
|
+
name: python-dist
|
|
41
|
+
path: dist/
|
|
42
|
+
compression-level: 0 # already compressed
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
document:
|
|
46
|
+
name: Document
|
|
47
|
+
runs-on: ubuntu-24.04
|
|
48
|
+
container:
|
|
49
|
+
env:
|
|
50
|
+
DOCPUT_REMOTE: git-pages+https://drewcassidy.me/docput
|
|
51
|
+
GIT_PAGES_PASSWORD: ${{ secrets.DREWCASSIDY_ME_PASSWORD }}
|
|
52
|
+
steps:
|
|
53
|
+
- name: Checkout
|
|
54
|
+
uses: https://code.forgejo.org/actions/checkout@v5
|
|
55
|
+
|
|
56
|
+
- name: Set up Project
|
|
57
|
+
uses: https://git.offworld.city/actions/setup-python-project@87a134c2c5e6c03139c4215575f0f1ebdfcb41a2
|
|
58
|
+
with:
|
|
59
|
+
python-version-file: pyproject.toml
|
|
60
|
+
pip-install: -e .[sphinx] --group docs
|
|
61
|
+
|
|
62
|
+
- name: Build docs
|
|
63
|
+
run: just docs build
|
|
64
|
+
|
|
65
|
+
- name: Push docs
|
|
66
|
+
if: forge.event_name == 'push' #skip for PRs
|
|
67
|
+
run: just docs push ${{ startsWith(forge.ref, 'refs/tags') && '--latest --visible' || '' }}
|
|
68
|
+
|
|
69
|
+
- name: Upload root files
|
|
70
|
+
if: forge.ref == 'refs/heads/main' # follow main branch
|
|
71
|
+
run: just docs update-root
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
publish:
|
|
75
|
+
name: Publish
|
|
76
|
+
if: forge.event_name == 'push' && startsWith(forge.ref, 'refs/tags')
|
|
77
|
+
needs:
|
|
78
|
+
- build
|
|
79
|
+
- document
|
|
80
|
+
runs-on: ubuntu-24.04
|
|
81
|
+
steps:
|
|
82
|
+
- name: Checkout
|
|
83
|
+
uses: https://code.forgejo.org/actions/checkout@v5
|
|
84
|
+
|
|
85
|
+
- name: Download dist
|
|
86
|
+
uses: https://code.forgejo.org/forgejo/download-artifact@v4
|
|
87
|
+
with:
|
|
88
|
+
name: python-dist
|
|
89
|
+
path: dist
|
|
90
|
+
|
|
91
|
+
- name: Get Changelog Information
|
|
92
|
+
id: yaclog-show
|
|
93
|
+
uses: https://git.offworld.city/drewcassidy/yaclog@1.7.2
|
|
94
|
+
|
|
95
|
+
- name: Create release
|
|
96
|
+
uses: https://code.forgejo.org/actions/forgejo-release@v2.11.1
|
|
97
|
+
with:
|
|
98
|
+
direction: upload
|
|
99
|
+
token: ${{ secrets.FORGEJO_TOKEN }}
|
|
100
|
+
release-dir: dist
|
|
101
|
+
title: docput ${{ steps.yaclog-show.outputs.name }}
|
|
102
|
+
release-notes-file: ${{ steps.yaclog-show.outputs.body-file }}
|
|
103
|
+
override: true
|
|
104
|
+
|
|
105
|
+
- name: Upload to PyPI
|
|
106
|
+
run: pipx run twine upload --skip-existing dist/*
|
|
107
|
+
env:
|
|
108
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
|
109
|
+
|
|
110
|
+
- name: Upload to Forgejo
|
|
111
|
+
run: pipx run twine upload dist/*
|
|
112
|
+
env:
|
|
113
|
+
TWINE_USERNAME: ${{ forge.repository_owner }}
|
|
114
|
+
TWINE_PASSWORD: ${{ secrets.FORGEJO_DEPLOY_TOKEN }}
|
|
115
|
+
TWINE_REPOSITORY_URL: ${{ forge.server_url }}/api/packages/${{ forge.repository_owner }}/pypi
|
docput-0.1.0/.gitignore
ADDED
docput-0.1.0/Justfile
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env just --justfile
|
|
2
|
+
|
|
3
|
+
mod docs
|
|
4
|
+
|
|
5
|
+
# Lint python source code
|
|
6
|
+
lint:
|
|
7
|
+
python -m flake8 docput/
|
|
8
|
+
zuban check docput/
|
|
9
|
+
|
|
10
|
+
# Format python source code and other support files
|
|
11
|
+
format: && docs::format
|
|
12
|
+
python -m black docput/
|
|
13
|
+
python -m yaclog format
|
|
14
|
+
{{ just_executable() }} --fmt --unstable
|
|
15
|
+
|
|
16
|
+
# Test
|
|
17
|
+
test:
|
|
18
|
+
python -m pytest --doctest-modules
|
|
19
|
+
|
|
20
|
+
build:
|
|
21
|
+
python -m build
|
|
22
|
+
|
|
23
|
+
pre-commit := f"""
|
|
24
|
+
#!/bin/sh
|
|
25
|
+
PATH={{`dirname "$(which python)"`}}:$PATH just lint test"""
|
|
26
|
+
|
|
27
|
+
# Install recommended git hooks. Do this after activating your virtual environment
|
|
28
|
+
install-hooks:
|
|
29
|
+
echo '{{ pre-commit }}' > .git/hooks/pre-commit
|
|
30
|
+
chmod +x .git/hooks/pre-commit
|
docput-0.1.0/LICENSE.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
=====================
|
|
3
|
+
|
|
4
|
+
Copyright © 2026 Andrew Cassidy
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person
|
|
7
|
+
obtaining a copy of this software and associated documentation
|
|
8
|
+
files (the “Software”), to deal in the Software without
|
|
9
|
+
restriction, including without limitation the rights to use,
|
|
10
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the
|
|
12
|
+
Software is furnished to do so, subject to the following
|
|
13
|
+
conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice, this permission notice, and the
|
|
16
|
+
affirmation that TRANS RIGHTS ARE HUMAN RIGHTS shall be included
|
|
17
|
+
in all copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
20
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
21
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
22
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
23
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
24
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
25
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
26
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
27
|
+
|
docput-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: docput
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deploy multi-versioned docs to static hosting
|
|
5
|
+
Author-email: Andrew Cassidy <drewcassidy@me.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Source, https://git.offworld.city/drewcassidy/docput
|
|
8
|
+
Keywords: sphinx,git-pages,documentation,ci
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Utilities
|
|
15
|
+
Classifier: Topic :: Documentation
|
|
16
|
+
Classifier: Topic :: Documentation :: Sphinx
|
|
17
|
+
Classifier: Framework :: Sphinx
|
|
18
|
+
Requires-Python: >=3.14
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE.md
|
|
21
|
+
Requires-Dist: click>=8.4
|
|
22
|
+
Requires-Dist: requests>=2.33
|
|
23
|
+
Provides-Extra: sphinx
|
|
24
|
+
Requires-Dist: Sphinx>=9; extra == "sphinx"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
docput
|
|
28
|
+
======
|
|
29
|
+
|
|
30
|
+
[source](https://git.offworld.city/drewcassidy/docput) •
|
|
31
|
+
[documentation](https://drewcassidy.me/docput/latest/) •
|
|
32
|
+
[pypi](https://pypi.org/project/docput)
|
|
33
|
+
|
|
34
|
+
docput allows you to easily deploy versioned documentation
|
|
35
|
+
to [git-pages](https://git-pages.org) or the local filesystem. Each version of your
|
|
36
|
+
documentation is deployed to its own subfolder, and does not need to be rebuilt when
|
|
37
|
+
other versions are added.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
Install and update with [pip](https://pip.pypa.io/en/stable/getting-started/):
|
|
42
|
+
|
|
43
|
+
```shell
|
|
44
|
+
$ pip install docput
|
|
45
|
+
```
|
docput-0.1.0/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
docput
|
|
2
|
+
======
|
|
3
|
+
|
|
4
|
+
[source](https://git.offworld.city/drewcassidy/docput) •
|
|
5
|
+
[documentation](https://drewcassidy.me/docput/latest/) •
|
|
6
|
+
[pypi](https://pypi.org/project/docput)
|
|
7
|
+
|
|
8
|
+
docput allows you to easily deploy versioned documentation
|
|
9
|
+
to [git-pages](https://git-pages.org) or the local filesystem. Each version of your
|
|
10
|
+
documentation is deployed to its own subfolder, and does not need to be rebuilt when
|
|
11
|
+
other versions are added.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Install and update with [pip](https://pip.pypa.io/en/stable/getting-started/):
|
|
16
|
+
|
|
17
|
+
```shell
|
|
18
|
+
$ pip install docput
|
|
19
|
+
```
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import sys
|
|
3
|
+
from io import Writer
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from docput.path import URLPath
|
|
10
|
+
from docput.remote import Remote, open_remote
|
|
11
|
+
from docput.typing import Reader
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RemoteParamType(click.ParamType[Remote]):
|
|
15
|
+
name = "URL"
|
|
16
|
+
|
|
17
|
+
def __init__(self, commit_on_close: bool = True):
|
|
18
|
+
self.commit_on_close = commit_on_close
|
|
19
|
+
|
|
20
|
+
def convert(
|
|
21
|
+
self, value: Any, param: click.Parameter | None, ctx: click.Context | None
|
|
22
|
+
) -> Remote:
|
|
23
|
+
match value:
|
|
24
|
+
case Remote():
|
|
25
|
+
remote = value
|
|
26
|
+
case str(url):
|
|
27
|
+
remote = open_remote(url)
|
|
28
|
+
case _:
|
|
29
|
+
self.fail(f"Unknown value type {type(value)}")
|
|
30
|
+
if self.commit_on_close:
|
|
31
|
+
assert ctx is not None, "Cannot open remote without a context to bind it to"
|
|
32
|
+
ctx.with_resource(remote)
|
|
33
|
+
return remote
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def remote_option[FC: Callable[..., Any] | click.Command](
|
|
37
|
+
commit_on_close=True,
|
|
38
|
+
help="Remote to interact with",
|
|
39
|
+
**kwargs,
|
|
40
|
+
) -> Callable[[FC], FC]:
|
|
41
|
+
return click.option(
|
|
42
|
+
"--remote",
|
|
43
|
+
"-r",
|
|
44
|
+
required=True,
|
|
45
|
+
type=RemoteParamType(commit_on_close),
|
|
46
|
+
envvar="DOCPUT_REMOTE",
|
|
47
|
+
show_envvar=True,
|
|
48
|
+
help=help,
|
|
49
|
+
**kwargs,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@click.group(context_settings={"show_default": True, "max_content_width": 120})
|
|
54
|
+
@click.version_option()
|
|
55
|
+
def main_group(): ...
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@main_group.command(short_help="Download a file.")
|
|
59
|
+
@remote_option(help="Remote to download from.")
|
|
60
|
+
@click.argument("source", type=click.Path(path_type=URLPath))
|
|
61
|
+
@click.argument("dest", type=click.File(mode="xb"))
|
|
62
|
+
def download(remote: Remote, source: URLPath, dest: Writer[bytes]):
|
|
63
|
+
"""Download SOURCE from the remote and write it to DEST"""
|
|
64
|
+
buff = remote.open_file(source)
|
|
65
|
+
shutil.copyfileobj(buff, dest)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@main_group.command(short_help="Upload a file or directory.") # type: ignore[attr-defined]
|
|
69
|
+
@remote_option(help="Remote to upload to.")
|
|
70
|
+
@click.option(
|
|
71
|
+
"--overwrite/--merge",
|
|
72
|
+
"-o/-m",
|
|
73
|
+
default=False,
|
|
74
|
+
is_flag=True,
|
|
75
|
+
show_default=True,
|
|
76
|
+
help="Merge or overwrite uploaded directories.",
|
|
77
|
+
)
|
|
78
|
+
@click.argument("source", type=click.Path(exists=True, allow_dash=True, path_type=Path))
|
|
79
|
+
@click.argument("dest", type=click.Path(path_type=URLPath))
|
|
80
|
+
def upload(
|
|
81
|
+
remote: Remote,
|
|
82
|
+
overwrite: bool,
|
|
83
|
+
source: Path | Reader[bytes],
|
|
84
|
+
dest: URLPath,
|
|
85
|
+
):
|
|
86
|
+
"""
|
|
87
|
+
Upload a file or directory SOURCE to the remote at DEST.
|
|
88
|
+
|
|
89
|
+
SOURCE is a path to a file or directory to upload. If SOURCE is `-`, its contents is read from standard input.
|
|
90
|
+
|
|
91
|
+
DEST is a path on the destination to write to.
|
|
92
|
+
|
|
93
|
+
If SOURCE is a file, its contents will be placed at DEST on the remote. Unlike tools like `cp` or `rsync`,
|
|
94
|
+
there is no special handling if DEST refers to a directory
|
|
95
|
+
"""
|
|
96
|
+
if isinstance(source, Path) and str(source) == "-":
|
|
97
|
+
source = sys.stdin.buffer
|
|
98
|
+
remote.write(source, dest, overwrite)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@main_group.command(short_help="Delete a file or directory.")
|
|
102
|
+
@remote_option(help="Remote to delete from.")
|
|
103
|
+
@click.argument("path", type=click.Path(path_type=URLPath))
|
|
104
|
+
def rm(remote: Remote, path: URLPath):
|
|
105
|
+
"""Delete the file or directory at PATH on the remote"""
|
|
106
|
+
remote.delete(path)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@main_group.command(short_help="Create a symlink.")
|
|
110
|
+
@remote_option(help="Remote to create a symlink on.")
|
|
111
|
+
@click.argument("source", type=click.Path(path_type=URLPath))
|
|
112
|
+
@click.argument("dest", type=click.Path(path_type=URLPath))
|
|
113
|
+
def ln(remote: Remote, source: URLPath, dest: URLPath):
|
|
114
|
+
"""Create a symlink on the remote at DEST pointing to SOURCE"""
|
|
115
|
+
remote.make_symlink(source, dest)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@main_group.command(short_help="Create a directory.")
|
|
119
|
+
@remote_option(help="Remote to create a directory on.")
|
|
120
|
+
@click.argument("path", type=click.Path(path_type=URLPath))
|
|
121
|
+
def mkdir(remote: Remote, path: URLPath):
|
|
122
|
+
"""Create a directory on the remote at PATH. Any existing file or directory at PATH will be deleted"""
|
|
123
|
+
remote.make_dir(path)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from tempfile import TemporaryDirectory
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from click import ClickException
|
|
9
|
+
|
|
10
|
+
from docput.config import get_config
|
|
11
|
+
from .main import main_group, remote_option
|
|
12
|
+
from ..manifest import VersionManifest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@main_group.group(help="Edit the version manifest.")
|
|
16
|
+
def manifest_group(): ...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@manifest_group.command(short_help="Open the version manifest in a text editor")
|
|
20
|
+
@remote_option()
|
|
21
|
+
@click.option(
|
|
22
|
+
"--open",
|
|
23
|
+
"-O",
|
|
24
|
+
"editor",
|
|
25
|
+
is_flag=False,
|
|
26
|
+
flag_value=os.environ.get("EDITOR") or "nano",
|
|
27
|
+
help="Open the manifest file in a text editor and write it to the remote on exit.",
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--url",
|
|
31
|
+
"root_url",
|
|
32
|
+
metavar="URL",
|
|
33
|
+
help="""
|
|
34
|
+
Set the root URL to a new value. The root URL is the base for all hrefs in the version manifest. This must be absolute (either a path starting
|
|
35
|
+
with "/", or a full URL like "https://example.com/docs/"). If it is not set, it is assumed to be "/".
|
|
36
|
+
""",
|
|
37
|
+
)
|
|
38
|
+
def edit(remote, editor: str | None, root_url: str | None):
|
|
39
|
+
"""Edit the version manifest."""
|
|
40
|
+
if editor is not None:
|
|
41
|
+
with TemporaryDirectory() as td:
|
|
42
|
+
tmp_path = Path(td) / get_config().get("manifest_url")
|
|
43
|
+
with open(tmp_path, "wb") as temp:
|
|
44
|
+
shutil.copyfileobj(remote.manifest.dump(), temp)
|
|
45
|
+
while True:
|
|
46
|
+
subprocess.run([editor, tmp_path], check=True)
|
|
47
|
+
try:
|
|
48
|
+
with open(tmp_path, "rb") as temp:
|
|
49
|
+
remote.manifest = VersionManifest(temp)
|
|
50
|
+
return
|
|
51
|
+
except Exception as e:
|
|
52
|
+
retry = click.confirm(f"Error parsing manifest file: {e}. Retry?")
|
|
53
|
+
if not retry:
|
|
54
|
+
raise ClickException(
|
|
55
|
+
f"Aborting after error parsing manifest file: {e}"
|
|
56
|
+
)
|
|
57
|
+
if root_url is not None:
|
|
58
|
+
remote.manifest.root_url = root_url
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, Callable
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from click import ClickException
|
|
7
|
+
|
|
8
|
+
from .main import main_group, remote_option
|
|
9
|
+
from ..config import get_config
|
|
10
|
+
from ..manifest import VersionRecord
|
|
11
|
+
from ..path import URLPath, reroot
|
|
12
|
+
from ..remote import Remote
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def ref_option[FC: Callable[..., Any] | click.Command](**kwargs) -> Callable[[FC], FC]: # type: ignore[name-defined]
|
|
16
|
+
return click.option(
|
|
17
|
+
"--ref",
|
|
18
|
+
"-R",
|
|
19
|
+
envvar=["DOCPUT_VERSION_REF", "FORGEJO_REF", "GITHUB_REF"],
|
|
20
|
+
show_envvar=True,
|
|
21
|
+
required=True,
|
|
22
|
+
**kwargs,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@main_group.group(short_help="Create or edit versions.")
|
|
27
|
+
def version_group(): ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@version_group.command(short_help="Push a documentation version to the remote.")
|
|
31
|
+
@click.argument("source", type=click.Path(exists=True, file_okay=False, path_type=Path))
|
|
32
|
+
@remote_option(help="Remote to push to.")
|
|
33
|
+
@ref_option(help="Version reference to push to.")
|
|
34
|
+
@click.option(
|
|
35
|
+
"--latest",
|
|
36
|
+
"-l",
|
|
37
|
+
is_flag=True,
|
|
38
|
+
default=False,
|
|
39
|
+
help="Mark this version as the latest release.",
|
|
40
|
+
)
|
|
41
|
+
@click.option(
|
|
42
|
+
"--visible/--hidden",
|
|
43
|
+
"-v/-h",
|
|
44
|
+
default=None,
|
|
45
|
+
help="Show this version in the version menu on the site.",
|
|
46
|
+
)
|
|
47
|
+
@click.option(
|
|
48
|
+
"--url",
|
|
49
|
+
"-u",
|
|
50
|
+
envvar=["DOCPUT_VERSION_URL"],
|
|
51
|
+
show_envvar=True,
|
|
52
|
+
help="""
|
|
53
|
+
Subdirectory on the site to write and link to. If not provided when pushing a new version, use the version ref with any text matching regex in the
|
|
54
|
+
config value `version.url_strip` removed. By default, this removes "refs/" from the start of the reference, so a version with the reference
|
|
55
|
+
`refs/tags/v1.0.0` would be published to `tags/v1.0.0`
|
|
56
|
+
""",
|
|
57
|
+
)
|
|
58
|
+
@click.option(
|
|
59
|
+
"--name",
|
|
60
|
+
"-n",
|
|
61
|
+
envvar=["DOCPUT_VERSION_NAME", "FORGEJO_REF_NAME", "GITHUB_REF_NAME"],
|
|
62
|
+
show_envvar=True,
|
|
63
|
+
help="The name to display for this version in the version menu.",
|
|
64
|
+
)
|
|
65
|
+
def push(
|
|
66
|
+
source: Path,
|
|
67
|
+
remote: Remote,
|
|
68
|
+
ref: str,
|
|
69
|
+
latest: bool,
|
|
70
|
+
visible: bool | None,
|
|
71
|
+
url: str | None,
|
|
72
|
+
name: str | None,
|
|
73
|
+
):
|
|
74
|
+
"""
|
|
75
|
+
Push a documentation version from SOURCE to the remote.
|
|
76
|
+
"""
|
|
77
|
+
config = get_config()
|
|
78
|
+
manifest = remote.manifest
|
|
79
|
+
if (version := manifest.versions.get(ref)) is None:
|
|
80
|
+
# version does not exist in the manifest, so we need to make sure some values get filled in
|
|
81
|
+
if url is None:
|
|
82
|
+
url = re.sub(config.get("url_strip_regex"), "", ref)
|
|
83
|
+
version = VersionRecord(ref, {})
|
|
84
|
+
manifest.versions[ref] = version
|
|
85
|
+
|
|
86
|
+
# remove any old deployed versions. Old href may not equal the new href
|
|
87
|
+
if version.url is not None:
|
|
88
|
+
remote.delete(version.url)
|
|
89
|
+
|
|
90
|
+
# update the version properties
|
|
91
|
+
if name is not None:
|
|
92
|
+
version.name = name
|
|
93
|
+
if url is not None:
|
|
94
|
+
version.url = url
|
|
95
|
+
if visible is not None:
|
|
96
|
+
version.visible = visible
|
|
97
|
+
|
|
98
|
+
# root of the version we are deploying to
|
|
99
|
+
if version.url is None:
|
|
100
|
+
raise ClickException(f"Version {ref} has no href and none was provided")
|
|
101
|
+
version_url = URLPath(version.url)
|
|
102
|
+
assert not version_url.is_absolute(), "version URL must be a relative path"
|
|
103
|
+
|
|
104
|
+
# write local version and symlink in the manifest
|
|
105
|
+
remote.write(source, version_url, overwrite=True)
|
|
106
|
+
remote.make_symlink(
|
|
107
|
+
URLPath("/") / config.get("manifest_url"),
|
|
108
|
+
reroot(URLPath("/", version_url), config.get("manifest_url")),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# if this is the latest, make a symlink from /latest
|
|
112
|
+
if latest:
|
|
113
|
+
manifest.latest = version
|
|
114
|
+
latest_url = URLPath(config.get("latest_url"))
|
|
115
|
+
assert not latest_url.is_absolute(), "latest URL must be a relative path"
|
|
116
|
+
remote.make_symlink(version_url, latest_url)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@version_group.command()
|
|
120
|
+
@remote_option(help="Remote to delete the version from.")
|
|
121
|
+
@ref_option(help="Version reference to delete.")
|
|
122
|
+
@click.option(
|
|
123
|
+
"--keep",
|
|
124
|
+
is_flag=True,
|
|
125
|
+
default=False,
|
|
126
|
+
help="Keep the corresponding documentation, just delete the manifest record.",
|
|
127
|
+
)
|
|
128
|
+
def delete(remote: Remote, ref, keep):
|
|
129
|
+
"""Delete a version from the remote."""
|
|
130
|
+
manifest = remote.manifest
|
|
131
|
+
if (latest := manifest.latest) is not None and latest.ref == ref:
|
|
132
|
+
manifest.latest = None
|
|
133
|
+
|
|
134
|
+
if (version := manifest.versions.pop(ref)) is None:
|
|
135
|
+
raise ClickException(f"Version {ref} does not exist in the manifest")
|
|
136
|
+
|
|
137
|
+
if not keep and version.url is not None:
|
|
138
|
+
remote.delete(version.url)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@version_group.command()
|
|
142
|
+
@remote_option(help="Remote to list versions on.", commit_on_close=False)
|
|
143
|
+
def list_command(remote: Remote):
|
|
144
|
+
"""List all versions in the manifest."""
|
|
145
|
+
for version in remote.manifest.versions.values():
|
|
146
|
+
print(version)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@version_group.command()
|
|
150
|
+
@remote_option()
|
|
151
|
+
@ref_option()
|
|
152
|
+
@click.option(
|
|
153
|
+
"--visible/--hidden",
|
|
154
|
+
"-v/-h",
|
|
155
|
+
help="Show this version in the version menu on the site.",
|
|
156
|
+
)
|
|
157
|
+
@click.option(
|
|
158
|
+
"--url",
|
|
159
|
+
"-u",
|
|
160
|
+
envvar=["DOCPUT_VERSION_URL"],
|
|
161
|
+
show_envvar=True,
|
|
162
|
+
help="URL on the site to write and link to. Must be a relative-path reference.",
|
|
163
|
+
)
|
|
164
|
+
@click.option(
|
|
165
|
+
"--name",
|
|
166
|
+
"-n",
|
|
167
|
+
envvar=["DOCPUT_VERSION_NAME"],
|
|
168
|
+
show_envvar=True,
|
|
169
|
+
help="The name to display for this version in the version menu.",
|
|
170
|
+
)
|
|
171
|
+
def edit(
|
|
172
|
+
remote: Remote, ref: str, visible: bool | None, url: str | None, name: str | None
|
|
173
|
+
):
|
|
174
|
+
"""Edit a version in the manifest."""
|
|
175
|
+
if (version := remote.manifest.versions.get(ref)) is None:
|
|
176
|
+
raise ClickException(f"Version {ref} does not exist in the manifest")
|
|
177
|
+
|
|
178
|
+
if visible is not None:
|
|
179
|
+
version.visible = visible
|
|
180
|
+
if url is not None:
|
|
181
|
+
version.url = url
|
|
182
|
+
if name is not None:
|
|
183
|
+
version.name = name
|