fr-docs 3.1.0__tar.gz → 3.3.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.
- {fr_docs-3.1.0 → fr_docs-3.3.0}/PKG-INFO +1 -1
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/__main__.py +18 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/build.py +147 -1
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/config.py +1 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/git.py +6 -8
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/html_pipeline.py +2 -2
- fr_docs-3.3.0/fr_docs/update.py +135 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/utils.py +12 -1
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs.egg-info/PKG-INFO +1 -1
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs.egg-info/SOURCES.txt +1 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/pyproject.toml +1 -1
- {fr_docs-3.1.0 → fr_docs-3.3.0}/LICENSE.md +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/README.md +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/__init__.py +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/config_accessors.py +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/frontmatter.py +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/init.py +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/markdown.py +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/search.py +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/slug.py +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/syntax.py +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs/template.py +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs.egg-info/dependency_links.txt +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs.egg-info/entry_points.txt +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs.egg-info/requires.txt +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/fr_docs.egg-info/top_level.txt +0 -0
- {fr_docs-3.1.0 → fr_docs-3.3.0}/setup.cfg +0 -0
|
@@ -12,6 +12,7 @@ import sys
|
|
|
12
12
|
|
|
13
13
|
from .build import main as build_main
|
|
14
14
|
from .init import main as init_main
|
|
15
|
+
from .update import main as update_main
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def main() -> None:
|
|
@@ -69,6 +70,16 @@ def main() -> None:
|
|
|
69
70
|
help="Initialize only the configuration or download fonts.",
|
|
70
71
|
)
|
|
71
72
|
|
|
73
|
+
# Update command
|
|
74
|
+
update_parser = subparsers.add_parser(
|
|
75
|
+
"update",
|
|
76
|
+
help="Update fr-docs resources (script.js, style.css, fonts) from the repo.",
|
|
77
|
+
)
|
|
78
|
+
update_parser.add_argument(
|
|
79
|
+
"--config",
|
|
80
|
+
help="Path to the configuration JSON file.",
|
|
81
|
+
)
|
|
82
|
+
|
|
72
83
|
args = parser.parse_args(sys.argv[1:])
|
|
73
84
|
|
|
74
85
|
if args.command == "build":
|
|
@@ -95,6 +106,13 @@ def main() -> None:
|
|
|
95
106
|
init_main(args.dir, args.target)
|
|
96
107
|
return
|
|
97
108
|
|
|
109
|
+
if args.command == "update":
|
|
110
|
+
update_argv: list[str] = []
|
|
111
|
+
if args.config:
|
|
112
|
+
update_argv.extend(["--config", args.config])
|
|
113
|
+
update_main(update_argv)
|
|
114
|
+
return
|
|
115
|
+
|
|
98
116
|
# Default to build for backwards compatibility.
|
|
99
117
|
if len(sys.argv) > 1 and sys.argv[1] in (
|
|
100
118
|
"--production",
|
|
@@ -68,6 +68,126 @@ def _best_zstd_level(raw: bytes, configured: int) -> int:
|
|
|
68
68
|
return best_level
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
def _script_version_hash(script_path):
|
|
72
|
+
"""Return (version, sha1_hex) for a script.js file.
|
|
73
|
+
|
|
74
|
+
Looks for a ``// version: <n>`` comment on the first line. If no version
|
|
75
|
+
is found, returns (0, sha1_of_content).
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
text = script_path.read_text(encoding="utf-8")
|
|
79
|
+
except OSError:
|
|
80
|
+
return 0, ""
|
|
81
|
+
first_line = text.split("\n", 1)[0].strip()
|
|
82
|
+
m = re.match(r"//\s*version:\s*(\d+)", first_line)
|
|
83
|
+
version = int(m.group(1)) if m else 0
|
|
84
|
+
return version, xxhash.xxh3_128_hexdigest(text.encode("utf-8"))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _git_script_versions(repo_root, script_rel_path):
|
|
88
|
+
"""Return a list of (version, sha1) for every historical version of the
|
|
89
|
+
script found in the git tree.
|
|
90
|
+
|
|
91
|
+
The script's own ``// version: <n>`` comment is the source of truth;
|
|
92
|
+
if a blob in git history has no version comment, its sha1 is still
|
|
93
|
+
recorded so we can detect whether the local file matches a known
|
|
94
|
+
historical version.
|
|
95
|
+
"""
|
|
96
|
+
versions = []
|
|
97
|
+
seen_blobs = set()
|
|
98
|
+
try:
|
|
99
|
+
out = subprocess.check_output(
|
|
100
|
+
["git", "log", "--all", "--pretty=format:%H", "--", script_rel_path],
|
|
101
|
+
cwd=repo_root,
|
|
102
|
+
text=True,
|
|
103
|
+
stderr=subprocess.DEVNULL,
|
|
104
|
+
).strip()
|
|
105
|
+
except (subprocess.CalledProcessError, OSError):
|
|
106
|
+
return versions
|
|
107
|
+
|
|
108
|
+
for commit in out.splitlines():
|
|
109
|
+
commit = commit.strip()
|
|
110
|
+
if not commit:
|
|
111
|
+
continue
|
|
112
|
+
try:
|
|
113
|
+
blob = subprocess.check_output(
|
|
114
|
+
["git", "rev-parse", f"{commit}:{script_rel_path}"],
|
|
115
|
+
cwd=repo_root,
|
|
116
|
+
text=True,
|
|
117
|
+
stderr=subprocess.DEVNULL,
|
|
118
|
+
).strip()
|
|
119
|
+
except (subprocess.CalledProcessError, OSError):
|
|
120
|
+
continue
|
|
121
|
+
if not blob or blob in seen_blobs:
|
|
122
|
+
continue
|
|
123
|
+
seen_blobs.add(blob)
|
|
124
|
+
try:
|
|
125
|
+
content = subprocess.check_output(
|
|
126
|
+
["git", "show", blob],
|
|
127
|
+
cwd=repo_root,
|
|
128
|
+
stderr=subprocess.DEVNULL,
|
|
129
|
+
).decode("utf-8", errors="replace")
|
|
130
|
+
except (subprocess.CalledProcessError, OSError):
|
|
131
|
+
continue
|
|
132
|
+
first_line = content.split("\n", 1)[0].strip()
|
|
133
|
+
m = re.match(r"//\s*version:\s*(\d+)", first_line)
|
|
134
|
+
ver = int(m.group(1)) if m else 0
|
|
135
|
+
h = xxhash.xxh3_128_hexdigest(content.encode("utf-8"))
|
|
136
|
+
versions.append((ver, h))
|
|
137
|
+
|
|
138
|
+
return versions
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _resolve_script_version(repo_root, script_rel_path, local_path):
|
|
142
|
+
"""Resolve the version and content to use for script.js.
|
|
143
|
+
|
|
144
|
+
Returns (version, content_text) where content_text is the script to write
|
|
145
|
+
to the output directory. If the local script has no version comment but
|
|
146
|
+
its hash matches a known historical version, the historical version
|
|
147
|
+
number is used. If the remote (repo HEAD) has a higher version, the
|
|
148
|
+
remote content is used instead.
|
|
149
|
+
"""
|
|
150
|
+
local_version, local_hash = _script_version_hash(local_path)
|
|
151
|
+
remote_version, remote_hash = _script_version_hash(
|
|
152
|
+
Path(repo_root, script_rel_path)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# If local has no version but matches a known historical blob, adopt
|
|
156
|
+
# that version number so future comparisons work.
|
|
157
|
+
if local_version == 0:
|
|
158
|
+
hist = _git_script_versions(repo_root, script_rel_path)
|
|
159
|
+
for ver, h in hist:
|
|
160
|
+
if h == local_hash and ver > 0:
|
|
161
|
+
local_version = ver
|
|
162
|
+
break
|
|
163
|
+
|
|
164
|
+
# If remote is newer, use remote content.
|
|
165
|
+
if remote_version > local_version:
|
|
166
|
+
try:
|
|
167
|
+
content = Path(repo_root, script_rel_path).read_text(encoding="utf-8")
|
|
168
|
+
return remote_version, content
|
|
169
|
+
except OSError:
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
# Otherwise use local content.
|
|
173
|
+
try:
|
|
174
|
+
content = local_path.read_text(encoding="utf-8")
|
|
175
|
+
except OSError:
|
|
176
|
+
content = ""
|
|
177
|
+
return local_version, content
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _write_script_js(out_path, version, content):
|
|
181
|
+
"""Write script.js with a ``// version: <n>`` first line."""
|
|
182
|
+
if not content:
|
|
183
|
+
return
|
|
184
|
+
first_line = content.split("\n", 1)[0].strip()
|
|
185
|
+
if re.match(r"//\s*version:\s*\d+", first_line):
|
|
186
|
+
out_path.write_text(content, encoding="utf-8")
|
|
187
|
+
else:
|
|
188
|
+
out_path.write_text(f"// version: {version}\n{content}", encoding="utf-8")
|
|
189
|
+
|
|
190
|
+
|
|
71
191
|
def _minify_static_asset(config, src: Path, dst: Path, name: str) -> None:
|
|
72
192
|
"""Minify a static asset in-place (JS via terser, CSS via html-minifier).
|
|
73
193
|
|
|
@@ -380,6 +500,12 @@ def main(argv=None) -> None:
|
|
|
380
500
|
print(f" Site prefix: {normalized_site_prefix(config)}")
|
|
381
501
|
print()
|
|
382
502
|
|
|
503
|
+
# Auto-update resources if enabled
|
|
504
|
+
if config.get("build", {}).get("auto_update", True):
|
|
505
|
+
from .update import update_resources
|
|
506
|
+
update_resources(config)
|
|
507
|
+
print()
|
|
508
|
+
|
|
383
509
|
# Collect source files for code references
|
|
384
510
|
search_includes = search_include_config(config)
|
|
385
511
|
if feature_enabled(config, "code_references") or (
|
|
@@ -402,7 +528,7 @@ def main(argv=None) -> None:
|
|
|
402
528
|
"symbol_index.json",
|
|
403
529
|
):
|
|
404
530
|
Path(config["_out_dir"], legacy_name).unlink(missing_ok=True)
|
|
405
|
-
for name in ("favicon.svg", "fonts.css", "
|
|
531
|
+
for name in ("favicon.svg", "fonts.css", "style.css"):
|
|
406
532
|
src = docs_path / name
|
|
407
533
|
dst = Path(config["_out_dir"]) / name
|
|
408
534
|
|
|
@@ -416,6 +542,26 @@ def main(argv=None) -> None:
|
|
|
416
542
|
except OSError as e:
|
|
417
543
|
print(f"OSError: {e}")
|
|
418
544
|
|
|
545
|
+
# script.js: check version comment and update from repo if needed
|
|
546
|
+
repo_root = Path(config["_docs_dir"]).resolve().parent
|
|
547
|
+
script_rel = str(Path(config["_docs_dir"]).resolve().relative_to(repo_root)) + "/script.js"
|
|
548
|
+
local_script = docs_path / "script.js"
|
|
549
|
+
out_script = Path(config["_out_dir"]) / "script.js"
|
|
550
|
+
|
|
551
|
+
if local_script.exists():
|
|
552
|
+
version, content = _resolve_script_version(repo_root, script_rel, local_script)
|
|
553
|
+
if config.get("production", False):
|
|
554
|
+
# Minify the resolved content
|
|
555
|
+
_minify_static_asset(config, local_script, out_script, "script.js")
|
|
556
|
+
# Re-write version header on minified output
|
|
557
|
+
minified = out_script.read_text(encoding="utf-8")
|
|
558
|
+
first_line = minified.split("\n", 1)[0].strip()
|
|
559
|
+
if not re.match(r"//\s*version:\s*\d+", first_line):
|
|
560
|
+
out_script.write_text(f"// version: {version}\n{minified}", encoding="utf-8")
|
|
561
|
+
else:
|
|
562
|
+
_write_script_js(out_script, version, content)
|
|
563
|
+
print(f" script.js: version {version}")
|
|
564
|
+
|
|
419
565
|
# Copy locally-fetched fonts (if present) so the site doesn't need
|
|
420
566
|
# to reach out to fonts.gstatic.com. fonts.css is minified along
|
|
421
567
|
# with style.css below.
|
|
@@ -86,9 +86,8 @@ def _build_slug_last_commits(slugs, config, repo_root):
|
|
|
86
86
|
out = subprocess.check_output(
|
|
87
87
|
["git", "log", "-1", "--pretty=format:%H", "--", src],
|
|
88
88
|
cwd=repo_root,
|
|
89
|
-
text=True,
|
|
90
89
|
stderr=subprocess.DEVNULL,
|
|
91
|
-
).strip()
|
|
90
|
+
).decode("utf-8", errors="replace").strip()
|
|
92
91
|
|
|
93
92
|
if out and re.fullmatch(r"[0-9a-f]{7,40}", out):
|
|
94
93
|
slug_last_commit[slug] = out
|
|
@@ -114,8 +113,8 @@ def collect_git_metadata(config):
|
|
|
114
113
|
remote_url = subprocess.check_output(
|
|
115
114
|
["git", "remote", "get-url", "origin"],
|
|
116
115
|
cwd=repo_root,
|
|
117
|
-
|
|
118
|
-
).strip()
|
|
116
|
+
stderr=subprocess.DEVNULL,
|
|
117
|
+
).decode("utf-8", errors="replace").strip()
|
|
119
118
|
|
|
120
119
|
if m := re.search(r"github.com[:/](.+?)(?:\.git)?$", remote_url):
|
|
121
120
|
git_meta["repo"] = m[1]
|
|
@@ -123,8 +122,8 @@ def collect_git_metadata(config):
|
|
|
123
122
|
log_out = subprocess.check_output(
|
|
124
123
|
["git", "log", "--pretty=format:%H\x01%s", "--reverse"],
|
|
125
124
|
cwd=repo_root,
|
|
126
|
-
|
|
127
|
-
)
|
|
125
|
+
stderr=subprocess.DEVNULL,
|
|
126
|
+
).decode("utf-8", errors="replace")
|
|
128
127
|
|
|
129
128
|
commits, versions = _parse_commit_log(log_out)
|
|
130
129
|
git_meta["commits"] = commits
|
|
@@ -152,9 +151,8 @@ def collect_git_metadata(config):
|
|
|
152
151
|
]
|
|
153
152
|
+ [src_map_path(config).replace("{slug}", s) for s in slugs],
|
|
154
153
|
cwd=repo_root,
|
|
155
|
-
text=True,
|
|
156
154
|
stderr=subprocess.DEVNULL,
|
|
157
|
-
)
|
|
155
|
+
).decode("utf-8", errors="replace")
|
|
158
156
|
|
|
159
157
|
git_meta["pages_by_commit"] = _build_pages_by_commit(out, slugs)
|
|
160
158
|
git_meta["slug_last_commit"] = _build_slug_last_commits(slugs, config, repo_root)
|
|
@@ -43,7 +43,7 @@ from .syntax import (
|
|
|
43
43
|
process_blockquotes,
|
|
44
44
|
)
|
|
45
45
|
from .template import TEMPLATE, build_toc_sidebar
|
|
46
|
-
from .utils import normalized_site_prefix, output_href
|
|
46
|
+
from .utils import normalized_site_prefix, output_href, output_asset_href
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
def _determine_tagged_sections(config):
|
|
@@ -128,7 +128,7 @@ def _get_search_preloads_html():
|
|
|
128
128
|
def _render_template_placeholders(config):
|
|
129
129
|
"""Extract common template placeholders from config."""
|
|
130
130
|
return {
|
|
131
|
-
"site_prefix":
|
|
131
|
+
"site_prefix": output_asset_href("", config),
|
|
132
132
|
"page_title": "",
|
|
133
133
|
"project_name": project_name(config),
|
|
134
134
|
"og_title": "",
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Update fr-docs resources (script.js, style.css, fonts) from the repo."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .config import load_config
|
|
10
|
+
from .config_accessors import out_dir, project_name, src_dir as config_src_dir
|
|
11
|
+
from .build import (
|
|
12
|
+
_resolve_script_version,
|
|
13
|
+
_write_script_js,
|
|
14
|
+
_minify_static_asset,
|
|
15
|
+
_script_version_hash,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _fetch_remote(repo_root, rel_path):
|
|
20
|
+
"""Fetch a file from the fr-docs repo via git show HEAD:path."""
|
|
21
|
+
try:
|
|
22
|
+
return subprocess.check_output(
|
|
23
|
+
["git", "show", f"HEAD:{rel_path}"],
|
|
24
|
+
cwd=repo_root,
|
|
25
|
+
stderr=subprocess.DEVNULL,
|
|
26
|
+
).decode("utf-8", errors="replace")
|
|
27
|
+
except (subprocess.CalledProcessError, OSError):
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _git_current_commit(repo_root):
|
|
32
|
+
try:
|
|
33
|
+
return subprocess.check_output(
|
|
34
|
+
["git", "rev-parse", "HEAD"],
|
|
35
|
+
cwd=repo_root,
|
|
36
|
+
stderr=subprocess.DEVNULL,
|
|
37
|
+
).decode("utf-8", errors="replace").strip()
|
|
38
|
+
except (subprocess.CalledProcessError, OSError):
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _git_remote_url(repo_root):
|
|
43
|
+
try:
|
|
44
|
+
return subprocess.check_output(
|
|
45
|
+
["git", "remote", "get-url", "origin"],
|
|
46
|
+
cwd=repo_root,
|
|
47
|
+
stderr=subprocess.DEVNULL,
|
|
48
|
+
).decode("utf-8", errors="replace").strip()
|
|
49
|
+
except (subprocess.CalledProcessError, OSError):
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _is_fr_docs_repo(repo_root):
|
|
54
|
+
"""Return True if the repo is the fr-docs repo itself."""
|
|
55
|
+
url = _git_remote_url(repo_root) or ""
|
|
56
|
+
return "Omena0/fr-docs" in url or "omena0/fr-docs" in url.lower()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def update_resources(config):
|
|
60
|
+
"""Update script.js, style.css, favicon.svg, and fonts from the fr-docs repo.
|
|
61
|
+
|
|
62
|
+
Only runs when the current repo IS the fr-docs repo (so we can read
|
|
63
|
+
the source files from git). For downstream repos, the resources are
|
|
64
|
+
pulled from PyPI via the `fr-docs` package or copied from the docs/
|
|
65
|
+
directory.
|
|
66
|
+
"""
|
|
67
|
+
docs_path = Path(config["_docs_dir"]).resolve()
|
|
68
|
+
repo_root = docs_path.parent
|
|
69
|
+
if not _is_fr_docs_repo(repo_root):
|
|
70
|
+
print(" Not the fr-docs repo; skipping remote resource update.")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
resources = ["script.js", "style.css", "favicon.svg"]
|
|
74
|
+
updated = []
|
|
75
|
+
for name in resources:
|
|
76
|
+
rel = f"docs/{name}"
|
|
77
|
+
remote = _fetch_remote(repo_root, rel)
|
|
78
|
+
if remote is None:
|
|
79
|
+
continue
|
|
80
|
+
local_path = docs_path / name
|
|
81
|
+
if name == "script.js":
|
|
82
|
+
version, content = _resolve_script_version(repo_root, rel, local_path)
|
|
83
|
+
_write_script_js(local_path, version, content)
|
|
84
|
+
updated.append(f"{name} (v{version})")
|
|
85
|
+
else:
|
|
86
|
+
local_path.write_text(remote, encoding="utf-8")
|
|
87
|
+
updated.append(name)
|
|
88
|
+
|
|
89
|
+
# Update fonts
|
|
90
|
+
fonts_dir = docs_path / "fonts"
|
|
91
|
+
if fonts_dir.is_dir():
|
|
92
|
+
remote_fonts = _fetch_remote(repo_root, "docs/fonts")
|
|
93
|
+
# Can't fetch a directory; instead list remote files via git ls-tree
|
|
94
|
+
try:
|
|
95
|
+
out = subprocess.check_output(
|
|
96
|
+
["git", "ls-tree", "-r", "--name-only", "HEAD", "docs/fonts"],
|
|
97
|
+
cwd=repo_root,
|
|
98
|
+
stderr=subprocess.DEVNULL,
|
|
99
|
+
).decode("utf-8", errors="replace")
|
|
100
|
+
for line in out.splitlines():
|
|
101
|
+
fname = Path(line).name
|
|
102
|
+
content = _fetch_remote(repo_root, line)
|
|
103
|
+
if content is not None:
|
|
104
|
+
(fonts_dir / fname).write_text(content, encoding="utf-8")
|
|
105
|
+
updated.append(f"fonts/{fname}")
|
|
106
|
+
except (subprocess.CalledProcessError, OSError):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
if updated:
|
|
110
|
+
print(f" Updated: {', '.join(updated)}")
|
|
111
|
+
else:
|
|
112
|
+
print(" No resources updated.")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def main(argv=None) -> None:
|
|
116
|
+
import argparse
|
|
117
|
+
|
|
118
|
+
parser = argparse.ArgumentParser(
|
|
119
|
+
prog="fr-docs update",
|
|
120
|
+
description="Update fr-docs resources (script.js, style.css, fonts) from the repo.",
|
|
121
|
+
)
|
|
122
|
+
parser.add_argument(
|
|
123
|
+
"--config",
|
|
124
|
+
help="Path to the configuration JSON file.",
|
|
125
|
+
)
|
|
126
|
+
args = parser.parse_args(argv)
|
|
127
|
+
|
|
128
|
+
config = load_config(args.config)
|
|
129
|
+
print(f"🔄 Updating {project_name(config)} resources...")
|
|
130
|
+
update_resources(config)
|
|
131
|
+
print("✅ Update complete.")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
if __name__ == "__main__":
|
|
135
|
+
main()
|
|
@@ -21,4 +21,15 @@ def output_href(path, config):
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def output_site_prefix(config: dict) -> str:
|
|
24
|
-
return normalized_site_prefix(config) if config.get("production", False) else ""
|
|
24
|
+
return normalized_site_prefix(config) if config.get("production", False) else "/"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def output_asset_href(path, config):
|
|
28
|
+
"""Return a root-relative href for a static asset.
|
|
29
|
+
|
|
30
|
+
Static assets (CSS, JS, favicon, fonts) must resolve from any page
|
|
31
|
+
depth, so they always use a root-relative path (e.g. ``/style.css``).
|
|
32
|
+
"""
|
|
33
|
+
clean = str(path or "").lstrip("/")
|
|
34
|
+
prefix = normalized_site_prefix(config)
|
|
35
|
+
return prefix + clean if prefix else "/" + clean
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "fr-docs"
|
|
9
|
-
version = "3.
|
|
9
|
+
version = "3.3.0"
|
|
10
10
|
description = "Fast static documentation generator with search, versioning, and zero-config setup"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.14"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|