bakefile 0.0.4__py3-none-any.whl → 0.0.6__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.
- bake/__init__.py +9 -0
- bake/bakebook/bakebook.py +85 -0
- bake/bakebook/decorator.py +50 -0
- bake/bakebook/get.py +175 -0
- bake/cli/bake/__init__.py +3 -0
- bake/cli/bake/__main__.py +5 -0
- bake/cli/bake/main.py +74 -0
- bake/cli/bake/reinvocation.py +63 -0
- bake/cli/bakefile/__init__.py +3 -0
- bake/cli/bakefile/__main__.py +5 -0
- bake/cli/bakefile/add_inline.py +29 -0
- bake/cli/bakefile/export.py +212 -0
- bake/cli/bakefile/find_python.py +18 -0
- bake/cli/bakefile/init.py +56 -0
- bake/cli/bakefile/lint.py +77 -0
- bake/cli/bakefile/main.py +43 -0
- bake/cli/bakefile/uv.py +146 -0
- bake/cli/common/app.py +54 -0
- bake/cli/common/callback.py +13 -0
- bake/cli/common/context.py +145 -0
- bake/cli/common/exception_handler.py +57 -0
- bake/cli/common/obj.py +216 -0
- bake/cli/common/params.py +72 -0
- bake/cli/utils/__init__.py +0 -0
- bake/cli/utils/version.py +18 -0
- bake/manage/__init__.py +0 -0
- bake/manage/add_inline.py +71 -0
- bake/manage/find_python.py +210 -0
- bake/manage/lint.py +101 -0
- bake/manage/run_uv.py +88 -0
- bake/manage/write_bakefile.py +20 -0
- bake/py.typed +0 -0
- bake/samples/__init__.py +0 -0
- bake/samples/simple.py +8 -0
- bake/ui/__init__.py +11 -0
- bake/ui/console.py +58 -0
- bake/ui/logger/__init__.py +33 -0
- bake/ui/logger/capsys.py +158 -0
- bake/ui/logger/setup.py +53 -0
- bake/ui/logger/utils.py +215 -0
- bake/ui/params.py +5 -0
- bake/ui/run/__init__.py +5 -0
- bake/ui/run/run.py +546 -0
- bake/ui/run/script.py +74 -0
- bake/ui/run/splitter.py +249 -0
- bake/ui/run/uv.py +83 -0
- bake/ui/style.py +2 -0
- bake/utils/__init__.py +11 -0
- bake/utils/constants.py +21 -0
- {bakefile → bake/utils}/env.py +3 -1
- bake/utils/exceptions.py +17 -0
- {bakefile-0.0.4.dist-info → bakefile-0.0.6.dist-info}/METADATA +15 -2
- bakefile-0.0.6.dist-info/RECORD +63 -0
- {bakefile-0.0.4.dist-info → bakefile-0.0.6.dist-info}/WHEEL +2 -2
- bakefile-0.0.6.dist-info/entry_points.txt +5 -0
- bakelib/__init__.py +4 -0
- bakelib/space/__init__.py +0 -0
- bakelib/space/base.py +193 -0
- bakelib/space/python.py +80 -0
- bakelib/space/utils.py +118 -0
- bakefile/__init__.py +0 -13
- bakefile/cli/bake/__init__.py +0 -3
- bakefile/cli/bake/main.py +0 -127
- bakefile/cli/bake/resolve_bakebook.py +0 -103
- bakefile/cli/bake/utils.py +0 -25
- bakefile/cli/bakefile.py +0 -19
- bakefile/cli/utils/version.py +0 -9
- bakefile/exceptions.py +0 -9
- bakefile-0.0.4.dist-info/RECORD +0 -16
- bakefile-0.0.4.dist-info/entry_points.txt +0 -4
- {bakefile/cli/utils → bake/bakebook}/__init__.py +0 -0
- {bakefile → bake}/cli/__init__.py +0 -0
- /bakefile/py.typed → /bake/cli/common/__init__.py +0 -0
bakelib/space/base.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Annotated, Literal
|
|
3
|
+
|
|
4
|
+
import orjson
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from bake import Bakebook, Context, command
|
|
8
|
+
from bake.ui import console
|
|
9
|
+
|
|
10
|
+
from .utils import (
|
|
11
|
+
HOMWBREW_BIN,
|
|
12
|
+
LOCAL_BIN,
|
|
13
|
+
VENV_BIN,
|
|
14
|
+
PlatformType,
|
|
15
|
+
ToolInfo,
|
|
16
|
+
get_expected_paths,
|
|
17
|
+
get_platform,
|
|
18
|
+
remove_git_clean_candidates,
|
|
19
|
+
setup_brew,
|
|
20
|
+
setup_bun,
|
|
21
|
+
setup_uv,
|
|
22
|
+
setup_uv_tool,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseSpace(Bakebook):
|
|
27
|
+
def _no_implementation(self, ctx: Context | None = None, *args, **kwargs):
|
|
28
|
+
_ = ctx, args, kwargs
|
|
29
|
+
console.error("No implementation")
|
|
30
|
+
raise typer.Exit(1)
|
|
31
|
+
|
|
32
|
+
@command(help="Run linters and formatters")
|
|
33
|
+
def lint(self, ctx: Context) -> None:
|
|
34
|
+
ctx.run('bunx prettier@latest --write "**/*.{js,jsx,ts,tsx,css,json,json5,yaml,yml,md\'}"')
|
|
35
|
+
|
|
36
|
+
@command(help="Run unit tests")
|
|
37
|
+
def test(self, ctx: Context) -> None:
|
|
38
|
+
self._no_implementation(ctx)
|
|
39
|
+
|
|
40
|
+
@command(help="Run integration tests")
|
|
41
|
+
def test_integration(self, ctx: Context) -> None:
|
|
42
|
+
self._no_implementation(ctx)
|
|
43
|
+
|
|
44
|
+
@command(help="Run all tests")
|
|
45
|
+
def test_all(self, ctx: Context) -> None:
|
|
46
|
+
self._no_implementation(ctx)
|
|
47
|
+
|
|
48
|
+
@command(help="Clean gitignored files with optional exclusions")
|
|
49
|
+
def clean(
|
|
50
|
+
self,
|
|
51
|
+
ctx: Context,
|
|
52
|
+
exclude_patterns: Annotated[
|
|
53
|
+
list[str] | None,
|
|
54
|
+
typer.Option(
|
|
55
|
+
"--exclude-patterns",
|
|
56
|
+
"-e",
|
|
57
|
+
help="Patterns to exclude",
|
|
58
|
+
),
|
|
59
|
+
] = None,
|
|
60
|
+
use_default_excludes: Annotated[
|
|
61
|
+
bool,
|
|
62
|
+
typer.Option(
|
|
63
|
+
"--no-default-excludes",
|
|
64
|
+
help="Do not apply default exclude patterns",
|
|
65
|
+
is_flag=True,
|
|
66
|
+
),
|
|
67
|
+
] = False,
|
|
68
|
+
) -> None:
|
|
69
|
+
results = ctx.run("git clean -fdX -n", stream=False, dry_run=False, echo=True)
|
|
70
|
+
|
|
71
|
+
exclude_patterns: set[str] = set(exclude_patterns if exclude_patterns else [])
|
|
72
|
+
|
|
73
|
+
if not use_default_excludes:
|
|
74
|
+
exclude_patterns |= {".env", ".cache"}
|
|
75
|
+
|
|
76
|
+
console.err.print(f"Exclude pattens: {exclude_patterns}")
|
|
77
|
+
|
|
78
|
+
remove_git_clean_candidates(
|
|
79
|
+
git_clean_dry_run_output=results.stdout,
|
|
80
|
+
exclude_patterns=exclude_patterns,
|
|
81
|
+
dry_run=ctx.dry_run,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@command(help="Clean all gitignored files")
|
|
85
|
+
def clean_all(self, ctx: Context) -> None:
|
|
86
|
+
ctx.run("git clean -fdX")
|
|
87
|
+
|
|
88
|
+
def setup_tool_managers(self, ctx: Context, platform: PlatformType) -> None:
|
|
89
|
+
_ = platform
|
|
90
|
+
setup_brew(ctx)
|
|
91
|
+
|
|
92
|
+
def setup_tools(self, ctx: Context, platform: PlatformType) -> None:
|
|
93
|
+
_ = platform
|
|
94
|
+
setup_bun(ctx)
|
|
95
|
+
setup_uv(ctx)
|
|
96
|
+
setup_uv_tool(ctx)
|
|
97
|
+
|
|
98
|
+
def setup_project(self, ctx: Context) -> None:
|
|
99
|
+
ctx.run("uv run pre-commit install")
|
|
100
|
+
|
|
101
|
+
@command(help="Setup development environment")
|
|
102
|
+
def setup_dev(self, ctx: Context) -> None:
|
|
103
|
+
platform = get_platform()
|
|
104
|
+
console.echo(f"Detected platform: {platform}")
|
|
105
|
+
|
|
106
|
+
if platform != "macos":
|
|
107
|
+
console.warning(f"Platform '{platform}' is not supported. Running in dry-run mode.")
|
|
108
|
+
overridden_dry_run = True
|
|
109
|
+
else:
|
|
110
|
+
overridden_dry_run = ctx.dry_run
|
|
111
|
+
|
|
112
|
+
with ctx.override_dry_run(overridden_dry_run):
|
|
113
|
+
self.clean(ctx=ctx)
|
|
114
|
+
self.setup_tool_managers(ctx=ctx, platform=platform)
|
|
115
|
+
self.setup_tools(ctx=ctx, platform=platform)
|
|
116
|
+
self.setup_project(ctx=ctx)
|
|
117
|
+
|
|
118
|
+
def _assert_which_path(
|
|
119
|
+
self,
|
|
120
|
+
ctx: Context,
|
|
121
|
+
tool_name: str,
|
|
122
|
+
tool_info: ToolInfo,
|
|
123
|
+
) -> bool:
|
|
124
|
+
result = ctx.run(f"which {tool_name}", stream=False)
|
|
125
|
+
if ctx.dry_run:
|
|
126
|
+
return True
|
|
127
|
+
actual_path = Path(result.stdout.strip())
|
|
128
|
+
|
|
129
|
+
if actual_path in set(tool_info.expected_paths):
|
|
130
|
+
console.success(f"{tool_name}: {actual_path}")
|
|
131
|
+
return True
|
|
132
|
+
|
|
133
|
+
console.warning(f"{tool_name}: unexpected location (got {actual_path})")
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
def _get_tools(self) -> dict[str, ToolInfo]:
|
|
137
|
+
return {
|
|
138
|
+
# homebrew only
|
|
139
|
+
"bun": ToolInfo(expected_paths=get_expected_paths("bun", {HOMWBREW_BIN})),
|
|
140
|
+
# homebrew or venv
|
|
141
|
+
"uv": ToolInfo(expected_paths=get_expected_paths("uv", {HOMWBREW_BIN, VENV_BIN})),
|
|
142
|
+
# local or venv
|
|
143
|
+
"bakefile": ToolInfo(
|
|
144
|
+
expected_paths=get_expected_paths("bakefile", {LOCAL_BIN, VENV_BIN})
|
|
145
|
+
),
|
|
146
|
+
"pre-commit": ToolInfo(
|
|
147
|
+
expected_paths=get_expected_paths("pre-commit", {LOCAL_BIN, VENV_BIN})
|
|
148
|
+
),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@command(help="List development tools")
|
|
152
|
+
def tools(
|
|
153
|
+
self,
|
|
154
|
+
ctx: Context,
|
|
155
|
+
format: Annotated[
|
|
156
|
+
Literal["json", "names"],
|
|
157
|
+
typer.Option("--format", "-f", help="Output format"),
|
|
158
|
+
] = "json",
|
|
159
|
+
) -> None:
|
|
160
|
+
_ = ctx
|
|
161
|
+
tools = self._get_tools()
|
|
162
|
+
if format == "json":
|
|
163
|
+
output: dict[str, dict[str, str | None]] = {k: v.model_dump() for k, v in tools.items()}
|
|
164
|
+
console.echo(orjson.dumps(output, option=orjson.OPT_INDENT_2).decode())
|
|
165
|
+
else:
|
|
166
|
+
console.echo("\n".join(sorted(tools.keys())))
|
|
167
|
+
|
|
168
|
+
@command(help="Assert development environment setup")
|
|
169
|
+
def assert_setup_dev(
|
|
170
|
+
self,
|
|
171
|
+
ctx: Context,
|
|
172
|
+
skip_test: Annotated[
|
|
173
|
+
bool,
|
|
174
|
+
typer.Option(
|
|
175
|
+
"--skip-test",
|
|
176
|
+
"-s",
|
|
177
|
+
help="Skip running tests",
|
|
178
|
+
is_flag=True,
|
|
179
|
+
),
|
|
180
|
+
] = False,
|
|
181
|
+
) -> None:
|
|
182
|
+
tools = self._get_tools()
|
|
183
|
+
for tool_name, tool_info in tools.items():
|
|
184
|
+
self._assert_which_path(ctx, tool_name, tool_info)
|
|
185
|
+
|
|
186
|
+
self.lint(ctx)
|
|
187
|
+
if not skip_test:
|
|
188
|
+
self.test(ctx)
|
|
189
|
+
|
|
190
|
+
@command(help="Upgrade all dependencies")
|
|
191
|
+
def update(self, ctx: Context) -> None:
|
|
192
|
+
ctx.run("uv python upgrade")
|
|
193
|
+
ctx.run("uv tool upgrade --all")
|
bakelib/space/python.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from bake import Context, params
|
|
4
|
+
|
|
5
|
+
from .base import BaseSpace, ToolInfo
|
|
6
|
+
from .utils import VENV_BIN, get_expected_paths
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _get_python_version() -> str | None:
|
|
10
|
+
path = Path(".python-version")
|
|
11
|
+
if not path.exists():
|
|
12
|
+
return None
|
|
13
|
+
return path.read_text().strip()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PythonSpace(BaseSpace):
|
|
17
|
+
def _get_tools(self) -> dict[str, ToolInfo]:
|
|
18
|
+
tools = super()._get_tools()
|
|
19
|
+
tools["python"] = ToolInfo(
|
|
20
|
+
version=_get_python_version(),
|
|
21
|
+
expected_paths=list(get_expected_paths("python", {VENV_BIN})),
|
|
22
|
+
)
|
|
23
|
+
return tools
|
|
24
|
+
|
|
25
|
+
def lint(self, ctx: Context) -> None:
|
|
26
|
+
super().lint(ctx=ctx)
|
|
27
|
+
|
|
28
|
+
ctx.run(
|
|
29
|
+
"uv run toml-sort --sort-inline-arrays --in-place "
|
|
30
|
+
"--sort-first=project,dependency-groups pyproject.toml"
|
|
31
|
+
)
|
|
32
|
+
ctx.run("uv run ruff format --exit-non-zero-on-format .")
|
|
33
|
+
ctx.run("uv run ruff check --fix --exit-non-zero-on-fix .")
|
|
34
|
+
ctx.run("uv run ty check --error-on-warning --no-progress .")
|
|
35
|
+
ctx.run("uv run deptry .")
|
|
36
|
+
|
|
37
|
+
def _test(self, ctx: Context, *, tests_path: str, verbose: bool = False) -> None:
|
|
38
|
+
cmd = (
|
|
39
|
+
f"uv run pytest {tests_path} --cov=src --cov-report=html"
|
|
40
|
+
" --cov-report=term-missing --cov-report=xml"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if verbose:
|
|
44
|
+
cmd += " -s -v"
|
|
45
|
+
|
|
46
|
+
ctx.run(cmd)
|
|
47
|
+
|
|
48
|
+
def test_integration(
|
|
49
|
+
self,
|
|
50
|
+
ctx: Context,
|
|
51
|
+
verbose: params.verbose_bool = False,
|
|
52
|
+
) -> None:
|
|
53
|
+
integration_tests_path = "tests/integration/"
|
|
54
|
+
if Path(integration_tests_path).exists():
|
|
55
|
+
tests_path = integration_tests_path
|
|
56
|
+
self._test(ctx, tests_path=tests_path, verbose=verbose)
|
|
57
|
+
else:
|
|
58
|
+
self._no_implementation(ctx)
|
|
59
|
+
|
|
60
|
+
def test(self, ctx: Context) -> None:
|
|
61
|
+
unit_tests_path = "tests/unit/"
|
|
62
|
+
tests_path = unit_tests_path if Path(unit_tests_path).exists() else "tests/"
|
|
63
|
+
self._test(ctx, tests_path=tests_path)
|
|
64
|
+
|
|
65
|
+
def test_all(self, ctx: Context) -> None:
|
|
66
|
+
unit_tests_path = "tests/unit/"
|
|
67
|
+
if Path(unit_tests_path).exists():
|
|
68
|
+
tests_path = "tests/"
|
|
69
|
+
self._test(ctx, tests_path=tests_path)
|
|
70
|
+
else:
|
|
71
|
+
self._no_implementation(ctx)
|
|
72
|
+
|
|
73
|
+
def setup_project(self, ctx: Context) -> None:
|
|
74
|
+
super().setup_project(ctx=ctx)
|
|
75
|
+
ctx.run("uv sync --all-extras --all-groups --frozen")
|
|
76
|
+
|
|
77
|
+
def update(self, ctx: Context) -> None:
|
|
78
|
+
super().update(ctx=ctx)
|
|
79
|
+
ctx.run("uv lock --upgrade")
|
|
80
|
+
ctx.run("uv sync --all-extras --all-groups")
|
bakelib/space/utils.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import sys
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
import pathspec
|
|
8
|
+
from pathspec.patterns.gitignore.basic import GitIgnoreBasicPattern
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
from bake import Context
|
|
12
|
+
from bake.ui import console
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def setup_brew(ctx: Context) -> None:
|
|
16
|
+
ctx.run("brew update")
|
|
17
|
+
ctx.run("brew upgrade")
|
|
18
|
+
ctx.run("brew cleanup")
|
|
19
|
+
ctx.run("brew list")
|
|
20
|
+
ctx.run("brew leaves")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ToolInfo(BaseModel):
|
|
24
|
+
version: str | None = None
|
|
25
|
+
expected_paths: list[Path] = Field(default_factory=list, exclude=True)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Platform(Enum):
|
|
29
|
+
MACOS = "macos"
|
|
30
|
+
LINUX = "linux"
|
|
31
|
+
WINDOWS = "windows"
|
|
32
|
+
OTHER = "other"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
PlatformType = Literal["macos", "linux", "windows", "other"]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_platform() -> PlatformType:
|
|
39
|
+
if sys.platform == "darwin":
|
|
40
|
+
return Platform.MACOS.value
|
|
41
|
+
elif sys.platform == "linux":
|
|
42
|
+
return Platform.LINUX.value
|
|
43
|
+
elif sys.platform == "win32":
|
|
44
|
+
return Platform.WINDOWS.value
|
|
45
|
+
return Platform.OTHER.value
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def setup_uv(ctx: Context) -> None:
|
|
49
|
+
ctx.run("brew install uv")
|
|
50
|
+
ctx.run("uv python upgrade")
|
|
51
|
+
ctx.run("uv tool upgrade --all")
|
|
52
|
+
ctx.run("uv tool update-shell")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def setup_bun(ctx: Context) -> None:
|
|
56
|
+
ctx.run("brew install oven-sh/bun/bun")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def setup_uv_tool(ctx: Context) -> None:
|
|
60
|
+
ctx.run("uv tool install bakefile")
|
|
61
|
+
ctx.run("uv tool install pre-commit")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
HOMWBREW_BIN = Path("/opt/homebrew/bin")
|
|
65
|
+
LOCAL_BIN = Path.home() / ".local" / "bin"
|
|
66
|
+
VENV_BIN = Path.cwd() / ".venv" / "bin"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_expected_paths(tool: str, locations: set[Path]) -> list[Path]:
|
|
70
|
+
return [loc / tool for loc in locations]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _skip_msg(path: Path, suffix: str, dry_run: bool) -> None:
|
|
74
|
+
verb = "Would skip" if dry_run else "Skipping"
|
|
75
|
+
console.echo(f"[yellow]~[/yellow] {verb} {suffix}{path}")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _remove_msg(path: Path, dry_run: bool) -> None:
|
|
79
|
+
verb = "Would remove" if dry_run else "Removing"
|
|
80
|
+
console.echo(f"[red]-[/red] [dim]{verb}[/dim] {path}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _should_remove_path(path: Path, dry_run: bool) -> None:
|
|
84
|
+
_remove_msg(path, dry_run)
|
|
85
|
+
if dry_run:
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
if path.is_dir():
|
|
89
|
+
shutil.rmtree(path)
|
|
90
|
+
else:
|
|
91
|
+
path.unlink(missing_ok=True)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def remove_git_clean_candidates(
|
|
95
|
+
git_clean_dry_run_output: str, exclude_patterns: set[str], dry_run: bool
|
|
96
|
+
) -> None:
|
|
97
|
+
spec = pathspec.PathSpec.from_lines(
|
|
98
|
+
GitIgnoreBasicPattern,
|
|
99
|
+
exclude_patterns,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
for line in git_clean_dry_run_output.splitlines():
|
|
103
|
+
line = line.strip()
|
|
104
|
+
if not line.startswith("Would remove "):
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
rel_path = line.removeprefix("Would remove ").strip()
|
|
108
|
+
path = Path(rel_path)
|
|
109
|
+
|
|
110
|
+
if spec.match_file(rel_path):
|
|
111
|
+
_skip_msg(path, "", dry_run)
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
if path.is_dir() and (path / ".git").exists():
|
|
115
|
+
_skip_msg(path, "git repository ", dry_run)
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
_should_remove_path(path, dry_run)
|
bakefile/__init__.py
DELETED
bakefile/cli/bake/__init__.py
DELETED
bakefile/cli/bake/main.py
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import typer
|
|
2
|
-
from typer.main import get_command_from_info
|
|
3
|
-
|
|
4
|
-
from bakefile import env
|
|
5
|
-
from bakefile.cli.bake.resolve_bakebook import resolve_bakebook
|
|
6
|
-
from bakefile.cli.utils.version import version_callback
|
|
7
|
-
from bakefile.exceptions import BakebookError
|
|
8
|
-
|
|
9
|
-
from .utils import get_bakebook_args
|
|
10
|
-
|
|
11
|
-
rich_markup_mode = "rich" if env.should_use_colors() else None
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
bake_app = typer.Typer(
|
|
15
|
-
add_completion=False,
|
|
16
|
-
rich_markup_mode=rich_markup_mode,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
local_bake_app = typer.Typer(
|
|
20
|
-
add_completion=False,
|
|
21
|
-
rich_markup_mode=rich_markup_mode,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
GET_BAKEBOOK = "get_bakebook"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# Common option definitions (reused across callbacks and commands)
|
|
29
|
-
chdir_option = typer.Option(None, "-C", "--chdir", help="Change directory before running")
|
|
30
|
-
file_name_option = typer.Option("bakefile.py", "--file-name", "-f", help="Path to bakefile.py")
|
|
31
|
-
bakebook_name_option = typer.Option(
|
|
32
|
-
"bakebook", "--book-name", "-b", help="Name of bakebook object to retrieve"
|
|
33
|
-
)
|
|
34
|
-
version_option = typer.Option(
|
|
35
|
-
False,
|
|
36
|
-
"--version",
|
|
37
|
-
help="Show version and exit",
|
|
38
|
-
callback=version_callback,
|
|
39
|
-
is_eager=True,
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def show_help_if_no_command(ctx: typer.Context) -> None:
|
|
44
|
-
if ctx.invoked_subcommand is None:
|
|
45
|
-
typer.echo(ctx.get_help())
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@local_bake_app.callback(
|
|
49
|
-
invoke_without_command=True,
|
|
50
|
-
)
|
|
51
|
-
def local_bake_app_callback(
|
|
52
|
-
ctx: typer.Context,
|
|
53
|
-
_chdir: str = chdir_option,
|
|
54
|
-
_file_name: str = file_name_option,
|
|
55
|
-
_bakebook_name: str = bakebook_name_option,
|
|
56
|
-
_version: bool = version_option,
|
|
57
|
-
):
|
|
58
|
-
show_help_if_no_command(ctx)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
@bake_app.callback(
|
|
62
|
-
invoke_without_command=True,
|
|
63
|
-
)
|
|
64
|
-
def bake_app_callback(
|
|
65
|
-
ctx: typer.Context,
|
|
66
|
-
_chdir: str = chdir_option,
|
|
67
|
-
_file_name: str = file_name_option,
|
|
68
|
-
_bakebook_name: str = bakebook_name_option,
|
|
69
|
-
_version: bool = version_option,
|
|
70
|
-
):
|
|
71
|
-
show_help_if_no_command(ctx)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
@bake_app.command(
|
|
75
|
-
name=GET_BAKEBOOK,
|
|
76
|
-
hidden=True,
|
|
77
|
-
context_settings={
|
|
78
|
-
"allow_extra_args": True,
|
|
79
|
-
"allow_interspersed_args": False,
|
|
80
|
-
"ignore_unknown_options": True,
|
|
81
|
-
},
|
|
82
|
-
)
|
|
83
|
-
def get_bakebook(
|
|
84
|
-
chdir: str = chdir_option,
|
|
85
|
-
file_name: str = file_name_option,
|
|
86
|
-
bakebook_name: str = bakebook_name_option,
|
|
87
|
-
):
|
|
88
|
-
try:
|
|
89
|
-
return resolve_bakebook(file_name=file_name, bakebook_name=bakebook_name, chdir=chdir)
|
|
90
|
-
except BakebookError as e:
|
|
91
|
-
# Print error with context about what values were used
|
|
92
|
-
context_parts = []
|
|
93
|
-
context_parts.append(f"chdir={chdir!r}")
|
|
94
|
-
context_parts.append(f"file_name={file_name!r}")
|
|
95
|
-
context_parts.append(f"bakebook_name={bakebook_name!r}")
|
|
96
|
-
|
|
97
|
-
typer.secho("⚠️ ", fg="yellow", err=True, nl=False)
|
|
98
|
-
typer.secho(str(e), fg="yellow", bold=True, err=True)
|
|
99
|
-
if context_parts:
|
|
100
|
-
typer.secho(f"({', '.join(context_parts)})", fg="yellow", err=True)
|
|
101
|
-
return None
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def try_get_local_bake_app() -> typer.Typer | None:
|
|
105
|
-
args = get_bakebook_args()
|
|
106
|
-
|
|
107
|
-
for registered_command in bake_app.registered_commands:
|
|
108
|
-
if registered_command.name == GET_BAKEBOOK:
|
|
109
|
-
command = get_command_from_info(
|
|
110
|
-
registered_command,
|
|
111
|
-
pretty_exceptions_short=bake_app.pretty_exceptions_short,
|
|
112
|
-
rich_markup_mode=bake_app.rich_markup_mode,
|
|
113
|
-
)
|
|
114
|
-
with command.make_context(info_name=GET_BAKEBOOK, args=args) as ctx:
|
|
115
|
-
bakebook = command.invoke(ctx)
|
|
116
|
-
if bakebook is not None:
|
|
117
|
-
local_bake_app.add_typer(bakebook)
|
|
118
|
-
return local_bake_app
|
|
119
|
-
return None
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def main():
|
|
123
|
-
local_bake_app = try_get_local_bake_app()
|
|
124
|
-
if local_bake_app is None:
|
|
125
|
-
bake_app()
|
|
126
|
-
else:
|
|
127
|
-
local_bake_app()
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import importlib.util
|
|
2
|
-
import os
|
|
3
|
-
import pathlib
|
|
4
|
-
import sys
|
|
5
|
-
import types
|
|
6
|
-
from typing import Any, TypeVar
|
|
7
|
-
|
|
8
|
-
import typer
|
|
9
|
-
|
|
10
|
-
from bakefile.exceptions import BakebookError
|
|
11
|
-
|
|
12
|
-
T = TypeVar("T")
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def change_directory(path: str) -> None:
|
|
16
|
-
if not path or not path.strip():
|
|
17
|
-
raise BakebookError("Directory path cannot be empty")
|
|
18
|
-
dir_path = pathlib.Path(path)
|
|
19
|
-
if not dir_path.exists():
|
|
20
|
-
raise BakebookError(f"Directory not found: {path}")
|
|
21
|
-
if not dir_path.is_dir():
|
|
22
|
-
raise BakebookError(f"Not a directory: {path}")
|
|
23
|
-
os.chdir(dir_path)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def validate_file_name(file_name: str) -> bool:
|
|
27
|
-
if "/" in file_name or "\\" in file_name:
|
|
28
|
-
raise BakebookError(f"File name must not contain path separators: {file_name}")
|
|
29
|
-
if not file_name.endswith(".py"):
|
|
30
|
-
raise BakebookError(f"File name must end with .py: {file_name}")
|
|
31
|
-
return True
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def resolve_file_path(file_name: str) -> pathlib.Path:
|
|
35
|
-
path = pathlib.Path.cwd() / file_name
|
|
36
|
-
if not path.exists():
|
|
37
|
-
raise BakebookError(f"File not found: {file_name}")
|
|
38
|
-
return path
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def load_module(path: pathlib.Path) -> types.ModuleType:
|
|
42
|
-
module_name = "bakefile"
|
|
43
|
-
spec = importlib.util.spec_from_file_location(module_name, path)
|
|
44
|
-
if spec is None or spec.loader is None:
|
|
45
|
-
raise BakebookError(f"Failed to load: {path}")
|
|
46
|
-
|
|
47
|
-
module: types.ModuleType = importlib.util.module_from_spec(spec)
|
|
48
|
-
sys.modules[module_name] = module
|
|
49
|
-
spec.loader.exec_module(module)
|
|
50
|
-
return module
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# Update this is lowest python support is >= 3.12
|
|
54
|
-
# Use a generic type parameter for this function instead of a "TypeVar".
|
|
55
|
-
def validate_bakebook(bakebook: Any, bakebook_name: str, expected_type: type[T]) -> T:
|
|
56
|
-
"""Validate bakebook is of expected type and return it.
|
|
57
|
-
|
|
58
|
-
Parameters
|
|
59
|
-
----------
|
|
60
|
-
bakebook : Any
|
|
61
|
-
The bakebook object to validate
|
|
62
|
-
bakebook_name : str
|
|
63
|
-
Name of the bakebook variable (for error messages)
|
|
64
|
-
expected_type : type[T]
|
|
65
|
-
The expected type (e.g., typer.Typer)
|
|
66
|
-
|
|
67
|
-
Returns
|
|
68
|
-
-------
|
|
69
|
-
T
|
|
70
|
-
The validated bakebook
|
|
71
|
-
|
|
72
|
-
Raises
|
|
73
|
-
------
|
|
74
|
-
BakebookError
|
|
75
|
-
If bakebook is not of expected type
|
|
76
|
-
"""
|
|
77
|
-
if not isinstance(bakebook, expected_type):
|
|
78
|
-
raise BakebookError(
|
|
79
|
-
f"Bakebook '{bakebook_name}' must be a {expected_type.__name__}, "
|
|
80
|
-
f"got {type(bakebook).__name__}"
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
return bakebook
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def get_bakebook(module: types.ModuleType, bakebook_name: str, path: pathlib.Path) -> typer.Typer:
|
|
87
|
-
if not hasattr(module, bakebook_name):
|
|
88
|
-
raise BakebookError(f"No '{bakebook_name}' found in {path}")
|
|
89
|
-
bakebook = getattr(module, bakebook_name)
|
|
90
|
-
bakebook = validate_bakebook(
|
|
91
|
-
bakebook=bakebook, bakebook_name=bakebook_name, expected_type=typer.Typer
|
|
92
|
-
)
|
|
93
|
-
return bakebook
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def resolve_bakebook(file_name: str, bakebook_name: str, chdir: str | None = None) -> typer.Typer:
|
|
97
|
-
if chdir:
|
|
98
|
-
change_directory(chdir)
|
|
99
|
-
|
|
100
|
-
validate_file_name(file_name)
|
|
101
|
-
path = resolve_file_path(file_name)
|
|
102
|
-
module = load_module(path)
|
|
103
|
-
return get_bakebook(module=module, bakebook_name=bakebook_name, path=path)
|
bakefile/cli/bake/utils.py
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
|
-
|
|
4
|
-
import click
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def get_bakebook_args(
|
|
8
|
-
args: list[str] | None = None,
|
|
9
|
-
windows_expand_args: bool = True,
|
|
10
|
-
) -> list[str]:
|
|
11
|
-
# source from https://github.com/fastapi/typer/blob/b7f39eaad60141988f5d9a58df72c44d6128cd53/typer/core.py#L175-L185
|
|
12
|
-
|
|
13
|
-
if args is None:
|
|
14
|
-
args = sys.argv[1:]
|
|
15
|
-
|
|
16
|
-
# Covered in Click tests
|
|
17
|
-
if os.name == "nt" and windows_expand_args: # pragma: no cover
|
|
18
|
-
args = click.utils._expand_args(args)
|
|
19
|
-
else:
|
|
20
|
-
args = list(args)
|
|
21
|
-
|
|
22
|
-
non_get_bakebook_args = ["--help", "--version"]
|
|
23
|
-
|
|
24
|
-
args = [arg for arg in args if arg not in non_get_bakebook_args]
|
|
25
|
-
return args
|
bakefile/cli/bakefile.py
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import typer
|
|
2
|
-
|
|
3
|
-
from bakefile.cli.utils.version import version_callback
|
|
4
|
-
|
|
5
|
-
app = typer.Typer(add_completion=True)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@app.command()
|
|
9
|
-
def main(
|
|
10
|
-
version: bool = typer.Option(
|
|
11
|
-
False,
|
|
12
|
-
"--version",
|
|
13
|
-
help="Show version and exit",
|
|
14
|
-
callback=version_callback,
|
|
15
|
-
is_eager=True,
|
|
16
|
-
),
|
|
17
|
-
) -> None:
|
|
18
|
-
_ = version
|
|
19
|
-
typer.echo("hello world")
|