afterpython 0.1.0__py3-none-any.whl → 0.1.0.dev5__py3-none-any.whl
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.
- afterpython/{templates/pre-commit-config-template.yaml → .pre-commit-config.yaml} +1 -1
- afterpython/_io/toml.py +3 -5
- afterpython/_paths.py +1 -1
- afterpython/_typing.py +2 -1
- afterpython/builders/__init__.py +3 -12
- afterpython/builders/metadata.py +3 -7
- afterpython/builders/molab.py +99 -0
- afterpython/cli/__init__.py +1 -0
- afterpython/cli/commands/build.py +66 -136
- afterpython/cli/commands/bump.py +0 -30
- afterpython/cli/commands/check.py +1 -1
- afterpython/cli/commands/clean.py +3 -4
- afterpython/cli/commands/commit.py +3 -21
- afterpython/cli/commands/commitizen.py +1 -1
- afterpython/cli/commands/dev.py +13 -80
- afterpython/cli/commands/format.py +1 -1
- afterpython/cli/commands/init.py +39 -48
- afterpython/cli/commands/install.py +2 -8
- afterpython/cli/commands/pre_commit.py +1 -1
- afterpython/cli/commands/preview.py +1 -1
- afterpython/cli/commands/start.py +4 -8
- afterpython/cli/commands/sync.py +21 -65
- afterpython/cli/commands/update.py +8 -31
- afterpython/cli/main.py +14 -75
- afterpython/const.py +2 -1
- afterpython/cz.toml +27 -0
- afterpython/pcu.py +1 -3
- afterpython/templates/deploy-workflow-template.yml +7 -5
- afterpython/templates/release-workflow-template.yml +3 -3
- afterpython/templates/ruff-template.toml +1 -1
- afterpython/tools/_afterpython.py +10 -73
- afterpython/tools/_git.py +2 -28
- afterpython/tools/commitizen.py +45 -5
- afterpython/tools/github_actions.py +53 -23
- afterpython/tools/myst.py +38 -82
- afterpython/tools/pre_commit.py +31 -6
- afterpython/tools/pyproject.py +2 -40
- afterpython/utils.py +3 -87
- {afterpython-0.1.0.dist-info → afterpython-0.1.0.dev5.dist-info}/METADATA +13 -39
- afterpython-0.1.0.dev5.dist-info/RECORD +47 -0
- {afterpython-0.1.0.dist-info → afterpython-0.1.0.dev5.dist-info}/WHEEL +1 -1
- afterpython/builders/content_json.py +0 -131
- afterpython/builders/index_md.py +0 -94
- afterpython/builders/jupyter_notebook.py +0 -293
- afterpython/builders/markdown.py +0 -271
- afterpython/cli/commands/add.py +0 -67
- afterpython/cli/commands/init_branch_rules.py +0 -9
- afterpython/cli/commands/lock.py +0 -23
- afterpython/cli/commands/remove.py +0 -67
- afterpython/templates/ci-workflow-template.yml +0 -197
- afterpython/templates/cz-template.toml +0 -7
- afterpython/templates/dependabot-template.yml +0 -30
- afterpython/tools/branch_rules.py +0 -106
- afterpython-0.1.0.dist-info/RECORD +0 -57
- {afterpython-0.1.0.dist-info → afterpython-0.1.0.dev5.dist-info}/entry_points.txt +0 -0
afterpython/tools/myst.py
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
2
|
from typing import TYPE_CHECKING
|
|
4
3
|
|
|
5
4
|
if TYPE_CHECKING:
|
|
6
5
|
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
from afterpython._typing import NodeEnv, tContentType
|
|
6
|
+
from afterpython._typing import tContentType, NodeEnv
|
|
9
7
|
|
|
10
8
|
import subprocess
|
|
11
9
|
|
|
12
10
|
import afterpython as ap
|
|
13
|
-
from afterpython._io.yaml import read_yaml, write_yaml
|
|
14
11
|
from afterpython.utils import deep_merge
|
|
12
|
+
from afterpython._io.yaml import write_yaml, read_yaml
|
|
15
13
|
|
|
16
14
|
|
|
17
15
|
def update_authors_yml(data_update: dict):
|
|
@@ -50,7 +48,7 @@ def update_authors_yml(data_update: dict):
|
|
|
50
48
|
write_yaml(file_path, existing_data)
|
|
51
49
|
|
|
52
50
|
|
|
53
|
-
def update_myst_yml(data_update: dict, path: Path
|
|
51
|
+
def update_myst_yml(data_update: dict, path: Path):
|
|
54
52
|
"""Update myst.yml while preserving order and formatting
|
|
55
53
|
|
|
56
54
|
Args:
|
|
@@ -66,83 +64,38 @@ def update_myst_yml(data_update: dict, path: Path, add_comments: bool = False):
|
|
|
66
64
|
)
|
|
67
65
|
|
|
68
66
|
existing_data = read_yaml(file_path) or {}
|
|
69
|
-
existing_data = deep_merge(existing_data, data_update
|
|
70
|
-
|
|
71
|
-
# NOTE: this checking existing_data["key1"].ca.items.get("key2") is buggy
|
|
72
|
-
# only add_comments=True during init to avoid adding duplicate comments
|
|
73
|
-
if add_comments:
|
|
74
|
-
# set comments for project section for convenience
|
|
75
|
-
if "project" in existing_data:
|
|
76
|
-
if not existing_data["project"].ca.items.get("id"):
|
|
77
|
-
existing_data["project"].yaml_set_comment_before_after_key(
|
|
78
|
-
"id",
|
|
79
|
-
before="See how to create Table of Contents at: https://mystmd.org/guide/table-of-contents",
|
|
80
|
-
)
|
|
81
|
-
if not existing_data["project"].ca.items.get("authors"):
|
|
82
|
-
existing_data["project"].yaml_set_comment_before_after_key(
|
|
83
|
-
"authors",
|
|
84
|
-
before="See more authors' fields at: https://mystmd.org/guide/frontmatter#frontmatter-authors",
|
|
85
|
-
)
|
|
86
|
-
if not existing_data["project"].ca.items.get("venue"):
|
|
87
|
-
existing_data["project"].yaml_set_comment_before_after_key(
|
|
88
|
-
"venue",
|
|
89
|
-
before="See more venue's fields at: https://mystmd.org/guide/frontmatter#venue",
|
|
90
|
-
)
|
|
91
|
-
if "site" in existing_data:
|
|
92
|
-
if not existing_data["site"].ca.items.get("options"):
|
|
93
|
-
existing_data["site"].yaml_set_comment_before_after_key(
|
|
94
|
-
"options",
|
|
95
|
-
before="See options at: https://mystmd.org/guide/website-templates#site-options",
|
|
96
|
-
)
|
|
97
|
-
if not existing_data["site"].ca.items.get("actions"):
|
|
98
|
-
existing_data["site"].yaml_set_comment_before_after_key(
|
|
99
|
-
"actions",
|
|
100
|
-
before="See web layout at: https://mystmd.org/guide/website-navigation",
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
write_yaml(file_path, existing_data)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def _write_index_file(content_type: tContentType):
|
|
107
|
-
"""Create a placeholder index.md file for MyST to prevent first file from becoming index.
|
|
108
|
-
|
|
109
|
-
MyST treats the first file in TOC as index. This placeholder ensures all actual
|
|
110
|
-
content files get proper slugs. The generated index.html is deleted post-build
|
|
111
|
-
since SvelteKit owns the landing page route.
|
|
67
|
+
existing_data = deep_merge(existing_data, data_update)
|
|
112
68
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
69
|
+
# set comments for project section for convenience
|
|
70
|
+
if "project" in existing_data:
|
|
71
|
+
if not existing_data["project"].ca.items.get("id"):
|
|
72
|
+
existing_data["project"].yaml_set_comment_before_after_key(
|
|
73
|
+
"id",
|
|
74
|
+
before="See how to create Table of Contents at: https://mystmd.org/guide/table-of-contents",
|
|
75
|
+
)
|
|
76
|
+
if not existing_data["project"].ca.items.get("authors"):
|
|
77
|
+
existing_data["project"].yaml_set_comment_before_after_key(
|
|
78
|
+
"authors",
|
|
79
|
+
before="See more authors' fields at: https://mystmd.org/guide/frontmatter#frontmatter-authors",
|
|
80
|
+
)
|
|
81
|
+
if not existing_data["project"].ca.items.get("venue"):
|
|
82
|
+
existing_data["project"].yaml_set_comment_before_after_key(
|
|
83
|
+
"venue",
|
|
84
|
+
before="See more venue's fields at: https://mystmd.org/guide/frontmatter#venue",
|
|
85
|
+
)
|
|
86
|
+
if "site" in existing_data:
|
|
87
|
+
if not existing_data["site"].ca.items.get("options"):
|
|
88
|
+
existing_data["site"].yaml_set_comment_before_after_key(
|
|
89
|
+
"options",
|
|
90
|
+
before="See options at: https://mystmd.org/guide/website-templates#site-options",
|
|
91
|
+
)
|
|
92
|
+
if not existing_data["site"].ca.items.get("actions"):
|
|
93
|
+
existing_data["site"].yaml_set_comment_before_after_key(
|
|
94
|
+
"actions",
|
|
95
|
+
before="See web layout at: https://mystmd.org/guide/website-navigation",
|
|
96
|
+
)
|
|
141
97
|
|
|
142
|
-
|
|
143
|
-
"""
|
|
144
|
-
index_file.write_text(index_content)
|
|
145
|
-
return index_file
|
|
98
|
+
write_yaml(file_path, existing_data)
|
|
146
99
|
|
|
147
100
|
|
|
148
101
|
def _write_welcome_file(content_type: tContentType):
|
|
@@ -163,7 +116,7 @@ Replace this placeholder content with your own. Here's what you can do:
|
|
|
163
116
|
|
|
164
117
|
## Resources
|
|
165
118
|
|
|
166
|
-
- [AfterPython's Project Website](https://
|
|
119
|
+
- [AfterPython's Project Website](https://ap.afterpython.org)
|
|
167
120
|
- [MyST Markdown Guide](https://mystmd.org)
|
|
168
121
|
|
|
169
122
|
Start building your amazing project! 🚀
|
|
@@ -198,11 +151,14 @@ def init_myst():
|
|
|
198
151
|
},
|
|
199
152
|
"site": {
|
|
200
153
|
"options": {
|
|
154
|
+
"favicon": "../static/favicon.ico",
|
|
155
|
+
"logo": "../static/logo.svg",
|
|
156
|
+
"logo_dark": "../static/logo.svg",
|
|
201
157
|
"analytics_google": "{{ GOOGLE_ANALYTICS_ID }}",
|
|
202
158
|
# "twitter": "",
|
|
203
159
|
},
|
|
204
160
|
},
|
|
205
161
|
}
|
|
206
|
-
update_myst_yml(myst_yml_defaults, path
|
|
162
|
+
update_myst_yml(myst_yml_defaults, path)
|
|
207
163
|
_write_welcome_file(content_type)
|
|
208
164
|
subprocess.run(["ap", "sync"])
|
afterpython/tools/pre_commit.py
CHANGED
|
@@ -1,8 +1,36 @@
|
|
|
1
|
-
import shutil
|
|
2
1
|
import subprocess
|
|
3
2
|
|
|
4
3
|
import afterpython as ap
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
from afterpython._io.yaml import write_yaml, read_yaml
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
pre_commit_default = {
|
|
9
|
+
# default is only ["pre-commit"], without changing the default,
|
|
10
|
+
# we need to explicitly pass in --hook-type whenever we run "pre-commit install --hook-type ..."
|
|
11
|
+
"default_install_hook_types": ["pre-commit", "commit-msg", "pre-push"],
|
|
12
|
+
"exclude": "^afterpython/_website/", # do not check the project website template
|
|
13
|
+
"repos": [
|
|
14
|
+
{
|
|
15
|
+
"repo": "https://github.com/pre-commit/pre-commit-hooks",
|
|
16
|
+
"rev": "v6.0.0",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{"id": "trailing-whitespace", "stages": ["pre-commit"]},
|
|
19
|
+
{"id": "end-of-file-fixer", "stages": ["pre-commit"]},
|
|
20
|
+
{"id": "check-yaml", "stages": ["pre-commit"]},
|
|
21
|
+
{"id": "check-toml", "stages": ["pre-commit"]},
|
|
22
|
+
{"id": "check-added-large-files", "stages": ["pre-commit"]},
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
# {
|
|
26
|
+
# "repo": "https://github.com/astral-sh/uv-pre-commit",
|
|
27
|
+
# "rev": "0.9.8",
|
|
28
|
+
# "hooks": [
|
|
29
|
+
# {"id": "uv-lock"}, # Update the uv lockfile
|
|
30
|
+
# ]
|
|
31
|
+
# },
|
|
32
|
+
],
|
|
33
|
+
}
|
|
6
34
|
|
|
7
35
|
|
|
8
36
|
def install_pre_commit():
|
|
@@ -29,9 +57,6 @@ def init_pre_commit():
|
|
|
29
57
|
if pre_commit_path.exists():
|
|
30
58
|
print(f".pre-commit-config.yaml already exists at {pre_commit_path}")
|
|
31
59
|
return
|
|
32
|
-
|
|
33
|
-
ap.paths.templates_path / "pre-commit-config-template.yaml"
|
|
34
|
-
)
|
|
35
|
-
shutil.copy(pre_commit_template_path, pre_commit_path)
|
|
60
|
+
write_yaml(pre_commit_path, pre_commit_default)
|
|
36
61
|
print(f"Created {pre_commit_path}")
|
|
37
62
|
install_pre_commit()
|
afterpython/tools/pyproject.py
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
2
|
from typing import TYPE_CHECKING
|
|
4
3
|
|
|
5
4
|
if TYPE_CHECKING:
|
|
6
5
|
from tomlkit.toml_document import TOMLDocument
|
|
7
6
|
|
|
8
7
|
import asyncio
|
|
9
|
-
from pathlib import Path
|
|
10
8
|
|
|
11
9
|
from pyproject_metadata import StandardMetadata
|
|
12
10
|
|
|
13
11
|
import afterpython as ap
|
|
14
|
-
from afterpython._io.toml import
|
|
12
|
+
from afterpython._io.toml import read_toml, write_toml, _to_tomlkit
|
|
15
13
|
|
|
16
14
|
|
|
17
15
|
def read_pyproject() -> TOMLDocument:
|
|
@@ -28,50 +26,14 @@ def read_metadata() -> StandardMetadata:
|
|
|
28
26
|
return StandardMetadata.from_pyproject(read_pyproject())
|
|
29
27
|
|
|
30
28
|
|
|
31
|
-
def find_package_directory() -> Path:
|
|
32
|
-
"""Find the user's package directory.
|
|
33
|
-
|
|
34
|
-
Supports both common Python project layouts:
|
|
35
|
-
- src layout: user_project/src/package_name/__init__.py
|
|
36
|
-
- flat layout: user_project/package_name/__init__.py
|
|
37
|
-
|
|
38
|
-
Returns:
|
|
39
|
-
Path to the package directory containing __init__.py
|
|
40
|
-
|
|
41
|
-
Raises:
|
|
42
|
-
FileNotFoundError: If package directory cannot be found in either layout
|
|
43
|
-
"""
|
|
44
|
-
metadata = read_metadata()
|
|
45
|
-
package_name = metadata.name
|
|
46
|
-
project_root = ap.paths.user_path
|
|
47
|
-
|
|
48
|
-
# Try src layout first (recommended layout)
|
|
49
|
-
src_layout_dir = project_root / "src" / package_name
|
|
50
|
-
if (src_layout_dir / "__init__.py").exists():
|
|
51
|
-
return src_layout_dir
|
|
52
|
-
|
|
53
|
-
# Try flat layout
|
|
54
|
-
flat_layout_dir = project_root / package_name
|
|
55
|
-
if (flat_layout_dir / "__init__.py").exists():
|
|
56
|
-
return flat_layout_dir
|
|
57
|
-
|
|
58
|
-
# Package not found in either location
|
|
59
|
-
raise FileNotFoundError(
|
|
60
|
-
f"Could not find package '{package_name}' in either src layout "
|
|
61
|
-
f"({src_layout_dir}) or flat layout ({flat_layout_dir}). "
|
|
62
|
-
f"Expected to find __init__.py in one of these locations."
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
|
|
66
29
|
def init_pyproject():
|
|
67
30
|
"""Initialize pyproject.toml with sensible defaults
|
|
68
31
|
- add [build-system] section with uv build backend (same as `uv init --package`)
|
|
69
32
|
- add [project.urls] section with homepage, repository, and documentation URLs
|
|
70
33
|
"""
|
|
71
34
|
import httpx
|
|
72
|
-
|
|
73
|
-
from afterpython.tools._git import get_git_user_config, get_github_url
|
|
74
35
|
from afterpython.utils import fetch_pypi_json
|
|
36
|
+
from afterpython.tools._git import get_git_user_config, get_github_url
|
|
75
37
|
|
|
76
38
|
build_backend = "uv_build"
|
|
77
39
|
|
afterpython/utils.py
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
2
|
from typing import TYPE_CHECKING
|
|
4
3
|
|
|
5
4
|
if TYPE_CHECKING:
|
|
6
5
|
from pathlib import Path
|
|
7
|
-
|
|
8
6
|
from httpx import AsyncClient
|
|
9
|
-
|
|
10
7
|
from afterpython._typing import NodeEnv
|
|
11
8
|
|
|
12
9
|
import os
|
|
@@ -23,7 +20,6 @@ def find_node_env() -> NodeEnv:
|
|
|
23
20
|
If no, install the Node.js version specified in NODEENV_VERSION
|
|
24
21
|
"""
|
|
25
22
|
from mystmd_py.nodeenv import find_any_node
|
|
26
|
-
|
|
27
23
|
from afterpython.const import NODEENV_VERSION
|
|
28
24
|
|
|
29
25
|
# from mystmd_py.main import ensure_valid_version
|
|
@@ -39,11 +35,6 @@ def has_uv() -> bool:
|
|
|
39
35
|
return shutil.which("uv") is not None
|
|
40
36
|
|
|
41
37
|
|
|
42
|
-
def has_pixi() -> bool:
|
|
43
|
-
"""Check if pixi is installed"""
|
|
44
|
-
return shutil.which("pixi") is not None
|
|
45
|
-
|
|
46
|
-
|
|
47
38
|
def has_gh() -> bool:
|
|
48
39
|
"""Check if gh is installed"""
|
|
49
40
|
return shutil.which("gh") is not None
|
|
@@ -91,7 +82,7 @@ def detect_license_from_file(file_path: str) -> str:
|
|
|
91
82
|
return ""
|
|
92
83
|
|
|
93
84
|
|
|
94
|
-
def deep_merge(base: dict, updates: dict
|
|
85
|
+
def deep_merge(base: dict, updates: dict) -> dict:
|
|
95
86
|
"""Deep merge updates into base, preserving structure and metadata.
|
|
96
87
|
|
|
97
88
|
Works with:
|
|
@@ -115,10 +106,8 @@ def deep_merge(base: dict, updates: dict, extend_lists: bool = True) -> dict:
|
|
|
115
106
|
if key in base:
|
|
116
107
|
if isinstance(base[key], dict) and isinstance(value, dict):
|
|
117
108
|
# Recursively merge dicts
|
|
118
|
-
deep_merge(base[key], value
|
|
119
|
-
elif (
|
|
120
|
-
isinstance(base[key], list) and isinstance(value, list) and extend_lists
|
|
121
|
-
):
|
|
109
|
+
deep_merge(base[key], value)
|
|
110
|
+
elif isinstance(base[key], list) and isinstance(value, list):
|
|
122
111
|
# Extend lists, but only add items that don't already exist
|
|
123
112
|
for item in value:
|
|
124
113
|
if item not in base[key]:
|
|
@@ -224,76 +213,3 @@ def handle_passthrough_help(
|
|
|
224
213
|
subprocess.run([*underlying_command, "--help"])
|
|
225
214
|
|
|
226
215
|
ctx.exit(0)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def normalize_static_path(path: str) -> str:
|
|
230
|
-
"""
|
|
231
|
-
Normalize paths for static assets.
|
|
232
|
-
|
|
233
|
-
Only allows two formats:
|
|
234
|
-
1. Pure filename/path without ./ or ../ (e.g., "logo.svg", "somewhere/logo.svg")
|
|
235
|
-
2. Absolute path starting with / (e.g., "/somewhere/logo.svg")
|
|
236
|
-
|
|
237
|
-
Args:
|
|
238
|
-
path: User-provided path from afterpython.toml
|
|
239
|
-
|
|
240
|
-
Returns:
|
|
241
|
-
Normalized path
|
|
242
|
-
|
|
243
|
-
Raises:
|
|
244
|
-
ValueError: If path contains ./ or ../ or other invalid patterns
|
|
245
|
-
|
|
246
|
-
Examples:
|
|
247
|
-
>>> normalize_static_path("logo.svg")
|
|
248
|
-
'logo.svg'
|
|
249
|
-
>>> normalize_static_path("somewhere/logo.svg")
|
|
250
|
-
'/somewhere/logo.svg'
|
|
251
|
-
>>> normalize_static_path("/somewhere/logo.svg")
|
|
252
|
-
'/somewhere/logo.svg'
|
|
253
|
-
>>> normalize_static_path("./logo.svg") # Raises ValueError
|
|
254
|
-
>>> normalize_static_path("../logo.svg") # Raises ValueError
|
|
255
|
-
>>> normalize_static_path("static/logo.svg") # Raises ValueError
|
|
256
|
-
"""
|
|
257
|
-
if not path:
|
|
258
|
-
return ""
|
|
259
|
-
|
|
260
|
-
# Reject paths with ./ or ../
|
|
261
|
-
if "./" in path or "../" in path or path.startswith(".") or path.startswith(".."):
|
|
262
|
-
raise ValueError(
|
|
263
|
-
f"Invalid path '{path}': paths cannot contain './' or '../'. "
|
|
264
|
-
f"Use either a pure filename (e.g., 'logo.svg') or absolute path (e.g., '/somewhere/logo.svg')"
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
# Reject paths with 'static' in them
|
|
268
|
-
if "static" in path.lower():
|
|
269
|
-
raise ValueError(
|
|
270
|
-
f"Invalid path '{path}': do not include 'static' in the path. "
|
|
271
|
-
f"Files are assumed to be in the static/ directory."
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
# If it starts with /, it's absolute - keep as is
|
|
275
|
-
if not path.startswith("/"):
|
|
276
|
-
return "/" + path
|
|
277
|
-
else:
|
|
278
|
-
return path
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
def get_relative_static_prefix(file_path: Path, content_type_path: Path) -> str:
|
|
282
|
-
"""Calculate the relative path prefix from a file to its content type's static/ folder.
|
|
283
|
-
|
|
284
|
-
Args:
|
|
285
|
-
file_path: Path to the content file (e.g., blog/layer/blog5.ipynb)
|
|
286
|
-
content_type_path: Path to the content type folder (e.g., blog/)
|
|
287
|
-
|
|
288
|
-
Returns:
|
|
289
|
-
Relative prefix to reach static/ (e.g., "./" or "../" or "../../")
|
|
290
|
-
"""
|
|
291
|
-
# Get the depth: how many directories deep the file is from content_type_path
|
|
292
|
-
relative = file_path.parent.relative_to(content_type_path)
|
|
293
|
-
depth = len(relative.parts)
|
|
294
|
-
|
|
295
|
-
# Build the correct prefix
|
|
296
|
-
if depth == 0:
|
|
297
|
-
return "./" # File is directly in content_type_path
|
|
298
|
-
else:
|
|
299
|
-
return "../" * depth
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: afterpython
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: CLI tool
|
|
3
|
+
Version: 0.1.0.dev5
|
|
4
|
+
Summary: CLI tool for AfterPython
|
|
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
|
|
7
7
|
Author-email: Stephen Yau <softwareentrepreneer+afterpython@gmail.com>
|
|
@@ -10,38 +10,39 @@ Classifier: Operating System :: OS Independent
|
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: Topic :: Software Development :: Build Tools
|
|
12
12
|
Classifier: Topic :: Software Development :: Documentation
|
|
13
|
-
Classifier: Typing :: Typed
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
17
|
Requires-Dist: click>=8.3.1
|
|
18
18
|
Requires-Dist: trogon>=0.6.0
|
|
19
19
|
Requires-Dist: httpx>=0.28.1
|
|
20
20
|
Requires-Dist: tomlkit>=0.13.3
|
|
21
21
|
Requires-Dist: ruamel-yaml>=0.18.16
|
|
22
22
|
Requires-Dist: gitpython>=3.1.45
|
|
23
|
+
Requires-Dist: pygithub>=2.8.1
|
|
23
24
|
Requires-Dist: python-dotenv>=1.2.1
|
|
24
|
-
Requires-Dist: pyproject-metadata>=0.
|
|
25
|
-
Requires-Dist: mystmd>=1.
|
|
25
|
+
Requires-Dist: pyproject-metadata>=0.9.1
|
|
26
|
+
Requires-Dist: mystmd>=1.6.6
|
|
26
27
|
Requires-Dist: pdoc>=16.0.0
|
|
27
|
-
Requires-Dist: ruff>=0.14.
|
|
28
|
-
Requires-Dist: pre-commit>=4.
|
|
28
|
+
Requires-Dist: ruff>=0.14.5
|
|
29
|
+
Requires-Dist: pre-commit>=4.4.0
|
|
29
30
|
Requires-Dist: commitizen>=4.10.0
|
|
30
31
|
Requires-Python: >=3.11
|
|
31
|
-
Project-URL: documentation, https://
|
|
32
|
-
Project-URL: homepage, https://afterpython.
|
|
32
|
+
Project-URL: documentation, https://ap.afterpython.org/doc
|
|
33
|
+
Project-URL: homepage, https://afterpython.org
|
|
33
34
|
Project-URL: repository, https://github.com/AfterPythonOrg/afterpython
|
|
34
35
|
Description-Content-Type: text/markdown
|
|
35
36
|
|
|
36
37
|
# AfterPython: Python Package Maintenance Toolkit and Project Website Generator
|
|
37
38
|
|
|
38
39
|
[](https://afterpython.org)
|
|
40
|
+
[](https://pixi.sh)
|
|
39
41
|

|
|
40
42
|
[](https://pypi.org/project/afterpython)
|
|
41
43
|

|
|
42
44
|
[](https://github.com/AfterPythonOrg/afterpython/discussions)
|
|
43
45
|
[](https://deepwiki.com/AfterPythonOrg/afterpython)
|
|
44
|
-
<!-- [](https://pixi.sh) -->
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
[MyST]: https://mystmd.org
|
|
@@ -56,7 +57,6 @@ Description-Content-Type: text/markdown
|
|
|
56
57
|
[Scikit-Learn]: https://scikit-learn.org
|
|
57
58
|
[WebLLM]: https://webllm.mlc.ai/
|
|
58
59
|
[project-website-template]: https://github.com/AfterPythonOrg/project-website-template
|
|
59
|
-
[commitizen]: https://github.com/commitizen-tools/commitizen
|
|
60
60
|
[uv]: https://docs.astral.sh/uv/
|
|
61
61
|
[ruff]: https://docs.astral.sh/ruff/
|
|
62
62
|
|
|
@@ -75,54 +75,28 @@ First-time maintainers must learn multiple tools and concepts, e.g. [MyST], [Sem
|
|
|
75
75
|
|
|
76
76
|
## Core Features
|
|
77
77
|
- [x] Write content directly in [MyST Markdown] or [Jupyter Notebook]
|
|
78
|
-
- [x] Go from writing to
|
|
79
|
-
- [x] Centralize all your content in a modern,
|
|
80
|
-
- [x] Zero-config orchestration — Pre-configured modern tooling with sane defaults (see [Tech Stack](#tech-stack)), so you can start maintaining packages immediately **without learning each tool**
|
|
78
|
+
- [x] Go from writing to website deployment in minutes — no need to learn any of the underlying tools
|
|
79
|
+
- [x] Centralize all your content in a modern, unified project website — from documentation to blog posts
|
|
81
80
|
- [ ] Export content as PDF — for example, combine all blog posts into a single PDF file
|
|
82
81
|
- [ ] **⚡ Full-text search** across **ALL** your content in your website — docs, blogs, tutorials, everything
|
|
83
82
|
- [ ] **🤖 Embedded AI Chatbot** that answers questions directly using an in-browser LLM — at no cost
|
|
84
83
|
|
|
85
|
-
---
|
|
86
|
-
## Project Website
|
|
87
|
-
> The project website for `afterpython` is created using `afterpython` itself. See the [**website**](https://afterpython.afterpython.org).
|
|
88
|
-
<picture>
|
|
89
|
-
<source media="(prefers-color-scheme: dark)" srcset="afterpython/static/website-dark.png">
|
|
90
|
-
<source media="(prefers-color-scheme: light)" srcset="afterpython/static/website-light.png">
|
|
91
|
-
<img alt="Project Website" src="afterpython/static/website-light.png">
|
|
92
|
-
</picture>
|
|
93
|
-
|
|
94
|
-
You can create your own website too and deploy it to GitHub Pages in **less than a minute**! See [Quickstart](https://afterpython.afterpython.org/doc/quickstart).
|
|
95
84
|
|
|
96
|
-
---
|
|
97
85
|
## Installation
|
|
98
86
|
```bash
|
|
99
87
|
# install afterpython as a dev dependency
|
|
100
88
|
uv add --dev afterpython
|
|
101
|
-
|
|
102
89
|
# initialize afterpython
|
|
103
90
|
ap init
|
|
104
91
|
```
|
|
105
92
|
|
|
106
93
|
|
|
107
|
-
---
|
|
108
|
-
## CLI Commands
|
|
109
|
-
```bash
|
|
110
|
-
# show all commands
|
|
111
|
-
ap --help
|
|
112
|
-
|
|
113
|
-
# or use terminal UI (TUI)
|
|
114
|
-
ap tui
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
---
|
|
119
94
|
## Tech Stack
|
|
120
95
|
- [MyST]
|
|
121
96
|
- [project-website-template]
|
|
122
97
|
- [pre-commit]
|
|
123
98
|
- [GitHub Actions]
|
|
124
99
|
- [pdoc]
|
|
125
|
-
- [commitizen]
|
|
126
100
|
- [uv]
|
|
127
101
|
- [ruff]
|
|
128
102
|
- [pagefind]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
afterpython/.pre-commit-config.yaml,sha256=7mpSRHaFcG2M6SOY-5Sbs9WxN8UISnZ2g0NwCGsLNCA,915
|
|
2
|
+
afterpython/__init__.py,sha256=aNochs72gU2NR1cBzIkk3NxSsdJ7ceHluI5gdYwaFpI,289
|
|
3
|
+
afterpython/_io/toml.py,sha256=nrx0CFPONgmkz5WDCSMyr4caic6nzu0uCd33qOmhhF0,3408
|
|
4
|
+
afterpython/_io/yaml.py,sha256=raksve_gaIFJim8lQrq-kQHkRjTloed7CT2FY0oUJSg,654
|
|
5
|
+
afterpython/_paths.py,sha256=1NrPQ4erjA_rIvDggx7QRY4_01zyU-yht9s95Q-4YgM,1584
|
|
6
|
+
afterpython/_typing.py,sha256=kmxBRbhft85C8Ycm5BFBP258Jozrw9tsl7KHOVPcF_o,146
|
|
7
|
+
afterpython/builders/__init__.py,sha256=MOv3spIh-ABwyUtvQAHhPZ2horWb0D93WNmOPNAzKYo,215
|
|
8
|
+
afterpython/builders/metadata.py,sha256=IL-fe3GJYbAaqtMvw5HhpZJ7O57h5GX2GJtKKdWVab8,1275
|
|
9
|
+
afterpython/builders/molab.py,sha256=Qji8piWJB1tbzhgtld0pQWIsUA7d7RsGCfnoLVKd5EA,3714
|
|
10
|
+
afterpython/cli/__init__.py,sha256=1vLZylRMnGaV7cJ-kHO96dChp33_P4fBIJ_Vi-yyZg4,85
|
|
11
|
+
afterpython/cli/commands/build.py,sha256=Ir4jA5r2prpqUEIRsEt8hQctAwFEEjLVyxLp2FzgkQk,6600
|
|
12
|
+
afterpython/cli/commands/bump.py,sha256=Wg-q3UQIkIRGT3umZCUBPpwIQTAHwQZVs1yHSZhmzKE,3088
|
|
13
|
+
afterpython/cli/commands/check.py,sha256=cLP4Vle3ENQHCXBv1okDlWK-L1XK-fv80m66xW3LQkg,1097
|
|
14
|
+
afterpython/cli/commands/clean.py,sha256=dCEkkWOTn93758wGTrWJG9V6c-lv2Z59CYfoP3i3rJE,2531
|
|
15
|
+
afterpython/cli/commands/commit.py,sha256=yRLOBs6JetZSGkFy9_uLzgR8yAX9djeUF291gabtuUo,880
|
|
16
|
+
afterpython/cli/commands/commitizen.py,sha256=naI8j4pfXIMsiOLmPMeZTGh831I3665_1_Sbn7yoKyE,811
|
|
17
|
+
afterpython/cli/commands/dev.py,sha256=QJmV9sARdxXTMX6oedeoJg3QAr7PYC0n3gcFkppd1X0,5106
|
|
18
|
+
afterpython/cli/commands/format.py,sha256=cXvuc-6XwULM1VJjS2B2bJBvnV1TVHFl1CeSs0SYQ24,738
|
|
19
|
+
afterpython/cli/commands/init.py,sha256=Sc0B-Gv1bpYHzD8hYoVTXY2QsOV36kWcKH5vgHBmp1g,2987
|
|
20
|
+
afterpython/cli/commands/install.py,sha256=wgH9DEuN7hsA6BslNJASqOBn4VHJrYHBvowXDHi4smE,573
|
|
21
|
+
afterpython/cli/commands/pre_commit.py,sha256=DdmbkcZgJ7QArAY1y5vODYZmHUXWEIuXRyp69AS-pn0,1981
|
|
22
|
+
afterpython/cli/commands/preview.py,sha256=EbuB-dFcgonmqumTb2aHlLpXRiXkGE4DI3ddJdHwcaM,619
|
|
23
|
+
afterpython/cli/commands/release.py,sha256=6SDEnPtuCL0_XUqWUlvRSKxAv011QqOD0kc5pKu7378,2479
|
|
24
|
+
afterpython/cli/commands/start.py,sha256=_ikrxPErYSGYtn8MAlbWD0CDEhYdoKvZ9e7XvrUZfog,3932
|
|
25
|
+
afterpython/cli/commands/sync.py,sha256=Cy9M52YmEGOZkhI8tX8RgM8SNad7q_LRlaNrkFEUYfM,4057
|
|
26
|
+
afterpython/cli/commands/update.py,sha256=f6dzoJIiEod7j44dgnaFe-v7INVdUbopVoWPxv7-Mpo,5491
|
|
27
|
+
afterpython/cli/main.py,sha256=GMcP7bmOA1glKGPsCuOXcd-9hv2k3BGkk_ofdo8oatw,2660
|
|
28
|
+
afterpython/const.py,sha256=lNmmyObrjSxxMkVpIsEqtd9Nms_ndrW_omIP79BWvPc,159
|
|
29
|
+
afterpython/cz.toml,sha256=JlenGROat8jvxvqRH6KluDs6f7w0JAy8Skd3ak7sGLY,1506
|
|
30
|
+
afterpython/main.py,sha256=w0z6mTib3u4p2HxMrk-tIJlZtur7uRaqsbE0ewlYlSs,490
|
|
31
|
+
afterpython/pcu.py,sha256=RWgPcdAFyNBVhWbQPuL1seji9uyT8Pqz-zE6jCf_Qb8,6091
|
|
32
|
+
afterpython/templates/deploy-workflow-template.yml,sha256=jR0PudUbgzJ3q9iVdIeyES-jFXdFT8G3DPyZR8Vq9Go,2438
|
|
33
|
+
afterpython/templates/release-workflow-template.yml,sha256=DpuFLo3_43rqwBjDf3XrWbYJNRdlhy7-wJFL_TxdRNE,1606
|
|
34
|
+
afterpython/templates/ruff-template.toml,sha256=_yTtZ0YPsJiCxy66ZDfYaStTJ9aXXW-JXy-KW6l2bBE,1010
|
|
35
|
+
afterpython/tools/_afterpython.py,sha256=W88GhtQADB09DV4A24GOYBeYrHsnVVzllLdWFiKgS1A,1904
|
|
36
|
+
afterpython/tools/_git.py,sha256=pOcbP_hb6db3qRlbB4Hlp7MkShkTmUefnIBU2kqoq4I,3211
|
|
37
|
+
afterpython/tools/commitizen.py,sha256=9aGXsO4tTyDs3s8qS_fiskdSabHqc9XjHwePThybJU0,1469
|
|
38
|
+
afterpython/tools/github_actions.py,sha256=M3uGLJKOCml1fQBc8W2lkZWsR5p-0UVUolFWvqf5mDo,2866
|
|
39
|
+
afterpython/tools/myst.py,sha256=m-1W_Tv3VrP6GQIoRaCJZuiy3AuedJ42UV7jHcrg56c,6238
|
|
40
|
+
afterpython/tools/pre_commit.py,sha256=stXUQfR0A6nPMCSuuKO-Icjc5F2z9VsZu2mKkIW1vWI,2193
|
|
41
|
+
afterpython/tools/pyproject.py,sha256=YwJJAB3lIqYK9wt8UtL4dfCli9UN7uzi3e-vtycB0HE,2325
|
|
42
|
+
afterpython/utils.py,sha256=7r4jsxn-Rr2gvCh7MJqxVIjzZH7Jnjt1z_sl95cFaEk,6831
|
|
43
|
+
afterpython/version.py,sha256=hihRV1S-gc1m46OaXD32ikkEkqzs-Gtq7AcAqOQV-wY,1603
|
|
44
|
+
afterpython-0.1.0.dev5.dist-info/WHEEL,sha256=YUH1mBqsx8Dh2cQG2rlcuRYUhJddG9iClegy4IgnHik,79
|
|
45
|
+
afterpython-0.1.0.dev5.dist-info/entry_points.txt,sha256=roa0PDgBueM1JPbRG0G2OSxzaN394OUL_oS9iEwVLBc,80
|
|
46
|
+
afterpython-0.1.0.dev5.dist-info/METADATA,sha256=JLl45YccZwBdpT11-PjaN1vej7rOarYGArD46xVOvdM,4742
|
|
47
|
+
afterpython-0.1.0.dev5.dist-info/RECORD,,
|