afterpython 0.0.1.dev2__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/.pre-commit-config.yaml +48 -0
- afterpython/__init__.py +13 -0
- afterpython/_io/toml.py +103 -0
- afterpython/_io/yaml.py +26 -0
- afterpython/_paths.py +39 -0
- afterpython/_typing.py +5 -0
- afterpython/builders/__init__.py +8 -0
- afterpython/builders/metadata.py +46 -0
- afterpython/builders/molab.py +99 -0
- afterpython/cli/__init__.py +4 -0
- afterpython/cli/commands/build.py +190 -0
- afterpython/cli/commands/bump.py +88 -0
- afterpython/cli/commands/check.py +36 -0
- afterpython/cli/commands/clean.py +84 -0
- afterpython/cli/commands/commit.py +34 -0
- afterpython/cli/commands/commitizen.py +32 -0
- afterpython/cli/commands/dev.py +146 -0
- afterpython/cli/commands/format.py +28 -0
- afterpython/cli/commands/init.py +98 -0
- afterpython/cli/commands/install.py +22 -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/start.py +140 -0
- afterpython/cli/commands/sync.py +108 -0
- afterpython/cli/commands/update.py +153 -0
- afterpython/cli/main.py +71 -0
- afterpython/const.py +5 -0
- afterpython/cz.toml +27 -0
- afterpython/main.py +25 -0
- afterpython/pcu.py +164 -0
- afterpython/templates/deploy-workflow-template.yml +90 -0
- afterpython/templates/release-workflow-template.yml +56 -0
- afterpython/templates/ruff-template.toml +32 -0
- afterpython/tools/_afterpython.py +65 -0
- afterpython/tools/_git.py +111 -0
- afterpython/tools/commitizen.py +53 -0
- afterpython/tools/github_actions.py +78 -0
- afterpython/tools/myst.py +164 -0
- afterpython/tools/pre_commit.py +62 -0
- afterpython/tools/pyproject.py +72 -0
- afterpython/utils.py +215 -0
- afterpython/version.py +39 -0
- afterpython-0.1.0.dev5.dist-info/METADATA +105 -0
- afterpython-0.1.0.dev5.dist-info/RECORD +47 -0
- afterpython-0.1.0.dev5.dist-info/WHEEL +4 -0
- afterpython-0.1.0.dev5.dist-info/entry_points.txt +4 -0
- afterpython/README.py +0 -158
- 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.dev2.dist-info/LICENSE +0 -201
- afterpython-0.0.1.dev2.dist-info/METADATA +0 -20
- afterpython-0.0.1.dev2.dist-info/RECORD +0 -9
- afterpython-0.0.1.dev2.dist-info/WHEEL +0 -4
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from afterpython._typing import NodeEnv
|
|
6
|
+
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from click.exceptions import Exit
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
command_kwargs = {
|
|
14
|
+
"add_help_option": False, # disable click's --help option so that myst start --help can work
|
|
15
|
+
"context_settings": dict(
|
|
16
|
+
ignore_unknown_options=True,
|
|
17
|
+
allow_extra_args=True,
|
|
18
|
+
),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command(**command_kwargs)
|
|
23
|
+
@click.pass_context
|
|
24
|
+
@click.option(
|
|
25
|
+
"--doc", is_flag=True, help="Start the development server for doc/ directory"
|
|
26
|
+
)
|
|
27
|
+
@click.option(
|
|
28
|
+
"--blog", is_flag=True, help="Start the development server for blog/ directory"
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--tutorial",
|
|
32
|
+
is_flag=True,
|
|
33
|
+
help="Start the development server for tutorial/ directory",
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--example",
|
|
37
|
+
is_flag=True,
|
|
38
|
+
help="Start the development server for example/ directory",
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"--guide", is_flag=True, help="Start the development server for guide/ directory"
|
|
42
|
+
)
|
|
43
|
+
def start(ctx, doc: bool, blog: bool, tutorial: bool, example: bool, guide: bool):
|
|
44
|
+
"""Simple wrapper for `myst start` for convenience"""
|
|
45
|
+
from afterpython.utils import find_node_env, has_content_for_myst
|
|
46
|
+
from afterpython.const import CONTENT_TYPES
|
|
47
|
+
from afterpython.utils import handle_passthrough_help
|
|
48
|
+
|
|
49
|
+
# Show both our options and myst's help and exit
|
|
50
|
+
handle_passthrough_help(
|
|
51
|
+
ctx,
|
|
52
|
+
["myst", "start"],
|
|
53
|
+
show_underlying=True,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
node_env: NodeEnv = find_node_env()
|
|
57
|
+
paths = ctx.obj["paths"]
|
|
58
|
+
|
|
59
|
+
if doc:
|
|
60
|
+
content_type = "doc"
|
|
61
|
+
elif blog:
|
|
62
|
+
content_type = "blog"
|
|
63
|
+
elif tutorial:
|
|
64
|
+
content_type = "tutorial"
|
|
65
|
+
elif example:
|
|
66
|
+
content_type = "example"
|
|
67
|
+
elif guide:
|
|
68
|
+
content_type = "guide"
|
|
69
|
+
else:
|
|
70
|
+
raise click.ClickException("No content type specified")
|
|
71
|
+
|
|
72
|
+
assert content_type in CONTENT_TYPES, f"Invalid content type: {content_type}"
|
|
73
|
+
|
|
74
|
+
# assert only one of the options is True
|
|
75
|
+
if sum([doc, blog, tutorial, example, guide]) != 1:
|
|
76
|
+
raise click.ClickException("Only one content type can be specified")
|
|
77
|
+
|
|
78
|
+
content_path = paths.afterpython_path / content_type
|
|
79
|
+
|
|
80
|
+
# Check if directory has any content files
|
|
81
|
+
if not has_content_for_myst(content_path):
|
|
82
|
+
click.echo(f"Skipping {content_type}/ (no content files found)")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
result = subprocess.run(
|
|
86
|
+
["myst", "start", *ctx.args], cwd=content_path, env=node_env, check=False
|
|
87
|
+
)
|
|
88
|
+
if result.returncode != 0:
|
|
89
|
+
raise Exit(result.returncode)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _run(ctx):
|
|
93
|
+
# Get the name of the function that called _run
|
|
94
|
+
import inspect
|
|
95
|
+
|
|
96
|
+
caller_frame = inspect.currentframe().f_back
|
|
97
|
+
command_name = (
|
|
98
|
+
caller_frame.f_code.co_name
|
|
99
|
+
) # e.g. doc, blog, tutorial, example, guide
|
|
100
|
+
|
|
101
|
+
# Create a new context for start that includes extra args for `myst start`
|
|
102
|
+
with start.make_context(
|
|
103
|
+
"start", [f"--{command_name}", *ctx.args], parent=ctx.parent
|
|
104
|
+
) as start_ctx:
|
|
105
|
+
return start.invoke(start_ctx)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@click.command(**command_kwargs)
|
|
109
|
+
@click.pass_context
|
|
110
|
+
def doc(ctx):
|
|
111
|
+
"""Start the development server for doc/ directory (equivalent to: start --doc)"""
|
|
112
|
+
_run(ctx)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@click.command(**command_kwargs)
|
|
116
|
+
@click.pass_context
|
|
117
|
+
def blog(ctx):
|
|
118
|
+
"""Start the development server for blog/ directory (equivalent to: start --blog)"""
|
|
119
|
+
_run(ctx)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@click.command(**command_kwargs)
|
|
123
|
+
@click.pass_context
|
|
124
|
+
def tutorial(ctx):
|
|
125
|
+
"""Start the development server for tutorial/ directory (equivalent to: start --tutorial)"""
|
|
126
|
+
_run(ctx)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@click.command(**command_kwargs)
|
|
130
|
+
@click.pass_context
|
|
131
|
+
def example(ctx):
|
|
132
|
+
"""Start the development server for example/ directory (equivalent to: start --example)"""
|
|
133
|
+
_run(ctx)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@click.command(**command_kwargs)
|
|
137
|
+
@click.pass_context
|
|
138
|
+
def guide(ctx):
|
|
139
|
+
"""Start the development server for guide/ directory (equivalent to: start --guide)"""
|
|
140
|
+
_run(ctx)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from pyproject_metadata import StandardMetadata
|
|
5
|
+
|
|
6
|
+
import afterpython as ap
|
|
7
|
+
from afterpython.utils import convert_author_name_to_id
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _sync_authors_yml(authors: list[tuple[str, str | None]]):
|
|
11
|
+
"""Sync authors.yml with authors in pyproject.toml"""
|
|
12
|
+
from afterpython.tools.myst import update_authors_yml
|
|
13
|
+
from afterpython._io.yaml import read_yaml
|
|
14
|
+
|
|
15
|
+
# read myst.yml from docs path to get "version"
|
|
16
|
+
doc_myst_yml = read_yaml(ap.paths.afterpython_path / "doc" / "myst.yml")
|
|
17
|
+
data = {
|
|
18
|
+
"version": doc_myst_yml["version"],
|
|
19
|
+
"project": {
|
|
20
|
+
"contributors": [
|
|
21
|
+
# NOTE: author is a tuple of (name, email), so author[0] is the name and author[1] is the email
|
|
22
|
+
{
|
|
23
|
+
"id": convert_author_name_to_id(str(author[0])),
|
|
24
|
+
"name": str(author[0]),
|
|
25
|
+
"email": str(author[1]),
|
|
26
|
+
# "github": ...
|
|
27
|
+
}
|
|
28
|
+
for author in authors
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
update_authors_yml(data)
|
|
33
|
+
click.echo("✓ Synced authors.yml with pyproject.toml")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@click.command()
|
|
37
|
+
def sync():
|
|
38
|
+
"""Sync between pyproject.toml+afterpython.toml and authors.yml+myst.yml files"""
|
|
39
|
+
from afterpython.const import CONTENT_TYPES
|
|
40
|
+
from afterpython.tools._afterpython import read_afterpython
|
|
41
|
+
from afterpython.tools.pyproject import read_metadata
|
|
42
|
+
from afterpython._io.toml import _from_tomlkit
|
|
43
|
+
from afterpython.tools.myst import update_myst_yml
|
|
44
|
+
|
|
45
|
+
pyproject: StandardMetadata = read_metadata()
|
|
46
|
+
project_name = str(pyproject.name)
|
|
47
|
+
afterpython = read_afterpython()
|
|
48
|
+
github_url = str(pyproject.urls.get("repository", ""))
|
|
49
|
+
company_name = str(_from_tomlkit(afterpython["company"]).get("name", ""))
|
|
50
|
+
company_url = str(_from_tomlkit(afterpython["company"]).get("url", ""))
|
|
51
|
+
website_url = str(_from_tomlkit(afterpython["website"]).get("url", ""))
|
|
52
|
+
authors = pyproject.authors
|
|
53
|
+
nav_bar = [
|
|
54
|
+
{
|
|
55
|
+
"title": content_type.capitalize() + "s",
|
|
56
|
+
"url": f"{website_url}/{content_type}",
|
|
57
|
+
}
|
|
58
|
+
if website_url
|
|
59
|
+
else ""
|
|
60
|
+
for content_type in CONTENT_TYPES
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
_sync_authors_yml(authors)
|
|
64
|
+
|
|
65
|
+
# update myst.yml files for each content type (e.g. doc/, blog/, tutorial/, example/, guide/)
|
|
66
|
+
# based on the current values in pyproject.toml and afterpython.toml
|
|
67
|
+
for content_type in CONTENT_TYPES:
|
|
68
|
+
path = ap.paths.afterpython_path / content_type
|
|
69
|
+
nav_bar_per_content_type = [
|
|
70
|
+
item for item in nav_bar if item["title"] != content_type.capitalize()
|
|
71
|
+
]
|
|
72
|
+
title = project_name + f"'s {content_type.capitalize()}"
|
|
73
|
+
data = {
|
|
74
|
+
"project": {
|
|
75
|
+
# using author ids defined in authors.yml
|
|
76
|
+
"authors": [
|
|
77
|
+
convert_author_name_to_id(str(author[0])) for author in authors
|
|
78
|
+
],
|
|
79
|
+
"venue": {
|
|
80
|
+
# NOTE: company's name is used as the venue title
|
|
81
|
+
"title": company_name,
|
|
82
|
+
"url": company_url,
|
|
83
|
+
},
|
|
84
|
+
"copyright": f"© {company_name or project_name} {datetime.now().year}. All rights reserved.",
|
|
85
|
+
"title": "",
|
|
86
|
+
"description": str(pyproject.description),
|
|
87
|
+
"keywords": list(map(str, pyproject.keywords)),
|
|
88
|
+
"github": github_url,
|
|
89
|
+
},
|
|
90
|
+
"site": {
|
|
91
|
+
"title": title,
|
|
92
|
+
"options": {
|
|
93
|
+
"logo_text": project_name,
|
|
94
|
+
"logo_url": website_url,
|
|
95
|
+
},
|
|
96
|
+
"nav": nav_bar_per_content_type,
|
|
97
|
+
"actions": [
|
|
98
|
+
{
|
|
99
|
+
"title": "⭐ Star",
|
|
100
|
+
"url": github_url,
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
update_myst_yml(data, path)
|
|
106
|
+
click.echo(
|
|
107
|
+
f"✓ Synced myst.yml in {path.name}/ with pyproject.toml and afterpython.toml"
|
|
108
|
+
)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from afterpython._typing import NodeEnv
|
|
6
|
+
from afterpython.pcu import Dependencies
|
|
7
|
+
|
|
8
|
+
import subprocess
|
|
9
|
+
import shutil
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from click.exceptions import Exit
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group()
|
|
16
|
+
def update():
|
|
17
|
+
"""Update website template, pyproject.toml dependencies, etc."""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@update.command()
|
|
22
|
+
@click.option(
|
|
23
|
+
"-u",
|
|
24
|
+
"--upgrade",
|
|
25
|
+
is_flag=True,
|
|
26
|
+
help="if enabled, the dependencies will be upgraded to the latest version",
|
|
27
|
+
)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--all",
|
|
30
|
+
is_flag=True,
|
|
31
|
+
help="update all versions in dependencies and tool configurations (e.g. pre-commit-config.yaml)",
|
|
32
|
+
)
|
|
33
|
+
def dependencies(upgrade: bool, all: bool):
|
|
34
|
+
"""Update pyproject.toml dependencies to the latest version"""
|
|
35
|
+
from afterpython.pcu import get_dependencies, update_dependencies
|
|
36
|
+
from afterpython.utils import has_uv
|
|
37
|
+
|
|
38
|
+
dependencies: Dependencies = get_dependencies()
|
|
39
|
+
has_at_least_one_update = False
|
|
40
|
+
for dep_type in dependencies:
|
|
41
|
+
if not len(dependencies[dep_type]):
|
|
42
|
+
continue
|
|
43
|
+
click.echo(f"- {dep_type} package(s):")
|
|
44
|
+
# category = extras or group name
|
|
45
|
+
for category, deps in dependencies[dep_type].items():
|
|
46
|
+
if dep_type in ["dependencies", "build-system"]:
|
|
47
|
+
category_name = ""
|
|
48
|
+
elif dep_type == "optional-dependencies":
|
|
49
|
+
category_name = f"extras: {category}"
|
|
50
|
+
elif dep_type == "dependency-groups":
|
|
51
|
+
category_name = f"group: {category}"
|
|
52
|
+
else:
|
|
53
|
+
raise ValueError(f"Invalid dependency type: {dep_type}")
|
|
54
|
+
for dep in deps:
|
|
55
|
+
msg = f" {dep.requirement.name}: {dep.min_version}"
|
|
56
|
+
has_update = dep.min_version != dep.latest_version
|
|
57
|
+
if has_update:
|
|
58
|
+
has_at_least_one_update = True
|
|
59
|
+
msg += (
|
|
60
|
+
f" → {click.style(dep.latest_version, fg='green', bold=True)}"
|
|
61
|
+
)
|
|
62
|
+
if category_name:
|
|
63
|
+
msg += f" ({category_name})"
|
|
64
|
+
click.echo(msg)
|
|
65
|
+
if not has_at_least_one_update:
|
|
66
|
+
click.echo(f"\n{click.style('No dependencies to update.', bold=True)}")
|
|
67
|
+
return
|
|
68
|
+
if has_at_least_one_update and upgrade:
|
|
69
|
+
update_dependencies(dependencies) # write the latest versions to pyproject.toml
|
|
70
|
+
if has_uv():
|
|
71
|
+
click.echo("Upgrading dependencies with uv...")
|
|
72
|
+
result = subprocess.run(["uv", "lock"], check=False)
|
|
73
|
+
if result.returncode != 0:
|
|
74
|
+
raise Exit(result.returncode)
|
|
75
|
+
result = subprocess.run(
|
|
76
|
+
["uv", "sync", "--all-extras", "--all-groups"], check=False
|
|
77
|
+
)
|
|
78
|
+
if result.returncode != 0:
|
|
79
|
+
raise Exit(result.returncode)
|
|
80
|
+
click.echo(
|
|
81
|
+
click.style(
|
|
82
|
+
"✓ All dependencies upgraded successfully 🎉", fg="green", bold=True
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
click.echo(
|
|
87
|
+
"uv not found. Updated pyproject.toml only (packages not installed)."
|
|
88
|
+
)
|
|
89
|
+
if all:
|
|
90
|
+
subprocess.run(["ap", "pre-commit", "autoupdate"])
|
|
91
|
+
click.echo("All pre-commit hooks updated successfully.")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
update.add_command(dependencies, name="deps") # alias for "dependencies"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@update.command()
|
|
98
|
+
@click.pass_context
|
|
99
|
+
@click.option(
|
|
100
|
+
"--no-backup",
|
|
101
|
+
is_flag=True,
|
|
102
|
+
help="if enabled, the existing project website template will not be backed up",
|
|
103
|
+
)
|
|
104
|
+
def website(ctx, no_backup: bool):
|
|
105
|
+
"""Update the project website template to the latest version"""
|
|
106
|
+
from afterpython.utils import find_node_env
|
|
107
|
+
|
|
108
|
+
website_template_repo = "AfterPythonOrg/project-website-template"
|
|
109
|
+
|
|
110
|
+
paths = ctx.obj["paths"]
|
|
111
|
+
website_path = paths.website_path
|
|
112
|
+
if not no_backup:
|
|
113
|
+
backup_path = website_path.parent / "_website.backup"
|
|
114
|
+
if backup_path.exists():
|
|
115
|
+
click.echo(f"Removing old backup at {backup_path}...")
|
|
116
|
+
shutil.rmtree(backup_path)
|
|
117
|
+
if website_path.exists():
|
|
118
|
+
click.echo(f"Creating backup at {backup_path}...")
|
|
119
|
+
shutil.copytree(
|
|
120
|
+
website_path,
|
|
121
|
+
backup_path,
|
|
122
|
+
ignore=shutil.ignore_patterns("node_modules", ".svelte-kit"),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Remove old template (but keep node_modules for faster reinstall)
|
|
126
|
+
if website_path.exists():
|
|
127
|
+
click.echo("Removing old project website template...")
|
|
128
|
+
shutil.rmtree(website_path)
|
|
129
|
+
website_path.mkdir(parents=True, exist_ok=True)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
click.echo("Updating the project website template...")
|
|
133
|
+
node_env: NodeEnv = find_node_env()
|
|
134
|
+
result = subprocess.run(
|
|
135
|
+
["pnpx", "degit", website_template_repo, str(website_path)],
|
|
136
|
+
env=node_env,
|
|
137
|
+
check=False,
|
|
138
|
+
)
|
|
139
|
+
if result.returncode != 0:
|
|
140
|
+
raise Exit(result.returncode)
|
|
141
|
+
result = subprocess.run(
|
|
142
|
+
["pnpm", "install"], cwd=website_path, env=node_env, check=False
|
|
143
|
+
)
|
|
144
|
+
if result.returncode != 0:
|
|
145
|
+
raise Exit(result.returncode)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
click.echo(f"✗ Error updating project website template: {e}", err=True)
|
|
148
|
+
if not no_backup:
|
|
149
|
+
click.echo("Restoring from backup...")
|
|
150
|
+
if website_path.exists():
|
|
151
|
+
shutil.rmtree(website_path)
|
|
152
|
+
shutil.copytree(backup_path, website_path)
|
|
153
|
+
raise
|
afterpython/cli/main.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from trogon import tui
|
|
6
|
+
from dotenv import load_dotenv, find_dotenv
|
|
7
|
+
|
|
8
|
+
import afterpython as ap
|
|
9
|
+
from afterpython import __version__
|
|
10
|
+
from afterpython.cli.commands.init import init
|
|
11
|
+
from afterpython.cli.commands.build import build
|
|
12
|
+
from afterpython.cli.commands.dev import dev
|
|
13
|
+
from afterpython.cli.commands.update import update
|
|
14
|
+
from afterpython.cli.commands.check import check
|
|
15
|
+
from afterpython.cli.commands.install import install
|
|
16
|
+
from afterpython.cli.commands.format import format
|
|
17
|
+
from afterpython.cli.commands.sync import sync
|
|
18
|
+
from afterpython.cli.commands.start import start, doc, blog, tutorial, example, guide
|
|
19
|
+
from afterpython.cli.commands.preview import preview
|
|
20
|
+
from afterpython.cli.commands.clean import clean
|
|
21
|
+
from afterpython.cli.commands.pre_commit import pre_commit
|
|
22
|
+
from afterpython.cli.commands.commitizen import commitizen
|
|
23
|
+
from afterpython.cli.commands.commit import commit
|
|
24
|
+
from afterpython.cli.commands.bump import bump
|
|
25
|
+
from afterpython.cli.commands.release import release
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@tui(command="tui", help="Open terminal UI")
|
|
29
|
+
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
|
|
30
|
+
@click.pass_context
|
|
31
|
+
@click.version_option(version=__version__)
|
|
32
|
+
def afterpython_group(ctx):
|
|
33
|
+
"""afterpython's CLI"""
|
|
34
|
+
load_dotenv(find_dotenv()) # Load environment variables from .env file
|
|
35
|
+
ctx.ensure_object(dict)
|
|
36
|
+
ctx.obj["paths"] = ap.paths
|
|
37
|
+
|
|
38
|
+
# Auto-sync before commands (except sync itself to avoid recursion)
|
|
39
|
+
if (
|
|
40
|
+
ctx.invoked_subcommand
|
|
41
|
+
and ctx.invoked_subcommand not in ["sync", "init"]
|
|
42
|
+
and os.getenv("AP_AUTO_SYNC", "0") == "1"
|
|
43
|
+
):
|
|
44
|
+
click.echo("Auto-syncing...")
|
|
45
|
+
subprocess.run(["ap", "sync"])
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
afterpython_group.add_command(init)
|
|
49
|
+
afterpython_group.add_command(build)
|
|
50
|
+
afterpython_group.add_command(dev)
|
|
51
|
+
afterpython_group.add_command(update)
|
|
52
|
+
afterpython_group.add_command(check)
|
|
53
|
+
afterpython_group.add_command(format)
|
|
54
|
+
afterpython_group.add_command(sync)
|
|
55
|
+
afterpython_group.add_command(start)
|
|
56
|
+
afterpython_group.add_command(install)
|
|
57
|
+
afterpython_group.add_command(doc)
|
|
58
|
+
afterpython_group.add_command(blog)
|
|
59
|
+
afterpython_group.add_command(tutorial)
|
|
60
|
+
afterpython_group.add_command(example)
|
|
61
|
+
afterpython_group.add_command(guide)
|
|
62
|
+
afterpython_group.add_command(preview)
|
|
63
|
+
afterpython_group.add_command(clean)
|
|
64
|
+
afterpython_group.add_command(pre_commit)
|
|
65
|
+
afterpython_group.add_command(pre_commit, name="pc")
|
|
66
|
+
afterpython_group.add_command(pre_commit, name="precommit")
|
|
67
|
+
afterpython_group.add_command(commitizen)
|
|
68
|
+
afterpython_group.add_command(commitizen, name="cz")
|
|
69
|
+
afterpython_group.add_command(commit)
|
|
70
|
+
afterpython_group.add_command(bump)
|
|
71
|
+
afterpython_group.add_command(release)
|
afterpython/const.py
ADDED
afterpython/cz.toml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[tool.commitizen]
|
|
2
|
+
name = "cz_conventional_commits"
|
|
3
|
+
# name = "cz_customize"
|
|
4
|
+
tag_format = "v$version"
|
|
5
|
+
version_scheme = "pep440"
|
|
6
|
+
version_provider = "uv"
|
|
7
|
+
update_changelog_on_bump = false
|
|
8
|
+
major_version_zero = true
|
|
9
|
+
|
|
10
|
+
# FIXME: commitizen can't even add a few custom types like "chore" and "wip", skip it for now
|
|
11
|
+
# [[tool.commitizen.customize.questions]]
|
|
12
|
+
# type = "list"
|
|
13
|
+
# name = "change_type"
|
|
14
|
+
# choices = [
|
|
15
|
+
# {value = "fix", name = "fix: A bug fix. Correlates with PATCH in SemVer"},
|
|
16
|
+
# {value = "feat", name = "feat: A new feature. Correlates with MINOR in SemVer"},
|
|
17
|
+
# {value = "docs", name = "docs: Documentation only changes"},
|
|
18
|
+
# {value = "style", name = "style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)"},
|
|
19
|
+
# {value = "refactor", name = "refactor: A code change that neither fixes a bug nor adds a feature"},
|
|
20
|
+
# {value = "perf", name = "perf: A code change that improves performance"},
|
|
21
|
+
# {value = "test", name = "test: Adding missing or correcting existing tests"},
|
|
22
|
+
# {value = "build", name = "build: Changes that affect the build system or external dependencies (example scopes: pip, docker, npm)"},
|
|
23
|
+
# {value = "ci", name = "ci: Changes to CI configuration files and scripts (example scopes: GitLabCI)"},
|
|
24
|
+
# {value = "chore", name = "chore: Other changes that don't modify src or test files"},
|
|
25
|
+
# {value = "wip", name = "wip: Work in progress"}
|
|
26
|
+
# ]
|
|
27
|
+
# message = "Select the type of change you are committing"
|
afterpython/main.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# import atexit
|
|
2
|
+
|
|
3
|
+
from afterpython.cli import afterpython_group
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def exit_cli():
|
|
7
|
+
"""Application Exitpoint."""
|
|
8
|
+
print("Cleanup actions here...")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_cli() -> None:
|
|
12
|
+
"""Application Entrypoint."""
|
|
13
|
+
# atexit.register(exit_cli)
|
|
14
|
+
afterpython_group(obj={})
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run_pcu() -> None:
|
|
18
|
+
"""Entrypoint for 'pcu' command (alias for 'ap update deps')."""
|
|
19
|
+
from afterpython.cli.commands.update import dependencies
|
|
20
|
+
|
|
21
|
+
dependencies()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
run_cli()
|