afterpython 0.3.5__py3-none-any.whl → 0.3.7__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.
@@ -1,7 +1,50 @@
1
1
  import click
2
2
 
3
3
  import afterpython as ap
4
- from afterpython.const import CONTENT_TYPES
4
+ from afterpython.const import CONTENT_TYPES, PLACEHOLDER_INDEX_MARKER
5
+
6
+
7
+ def _legacy_placeholder_content(content_type: str) -> str:
8
+ return f"""---
9
+ title: ← {content_type.capitalize()}
10
+ ---
11
+
12
+ This is a placeholder index page. The actual {content_type} landing page is rendered by SvelteKit.
13
+ """
14
+
15
+
16
+ def _legacy_welcome_content(content_type: str) -> str:
17
+ return f"""# Welcome to AfterPython
18
+
19
+ Welcome to your project's {content_type}! This is a starter page to help you get started.
20
+
21
+ ## Getting Started
22
+
23
+ Replace this placeholder content with your own. Here's what you can do:
24
+
25
+ - Creating new `.md` or `.ipynb` files in the `afterpython/{content_type}/` directory
26
+ - Writing in MyST Markdown format
27
+ - Adding images to the `afterpython/static/` directory and referencing them
28
+
29
+ ## Resources
30
+
31
+ - [AfterPython's Project Website](https://afterpython.afterpython.org)
32
+ - [MyST Markdown Guide](https://mystmd.org)
33
+
34
+ Start building your amazing project! 🚀
35
+ """
36
+
37
+
38
+ def _is_afterpython_placeholder_index(index_md, content_type: str) -> bool:
39
+ """Return True only for non-doc index.md files generated by AfterPython."""
40
+ content = index_md.read_text(encoding="utf-8", errors="ignore")
41
+ normalized_content = content.strip()
42
+
43
+ return (
44
+ PLACEHOLDER_INDEX_MARKER in content
45
+ or normalized_content == _legacy_placeholder_content(content_type).strip()
46
+ or normalized_content == _legacy_welcome_content(content_type).strip()
47
+ )
5
48
 
6
49
 
7
50
  def create_placeholder_index_md_files():
@@ -71,10 +114,23 @@ def delete_placeholder_index_md_files():
71
114
  myst_yml_path = content_path / "myst.yml"
72
115
  index_md = content_path / "index.md"
73
116
 
74
- # Delete index.md from source
117
+ # Delete index.md from source only when it is an AfterPython-generated placeholder.
75
118
  if index_md.exists():
119
+ if not _is_afterpython_placeholder_index(index_md, content_type):
120
+ raise click.ClickException(
121
+ f"Found existing 'afterpython/{content_type}/index.md'.\n"
122
+ f"\n"
123
+ f"'afterpython/{content_type}/index.md' is reserved for internal use by AfterPython.\n"
124
+ f"The '/{content_type}' route is owned by the SvelteKit listing page,\n"
125
+ f"so user-authored non-doc index.md files are not supported.\n"
126
+ f"\n"
127
+ f"Please rename this file to something else "
128
+ f"(e.g., '{content_type}_intro.md') and update the reference in "
129
+ f"afterpython/{content_type}/myst.yml."
130
+ )
131
+
76
132
  index_md.unlink()
77
- click.echo(f"Deleted: {index_md}")
133
+ click.echo(f"Deleted placeholder: {index_md}")
78
134
 
79
135
  # Delete index.html from build output
80
136
  index_html = content_path / "_build" / "html" / "index.html"
@@ -57,6 +57,11 @@ def build_metadata():
57
57
  website = _from_tomlkit(afterpython.get("website", {}))
58
58
  metadata_json["announcement"] = str(website.get("announcement", "")).strip()
59
59
  metadata_json["readme_py"] = _resolve_readme_py(website)
60
+ # Logos are served from the site root in _website/ (assets copied from
61
+ # afterpython/static/ to _website/static/), so we normalize to a leading-
62
+ # slash form that the Svelte side can hand straight to `asset()`.
63
+ metadata_json["logo"] = _resolve_logo_path(website.get("logo", ""))
64
+ metadata_json["logo_dark"] = _resolve_logo_path(website.get("logo_dark", ""))
60
65
 
61
66
  with open(build_path / "metadata.json", "w") as f:
62
67
  json.dump(metadata_json, f, indent=2)
@@ -64,6 +69,22 @@ def build_metadata():
64
69
  convert_paths()
65
70
 
66
71
 
72
+ def _resolve_logo_path(raw: object) -> str:
73
+ """Normalize a logo path from afterpython.toml for consumption by _website/.
74
+
75
+ normalize_static_path() returns either a bare filename ("logo.svg") or an
76
+ absolute path ("/some/dir/logo.svg"). The Svelte side passes this to
77
+ `asset()`, which expects a leading slash, so we prepend one when missing.
78
+ Returns "" when unset so the Svelte side can fall back to its default.
79
+ """
80
+ from afterpython.utils import normalize_static_path
81
+
82
+ path = normalize_static_path(str(raw))
83
+ if not path:
84
+ return ""
85
+ return path if path.startswith("/") else f"/{path}"
86
+
87
+
67
88
  def _resolve_readme_py(website_config: dict) -> str:
68
89
  """Resolve the readme_py field for metadata.json.
69
90
 
@@ -112,8 +112,6 @@ def dependencies(upgrade: bool, all_: bool, exclude: tuple[str, ...]):
112
112
  "uv not found. Updated pyproject.toml only (packages not installed)."
113
113
  )
114
114
  if upgrade and all_:
115
- subprocess.run(["ap", "pre-commit", "autoupdate"])
116
- click.echo("All pre-commit hooks updated successfully.")
117
115
  if has_pixi():
118
116
  click.echo("Upgrading dependencies with pixi...")
119
117
  pixi_exclude_args = ["--exclude", "python"]
@@ -137,6 +135,16 @@ def dependencies(upgrade: bool, all_: bool, exclude: tuple[str, ...]):
137
135
  bold=True,
138
136
  )
139
137
  )
138
+ result = subprocess.run(["ap", "pre-commit", "autoupdate"], check=False)
139
+ if result.returncode == 0:
140
+ click.echo("All pre-commit hooks updated successfully.")
141
+ else:
142
+ click.echo(
143
+ click.style(
144
+ "Skipped pre-commit autoupdate (no config or command failed).",
145
+ fg="yellow",
146
+ )
147
+ )
140
148
 
141
149
 
142
150
  update.add_command(dependencies, name="deps") # alias for "dependencies"
afterpython/const.py CHANGED
@@ -2,3 +2,4 @@ from afterpython._typing import tContentType
2
2
 
3
3
  NODEENV_VERSION = "24.11.0"
4
4
  CONTENT_TYPES: set[tContentType] = {"doc", "blog", "tutorial", "example", "guide"}
5
+ PLACEHOLDER_INDEX_MARKER = "<!-- afterpython:placeholder-index -->"
@@ -124,7 +124,7 @@ jobs:
124
124
  uses: actions/checkout@v6
125
125
 
126
126
  - name: Set up pixi
127
- uses: prefix-dev/setup-pixi@v0.9.5
127
+ uses: prefix-dev/setup-pixi@v0.9.6
128
128
  with:
129
129
  pixi-version: v0.67.2
130
130
  cache: true
afterpython/tools/myst.py CHANGED
@@ -9,8 +9,11 @@ if TYPE_CHECKING:
9
9
 
10
10
  import subprocess
11
11
 
12
+ import click
13
+
12
14
  import afterpython as ap
13
15
  from afterpython._io.yaml import read_yaml, write_yaml
16
+ from afterpython.const import PLACEHOLDER_INDEX_MARKER
14
17
  from afterpython.utils import deep_merge
15
18
 
16
19
 
@@ -113,7 +116,7 @@ def _write_index_file(content_type: tContentType):
113
116
  Note: This only creates the file. TOC modification is handled by the build process.
114
117
 
115
118
  Raises:
116
- FileExistsError: If index.md already exists (users shouldn't create this file).
119
+ click.ClickException: If a non-placeholder index.md already exists.
117
120
  """
118
121
  if content_type == "doc":
119
122
  return # Doc doesn't need a placeholder index.md
@@ -123,22 +126,29 @@ def _write_index_file(content_type: tContentType):
123
126
 
124
127
  # Check if user created an index.md file
125
128
  if index_file.exists():
126
- raise FileExistsError(
129
+ existing_content = index_file.read_text(encoding="utf-8", errors="ignore")
130
+ if PLACEHOLDER_INDEX_MARKER in existing_content:
131
+ return index_file
132
+
133
+ raise click.ClickException(
127
134
  f"\n"
128
- f"Found existing 'index.md' in afterpython/{content_type}/\n"
135
+ f"Found existing 'afterpython/{content_type}/index.md'\n"
129
136
  f"\n"
130
- f"The 'index.md' file is reserved for internal use by AfterPython.\n"
137
+ f"'afterpython/{content_type}/index.md' is reserved for internal use by AfterPython.\n"
131
138
  f"MyST treats the first file in TOC as index, which would conflict with\n"
132
- f"SvelteKit (project-website-template)'s landing page route (/{content_type}).\n"
139
+ f"the SvelteKit listing page route '/{content_type}', which is owned by\n"
140
+ f"AfterPython's project website.\n"
133
141
  f"\n"
134
142
  f"Please rename your file to something else (e.g., '{content_type}_intro.md')\n"
135
- f"and update the reference in afterpython/{content_type}/myst.yml"
143
+ f"and update the reference in afterpython/{content_type}/myst.yml."
136
144
  )
137
145
 
138
146
  index_content = f"""---
139
147
  title: ← {content_type.capitalize()}
140
148
  ---
141
149
 
150
+ {PLACEHOLDER_INDEX_MARKER}
151
+
142
152
  This is a placeholder index page. The actual {content_type} landing page is rendered by SvelteKit.
143
153
  """
144
154
  index_file.write_text(index_content)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: afterpython
3
- Version: 0.3.5
3
+ Version: 0.3.7
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
@@ -7,12 +7,12 @@ afterpython/builders/__init__.py,sha256=H7f9mFKXofWOAC3hkrxiFzvW5NziF8wYyduUBykH
7
7
  afterpython/builders/build_pdoc.py,sha256=rasjCtTah5GsZYokqU--Fp3bvTEf464ievENfOoANOk,2040
8
8
  afterpython/builders/content_json.py,sha256=-LTkeA8oA6XEudc2b1H7U0-UwcJlHn7xgICeweMYrG0,4805
9
9
  afterpython/builders/faq_json.py,sha256=mMfFepRBL_1QYO0tQ6orf5ESo2LVntk55nfFzencViM,1193
10
- afterpython/builders/index_md.py,sha256=SDVHnVftL4D1AA8jw2YvRdTem_Ho8v8DcrORVjVtW8A,3762
10
+ afterpython/builders/index_md.py,sha256=M4TNqrMl9_7HKiu11VOGiYvgffBQp3ZZbgLvlczZAVA,5998
11
11
  afterpython/builders/jupyter_notebook.py,sha256=8yjgwWBaHfEzvN90j4biR6-RaTUHeIQ59h4NKethHOk,9693
12
12
  afterpython/builders/llms_txt.py,sha256=9x-B1i1buTDJt4zGs8nugyto_pI5QIp7WUBcv-zpcso,9226
13
13
  afterpython/builders/marimo_notebook.py,sha256=cIah_ly67tTj4j1y7yL19MRQ9NN6vESmS2JKMsEuaA0,5386
14
14
  afterpython/builders/markdown.py,sha256=0ExfRqvOTWsgUkAVaGGNkiNDtNedRr7JR1yTGxpbsmA,8736
15
- afterpython/builders/metadata.py,sha256=mXTeEpxdwNFlaUwFYLLU1UXvSX2T-pzIxzssFdmnyko,2744
15
+ afterpython/builders/metadata.py,sha256=_GNXcrVM5mFvRGrPt6Liqy-D2jBzfIZz4nI1kOD2uJ4,3752
16
16
  afterpython/builders/url_md.py,sha256=uuW3YCyphd-Qvl9Yopv48utu6y1tyfJHNc25sS-O6Yw,3526
17
17
  afterpython/cli/__init__.py,sha256=G4AB-rllhmr7tURFc5cXJfkjZZ2bXtMBg3oy4zUGEFw,84
18
18
  afterpython/cli/commands/add.py,sha256=Ppp4msdvOwN9_-fdG5-b0-IV9p0Eef9CiIEU3srUjYk,1792
@@ -35,13 +35,13 @@ afterpython/cli/commands/remove.py,sha256=Fl79JSI9IdHyzYflocXxcxgBkBPkNFAW98Yuij
35
35
  afterpython/cli/commands/serve.py,sha256=L0LIhMGByQA_mNTOAGHQTQ8TcBfi5sgmO1_iGI24QgI,822
36
36
  afterpython/cli/commands/start.py,sha256=q4rUkIllOO8wJQ9ABtsx-dqkW1yC0vltAtSkiHN_GH4,4002
37
37
  afterpython/cli/commands/sync.py,sha256=Av67jsCBxhh1skixihPgNyvZMUsh_j0pQGmG4EoR_A8,6318
38
- afterpython/cli/commands/update.py,sha256=082K1rg-ctCuKB_E7g_p-UWkTA9hoC65yhkIrtLZQ18,9408
38
+ afterpython/cli/commands/update.py,sha256=s84yzIBsG1lQQ61gWkLLP-KzxZ3GIfwosb0Ge2mR7AI,9685
39
39
  afterpython/cli/main.py,sha256=-9eQiIrUViSvof-Ic2RHF3AVZharPSQh9sJ7tkhvf6Q,5042
40
- afterpython/const.py,sha256=B_yUBmHPyu98aOibwt7idWbhKNBBed0TeYXNBd8tPTk,157
40
+ afterpython/const.py,sha256=pZERFcKA60HsTob9kFCh34M_bZhyphHgLKUHab9pNFg,225
41
41
  afterpython/main.py,sha256=w0z6mTib3u4p2HxMrk-tIJlZtur7uRaqsbE0ewlYlSs,490
42
42
  afterpython/pcu.py,sha256=EoEhSzFZ49b5qc9KH2RLxIHSzY8YFM5uhtw07chcrr8,6093
43
43
  afterpython/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
- afterpython/templates/ci-workflow-template.yml,sha256=B3Rn5Ta1Bot1jKQ8ALBqQI8BDQ8mwIhpIA_umnXJ9oc,7115
44
+ afterpython/templates/ci-workflow-template.yml,sha256=-7BVbNArhaUNtJzxdfIX9scCQcba7LoWHPJXLBm-OxM,7115
45
45
  afterpython/templates/cz-template.toml,sha256=2qwbQLb0nbZaA63oWXFFzvzPNBsFZHATL1C2rdNdjjs,185
46
46
  afterpython/templates/dependabot-template.yml,sha256=CBQEgh8EsXw2X7IqzGtFI7lVT9liF6PW4DvGLB0X2LY,1136
47
47
  afterpython/templates/deploy-workflow-template.yml,sha256=o9blTGoM_KLB3wZUQcfdjxSOz08ldNJDfv1hA7FEbp8,2397
@@ -54,12 +54,12 @@ afterpython/tools/_git.py,sha256=aO-qDEQ4OevD_B74DYKK4nQKYeiaS1BXmYc8paJRfbQ,377
54
54
  afterpython/tools/branch_rules.py,sha256=ciQ2H2YBnoCZu_plyVeIHzf2FcAILRFhpjzOR2_IfJg,3159
55
55
  afterpython/tools/commitizen.py,sha256=ZO1qHmzUU0myd85tncv76WtIR7DB_AoQGSUvaf78Ohg,438
56
56
  afterpython/tools/github_actions.py,sha256=0xzOc41QF1PKagD1CW7BAt_Rugy0KCEx_KfcrPKVl3c,3168
57
- afterpython/tools/myst.py,sha256=VunqoTxWE6y9LXYPBh_JvLMW8kqGMGBkqYkhr_bLvj8,8269
57
+ afterpython/tools/myst.py,sha256=03m1bBKHxcDgruwrNjf7zAu5eH6TVDvsXSd936eIGF4,8586
58
58
  afterpython/tools/pre_commit.py,sha256=oNsrUkPI13rSSBSyhyTTKPnQGhDxLIPYhxu6kL0fDmo,1206
59
59
  afterpython/tools/pyproject.py,sha256=hz-DOf-pkGBF2m_RqEZipAaO-NDbsLxrrc65LWAIwLA,3698
60
60
  afterpython/utils.py,sha256=fVa3jIzCvhn7QA7N0M6d_pvojpPFrN-hae8trUYhPKU,11701
61
61
  afterpython/version.py,sha256=hihRV1S-gc1m46OaXD32ikkEkqzs-Gtq7AcAqOQV-wY,1603
62
- afterpython-0.3.5.dist-info/WHEEL,sha256=fWriCkzqm-pffF5af4gJC9iI5FMFaJTuN9UxxxzOmdY,81
63
- afterpython-0.3.5.dist-info/entry_points.txt,sha256=roa0PDgBueM1JPbRG0G2OSxzaN394OUL_oS9iEwVLBc,80
64
- afterpython-0.3.5.dist-info/METADATA,sha256=EJ8Q7Lihz45tBkcqE39KF3kx92cxdURpKdjmis6XKi4,6180
65
- afterpython-0.3.5.dist-info/RECORD,,
62
+ afterpython-0.3.7.dist-info/WHEEL,sha256=f5fWSvWsg5Knq5GWa6t1nJIug0Tqo69GqAWD_9LbBKw,81
63
+ afterpython-0.3.7.dist-info/entry_points.txt,sha256=roa0PDgBueM1JPbRG0G2OSxzaN394OUL_oS9iEwVLBc,80
64
+ afterpython-0.3.7.dist-info/METADATA,sha256=838abK-PJzhIAb2gK1tamV9PysHdyrZM5MwdEqFKyrk,6180
65
+ afterpython-0.3.7.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.11.14
2
+ Generator: uv 0.11.16
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any