afterpython 0.1.0__tar.gz → 0.1.0.dev5__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.
- {afterpython-0.1.0 → afterpython-0.1.0.dev5}/PKG-INFO +13 -39
- {afterpython-0.1.0 → afterpython-0.1.0.dev5}/README.md +3 -30
- afterpython-0.1.0/src/afterpython/templates/pre-commit-config-template.yaml → afterpython-0.1.0.dev5/afterpython/.pre-commit-config.yaml +1 -1
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/_io/toml.py +3 -5
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/_typing.py +2 -1
- afterpython-0.1.0.dev5/afterpython/builders/__init__.py +8 -0
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/builders/metadata.py +3 -7
- afterpython-0.1.0.dev5/afterpython/builders/molab.py +99 -0
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/__init__.py +1 -0
- afterpython-0.1.0.dev5/afterpython/cli/commands/build.py +190 -0
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/bump.py +0 -30
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/check.py +1 -1
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/clean.py +3 -4
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/commit.py +3 -21
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/commitizen.py +1 -1
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/dev.py +13 -80
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/format.py +1 -1
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/init.py +39 -48
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/install.py +2 -8
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/pre_commit.py +1 -1
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/preview.py +1 -1
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/start.py +4 -8
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/sync.py +21 -65
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/update.py +8 -31
- afterpython-0.1.0.dev5/afterpython/cli/main.py +71 -0
- afterpython-0.1.0.dev5/afterpython/const.py +5 -0
- afterpython-0.1.0.dev5/afterpython/cz.toml +27 -0
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/pcu.py +1 -3
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/templates/deploy-workflow-template.yml +7 -5
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/templates/release-workflow-template.yml +3 -3
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/templates/ruff-template.toml +1 -1
- afterpython-0.1.0.dev5/afterpython/tools/_afterpython.py +65 -0
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/tools/_git.py +2 -28
- afterpython-0.1.0.dev5/afterpython/tools/commitizen.py +53 -0
- afterpython-0.1.0.dev5/afterpython/tools/github_actions.py +78 -0
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/tools/myst.py +38 -82
- afterpython-0.1.0.dev5/afterpython/tools/pre_commit.py +62 -0
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/tools/pyproject.py +2 -40
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/utils.py +3 -87
- {afterpython-0.1.0 → afterpython-0.1.0.dev5}/pyproject.toml +43 -13
- afterpython-0.1.0/src/afterpython/builders/__init__.py +0 -17
- afterpython-0.1.0/src/afterpython/builders/content_json.py +0 -131
- afterpython-0.1.0/src/afterpython/builders/index_md.py +0 -94
- afterpython-0.1.0/src/afterpython/builders/jupyter_notebook.py +0 -293
- afterpython-0.1.0/src/afterpython/builders/markdown.py +0 -271
- afterpython-0.1.0/src/afterpython/cli/commands/add.py +0 -67
- afterpython-0.1.0/src/afterpython/cli/commands/build.py +0 -260
- afterpython-0.1.0/src/afterpython/cli/commands/init_branch_rules.py +0 -9
- afterpython-0.1.0/src/afterpython/cli/commands/lock.py +0 -23
- afterpython-0.1.0/src/afterpython/cli/commands/remove.py +0 -67
- afterpython-0.1.0/src/afterpython/cli/main.py +0 -132
- afterpython-0.1.0/src/afterpython/const.py +0 -4
- afterpython-0.1.0/src/afterpython/templates/ci-workflow-template.yml +0 -197
- afterpython-0.1.0/src/afterpython/templates/cz-template.toml +0 -7
- afterpython-0.1.0/src/afterpython/templates/dependabot-template.yml +0 -30
- afterpython-0.1.0/src/afterpython/tools/_afterpython.py +0 -128
- afterpython-0.1.0/src/afterpython/tools/branch_rules.py +0 -106
- afterpython-0.1.0/src/afterpython/tools/commitizen.py +0 -13
- afterpython-0.1.0/src/afterpython/tools/github_actions.py +0 -48
- afterpython-0.1.0/src/afterpython/tools/pre_commit.py +0 -37
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/__init__.py +0 -0
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/_io/yaml.py +0 -0
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/_paths.py +1 -1
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/cli/commands/release.py +0 -0
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/main.py +0 -0
- {afterpython-0.1.0/src → afterpython-0.1.0.dev5}/afterpython/version.py +0 -0
|
@@ -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]
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# AfterPython: Python Package Maintenance Toolkit and Project Website Generator
|
|
2
2
|
|
|
3
3
|
[](https://afterpython.org)
|
|
4
|
+
[](https://pixi.sh)
|
|
4
5
|

|
|
5
6
|
[](https://pypi.org/project/afterpython)
|
|
6
7
|

|
|
7
8
|
[](https://github.com/AfterPythonOrg/afterpython/discussions)
|
|
8
9
|
[](https://deepwiki.com/AfterPythonOrg/afterpython)
|
|
9
|
-
<!-- [](https://pixi.sh) -->
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
[MyST]: https://mystmd.org
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
[Scikit-Learn]: https://scikit-learn.org
|
|
22
22
|
[WebLLM]: https://webllm.mlc.ai/
|
|
23
23
|
[project-website-template]: https://github.com/AfterPythonOrg/project-website-template
|
|
24
|
-
[commitizen]: https://github.com/commitizen-tools/commitizen
|
|
25
24
|
[uv]: https://docs.astral.sh/uv/
|
|
26
25
|
[ruff]: https://docs.astral.sh/ruff/
|
|
27
26
|
|
|
@@ -40,54 +39,28 @@ First-time maintainers must learn multiple tools and concepts, e.g. [MyST], [Sem
|
|
|
40
39
|
|
|
41
40
|
## Core Features
|
|
42
41
|
- [x] Write content directly in [MyST Markdown] or [Jupyter Notebook]
|
|
43
|
-
- [x] Go from writing to
|
|
44
|
-
- [x] Centralize all your content in a modern,
|
|
45
|
-
- [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**
|
|
42
|
+
- [x] Go from writing to website deployment in minutes — no need to learn any of the underlying tools
|
|
43
|
+
- [x] Centralize all your content in a modern, unified project website — from documentation to blog posts
|
|
46
44
|
- [ ] Export content as PDF — for example, combine all blog posts into a single PDF file
|
|
47
45
|
- [ ] **⚡ Full-text search** across **ALL** your content in your website — docs, blogs, tutorials, everything
|
|
48
46
|
- [ ] **🤖 Embedded AI Chatbot** that answers questions directly using an in-browser LLM — at no cost
|
|
49
47
|
|
|
50
|
-
---
|
|
51
|
-
## Project Website
|
|
52
|
-
> The project website for `afterpython` is created using `afterpython` itself. See the [**website**](https://afterpython.afterpython.org).
|
|
53
|
-
<picture>
|
|
54
|
-
<source media="(prefers-color-scheme: dark)" srcset="afterpython/static/website-dark.png">
|
|
55
|
-
<source media="(prefers-color-scheme: light)" srcset="afterpython/static/website-light.png">
|
|
56
|
-
<img alt="Project Website" src="afterpython/static/website-light.png">
|
|
57
|
-
</picture>
|
|
58
|
-
|
|
59
|
-
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).
|
|
60
48
|
|
|
61
|
-
---
|
|
62
49
|
## Installation
|
|
63
50
|
```bash
|
|
64
51
|
# install afterpython as a dev dependency
|
|
65
52
|
uv add --dev afterpython
|
|
66
|
-
|
|
67
53
|
# initialize afterpython
|
|
68
54
|
ap init
|
|
69
55
|
```
|
|
70
56
|
|
|
71
57
|
|
|
72
|
-
---
|
|
73
|
-
## CLI Commands
|
|
74
|
-
```bash
|
|
75
|
-
# show all commands
|
|
76
|
-
ap --help
|
|
77
|
-
|
|
78
|
-
# or use terminal UI (TUI)
|
|
79
|
-
ap tui
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
---
|
|
84
58
|
## Tech Stack
|
|
85
59
|
- [MyST]
|
|
86
60
|
- [project-website-template]
|
|
87
61
|
- [pre-commit]
|
|
88
62
|
- [GitHub Actions]
|
|
89
63
|
- [pdoc]
|
|
90
|
-
- [commitizen]
|
|
91
64
|
- [uv]
|
|
92
65
|
- [ruff]
|
|
93
66
|
- [pagefind]
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
2
|
from typing import TYPE_CHECKING
|
|
4
3
|
|
|
5
4
|
if TYPE_CHECKING:
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
5
|
from tomlkit.toml_document import TOMLDocument
|
|
6
|
+
from pathlib import Path
|
|
9
7
|
|
|
10
8
|
import tomlkit
|
|
11
9
|
|
|
@@ -18,7 +16,7 @@ def _from_tomlkit(value):
|
|
|
18
16
|
- tomlkit containers → plain Python containers
|
|
19
17
|
- tomlkit primitives → plain Python primitives
|
|
20
18
|
"""
|
|
21
|
-
from tomlkit.items import
|
|
19
|
+
from tomlkit.items import Array, InlineTable, Table, AoT
|
|
22
20
|
|
|
23
21
|
if value is None:
|
|
24
22
|
return None
|
|
@@ -60,7 +58,7 @@ def _to_tomlkit(value):
|
|
|
60
58
|
- List of dicts → array of inline tables: [{key = val}]
|
|
61
59
|
- Nested dicts → nested tables with proper structure
|
|
62
60
|
"""
|
|
63
|
-
from tomlkit import
|
|
61
|
+
from tomlkit import inline_table, array
|
|
64
62
|
|
|
65
63
|
if value is None:
|
|
66
64
|
return None
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
2
|
from typing import TYPE_CHECKING
|
|
4
3
|
|
|
5
4
|
if TYPE_CHECKING:
|
|
@@ -7,10 +6,9 @@ if TYPE_CHECKING:
|
|
|
7
6
|
|
|
8
7
|
import json
|
|
9
8
|
|
|
10
|
-
import click
|
|
11
|
-
|
|
12
9
|
import afterpython as ap
|
|
13
10
|
|
|
11
|
+
|
|
14
12
|
build_path = ap.paths.build_path
|
|
15
13
|
|
|
16
14
|
|
|
@@ -25,22 +23,20 @@ def convert_paths():
|
|
|
25
23
|
markdown_text = metadata["description"]
|
|
26
24
|
|
|
27
25
|
# Replace with the correct paths
|
|
28
|
-
updated_markdown = markdown_text.replace("afterpython/static/", "/")
|
|
26
|
+
updated_markdown = markdown_text.replace("./afterpython/static/", "/")
|
|
29
27
|
|
|
30
28
|
# Write back to metadata.json
|
|
31
29
|
metadata["description"] = updated_markdown
|
|
32
30
|
with open(build_path / "metadata.json", "w") as f:
|
|
33
31
|
json.dump(metadata, f, indent=2)
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
print("Completed path conversion in metadata.json")
|
|
36
34
|
|
|
37
35
|
|
|
38
36
|
def build_metadata():
|
|
39
37
|
"""Build metadata.json using pyproject.toml"""
|
|
40
38
|
from afterpython.tools.pyproject import read_metadata
|
|
41
39
|
|
|
42
|
-
click.echo("Building metadata.json...")
|
|
43
|
-
|
|
44
40
|
metadata: StandardMetadata = read_metadata()
|
|
45
41
|
|
|
46
42
|
# Write to metadata.json
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from afterpython._typing import tContentType
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from pyproject_metadata import StandardMetadata
|
|
11
|
+
|
|
12
|
+
import afterpython as ap
|
|
13
|
+
from afterpython.tools.pyproject import read_metadata
|
|
14
|
+
from afterpython.const import CONTENT_TYPES
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _get_molab_badge() -> str:
|
|
18
|
+
return "https://marimo.io/molab-shield.svg"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _create_molab_url(github_url: str, content_path: Path):
|
|
22
|
+
"""Create a molab URL for a given content type and notebook path.
|
|
23
|
+
Args:
|
|
24
|
+
github_url: str, e.g. "https://github.com/AfterPythonOrg/afterpython"
|
|
25
|
+
content_path: str, e.g. "tutorial/test.ipynb"
|
|
26
|
+
"""
|
|
27
|
+
github_url = github_url.replace("https://github.com/", "github/")
|
|
28
|
+
return f"https://molab.marimo.io/{github_url}/blob/main/afterpython/{content_path.as_posix()}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def add_molab_badge_to_jupyter_notebooks(content_type: tContentType):
|
|
32
|
+
"""Add a badge markdown cell to the top of Jupyter notebooks in the given content type directory, e.g. tutorial/, blog/, etc.
|
|
33
|
+
# NOTE
|
|
34
|
+
Note that the jupyter notebooks need to exist in the github repository first.
|
|
35
|
+
If you have renamed a notebook, you need to push the changes to the github repository first for the badge to work.
|
|
36
|
+
"""
|
|
37
|
+
assert content_type.lower() in CONTENT_TYPES, (
|
|
38
|
+
f"Invalid content type: {content_type}"
|
|
39
|
+
)
|
|
40
|
+
metadata: StandardMetadata = read_metadata()
|
|
41
|
+
|
|
42
|
+
if "repository" not in metadata.urls:
|
|
43
|
+
return "Repository URL not found in [project.urls] in pyproject.toml, cannot add molab badge"
|
|
44
|
+
else:
|
|
45
|
+
github_url = metadata.urls["repository"]
|
|
46
|
+
|
|
47
|
+
path = ap.paths.afterpython_path / content_type.lower()
|
|
48
|
+
|
|
49
|
+
# iterate over all files in the path
|
|
50
|
+
for notebook_path in path.rglob("*.ipynb"):
|
|
51
|
+
# Skip files in _build directory
|
|
52
|
+
if "_build" in notebook_path.parts:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# Get path relative to afterpython/ (includes tutorial/ in the path)
|
|
56
|
+
content_path = notebook_path.relative_to(ap.paths.afterpython_path)
|
|
57
|
+
molab_url = _create_molab_url(github_url, content_path)
|
|
58
|
+
badge_md = f"[})]({molab_url})"
|
|
59
|
+
|
|
60
|
+
# Read the notebook
|
|
61
|
+
try:
|
|
62
|
+
# Read the notebook
|
|
63
|
+
with open(notebook_path) as f:
|
|
64
|
+
notebook = json.load(f)
|
|
65
|
+
except json.JSONDecodeError:
|
|
66
|
+
print(f"✗ Skipping {content_path} - invalid or empty JSON")
|
|
67
|
+
continue
|
|
68
|
+
except Exception as e:
|
|
69
|
+
print(f"✗ Error reading {content_path}: {e}")
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
# Validate notebook structure
|
|
73
|
+
if "cells" not in notebook:
|
|
74
|
+
print(f"✗ Skipping {content_path} - not a valid Jupyter notebook")
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
# Create a markdown cell with the badge
|
|
78
|
+
badge_cell = {
|
|
79
|
+
"cell_type": "markdown",
|
|
80
|
+
"id": "molab-badge-cell", # Unique ID for the badge cell
|
|
81
|
+
"metadata": {
|
|
82
|
+
"tags": ["molab", "hide-input"] # Optional: use MyST tags
|
|
83
|
+
},
|
|
84
|
+
"source": [badge_md],
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Check if first cell is already a badge cell (avoid duplicates)
|
|
88
|
+
if notebook["cells"] and notebook["cells"][0].get("id") == "molab-badge-cell":
|
|
89
|
+
# Update existing badge
|
|
90
|
+
notebook["cells"][0] = badge_cell
|
|
91
|
+
print(f"✓ Updated molab badge in: {content_path}")
|
|
92
|
+
else:
|
|
93
|
+
# Insert at the beginning
|
|
94
|
+
notebook["cells"].insert(0, badge_cell)
|
|
95
|
+
print(f"✓ Added molab badge to: {content_path}")
|
|
96
|
+
|
|
97
|
+
# Write back
|
|
98
|
+
with open(notebook_path, "w") as f:
|
|
99
|
+
json.dump(notebook, f, indent=1)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from afterpython._typing import NodeEnv
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
import subprocess
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
from click.exceptions import Exit
|
|
15
|
+
|
|
16
|
+
import afterpython as ap
|
|
17
|
+
from afterpython.utils import find_node_env
|
|
18
|
+
from afterpython.const import CONTENT_TYPES
|
|
19
|
+
from afterpython.builders import (
|
|
20
|
+
build_metadata,
|
|
21
|
+
add_molab_badge_to_jupyter_notebooks,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def prebuild():
|
|
26
|
+
def _check_initialized():
|
|
27
|
+
# Check if 'ap init' has been run
|
|
28
|
+
afterpython_toml = ap.paths.afterpython_path / "afterpython.toml"
|
|
29
|
+
if not afterpython_toml.exists():
|
|
30
|
+
raise click.ClickException(
|
|
31
|
+
"AfterPython is not initialized!\n"
|
|
32
|
+
"Run 'ap init' first to set up your project."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def _clean_build_directory():
|
|
36
|
+
print("Cleaning up build directory...")
|
|
37
|
+
build_path = ap.paths.build_path
|
|
38
|
+
if build_path.exists():
|
|
39
|
+
shutil.rmtree(build_path)
|
|
40
|
+
build_path.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
|
|
42
|
+
_check_initialized()
|
|
43
|
+
_clean_build_directory()
|
|
44
|
+
|
|
45
|
+
if os.getenv("AP_MOLAB_BADGE", "0") == "1":
|
|
46
|
+
for content_type in CONTENT_TYPES:
|
|
47
|
+
add_molab_badge_to_jupyter_notebooks(content_type)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def postbuild():
|
|
51
|
+
def _move_files(
|
|
52
|
+
source: Path,
|
|
53
|
+
destination: Path,
|
|
54
|
+
is_copy: bool = True,
|
|
55
|
+
ignore_copy: Callable[[str, list[str]], set[str]] | None = None,
|
|
56
|
+
):
|
|
57
|
+
"""
|
|
58
|
+
Move or copy files from source to destination.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
source: Source path
|
|
62
|
+
destination: Destination path
|
|
63
|
+
is_copy: If True, copy files (merge with existing). If False, move files (replace destination)
|
|
64
|
+
ignore_copy: Optional ignore function for copytree, takes (directory, contents) and returns set of names to ignore
|
|
65
|
+
"""
|
|
66
|
+
if not source.exists():
|
|
67
|
+
return
|
|
68
|
+
if is_copy:
|
|
69
|
+
shutil.copytree(source, destination, dirs_exist_ok=True, ignore=ignore_copy)
|
|
70
|
+
print(f"Copied: {source} to {destination}")
|
|
71
|
+
else:
|
|
72
|
+
# Remove destination if it exists (move will fail otherwise)
|
|
73
|
+
if destination.exists():
|
|
74
|
+
shutil.rmtree(destination)
|
|
75
|
+
shutil.move(str(source), str(destination))
|
|
76
|
+
print(f"Moved: {source} to {destination}")
|
|
77
|
+
|
|
78
|
+
website_static = ap.paths.website_path / "static"
|
|
79
|
+
website_static.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
|
|
81
|
+
for content_type in CONTENT_TYPES:
|
|
82
|
+
myst_build = ap.paths.afterpython_path / content_type / "_build"
|
|
83
|
+
afterpython_content_build = ap.paths.build_path / content_type
|
|
84
|
+
# Move myst builds from afterpython/{content_type}/_build to afterpython/_build/{content_type}
|
|
85
|
+
_move_files(myst_build, afterpython_content_build, is_copy=False)
|
|
86
|
+
# Copy afterpython/_build/{content_type}/html to afterpython/_website/static/{content_type}
|
|
87
|
+
_move_files(
|
|
88
|
+
afterpython_content_build / "html",
|
|
89
|
+
website_static / content_type,
|
|
90
|
+
is_copy=True,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Copy all files from afterpython/_build to afterpython/_website/static/
|
|
94
|
+
_move_files(
|
|
95
|
+
ap.paths.build_path,
|
|
96
|
+
website_static,
|
|
97
|
+
is_copy=True,
|
|
98
|
+
# ignore all content builds e.g. doc/, blog/, only their html/ files will be copied
|
|
99
|
+
ignore_copy=lambda dir, contents: [
|
|
100
|
+
name for name in contents if name in CONTENT_TYPES
|
|
101
|
+
],
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Copy all static files from afterpython/static/ to afterpython/_website/static/
|
|
105
|
+
_move_files(ap.paths.static_path, website_static, is_copy=True)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@click.command(
|
|
109
|
+
add_help_option=False, # disable click's --help option so that ap build --help can work
|
|
110
|
+
context_settings=dict(
|
|
111
|
+
ignore_unknown_options=True,
|
|
112
|
+
allow_extra_args=True,
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
@click.pass_context
|
|
116
|
+
@click.option(
|
|
117
|
+
"--dev",
|
|
118
|
+
is_flag=True,
|
|
119
|
+
hidden=True, # Internal flag used by `ap dev`, not exposed to users
|
|
120
|
+
help="Development build - only build metadata for the landing page, skip content and production builds",
|
|
121
|
+
)
|
|
122
|
+
@click.option(
|
|
123
|
+
"--execute", is_flag=True, help="Execute Jupyter notebooks for all content types"
|
|
124
|
+
)
|
|
125
|
+
def build(ctx, dev: bool, execute: bool):
|
|
126
|
+
"""Build the project website and all contents for production.
|
|
127
|
+
|
|
128
|
+
This command builds MyST content (doc/blog/tutorial/example/guide) and the SvelteKit website.
|
|
129
|
+
|
|
130
|
+
Any extra arguments are passed to the 'myst build --html' command for each content type.
|
|
131
|
+
See "myst build --help" for more details.
|
|
132
|
+
|
|
133
|
+
Use --execute to execute Jupyter notebooks for all content types.
|
|
134
|
+
"""
|
|
135
|
+
from afterpython.utils import has_content_for_myst
|
|
136
|
+
from afterpython.utils import handle_passthrough_help
|
|
137
|
+
|
|
138
|
+
# Show both our options and myst's help and exit
|
|
139
|
+
handle_passthrough_help(
|
|
140
|
+
ctx,
|
|
141
|
+
["myst", "build"],
|
|
142
|
+
show_underlying=True,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
paths = ctx.obj["paths"]
|
|
146
|
+
prebuild()
|
|
147
|
+
|
|
148
|
+
click.echo("Building metadata.json...")
|
|
149
|
+
build_metadata()
|
|
150
|
+
|
|
151
|
+
# myst's production build
|
|
152
|
+
if not dev:
|
|
153
|
+
node_env: NodeEnv = find_node_env()
|
|
154
|
+
for content_type in CONTENT_TYPES:
|
|
155
|
+
content_path = paths.afterpython_path / content_type
|
|
156
|
+
if not has_content_for_myst(content_path):
|
|
157
|
+
click.echo(f"Skipping {content_type}/ (no content files found)")
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
click.echo(f"Building {content_type}/...")
|
|
161
|
+
# NOTE: needs to set BASE_URL so that the project website can link to the content pages correctly at e.g. localhost:5173/doc
|
|
162
|
+
# BASE_PATH is set by the GitHub Actions workflow
|
|
163
|
+
base_path = os.getenv("BASE_PATH", "")
|
|
164
|
+
base_url = f"{base_path}/{content_type}"
|
|
165
|
+
build_env = {**node_env, "BASE_URL": base_url}
|
|
166
|
+
result = subprocess.run(
|
|
167
|
+
[
|
|
168
|
+
"myst",
|
|
169
|
+
"build",
|
|
170
|
+
"--html",
|
|
171
|
+
*(["--execute"] if execute else []),
|
|
172
|
+
*ctx.args,
|
|
173
|
+
],
|
|
174
|
+
cwd=content_path,
|
|
175
|
+
env=build_env,
|
|
176
|
+
check=False,
|
|
177
|
+
)
|
|
178
|
+
if result.returncode != 0:
|
|
179
|
+
raise Exit(result.returncode)
|
|
180
|
+
|
|
181
|
+
postbuild()
|
|
182
|
+
|
|
183
|
+
# website's production build
|
|
184
|
+
if not dev:
|
|
185
|
+
click.echo("Building project website...")
|
|
186
|
+
result = subprocess.run(
|
|
187
|
+
["pnpm", "build"], cwd=paths.website_path, env=node_env, check=False
|
|
188
|
+
)
|
|
189
|
+
if result.returncode != 0:
|
|
190
|
+
raise Exit(result.returncode)
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
|
-
from click.exceptions import Exit
|
|
5
|
-
|
|
6
|
-
from afterpython.utils import has_pixi
|
|
7
4
|
|
|
8
5
|
|
|
9
6
|
@click.command(
|
|
@@ -89,30 +86,3 @@ def bump(ctx, release: bool, pre: bool):
|
|
|
89
86
|
args = ["--prerelease", prerelease_type, *ctx.args]
|
|
90
87
|
|
|
91
88
|
subprocess.run(["ap", "cz", "bump", *args])
|
|
92
|
-
|
|
93
|
-
# Update pixi.lock after version bump (editable install requires lock refresh)
|
|
94
|
-
if has_pixi():
|
|
95
|
-
result = subprocess.run(["pixi", "install"], check=False)
|
|
96
|
-
if result.returncode != 0:
|
|
97
|
-
raise Exit(result.returncode)
|
|
98
|
-
|
|
99
|
-
# Check if pixi.lock was modified
|
|
100
|
-
status = subprocess.run(
|
|
101
|
-
["git", "diff", "--name-only", "pixi.lock"],
|
|
102
|
-
capture_output=True,
|
|
103
|
-
text=True,
|
|
104
|
-
check=False,
|
|
105
|
-
)
|
|
106
|
-
if status.stdout.strip():
|
|
107
|
-
# Stage and commit pixi.lock
|
|
108
|
-
subprocess.run(["git", "add", "pixi.lock"], check=True)
|
|
109
|
-
subprocess.run(
|
|
110
|
-
[
|
|
111
|
-
"git",
|
|
112
|
-
"commit",
|
|
113
|
-
"-m",
|
|
114
|
-
"build: update pixi.lock after version bump",
|
|
115
|
-
],
|
|
116
|
-
check=True,
|
|
117
|
-
)
|
|
118
|
-
click.echo("✓ Committed pixi.lock update")
|
|
@@ -13,7 +13,7 @@ from click.exceptions import Exit
|
|
|
13
13
|
)
|
|
14
14
|
@click.pass_context
|
|
15
15
|
def check(ctx):
|
|
16
|
-
"""
|
|
16
|
+
"""Simple wrapper for ruff check for convenience, always use the afterpython/ruff.toml if it exists"""
|
|
17
17
|
from afterpython.utils import handle_passthrough_help
|
|
18
18
|
|
|
19
19
|
# Show both our options and ruff's help and exit
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
2
|
from typing import TYPE_CHECKING
|
|
4
3
|
|
|
5
4
|
if TYPE_CHECKING:
|
|
@@ -11,8 +10,8 @@ import subprocess
|
|
|
11
10
|
import click
|
|
12
11
|
from click.exceptions import Exit
|
|
13
12
|
|
|
14
|
-
from afterpython.const import CONTENT_TYPES
|
|
15
13
|
from afterpython.utils import find_node_env, has_content_for_myst
|
|
14
|
+
from afterpython.const import CONTENT_TYPES
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
@click.command(
|
|
@@ -26,10 +25,10 @@ from afterpython.utils import find_node_env, has_content_for_myst
|
|
|
26
25
|
@click.option(
|
|
27
26
|
"--all",
|
|
28
27
|
is_flag=True,
|
|
29
|
-
help="
|
|
28
|
+
help='--all passed to "myst clean", also clean afterpython build directories (_build/ and _website/build/)',
|
|
30
29
|
)
|
|
31
30
|
def clean(ctx, all: bool):
|
|
32
|
-
"""Clean build
|
|
31
|
+
"""Clean the build directory"""
|
|
33
32
|
from afterpython.utils import handle_passthrough_help
|
|
34
33
|
|
|
35
34
|
# Show both our options and myst's help and exit
|