fr-docs 2.1.0__tar.gz → 2.1.2__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.
Files changed (26) hide show
  1. {fr_docs-2.1.0 → fr_docs-2.1.2}/PKG-INFO +1 -1
  2. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/build.py +78 -2
  3. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/html_pipeline.py +13 -0
  4. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/template.py +6 -2
  5. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs.egg-info/PKG-INFO +1 -1
  6. {fr_docs-2.1.0 → fr_docs-2.1.2}/pyproject.toml +1 -1
  7. {fr_docs-2.1.0 → fr_docs-2.1.2}/LICENSE.md +0 -0
  8. {fr_docs-2.1.0 → fr_docs-2.1.2}/README.md +0 -0
  9. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/__init__.py +0 -0
  10. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/__main__.py +0 -0
  11. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/config.py +0 -0
  12. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/config_accessors.py +0 -0
  13. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/frontmatter.py +0 -0
  14. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/git.py +0 -0
  15. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/init.py +0 -0
  16. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/markdown.py +0 -0
  17. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/search.py +0 -0
  18. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/slug.py +0 -0
  19. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/syntax.py +0 -0
  20. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs/utils.py +0 -0
  21. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs.egg-info/SOURCES.txt +0 -0
  22. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs.egg-info/dependency_links.txt +0 -0
  23. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs.egg-info/entry_points.txt +0 -0
  24. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs.egg-info/requires.txt +0 -0
  25. {fr_docs-2.1.0 → fr_docs-2.1.2}/fr_docs.egg-info/top_level.txt +0 -0
  26. {fr_docs-2.1.0 → fr_docs-2.1.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fr-docs
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Summary: Fast static documentation generator with search, versioning, and zero-config setup
5
5
  License: # PolyForm Noncommercial License 1.0.0
6
6
 
@@ -8,8 +8,11 @@ import argparse
8
8
  import concurrent.futures
9
9
  import json
10
10
  import os
11
+ import re
11
12
  import shutil
13
+ import subprocess
12
14
  import sys
15
+ import tempfile
13
16
  from pathlib import Path
14
17
 
15
18
  import xxhash
@@ -42,6 +45,76 @@ def _compute_hash(data: str) -> str:
42
45
  return xxhash.xxh3_128_hexdigest(data.encode("utf-8"))
43
46
 
44
47
 
48
+ def _minify_static_asset(config, src: Path, dst: Path, name: str) -> None:
49
+ """Minify a static asset in-place (JS via terser, CSS via html-minifier).
50
+
51
+ Falls back to a plain copy if the minifier is unavailable. CSS is minified
52
+ by wrapping it in a `<style>` tag (html-minifier-next only processes CSS
53
+ embedded in HTML) and stripping the wrapper afterwards.
54
+ """
55
+ repo_root = Path(config["_docs_dir"]).resolve().parent
56
+ node_modules = repo_root / "node_modules"
57
+
58
+ if name.endswith(".js"):
59
+ terser_bin = node_modules / ".bin" / "terser"
60
+ cmd = [
61
+ str(terser_bin) if terser_bin.exists() else "npx",
62
+ "--yes" if not terser_bin.exists() else "",
63
+ *(["terser"] if not terser_bin.exists() else []),
64
+ str(src),
65
+ "--compress",
66
+ "warnings=false",
67
+ "--mangle",
68
+ "--output",
69
+ str(dst),
70
+ ]
71
+ cmd = [c for c in cmd if c]
72
+ result = subprocess.run(cmd, capture_output=True, text=True, check=False, cwd=str(repo_root))
73
+ if result.returncode != 0 or not dst.exists() or dst.stat().st_size == 0:
74
+ print(f" ! minify failed for {name}, copying raw: {result.stderr[-300:]}")
75
+ shutil.copyfile(src, dst)
76
+ return
77
+
78
+ # CSS: html-minifier-next only minifies CSS embedded in HTML, so wrap the
79
+ # stylesheet in a <style> tag, minify, then strip the wrapper.
80
+ minifier_bin = node_modules / ".bin" / "html-minifier-next"
81
+ raw_css = src.read_text(encoding="utf-8")
82
+ wrapped = f"<style>{raw_css}</style>"
83
+ wrapper = Path(tempfile.mkstemp(suffix=".html")[1])
84
+ min_out = Path(tempfile.mkstemp(suffix=".html")[1])
85
+ try:
86
+ wrapper.write_text(wrapped, encoding="utf-8")
87
+ cmd = [
88
+ str(minifier_bin) if minifier_bin.exists() else "npx",
89
+ "--yes" if not minifier_bin.exists() else "",
90
+ *(["html-minifier-next"] if not minifier_bin.exists() else ""),
91
+ "--minify-css=true",
92
+ "--remove-comments",
93
+ "--output",
94
+ str(min_out),
95
+ str(wrapper),
96
+ ]
97
+ cmd = [c for c in cmd if c]
98
+ result = subprocess.run(cmd, capture_output=True, text=True, check=False, cwd=str(repo_root))
99
+ if result.returncode == 0 and min_out.exists():
100
+ out = min_out.read_text(encoding="utf-8")
101
+ m = re.search(r"<style>([\s\S]*)</style>", out)
102
+ if m:
103
+ dst.write_text(m.group(1), encoding="utf-8")
104
+ return
105
+ print(f" ! minify failed for {name}, copying raw: {result.stderr[-300:]}")
106
+ finally:
107
+ try:
108
+ wrapper.unlink()
109
+ except OSError:
110
+ pass
111
+ try:
112
+ min_out.unlink()
113
+ except OSError:
114
+ pass
115
+ shutil.copyfile(src, dst)
116
+
117
+
45
118
  class BuildCache:
46
119
  """Manage caching of build data to avoid unnecessary regeneration."""
47
120
 
@@ -287,7 +360,7 @@ def main(argv=None):
287
360
  config["_source_files"] = source_files
288
361
  print(f" Found {len(source_files)} source files")
289
362
 
290
- # Copy static assets
363
+ # Copy static assets (minifying JS/CSS when production)
291
364
  docs_path = Path(config["_docs_dir"])
292
365
  os.makedirs(Path(config["_out_dir"]), exist_ok=True)
293
366
  for legacy_name in (
@@ -303,7 +376,10 @@ def main(argv=None):
303
376
  dst = Path(config["_out_dir"]) / name
304
377
 
305
378
  try:
306
- shutil.copyfile(src, dst)
379
+ if config.get("production", False) and name.endswith((".js", ".css")):
380
+ _minify_static_asset(config, src, dst, name)
381
+ else:
382
+ shutil.copyfile(src, dst)
307
383
  except FileNotFoundError:
308
384
  print(f"File not found: {os.getcwd()}, {src}->{dst}")
309
385
  except OSError as e:
@@ -81,6 +81,18 @@ def _render_template_placeholders(config):
81
81
  <input type="text" id="header-search" placeholder="Search docs… (Ctrl+K)" autocomplete="off">
82
82
  <div id="search-results" class="search-results"></div>"""
83
83
 
84
+ def _get_search_preloads_html():
85
+ if not feature_enabled(config, "search"):
86
+ return ""
87
+ sp = output_href("", config)
88
+ return (
89
+ f'<link rel="preload" as="fetch" href="{sp}search_index.zst" crossorigin>\n'
90
+ f' <link rel="preload" as="fetch" href="{sp}symbol_index.zst" crossorigin>\n'
91
+ f' <link rel="preload" as="fetch" href="{sp}file_index.zst" crossorigin>\n'
92
+ f' <link rel="preload" as="fetch" href="{sp}git_meta.zst" crossorigin>\n'
93
+ f' <link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/fzstd@0.1.1/umd/index.min.js">'
94
+ )
95
+
84
96
  return {
85
97
  "site_prefix": output_href("", config),
86
98
  "page_title": "",
@@ -95,6 +107,7 @@ def _render_template_placeholders(config):
95
107
  "subtitle_html": "",
96
108
  "body": "",
97
109
  "search_index_inline": "",
110
+ "search_preloads": _get_search_preloads_html(),
98
111
  "copyright_year": _get_copyright_year(),
99
112
  "copyright_holder": _get_copyright_holder(),
100
113
  }
@@ -17,11 +17,14 @@ TEMPLATE = """\
17
17
  <meta property="og:site_name" content="{project_name} Docs">
18
18
  <meta name="theme-color" content="#6366f1">
19
19
  <meta name="color-scheme" content="dark">
20
+ <link rel="stylesheet" href="{site_prefix}style.css">
20
21
  <link rel="icon" href="{site_prefix}favicon.svg" type="image/svg+xml">
21
22
  <link rel="preconnect" href="https://fonts.googleapis.com">
22
23
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
23
- <link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,400..800&family=JetBrains+Mono:wght@400..700&display=swap" rel="stylesheet">
24
- <link rel="stylesheet" href="{site_prefix}style.css">
24
+ <link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,400..800&family=JetBrains+Mono:wght@400..700&display=optional" rel="stylesheet">
25
+ <link rel="preload" as="font" href="https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmM.woff2" type="font/woff2" crossorigin>
26
+ <link rel="preload" as="font" href="https://fonts.gstatic.com/s/inter/v20/UcCo3FwrK3iLTcviYwYZ8UA3.woff2" type="font/woff2" crossorigin>
27
+ <link rel="preload" as="font" href="https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0P.woff2" type="font/woff2" crossorigin>
25
28
  </head>
26
29
  <body>
27
30
  <!-- Header -->
@@ -72,6 +75,7 @@ TEMPLATE = """\
72
75
  </button>
73
76
 
74
77
  {search_index_inline}
78
+ {search_preloads}
75
79
  <script id="code-refs-data" type="application/json">{code_refs_json}</script>
76
80
  <script id="search-config" type="application/json">{search_config_json}</script>
77
81
  <script src="{site_prefix}script.js" defer></script>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fr-docs
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Summary: Fast static documentation generator with search, versioning, and zero-config setup
5
5
  License: # PolyForm Noncommercial License 1.0.0
6
6
 
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "fr-docs"
9
- version = "2.1.0"
9
+ version = "2.1.2"
10
10
  description = "Fast static documentation generator with search, versioning, and zero-config setup"
11
11
  requires-python = ">=3.14"
12
12
  dependencies = [
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