afterpython 0.0.1.dev1__py3-none-any.whl → 0.1.0__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/__init__.py +13 -0
- afterpython/_io/toml.py +105 -0
- afterpython/_io/yaml.py +26 -0
- afterpython/_paths.py +39 -0
- afterpython/_typing.py +4 -0
- afterpython/builders/__init__.py +17 -0
- afterpython/builders/content_json.py +131 -0
- afterpython/builders/index_md.py +94 -0
- afterpython/builders/jupyter_notebook.py +293 -0
- afterpython/builders/markdown.py +271 -0
- afterpython/builders/metadata.py +50 -0
- afterpython/cli/__init__.py +3 -0
- afterpython/cli/commands/add.py +67 -0
- afterpython/cli/commands/build.py +260 -0
- afterpython/cli/commands/bump.py +118 -0
- afterpython/cli/commands/check.py +36 -0
- afterpython/cli/commands/clean.py +85 -0
- afterpython/cli/commands/commit.py +52 -0
- afterpython/cli/commands/commitizen.py +32 -0
- afterpython/cli/commands/dev.py +213 -0
- afterpython/cli/commands/format.py +28 -0
- afterpython/cli/commands/init.py +107 -0
- afterpython/cli/commands/init_branch_rules.py +9 -0
- afterpython/cli/commands/install.py +28 -0
- afterpython/cli/commands/lock.py +23 -0
- afterpython/cli/commands/pre_commit.py +66 -0
- afterpython/cli/commands/preview.py +22 -0
- afterpython/cli/commands/release.py +67 -0
- afterpython/cli/commands/remove.py +67 -0
- afterpython/cli/commands/start.py +144 -0
- afterpython/cli/commands/sync.py +152 -0
- afterpython/cli/commands/update.py +176 -0
- afterpython/cli/main.py +132 -0
- afterpython/const.py +4 -0
- afterpython/main.py +25 -1
- afterpython/pcu.py +166 -0
- afterpython/templates/ci-workflow-template.yml +197 -0
- afterpython/templates/cz-template.toml +7 -0
- afterpython/templates/dependabot-template.yml +30 -0
- afterpython/templates/deploy-workflow-template.yml +88 -0
- afterpython/templates/pre-commit-config-template.yaml +48 -0
- afterpython/templates/release-workflow-template.yml +56 -0
- afterpython/templates/ruff-template.toml +32 -0
- afterpython/tools/_afterpython.py +128 -0
- afterpython/tools/_git.py +137 -0
- afterpython/tools/branch_rules.py +106 -0
- afterpython/tools/commitizen.py +13 -0
- afterpython/tools/github_actions.py +48 -0
- afterpython/tools/myst.py +208 -0
- afterpython/tools/pre_commit.py +37 -0
- afterpython/tools/pyproject.py +110 -0
- afterpython/utils.py +299 -0
- afterpython/version.py +39 -0
- afterpython-0.1.0.dist-info/METADATA +131 -0
- afterpython-0.1.0.dist-info/RECORD +57 -0
- afterpython-0.1.0.dist-info/WHEEL +4 -0
- afterpython-0.1.0.dist-info/entry_points.txt +4 -0
- afterpython/images/favicon.ico +0 -0
- afterpython/images/logo-trace.svg +0 -3
- afterpython/images/logo.png +0 -0
- afterpython/images/logo.svg +0 -2
- afterpython-0.0.1.dev1.dist-info/LICENSE +0 -201
- afterpython-0.0.1.dev1.dist-info/METADATA +0 -17
- afterpython-0.0.1.dev1.dist-info/RECORD +0 -9
- afterpython-0.0.1.dev1.dist-info/WHEEL +0 -4
afterpython/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# import builtins
|
|
2
|
+
# from pprint import pprint
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
|
|
5
|
+
from afterpython._paths import Paths
|
|
6
|
+
|
|
7
|
+
# make pprint available in the global namespace
|
|
8
|
+
# builtins.pprint = pprint
|
|
9
|
+
|
|
10
|
+
paths = Paths()
|
|
11
|
+
|
|
12
|
+
__version__ = version("afterpython")
|
|
13
|
+
__all__ = ("__version__", "paths")
|
afterpython/_io/toml.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from tomlkit.toml_document import TOMLDocument
|
|
9
|
+
|
|
10
|
+
import tomlkit
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# VIBE-CODED
|
|
14
|
+
def _from_tomlkit(value):
|
|
15
|
+
"""Recursively convert tomlkit objects to plain Python data structures.
|
|
16
|
+
|
|
17
|
+
This ensures compatibility with other libraries (like ruamel.yaml):
|
|
18
|
+
- tomlkit containers → plain Python containers
|
|
19
|
+
- tomlkit primitives → plain Python primitives
|
|
20
|
+
"""
|
|
21
|
+
from tomlkit.items import AoT, Array, InlineTable, Table
|
|
22
|
+
|
|
23
|
+
if value is None:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
# Check containers first (these are the problematic ones)
|
|
27
|
+
if isinstance(value, (Table, InlineTable)):
|
|
28
|
+
# tomlkit dict-like → plain dict
|
|
29
|
+
return {k: _from_tomlkit(v) for k, v in value.items()}
|
|
30
|
+
elif isinstance(value, (Array, AoT)):
|
|
31
|
+
# tomlkit array → plain list
|
|
32
|
+
return [_from_tomlkit(item) for item in value]
|
|
33
|
+
elif isinstance(value, dict):
|
|
34
|
+
# Already a dict, but recurse to handle nested tomlkit objects
|
|
35
|
+
return {k: _from_tomlkit(v) for k, v in value.items()}
|
|
36
|
+
elif isinstance(value, list):
|
|
37
|
+
# Already a list, but recurse to handle nested tomlkit objects
|
|
38
|
+
return [_from_tomlkit(item) for item in value]
|
|
39
|
+
|
|
40
|
+
# Primitives - tomlkit primitives inherit from Python types
|
|
41
|
+
# Just convert explicitly to be safe
|
|
42
|
+
elif isinstance(value, bool): # MUST check bool before int!
|
|
43
|
+
return bool(value)
|
|
44
|
+
elif isinstance(value, int):
|
|
45
|
+
return int(value)
|
|
46
|
+
elif isinstance(value, float):
|
|
47
|
+
return float(value)
|
|
48
|
+
elif isinstance(value, str):
|
|
49
|
+
return str(value)
|
|
50
|
+
else:
|
|
51
|
+
# datetime, date, time, or already plain Python type
|
|
52
|
+
return value
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# VIBE-CODED
|
|
56
|
+
def _to_tomlkit(value):
|
|
57
|
+
"""Recursively convert Python data structures to tomlkit objects.
|
|
58
|
+
|
|
59
|
+
This ensures proper TOML formatting:
|
|
60
|
+
- List of dicts → array of inline tables: [{key = val}]
|
|
61
|
+
- Nested dicts → nested tables with proper structure
|
|
62
|
+
"""
|
|
63
|
+
from tomlkit import array, inline_table
|
|
64
|
+
|
|
65
|
+
if value is None:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
if isinstance(value, dict):
|
|
69
|
+
# Plain dict → recursively convert values
|
|
70
|
+
return {k: _to_tomlkit(v) for k, v in value.items()}
|
|
71
|
+
elif isinstance(value, list):
|
|
72
|
+
if not value:
|
|
73
|
+
# Empty list → empty tomlkit array
|
|
74
|
+
return array()
|
|
75
|
+
|
|
76
|
+
# Check if ALL items are dicts (should be inline tables)
|
|
77
|
+
if all(isinstance(item, dict) for item in value):
|
|
78
|
+
arr = array()
|
|
79
|
+
for item in value:
|
|
80
|
+
tbl = inline_table()
|
|
81
|
+
# Recursively convert the dict values
|
|
82
|
+
tbl.update({k: _to_tomlkit(v) for k, v in item.items()})
|
|
83
|
+
arr.append(tbl)
|
|
84
|
+
return arr
|
|
85
|
+
else:
|
|
86
|
+
# Mixed or non-dict items → regular array
|
|
87
|
+
arr = array()
|
|
88
|
+
for item in value:
|
|
89
|
+
arr.append(_to_tomlkit(item))
|
|
90
|
+
return arr
|
|
91
|
+
else:
|
|
92
|
+
# Primitives (str, int, float, bool, datetime, etc.)
|
|
93
|
+
# tomlkit handles these natively
|
|
94
|
+
return value
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def read_toml(file_path: Path) -> TOMLDocument:
|
|
98
|
+
with open(file_path, "rb") as f:
|
|
99
|
+
data: TOMLDocument = tomlkit.parse(f.read())
|
|
100
|
+
return data
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def write_toml(file_path: Path, data: TOMLDocument | dict):
|
|
104
|
+
with open(file_path, "w") as f:
|
|
105
|
+
f.write(tomlkit.dumps(data))
|
afterpython/_io/yaml.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from ruamel.yaml import YAML
|
|
4
|
+
from ruamel.yaml.comments import CommentedMap
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _get_yaml() -> YAML:
|
|
8
|
+
"""Get configured YAML instance that preserves order, comments, and formatting"""
|
|
9
|
+
yaml = YAML()
|
|
10
|
+
yaml.preserve_quotes = True
|
|
11
|
+
yaml.default_flow_style = False
|
|
12
|
+
yaml.width = 4096 # Prevent line wrapping
|
|
13
|
+
return yaml
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def read_yaml(file_path: Path) -> CommentedMap:
|
|
17
|
+
yaml = _get_yaml()
|
|
18
|
+
with open(file_path) as f:
|
|
19
|
+
return yaml.load(f)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def write_yaml(file_path: Path, data: dict):
|
|
23
|
+
"""Write YAML data"""
|
|
24
|
+
yaml = _get_yaml()
|
|
25
|
+
with open(file_path, "w") as f:
|
|
26
|
+
yaml.dump(data, f)
|
afterpython/_paths.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class Paths:
|
|
7
|
+
package_path: Path = field(init=False)
|
|
8
|
+
templates_path: Path = field(init=False)
|
|
9
|
+
user_path: Path = field(init=False)
|
|
10
|
+
pyproject_path: Path = field(init=False)
|
|
11
|
+
afterpython_path: Path = field(init=False)
|
|
12
|
+
website_path: Path = field(init=False)
|
|
13
|
+
build_path: Path = field(init=False)
|
|
14
|
+
static_path: Path = field(init=False)
|
|
15
|
+
|
|
16
|
+
def __post_init__(self):
|
|
17
|
+
# package path is the path to the afterpython package in user's site-packages
|
|
18
|
+
self.package_path = Path(__file__).resolve().parents[0]
|
|
19
|
+
self.templates_path = self.package_path / "templates"
|
|
20
|
+
self.user_path = self._find_project_root()
|
|
21
|
+
self.pyproject_path = self.user_path / "pyproject.toml"
|
|
22
|
+
self.afterpython_path = self.user_path / "afterpython"
|
|
23
|
+
self.website_path = self.afterpython_path / "_website"
|
|
24
|
+
self.build_path = self.afterpython_path / "_build"
|
|
25
|
+
self.static_path = self.afterpython_path / "static"
|
|
26
|
+
|
|
27
|
+
def _find_project_root(self) -> Path:
|
|
28
|
+
"""Find the project root by looking for pyproject.toml in current or parent directories."""
|
|
29
|
+
current = Path.cwd()
|
|
30
|
+
|
|
31
|
+
# Check current directory and all parents
|
|
32
|
+
for path in [current, *current.parents]:
|
|
33
|
+
if (path / "pyproject.toml").exists():
|
|
34
|
+
return path
|
|
35
|
+
|
|
36
|
+
# If no pyproject.toml found, raise an error like uv does
|
|
37
|
+
raise FileNotFoundError(
|
|
38
|
+
"No pyproject.toml found in current directory or any parent directory"
|
|
39
|
+
)
|
afterpython/_typing.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from afterpython.builders.content_json import build_content_json
|
|
2
|
+
from afterpython.builders.index_md import (
|
|
3
|
+
create_placeholder_index_md_files,
|
|
4
|
+
delete_placeholder_index_md_files,
|
|
5
|
+
)
|
|
6
|
+
from afterpython.builders.jupyter_notebook import build_jupyter_notebooks
|
|
7
|
+
from afterpython.builders.markdown import build_markdown
|
|
8
|
+
from afterpython.builders.metadata import build_metadata
|
|
9
|
+
|
|
10
|
+
__all__ = (
|
|
11
|
+
"build_content_json",
|
|
12
|
+
"build_jupyter_notebooks",
|
|
13
|
+
"build_markdown",
|
|
14
|
+
"build_metadata",
|
|
15
|
+
"create_placeholder_index_md_files",
|
|
16
|
+
"delete_placeholder_index_md_files",
|
|
17
|
+
)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Build {content_type}.json for non-doc content type (blog, tutorial, example, guide)
|
|
3
|
+
|
|
4
|
+
Combines all individual content JSON files from MyST build output into a single
|
|
5
|
+
manifest file for SvelteKit to consume (e.g., blog.json, tutorial.json).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from afterpython._typing import tContentType
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
import click
|
|
19
|
+
|
|
20
|
+
import afterpython as ap
|
|
21
|
+
from afterpython.const import CONTENT_TYPES
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def standardize_data(content_type: tContentType, content: dict) -> dict:
|
|
25
|
+
"""
|
|
26
|
+
Standardize the content dictionary to a single level.
|
|
27
|
+
"""
|
|
28
|
+
from afterpython.tools._afterpython import read_afterpython
|
|
29
|
+
|
|
30
|
+
afterpython = read_afterpython()
|
|
31
|
+
website = afterpython.get("website", {})
|
|
32
|
+
content_type_config = website.get(content_type, {})
|
|
33
|
+
featured_post = content_type_config.get("featured_post", "")
|
|
34
|
+
|
|
35
|
+
frontmatter = content.get("frontmatter", {})
|
|
36
|
+
# e.g. featured_post = "blog4.md" or "blog4"
|
|
37
|
+
is_featured = content["slug"] == featured_post or content["location"].endswith(
|
|
38
|
+
featured_post
|
|
39
|
+
)
|
|
40
|
+
return {
|
|
41
|
+
"version": content["version"],
|
|
42
|
+
"slug": content["slug"],
|
|
43
|
+
"location": content["location"],
|
|
44
|
+
"featured": is_featured,
|
|
45
|
+
# frontmatter fields
|
|
46
|
+
"title": frontmatter.get("title", ""),
|
|
47
|
+
"description": frontmatter.get("description", ""),
|
|
48
|
+
"tags": frontmatter.get("tags", []),
|
|
49
|
+
"date": frontmatter.get("date", ""),
|
|
50
|
+
"thumbnail": frontmatter.get("thumbnail", ""),
|
|
51
|
+
"thumbnailOptimized": frontmatter.get("thumbnailOptimized", ""),
|
|
52
|
+
"authors": frontmatter.get("authors", []),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def convert_paths(content_type: tContentType, output_file: Path):
|
|
57
|
+
"""
|
|
58
|
+
Convert paths (generated by MyST build) in the content_json file to use the new paths in the build output.
|
|
59
|
+
e.g. convert /blog_thumbnail-08d4f63a34eb7b55b6a00c25218c3295.png to /blog/build/blog_thumbnail-08d4f63a34eb7b55b6a00c25218c3295.png
|
|
60
|
+
"""
|
|
61
|
+
with open(output_file, encoding="utf-8") as f:
|
|
62
|
+
content = json.load(f)
|
|
63
|
+
|
|
64
|
+
for item in content:
|
|
65
|
+
if not item.get("thumbnailOptimized"):
|
|
66
|
+
item["thumbnailOptimized"] = item["thumbnail"]
|
|
67
|
+
item["thumbnail"] = f"/{content_type}/build" + item["thumbnail"]
|
|
68
|
+
item["thumbnailOptimized"] = (
|
|
69
|
+
f"/{content_type}/build" + item["thumbnailOptimized"]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
73
|
+
json.dump(content, f, indent=2, ensure_ascii=False)
|
|
74
|
+
|
|
75
|
+
click.echo(f"Completed path conversion in {output_file}")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def build_content_json():
|
|
79
|
+
"""Build {content_type}.json manifest for non-doc content types.
|
|
80
|
+
|
|
81
|
+
Reads all JSON files from afterpython/_build/{content_type}/site/content/
|
|
82
|
+
(excluding index.json) and combines them into a single manifest file at
|
|
83
|
+
afterpython/_build/{content_type}.json.
|
|
84
|
+
|
|
85
|
+
This manifest is used by SvelteKit landing pages to list all content items.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
content_type: Content type (blog, tutorial, example, guide)
|
|
89
|
+
"""
|
|
90
|
+
for content_type in CONTENT_TYPES:
|
|
91
|
+
if content_type == "doc":
|
|
92
|
+
continue # Doc doesn't need a combined manifest
|
|
93
|
+
|
|
94
|
+
content_path = ap.paths.afterpython_path / content_type
|
|
95
|
+
content_dir = content_path / "_build" / "site" / "content"
|
|
96
|
+
myst_yml_path = content_path / "myst.yml"
|
|
97
|
+
if (
|
|
98
|
+
not content_path.exists()
|
|
99
|
+
or not myst_yml_path.exists()
|
|
100
|
+
or not content_dir.exists()
|
|
101
|
+
):
|
|
102
|
+
click.echo(f"Skipping {content_type}.json (missing files)")
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
click.echo(f"Building {content_type}.json...")
|
|
106
|
+
|
|
107
|
+
# Collect all JSON files except index.json, which is created by a placeholder index.md file
|
|
108
|
+
content_items = []
|
|
109
|
+
for json_file in content_dir.glob("*.json"):
|
|
110
|
+
if json_file.stem == "index":
|
|
111
|
+
continue # Skip index.json
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
with open(json_file, encoding="utf-8") as f:
|
|
115
|
+
data = json.load(f)
|
|
116
|
+
|
|
117
|
+
# Preserve all data from MyST JSON files
|
|
118
|
+
# The project-website-template will parse the fields it needs
|
|
119
|
+
content_items.append(standardize_data(content_type, data))
|
|
120
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
121
|
+
click.echo(f"Warning: Failed to parse {json_file}: {e}")
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
# Write combined manifest
|
|
125
|
+
output_file = ap.paths.build_path / f"{content_type}.json"
|
|
126
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
127
|
+
json.dump(content_items, f, indent=2, ensure_ascii=False)
|
|
128
|
+
|
|
129
|
+
click.echo(f"Generated {output_file} with {len(content_items)} items")
|
|
130
|
+
|
|
131
|
+
convert_paths(content_type, output_file)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
import afterpython as ap
|
|
4
|
+
from afterpython.const import CONTENT_TYPES
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def create_placeholder_index_md_files():
|
|
8
|
+
"""Create placeholder index.md files for non-doc content types.
|
|
9
|
+
|
|
10
|
+
Without these placeholders, MyST treats the first file in TOC as index,
|
|
11
|
+
causing it to use routes like /blog instead of /blog/blog1. These routes
|
|
12
|
+
are reserved for SvelteKit landing pages. Placeholders ensure all actual
|
|
13
|
+
content files get proper slugs and are deleted post-build.
|
|
14
|
+
"""
|
|
15
|
+
from afterpython._io.yaml import read_yaml, write_yaml
|
|
16
|
+
from afterpython.tools.myst import _write_index_file
|
|
17
|
+
|
|
18
|
+
for content_type in CONTENT_TYPES:
|
|
19
|
+
if content_type == "doc":
|
|
20
|
+
continue # Doc doesn't need a placeholder index.md
|
|
21
|
+
|
|
22
|
+
content_path = ap.paths.afterpython_path / content_type
|
|
23
|
+
if not content_path.exists():
|
|
24
|
+
click.echo(
|
|
25
|
+
f"No content found in {content_path}, skip creating placeholder index.md"
|
|
26
|
+
)
|
|
27
|
+
continue
|
|
28
|
+
|
|
29
|
+
myst_yml_path = content_path / "myst.yml"
|
|
30
|
+
if not myst_yml_path.exists():
|
|
31
|
+
click.echo(
|
|
32
|
+
f"No myst.yml found in {content_path}, skip creating placeholder index.md"
|
|
33
|
+
)
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
content_path = ap.paths.afterpython_path / content_type
|
|
37
|
+
myst_yml_path = content_path / "myst.yml"
|
|
38
|
+
|
|
39
|
+
# Prepend index.md to TOC in myst.yml
|
|
40
|
+
if myst_yml_path.exists():
|
|
41
|
+
# Create placeholder index.md file
|
|
42
|
+
_write_index_file(content_type)
|
|
43
|
+
myst_data = read_yaml(myst_yml_path) or {}
|
|
44
|
+
toc = myst_data.get("project", {}).get("toc", [])
|
|
45
|
+
|
|
46
|
+
# Check if index.md is already first in TOC
|
|
47
|
+
if not toc or toc[0].get("file") != "index.md":
|
|
48
|
+
# Prepend index.md to TOC in myst.yml
|
|
49
|
+
new_toc = [{"file": "index.md"}, *toc]
|
|
50
|
+
if "project" not in myst_data:
|
|
51
|
+
myst_data["project"] = {}
|
|
52
|
+
myst_data["project"]["toc"] = new_toc
|
|
53
|
+
write_yaml(myst_yml_path, myst_data)
|
|
54
|
+
click.echo(f"Added index.md to TOC in {myst_yml_path}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def delete_placeholder_index_md_files():
|
|
58
|
+
"""Delete placeholder index.md and built index.html for non-doc content types.
|
|
59
|
+
|
|
60
|
+
These files were created pre-build to ensure proper slug generation. Now that
|
|
61
|
+
MyST has built the content with correct slugs, we delete them so SvelteKit
|
|
62
|
+
can own the landing page routes (/blog, /tutorial, etc.) in the project website.
|
|
63
|
+
"""
|
|
64
|
+
from afterpython._io.yaml import read_yaml, write_yaml
|
|
65
|
+
|
|
66
|
+
for content_type in CONTENT_TYPES:
|
|
67
|
+
if content_type == "doc":
|
|
68
|
+
continue # Doc doesn't need a placeholder index.md
|
|
69
|
+
|
|
70
|
+
content_path = ap.paths.afterpython_path / content_type
|
|
71
|
+
myst_yml_path = content_path / "myst.yml"
|
|
72
|
+
index_md = content_path / "index.md"
|
|
73
|
+
|
|
74
|
+
# Delete index.md from source
|
|
75
|
+
if index_md.exists():
|
|
76
|
+
index_md.unlink()
|
|
77
|
+
click.echo(f"Deleted: {index_md}")
|
|
78
|
+
|
|
79
|
+
# Delete index.html from build output
|
|
80
|
+
index_html = content_path / "_build" / "html" / "index.html"
|
|
81
|
+
if index_html.exists():
|
|
82
|
+
index_html.unlink()
|
|
83
|
+
click.echo(f"Deleted: {index_html}")
|
|
84
|
+
|
|
85
|
+
# Remove index.md from TOC in myst.yml
|
|
86
|
+
if myst_yml_path.exists():
|
|
87
|
+
myst_data = read_yaml(myst_yml_path) or {}
|
|
88
|
+
toc = myst_data.get("project", {}).get("toc", [])
|
|
89
|
+
|
|
90
|
+
# Remove index.md from TOC if it's the first entry
|
|
91
|
+
if toc and toc[0].get("file") == "index.md":
|
|
92
|
+
myst_data["project"]["toc"] = toc[1:]
|
|
93
|
+
write_yaml(myst_yml_path, myst_data)
|
|
94
|
+
click.echo(f"Removed index.md from TOC in {myst_yml_path}")
|