afterpython 0.4.0__tar.gz → 0.4.1__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 (72) hide show
  1. {afterpython-0.4.0 → afterpython-0.4.1}/PKG-INFO +16 -15
  2. {afterpython-0.4.0 → afterpython-0.4.1}/README.md +1 -1
  3. {afterpython-0.4.0 → afterpython-0.4.1}/pyproject.toml +16 -15
  4. {afterpython-0.4.0 → afterpython-0.4.1}/pyproject.toml.orig +16 -16
  5. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/builders/__init__.py +2 -0
  6. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/builders/build_pdoc.py +7 -0
  7. afterpython-0.4.1/src/afterpython/builders/comments_json.py +107 -0
  8. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/build.py +2 -0
  9. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/init.py +113 -51
  10. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/const.py +1 -1
  11. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/templates/afterpython-template.toml +6 -0
  12. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/templates/ci-workflow-template.yml +10 -10
  13. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/templates/deploy-workflow-template.yml +2 -2
  14. afterpython-0.4.1/src/afterpython/templates/pdoc/custom.css +37 -0
  15. afterpython-0.4.1/src/afterpython/templates/pdoc/frame.html.jinja2 +21 -0
  16. afterpython-0.4.1/src/afterpython/templates/pdoc/syntax-highlighting.css +155 -0
  17. afterpython-0.4.1/src/afterpython/templates/pdoc/theme.css +44 -0
  18. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/templates/release-workflow-template.yml +1 -1
  19. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/templates/ruff-template.toml +1 -1
  20. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/tools/myst.py +36 -19
  21. afterpython-0.4.0/src/afterpython/templates/pdoc/frame.html.jinja2 +0 -7
  22. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/__init__.py +0 -0
  23. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/_io/toml.py +0 -0
  24. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/_io/yaml.py +0 -0
  25. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/_paths.py +0 -0
  26. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/_typing.py +0 -0
  27. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/builders/content_json.py +0 -0
  28. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/builders/faq_json.py +0 -0
  29. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/builders/index_md.py +0 -0
  30. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/builders/jupyter_notebook.py +0 -0
  31. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/builders/llms_txt.py +0 -0
  32. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/builders/marimo_notebook.py +0 -0
  33. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/builders/markdown.py +0 -0
  34. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/builders/metadata.py +0 -0
  35. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/builders/url_md.py +0 -0
  36. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/__init__.py +0 -0
  37. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/add.py +0 -0
  38. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/bump.py +0 -0
  39. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/changelog.py +0 -0
  40. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/check.py +0 -0
  41. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/clean.py +0 -0
  42. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/commit_msg.py +0 -0
  43. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/dev.py +0 -0
  44. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/format.py +0 -0
  45. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/init_branch_rules.py +0 -0
  46. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/install.py +0 -0
  47. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/lint.py +0 -0
  48. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/lock.py +0 -0
  49. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/prek.py +0 -0
  50. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/preview.py +0 -0
  51. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/release.py +0 -0
  52. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/remove.py +0 -0
  53. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/serve.py +0 -0
  54. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/start.py +0 -0
  55. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/sync.py +0 -0
  56. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/commands/update.py +0 -0
  57. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/cli/main.py +0 -0
  58. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/main.py +0 -0
  59. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/pcu.py +0 -0
  60. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/py.typed +0 -0
  61. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/templates/cliff-template.toml +0 -0
  62. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/templates/dependabot-template.yml +0 -0
  63. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/templates/pre-commit-config-template.yaml +0 -0
  64. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/templates/ty-template.toml +0 -0
  65. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/tools/_afterpython.py +0 -0
  66. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/tools/_git.py +0 -0
  67. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/tools/branch_rules.py +0 -0
  68. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/tools/github_actions.py +0 -0
  69. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/tools/prek.py +0 -0
  70. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/tools/pyproject.py +0 -0
  71. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/utils.py +0 -0
  72. {afterpython-0.4.0 → afterpython-0.4.1}/src/afterpython/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: afterpython
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: CLI tool to streamline Python package maintenance and generate a dedicated project website
5
5
  Keywords: package-maintenance,project-website,static-site-generator,developer-tools,documentation,cli-tool,ci/cd,mystmd,semantic-release,python-packaging
6
6
  Author: Stephen Yau
@@ -11,26 +11,27 @@ Classifier: Intended Audience :: Developers
11
11
  Classifier: Topic :: Software Development :: Build Tools
12
12
  Classifier: Topic :: Software Development :: Documentation
13
13
  Classifier: Typing :: Typed
14
- Classifier: Programming Language :: Python :: 3.12
15
14
  Classifier: Programming Language :: Python :: 3.13
16
15
  Classifier: Programming Language :: Python :: 3.14
17
- Requires-Dist: click>=8.5.0
16
+ Classifier: Programming Language :: Python :: 3.15
17
+ Requires-Dist: click>=8.5.1
18
18
  Requires-Dist: trogon>=0.6.0
19
- Requires-Dist: httpx2>=2.13.1
20
- Requires-Dist: tomlkit>=0.15.1
19
+ Requires-Dist: httpx2>=2.16.0
20
+ Requires-Dist: tomlkit>=0.15.2
21
21
  Requires-Dist: ruamel-yaml>=0.19.1
22
- Requires-Dist: gitpython>=3.2.0
23
- Requires-Dist: python-dotenv>=1.2.3
22
+ Requires-Dist: gitpython>=3.2.1
23
+ Requires-Dist: python-dotenv>=1.2.4
24
24
  Requires-Dist: pyproject-metadata>=0.12.1
25
- Requires-Dist: jupytext>=1.19.5
26
- Requires-Dist: mystmd>=1.11.0
25
+ Requires-Dist: jupytext>=1.19.6
26
+ Requires-Dist: mystmd>=1.12.0
27
27
  Requires-Dist: pdoc>=16.0.0
28
- Requires-Dist: ruff>=0.16.9
29
- Requires-Dist: prek>=0.5.4
30
- Requires-Dist: ty>=0.0.84
28
+ Requires-Dist: ruff>=0.17.4
29
+ Requires-Dist: prek>=0.5.10
30
+ Requires-Dist: ty>=0.0.99
31
31
  Requires-Dist: git-cliff>=2.14.2
32
- Requires-Dist: marimo[recommended,lsp]>=0.25.0 ; extra == 'marimo'
33
- Requires-Python: >=3.12
32
+ Requires-Dist: questionary>=2.1.1
33
+ Requires-Dist: marimo[recommended,lsp]>=0.25.2 ; extra == 'marimo'
34
+ Requires-Python: >=3.13
34
35
  Project-URL: homepage, https://afterpython.afterpython.org
35
36
  Project-URL: repository, https://github.com/AfterPythonOrg/afterpython
36
37
  Project-URL: documentation, https://afterpython.afterpython.org/doc
@@ -44,8 +45,8 @@ Description-Content-Type: text/markdown
44
45
  [![PyPI](https://img.shields.io/pypi/v/afterpython.svg?cacheSeconds=300)](https://pypi.org/project/afterpython)
45
46
  ![PyPI - Support Python Versions](https://img.shields.io/pypi/pyversions/afterpython?cacheSeconds=300)
46
47
  [![Discussions](https://img.shields.io/badge/Discussions-Let's%20Chat-green)](https://github.com/AfterPythonOrg/afterpython/discussions)
47
- [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/AfterPythonOrg/afterpython)
48
48
  [![View Code Wiki](https://assets.codewiki.google/readme-badge/static.svg)](https://codewiki.google/github.com/afterpythonorg/afterpython?utm_source=badge&utm_medium=github&utm_campaign=github.com/afterpythonorg/afterpython)
49
+ <!--[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/AfterPythonOrg/afterpython)-->
49
50
  <!-- ![PyPI downloads](https://img.shields.io/pypi/dm/afterpython) -->
50
51
  <!-- [![Pixi Badge](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/prefix-dev/pixi/main/assets/badge/v0.json)](https://pixi.sh) -->
51
52
 
@@ -5,8 +5,8 @@
5
5
  [![PyPI](https://img.shields.io/pypi/v/afterpython.svg?cacheSeconds=300)](https://pypi.org/project/afterpython)
6
6
  ![PyPI - Support Python Versions](https://img.shields.io/pypi/pyversions/afterpython?cacheSeconds=300)
7
7
  [![Discussions](https://img.shields.io/badge/Discussions-Let's%20Chat-green)](https://github.com/AfterPythonOrg/afterpython/discussions)
8
- [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/AfterPythonOrg/afterpython)
9
8
  [![View Code Wiki](https://assets.codewiki.google/readme-badge/static.svg)](https://codewiki.google/github.com/afterpythonorg/afterpython?utm_source=badge&utm_medium=github&utm_campaign=github.com/afterpythonorg/afterpython)
9
+ <!--[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/AfterPythonOrg/afterpython)-->
10
10
  <!-- ![PyPI downloads](https://img.shields.io/pypi/dm/afterpython) -->
11
11
  <!-- [![Pixi Badge](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/prefix-dev/pixi/main/assets/badge/v0.json)](https://pixi.sh) -->
12
12
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "afterpython"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  description = "CLI tool to streamline Python package maintenance and generate a dedicated project website"
5
5
  license = "Apache-2.0"
6
6
  readme = "README.md"
@@ -22,27 +22,28 @@ classifiers = [
22
22
  "Topic :: Software Development :: Build Tools",
23
23
  "Topic :: Software Development :: Documentation",
24
24
  "Typing :: Typed",
25
- "Programming Language :: Python :: 3.12",
26
25
  "Programming Language :: Python :: 3.13",
27
26
  "Programming Language :: Python :: 3.14",
27
+ "Programming Language :: Python :: 3.15",
28
28
  ]
29
- requires-python = ">=3.12"
29
+ requires-python = ">=3.13"
30
30
  dependencies = [
31
- "click>=8.5.0",
31
+ "click>=8.5.1",
32
32
  "trogon>=0.6.0",
33
- "httpx2>=2.13.1",
34
- "tomlkit>=0.15.1",
33
+ "httpx2>=2.16.0",
34
+ "tomlkit>=0.15.2",
35
35
  "ruamel-yaml>=0.19.1",
36
- "gitpython>=3.2.0",
37
- "python-dotenv>=1.2.3",
36
+ "gitpython>=3.2.1",
37
+ "python-dotenv>=1.2.4",
38
38
  "pyproject-metadata>=0.12.1",
39
- "jupytext>=1.19.5",
40
- "mystmd>=1.11.0",
39
+ "jupytext>=1.19.6",
40
+ "mystmd>=1.12.0",
41
41
  "pdoc>=16.0.0",
42
- "ruff>=0.16.9",
43
- "prek>=0.5.4",
44
- "ty>=0.0.84",
42
+ "ruff>=0.17.4",
43
+ "prek>=0.5.10",
44
+ "ty>=0.0.99",
45
45
  "git-cliff>=2.14.2",
46
+ "questionary>=2.1.1",
46
47
  ]
47
48
 
48
49
  [[project.authors]]
@@ -50,7 +51,7 @@ name = "Stephen Yau"
50
51
  email = "softwareentrepreneer+afterpython@gmail.com"
51
52
 
52
53
  [project.optional-dependencies]
53
- marimo = ["marimo[recommended,lsp]>=0.25.0"]
54
+ marimo = ["marimo[recommended,lsp]>=0.25.2"]
54
55
 
55
56
  [project.urls]
56
57
  homepage = "https://afterpython.afterpython.org"
@@ -62,7 +63,7 @@ ap = "afterpython.main:run_cli"
62
63
  pcu = "afterpython.main:run_pcu"
63
64
 
64
65
  [build-system]
65
- requires = ["uv_build>=0.12.21,<0.13.0"]
66
+ requires = ["uv_build>=0.13.7,<0.14.0"]
66
67
  build-backend = "uv_build"
67
68
 
68
69
  [tool.uv.build-backend]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "afterpython"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  description = "CLI tool to streamline Python package maintenance and generate a dedicated project website"
5
5
  license = "Apache-2.0"
6
6
  authors = [
@@ -25,32 +25,32 @@ classifiers = [
25
25
  "Topic :: Software Development :: Build Tools",
26
26
  "Topic :: Software Development :: Documentation",
27
27
  "Typing :: Typed",
28
- "Programming Language :: Python :: 3.12",
29
28
  "Programming Language :: Python :: 3.13",
30
29
  "Programming Language :: Python :: 3.14",
31
- # "Programming Language :: Python :: 3.15",
30
+ "Programming Language :: Python :: 3.15",
32
31
  ]
33
- requires-python = ">=3.12"
32
+ requires-python = ">=3.13"
34
33
  dependencies = [
35
- "click>=8.5.0",
34
+ "click>=8.5.1",
36
35
  "trogon>=0.6.0",
37
- "httpx2>=2.13.1",
38
- "tomlkit>=0.15.1",
36
+ "httpx2>=2.16.0",
37
+ "tomlkit>=0.15.2",
39
38
  "ruamel-yaml>=0.19.1",
40
- "gitpython>=3.2.0",
41
- "python-dotenv>=1.2.3",
39
+ "gitpython>=3.2.1",
40
+ "python-dotenv>=1.2.4",
42
41
  "pyproject-metadata>=0.12.1",
43
- "jupytext>=1.19.5",
44
- "mystmd>=1.11.0",
42
+ "jupytext>=1.19.6",
43
+ "mystmd>=1.12.0",
45
44
  "pdoc>=16.0.0",
46
- "ruff>=0.16.9",
47
- "prek>=0.5.4",
48
- "ty>=0.0.84",
45
+ "ruff>=0.17.4",
46
+ "prek>=0.5.10",
47
+ "ty>=0.0.99",
49
48
  "git-cliff>=2.14.2",
49
+ "questionary>=2.1.1",
50
50
  ]
51
51
 
52
52
  [project.optional-dependencies]
53
- marimo = ["marimo[recommended,lsp]>=0.25.0"]
53
+ marimo = ["marimo[recommended,lsp]>=0.25.2"]
54
54
 
55
55
  [project.urls]
56
56
  homepage = "https://afterpython.afterpython.org"
@@ -62,7 +62,7 @@ ap = "afterpython.main:run_cli"
62
62
  pcu = "afterpython.main:run_pcu"
63
63
 
64
64
  [build-system]
65
- requires = ["uv_build>=0.12.21,<0.13.0"]
65
+ requires = ["uv_build>=0.13.7,<0.14.0"]
66
66
  build-backend = "uv_build"
67
67
 
68
68
  [tool.uv]
@@ -1,4 +1,5 @@
1
1
  from afterpython.builders.build_pdoc import build_pdoc
2
+ from afterpython.builders.comments_json import build_comments_json
2
3
  from afterpython.builders.content_json import build_content_json
3
4
  from afterpython.builders.faq_json import build_faq_json
4
5
  from afterpython.builders.index_md import (
@@ -13,6 +14,7 @@ from afterpython.builders.metadata import build_metadata
13
14
  from afterpython.builders.url_md import build_url_md
14
15
 
15
16
  __all__ = (
17
+ "build_comments_json",
16
18
  "build_content_json",
17
19
  "build_faq_json",
18
20
  "build_jupyter_notebooks",
@@ -19,9 +19,15 @@ def build_pdoc(base_path: str) -> None:
19
19
  project logo appears in the top-left of every API page and links
20
20
  back to the project website root (matches MyST's UX).
21
21
  """
22
+ from afterpython._io.toml import _from_tomlkit
23
+ from afterpython.tools._afterpython import read_afterpython
24
+
22
25
  package_dir = find_package_directory()
23
26
  output_dir = ap.paths.build_path / "api_reference"
24
27
  template_dir = ap.paths.templates_path / "pdoc"
28
+ # e.g. "google"; unset leaves it to pdoc (a module's __docformat__, else restructuredtext)
29
+ website = _from_tomlkit(read_afterpython().get("website", {}))
30
+ docformat = website.get("api_reference_docformat")
25
31
 
26
32
  click.echo("Building api_reference/...")
27
33
 
@@ -45,6 +51,7 @@ def build_pdoc(base_path: str) -> None:
45
51
  f"{base_path}/",
46
52
  "--template-directory",
47
53
  str(template_dir),
54
+ *(["--docformat", str(docformat)] if docformat else []),
48
55
  str(package_dir),
49
56
  ],
50
57
  env=env,
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+
6
+ import click
7
+
8
+ import afterpython as ap
9
+
10
+ GISCUS_API = "https://giscus.app/api/discussions/categories"
11
+ DEFAULT_CATEGORY = "Announcements"
12
+
13
+
14
+ def _read_comments_config() -> dict:
15
+ from afterpython._io.toml import _from_tomlkit
16
+ from afterpython.tools._afterpython import read_afterpython
17
+
18
+ website = _from_tomlkit(read_afterpython().get("website", {}))
19
+ return dict(website.get("comments", {}))
20
+
21
+
22
+ def _get_github_repo() -> str | None:
23
+ """Return "owner/repo" from pyproject.toml's repository URL, or the git remote."""
24
+ from afterpython.tools._git import get_github_url
25
+ from afterpython.tools.pyproject import read_metadata
26
+
27
+ url = str(read_metadata().urls.get("repository", "")) or get_github_url() or ""
28
+ match = re.search(r"github\.com[/:]([^/]+)/([^/]+?)(?:\.git)?/?$", url)
29
+ return f"{match[1]}/{match[2]}" if match else None
30
+
31
+
32
+ def _fetch_giscus_categories(repo: str) -> dict:
33
+ """Fetch the repo and category IDs giscus needs. Raises click.ClickException on failure."""
34
+ import httpx2
35
+
36
+ try:
37
+ response = httpx2.get(GISCUS_API, params={"repo": repo}, timeout=10.0)
38
+ except httpx2.HTTPError as e:
39
+ raise click.ClickException(f"could not reach giscus.app ({e})") from e
40
+ try:
41
+ data = response.json()
42
+ except ValueError:
43
+ data = {}
44
+ if response.status_code != 200 or "repositoryId" not in data:
45
+ error = data.get("error") or f"HTTP {response.status_code}"
46
+ raise click.ClickException(error)
47
+ return data
48
+
49
+
50
+ def build_comments_json():
51
+ """Build comments.json for the giscus comment section on content pages.
52
+
53
+ Reads [website.comments] in afterpython.toml. The website template only
54
+ shows comments when comments.json exists, so a disabled or failed setup
55
+ just leaves it out. Never fails the build.
56
+ """
57
+ stale = ap.paths.website_path / "static" / "comments.json"
58
+ stale.unlink(missing_ok=True)
59
+
60
+ config = _read_comments_config()
61
+ if not config.get("enabled", False):
62
+ click.echo("Comments are disabled, skip building comments.json")
63
+ return
64
+
65
+ def warn(reason: str):
66
+ click.secho(f"Warning: skip comments, {reason}", fg="yellow")
67
+
68
+ repo = _get_github_repo()
69
+ if not repo:
70
+ warn(
71
+ "no GitHub repository found "
72
+ "(set [project.urls] repository in pyproject.toml)"
73
+ )
74
+ return
75
+
76
+ category = str(config.get("category") or DEFAULT_CATEGORY)
77
+ try:
78
+ data = _fetch_giscus_categories(repo)
79
+ except click.ClickException as e:
80
+ warn(
81
+ f"giscus is not ready for {repo}: {e.message}\n"
82
+ f" 1. Enable Discussions: https://github.com/{repo}/settings\n"
83
+ " 2. Install the giscus app: https://github.com/apps/giscus\n"
84
+ " Or set [website.comments] enabled = false in afterpython.toml"
85
+ )
86
+ return
87
+
88
+ category_id = next(
89
+ (c["id"] for c in data.get("categories", []) if c["name"] == category), None
90
+ )
91
+ if not category_id:
92
+ names = ", ".join(c["name"] for c in data.get("categories", []))
93
+ warn(
94
+ f'Discussions category "{category}" not found in {repo} '
95
+ f"(available: {names or 'none'})"
96
+ )
97
+ return
98
+
99
+ click.echo(f"Building comments.json ({repo}, {category})...")
100
+ comments = {
101
+ "repo": repo,
102
+ "repoId": data["repositoryId"],
103
+ "category": category,
104
+ "categoryId": category_id,
105
+ }
106
+ with open(ap.paths.build_path / "comments.json", "w") as f:
107
+ json.dump(comments, f, indent=2)
@@ -17,6 +17,7 @@ from click.exceptions import Exit
17
17
 
18
18
  import afterpython as ap
19
19
  from afterpython.builders import (
20
+ build_comments_json,
20
21
  build_content_json,
21
22
  build_faq_json,
22
23
  build_jupyter_notebooks,
@@ -137,6 +138,7 @@ def prebuild():
137
138
  create_placeholder_index_md_files()
138
139
  build_metadata()
139
140
  build_faq_json()
141
+ build_comments_json()
140
142
  build_marimo_readme(mode=_get_readme_py_mode())
141
143
  build_markdown()
142
144
  build_jupyter_notebooks()
@@ -169,14 +169,62 @@ def init_py_typed():
169
169
  click.echo(f"Created {py_typed_path}")
170
170
 
171
171
 
172
- def _preflight_init(skip_website: bool) -> None:
172
+ def _preflight_init(website: bool) -> None:
173
173
  """Validate external prerequisites before any filesystem writes, so a
174
174
  failure aborts cleanly instead of leaving a half-initialized project."""
175
- if not skip_website:
176
- from afterpython.tools.myst import ensure_pnpm_11
175
+ if website:
176
+ from afterpython.tools.myst import ensure_pnpm_12
177
177
  from afterpython.utils import find_node_env
178
178
 
179
- ensure_pnpm_11(find_node_env())
179
+ ensure_pnpm_12(find_node_env())
180
+
181
+
182
+ def _select_steps(yes: bool, skip_website: bool, ask_commit_types: bool) -> set[str]:
183
+ """Ask which optional setup steps to run, as one checkbox list (all checked by default).
184
+
185
+ Without a terminal (CI, piped input) or with --yes, nothing is asked and the defaults are used.
186
+ """
187
+ import sys
188
+
189
+ import questionary
190
+
191
+ # (step, label, checked by default)
192
+ options = [
193
+ (
194
+ "website",
195
+ "Project website (MyST, template, deploy workflow)",
196
+ not skip_website,
197
+ )
198
+ ]
199
+ # only relevant when afterpython.toml is created, it's ignored otherwise
200
+ if ask_commit_types:
201
+ options.append(
202
+ (
203
+ "commit_types",
204
+ "Commit message check (conventional commit types, e.g. 'feat: add X')",
205
+ True,
206
+ )
207
+ )
208
+ options += [
209
+ ("prek", "Pre-commit hooks (prek)", True),
210
+ ("ruff", "ruff: lint & format", True),
211
+ ("ty", "ty: type checking (`ap check`)", True),
212
+ ("cliff", "git-cliff: changelog", True),
213
+ ("release", "Release workflow (.github/workflows/release.yml)", True),
214
+ ("dependabot", "Dependabot (auto-update GitHub Actions versions)", True),
215
+ ]
216
+
217
+ if yes or not (sys.stdin.isatty() and sys.stdout.isatty()):
218
+ return {step for step, _, checked in options if checked}
219
+
220
+ choices = [
221
+ questionary.Choice(label, value=step, checked=checked)
222
+ for step, label, checked in options
223
+ ]
224
+ selected = questionary.checkbox("Select what to set up", choices=choices).ask()
225
+ if selected is None: # Ctrl-C, before anything was written
226
+ raise click.Abort()
227
+ return set(selected)
180
228
 
181
229
 
182
230
  @click.group(invoke_without_command=True)
@@ -184,7 +232,7 @@ def _preflight_init(skip_website: bool) -> None:
184
232
  "--yes",
185
233
  "-y",
186
234
  is_flag=True,
187
- help="Automatically answer yes to all prompts",
235
+ help="Set up everything without asking",
188
236
  )
189
237
  @click.option(
190
238
  "--skip-website",
@@ -205,32 +253,27 @@ def init(ctx, yes, skip_website: bool):
205
253
  from afterpython.tools.prek import init_prek, sync_hooks
206
254
  from afterpython.tools.pyproject import init_pyproject
207
255
 
208
- _preflight_init(skip_website)
209
-
210
256
  paths = ctx.obj["paths"]
211
- click.echo("Initializing afterpython...")
212
257
  afterpython_path = paths.afterpython_path
213
258
  static_path = paths.static_path
214
259
 
260
+ steps = _select_steps(
261
+ yes,
262
+ skip_website,
263
+ ask_commit_types=not (afterpython_path / "afterpython.toml").exists(),
264
+ )
265
+ _preflight_init(website="website" in steps)
266
+
267
+ click.echo("Initializing afterpython...")
215
268
  try:
216
269
  afterpython_path.mkdir(parents=True, exist_ok=True)
217
270
  static_path.mkdir(parents=True, exist_ok=True)
218
271
 
219
272
  init_pyproject()
220
273
 
221
- # only asked when afterpython.toml is created, it's ignored otherwise
222
- commit_types = (
223
- yes
224
- or (afterpython_path / "afterpython.toml").exists()
225
- or click.confirm(
226
- "\nCheck commit messages against conventional commit types "
227
- "(e.g. 'feat: add X', types listed in afterpython.toml)?",
228
- default=True,
229
- )
230
- )
231
- init_afterpython(commit_types=commit_types)
274
+ init_afterpython(commit_types="commit_types" in steps)
232
275
 
233
- if not skip_website:
276
+ if "website" in steps:
234
277
  # check=True so a failure inside `ap init website` aborts the parent
235
278
  # run instead of silently continuing to write more files.
236
279
  subprocess.run(["ap", "init", "website"], check=True)
@@ -239,42 +282,20 @@ def init(ctx, yes, skip_website: bool):
239
282
 
240
283
  create_workflow("ci")
241
284
 
242
- if yes or click.confirm(
243
- f"\nCreate .pre-commit-config.yaml in {afterpython_path}?", default=True
244
- ):
285
+ if "prek" in steps:
245
286
  init_prek()
246
-
247
- if yes or click.confirm(
248
- f"\nCreate ruff.toml in {afterpython_path} (and point pyproject.toml's [tool.ruff] to it)?",
249
- default=True,
250
- ):
287
+ if "ruff" in steps:
251
288
  init_ruff()
252
-
253
- if yes or click.confirm(
254
- f"\nCreate ty.toml in {afterpython_path} (type checking with `ap check`)?",
255
- default=True,
256
- ):
289
+ if "ty" in steps:
257
290
  init_ty()
258
- # whatever the answers: keep ruff/ty hooks only for the tools that were set up
291
+ # whatever was selected: keep ruff/ty hooks only for the tools that were set up
259
292
  sync_hooks()
260
293
 
261
- if yes or click.confirm(
262
- f"\nCreate cliff.toml in {afterpython_path} (changelog generation with git-cliff)?",
263
- default=True,
264
- ):
294
+ if "cliff" in steps:
265
295
  init_cliff()
266
-
267
- if yes or click.confirm(
268
- "\nCreate release workflow in .github/workflows/release.yml?",
269
- default=True,
270
- ):
296
+ if "release" in steps:
271
297
  create_workflow("release")
272
-
273
- if yes or click.confirm(
274
- "\nCreate Dependabot configuration (.github/dependabot.yml) "
275
- "to auto-update GitHub Actions versions?",
276
- default=True,
277
- ):
298
+ if "dependabot" in steps:
278
299
  create_dependabot()
279
300
  except BaseException:
280
301
  if afterpython_path.exists():
@@ -327,12 +348,12 @@ def init_website_subcommand():
327
348
  the website to an existing AfterPython project.
328
349
  """
329
350
  from afterpython.tools.github_actions import create_workflow
330
- from afterpython.tools.myst import ensure_pnpm_11, init_myst
351
+ from afterpython.tools.myst import ensure_pnpm_12, init_myst
331
352
  from afterpython.utils import find_node_env
332
353
 
333
354
  # Pre-flight: same rationale as `ap init` — guard the standalone entry too,
334
355
  # since this subcommand can be invoked directly via `ap init website`.
335
- ensure_pnpm_11(find_node_env())
356
+ ensure_pnpm_12(find_node_env())
336
357
 
337
358
  init_faq()
338
359
  init_myst()
@@ -342,3 +363,44 @@ def init_website_subcommand():
342
363
  # of a broken website install.
343
364
  subprocess.run(["ap", "update", "website"], check=True)
344
365
  create_workflow("deploy")
366
+
367
+
368
+ @init.command("content")
369
+ @click.option("--all", is_flag=True, help="Initialize all content types")
370
+ @click.option("--doc", is_flag=True, help="Initialize afterpython/doc/")
371
+ @click.option("--blog", is_flag=True, help="Initialize afterpython/blog/")
372
+ @click.option("--tutorial", is_flag=True, help="Initialize afterpython/tutorial/")
373
+ @click.option("--example", is_flag=True, help="Initialize afterpython/example/")
374
+ @click.option("--guide", is_flag=True, help="Initialize afterpython/guide/")
375
+ def init_content_subcommand(
376
+ all: bool, doc: bool, blog: bool, tutorial: bool, example: bool, guide: bool
377
+ ):
378
+ """Initialize content directories (MyST config) without touching the website
379
+
380
+ Content types that already have a myst.yml are skipped.
381
+
382
+ Examples:
383
+ ap init content --blog # afterpython/blog/
384
+ ap init content --blog --tutorial # afterpython/blog/ and afterpython/tutorial/
385
+ ap init content --all # every content type that is missing
386
+ """
387
+ from afterpython._typing import tContentType
388
+ from afterpython.const import CONTENT_TYPES
389
+ from afterpython.tools.myst import init_myst
390
+
391
+ flags: dict[tContentType, bool] = {
392
+ "doc": doc,
393
+ "blog": blog,
394
+ "tutorial": tutorial,
395
+ "example": example,
396
+ "guide": guide,
397
+ }
398
+ if all:
399
+ content_types = CONTENT_TYPES
400
+ else:
401
+ content_types = {content_type for content_type, flag in flags.items() if flag}
402
+ if not content_types:
403
+ raise click.UsageError(
404
+ "Specify one or more of --doc/--blog/--tutorial/--example/--guide, or --all"
405
+ )
406
+ init_myst(content_types)
@@ -1,5 +1,5 @@
1
1
  from afterpython._typing import tContentType
2
2
 
3
- NODEENV_VERSION = "24.11.0"
3
+ NODEENV_VERSION = "24.21.0"
4
4
  CONTENT_TYPES: set[tContentType] = {"doc", "blog", "tutorial", "example", "guide"}
5
5
  PLACEHOLDER_INDEX_MARKER = "<!-- afterpython:placeholder-index -->"
@@ -13,6 +13,12 @@ announcement = ""
13
13
  readme_py = "wasm" # marimo html export format: "wasm" or "static"
14
14
  execute_readme_py = false # execute README.py cells at build time and embed outputs as a preview (wasm mode only)
15
15
  api_reference = false # set to true to enable the pdoc-generated API reference
16
+ # api_reference_docformat = "google" # docstring format for pdoc: "google", "numpy", "restructuredtext" or "markdown"
17
+
18
+ [website.comments] # giscus comment section on blog posts, tutorials, examples and guides
19
+ enabled = true
20
+ # Discussions category for comments. Announcement format means only maintainers can start discussions there; visitors can only reply.
21
+ category = "Announcements"
16
22
 
17
23
  # [website.blog]
18
24
  # thumbnail = "blog_default_thumbnail.png" # inside blog/static/ folder, default thumbnail for blog posts