bakefile 0.0.10__tar.gz → 0.0.12__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.
Files changed (77) hide show
  1. {bakefile-0.0.10 → bakefile-0.0.12}/PKG-INFO +3 -1
  2. {bakefile-0.0.10 → bakefile-0.0.12}/pyproject.toml +10 -2
  3. bakefile-0.0.12/src/bake/cli/bake/main.py +116 -0
  4. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/bake/reinvocation.py +6 -3
  5. bakefile-0.0.12/src/bake/cli/bakefile/main.py +77 -0
  6. bakefile-0.0.12/src/bake/cli/common/app.py +56 -0
  7. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/common/context.py +4 -0
  8. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/common/obj.py +4 -3
  9. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/common/params.py +10 -2
  10. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/manage/lint.py +10 -7
  11. bakefile-0.0.12/src/bake/ui/console.py +122 -0
  12. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/ui/run/run.py +52 -13
  13. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/ui/run/uv.py +2 -10
  14. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/utils/__init__.py +8 -2
  15. bakefile-0.0.12/src/bake/utils/settings.py +25 -0
  16. {bakefile-0.0.10 → bakefile-0.0.12}/src/bakelib/__init__.py +2 -0
  17. bakefile-0.0.12/src/bakelib/refreshable_cache/__init__.py +17 -0
  18. bakefile-0.0.12/src/bakelib/refreshable_cache/cache.py +250 -0
  19. bakefile-0.0.12/src/bakelib/refreshable_cache/exceptions.py +2 -0
  20. {bakefile-0.0.10 → bakefile-0.0.12}/src/bakelib/space/base.py +26 -12
  21. bakefile-0.0.12/src/bakelib/space/lib.py +161 -0
  22. {bakefile-0.0.10 → bakefile-0.0.12}/src/bakelib/space/python.py +12 -0
  23. bakefile-0.0.12/src/bakelib/space/python_lib.py +77 -0
  24. {bakefile-0.0.10 → bakefile-0.0.12}/src/bakelib/space/utils.py +10 -0
  25. bakefile-0.0.10/src/bake/cli/bake/main.py +0 -74
  26. bakefile-0.0.10/src/bake/cli/bakefile/main.py +0 -43
  27. bakefile-0.0.10/src/bake/cli/common/app.py +0 -54
  28. bakefile-0.0.10/src/bake/cli/common/callback.py +0 -13
  29. bakefile-0.0.10/src/bake/ui/console.py +0 -58
  30. bakefile-0.0.10/src/bake/utils/env.py +0 -10
  31. {bakefile-0.0.10 → bakefile-0.0.12}/README.md +0 -0
  32. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/__init__.py +0 -0
  33. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/bakebook/__init__.py +0 -0
  34. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/bakebook/bakebook.py +0 -0
  35. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/bakebook/decorator.py +0 -0
  36. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/bakebook/get.py +0 -0
  37. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/__init__.py +0 -0
  38. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/bake/__init__.py +0 -0
  39. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/bake/__main__.py +0 -0
  40. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/bakefile/__init__.py +0 -0
  41. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/bakefile/__main__.py +0 -0
  42. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/bakefile/add_inline.py +0 -0
  43. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/bakefile/export.py +0 -0
  44. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/bakefile/find_python.py +0 -0
  45. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/bakefile/init.py +0 -0
  46. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/bakefile/lint.py +0 -0
  47. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/bakefile/uv.py +0 -0
  48. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/common/__init__.py +0 -0
  49. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/common/exception_handler.py +0 -0
  50. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/utils/__init__.py +0 -0
  51. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/cli/utils/version.py +0 -0
  52. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/manage/__init__.py +0 -0
  53. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/manage/add_inline.py +0 -0
  54. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/manage/find_python.py +0 -0
  55. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/manage/run_uv.py +0 -0
  56. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/manage/write_bakefile.py +0 -0
  57. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/py.typed +0 -0
  58. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/samples/__init__.py +0 -0
  59. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/samples/simple.py +0 -0
  60. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/ui/__init__.py +0 -0
  61. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/ui/logger/__init__.py +0 -0
  62. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/ui/logger/capsys.py +0 -0
  63. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/ui/logger/setup.py +0 -0
  64. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/ui/logger/utils.py +0 -0
  65. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/ui/params.py +0 -0
  66. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/ui/run/__init__.py +0 -0
  67. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/ui/run/script.py +0 -0
  68. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/ui/run/splitter.py +0 -0
  69. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/ui/style.py +0 -0
  70. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/utils/constants.py +0 -0
  71. {bakefile-0.0.10 → bakefile-0.0.12}/src/bake/utils/exceptions.py +0 -0
  72. {bakefile-0.0.10 → bakefile-0.0.12}/src/bakelib/environ/__init__.py +0 -0
  73. {bakefile-0.0.10 → bakefile-0.0.12}/src/bakelib/environ/bakebook.py +0 -0
  74. {bakefile-0.0.10 → bakefile-0.0.12}/src/bakelib/environ/base.py +0 -0
  75. {bakefile-0.0.10 → bakefile-0.0.12}/src/bakelib/environ/get_bakebook.py +0 -0
  76. {bakefile-0.0.10 → bakefile-0.0.12}/src/bakelib/environ/presets.py +0 -0
  77. {bakefile-0.0.10 → bakefile-0.0.12}/src/bakelib/space/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bakefile
3
- Version: 0.0.10
3
+ Version: 0.0.12
4
4
  Summary: Add your description here
5
5
  Author: Wisaroot Lertthaweedech
6
6
  Author-email: Wisaroot Lertthaweedech <l.wisaroot@gmail.com>
@@ -18,7 +18,9 @@ Requires-Dist: tomli>=2.0.0 ; python_full_version < '3.11'
18
18
  Requires-Dist: ty>=0.0.8
19
19
  Requires-Dist: typer>=0.21.0
20
20
  Requires-Dist: uv>=0.9.20
21
+ Requires-Dist: keyring>=25.7.0 ; extra == 'lib'
21
22
  Requires-Dist: pathspec>=1.0.3 ; extra == 'lib'
23
+ Requires-Dist: tenacity>=9.1.2 ; extra == 'lib'
22
24
  Requires-Python: >=3.10
23
25
  Provides-Extra: lib
24
26
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bakefile"
3
- version = "0.0.10" # use git tag
3
+ version = "0.0.12" # use git tag
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -26,7 +26,9 @@ dependencies = [
26
26
 
27
27
  [project.optional-dependencies]
28
28
  lib = [
29
- "pathspec>=1.0.3"
29
+ "keyring>=25.7.0",
30
+ "pathspec>=1.0.3",
31
+ "tenacity>=9.1.2"
30
32
  ]
31
33
 
32
34
  [project.scripts]
@@ -79,5 +81,11 @@ select = [
79
81
  module-name = ["bake", "bakelib"]
80
82
  module-root = "src"
81
83
 
84
+ [[tool.uv.index]]
85
+ name = "testpypi"
86
+ url = "https://test.pypi.org/simple/"
87
+ publish-url = "https://test.pypi.org/legacy/"
88
+ explicit = true
89
+
82
90
  [tool.uv.sources]
83
91
  python-package = {path = "examples/python-package", editable = true}
@@ -0,0 +1,116 @@
1
+ import sys
2
+ from collections.abc import Callable
3
+ from contextlib import contextmanager
4
+ from pathlib import Path
5
+
6
+ import typer
7
+
8
+ from bake.cli.bake.reinvocation import _reinvoke_with_detected_python
9
+ from bake.cli.common.app import (
10
+ add_completion,
11
+ call_app_with_chdir,
12
+ rich_markup_mode,
13
+ show_help_if_no_command,
14
+ )
15
+ from bake.cli.common.context import Context
16
+ from bake.cli.common.obj import BakefileObject, get_bakefile_object, is_bakebook_optional
17
+ from bake.cli.common.params import (
18
+ bakebook_name_option,
19
+ chdir_option,
20
+ dry_run_option,
21
+ file_name_option,
22
+ is_chain_commands_option,
23
+ verbosity_option,
24
+ version_option,
25
+ )
26
+ from bake.ui import console
27
+ from bake.utils.constants import (
28
+ DEFAULT_BAKEBOOK_NAME,
29
+ DEFAULT_CHDIR,
30
+ DEFAULT_FILE_NAME,
31
+ DEFAULT_IS_CHAIN_COMMAND,
32
+ )
33
+ from bake.utils.settings import bake_settings
34
+
35
+
36
+ def bake_app_callback_with_obj(obj: BakefileObject) -> Callable[..., None]:
37
+ def bake_app_callback(
38
+ ctx: Context,
39
+ _chdir: chdir_option = DEFAULT_CHDIR,
40
+ _file_name: file_name_option = DEFAULT_FILE_NAME,
41
+ _bakebook_name: bakebook_name_option = DEFAULT_BAKEBOOK_NAME,
42
+ _version: version_option = False,
43
+ _is_chain_commands: is_chain_commands_option = DEFAULT_IS_CHAIN_COMMAND,
44
+ _verbosity: verbosity_option = 0,
45
+ _dry_run: dry_run_option = False,
46
+ ):
47
+ ctx.obj = obj
48
+ show_help_if_no_command(ctx)
49
+
50
+ return bake_app_callback
51
+
52
+
53
+ @contextmanager
54
+ def set_argv(argv: list[str]):
55
+ original = sys.argv.copy()
56
+ sys.argv = argv
57
+ try:
58
+ yield
59
+ finally:
60
+ sys.argv = original
61
+
62
+
63
+ def _run_chain_commands(
64
+ remaining_args: list[str], prog_name: str, bake_app: typer.Typer, bakefile_path: Path | None
65
+ ) -> int:
66
+ exit_code = 0
67
+ for cmd in remaining_args:
68
+ try:
69
+ with set_argv([prog_name, cmd]):
70
+ console.cmd(" ".join(sys.argv))
71
+ call_app_with_chdir(app=bake_app, bakefile_path=bakefile_path, prog_name=prog_name)
72
+ except SystemExit as e:
73
+ if e.code is not None and e.code != 0:
74
+ exit_code = e.code if isinstance(e.code, int) else 1
75
+ break
76
+ return exit_code
77
+
78
+
79
+ def main():
80
+ bakefile_obj = get_bakefile_object(rich_markup_mode=rich_markup_mode)
81
+ bakefile_obj.setup_logging()
82
+ bakefile_obj.resolve_bakefile_path()
83
+
84
+ # Check re-invocation with resolved bakefile path
85
+ # If re-invocation happens, process is replaced and we don't return
86
+ _reinvoke_with_detected_python(bakefile_obj.bakefile_path)
87
+ # If returned above, we're in the correct Python
88
+
89
+ bakefile_obj.get_bakebook(
90
+ allow_missing=is_bakebook_optional(remaining_args=bakefile_obj.remaining_args)
91
+ )
92
+
93
+ bakefile_obj.warn_if_no_bakebook(color_echo=bake_settings.should_use_colors())
94
+
95
+ bake_app = typer.Typer(
96
+ add_completion=add_completion,
97
+ rich_markup_mode=rich_markup_mode,
98
+ )
99
+
100
+ callback = bake_app_callback_with_obj(obj=bakefile_obj)
101
+ bake_app.callback(invoke_without_command=True)(callback)
102
+
103
+ prog_name = "bake"
104
+
105
+ if bakefile_obj.bakebook is not None:
106
+ bake_app.add_typer(bakefile_obj.bakebook._app)
107
+
108
+ if bakefile_obj.is_chain_commands and bakefile_obj.remaining_args:
109
+ exit_code = _run_chain_commands(
110
+ remaining_args=bakefile_obj.remaining_args,
111
+ prog_name=prog_name,
112
+ bake_app=bake_app,
113
+ bakefile_path=bakefile_obj.bakefile_path,
114
+ )
115
+ raise SystemExit(exit_code)
116
+ call_app_with_chdir(app=bake_app, bakefile_path=bakefile_obj.bakefile_path, prog_name=prog_name)
@@ -4,7 +4,7 @@ import subprocess
4
4
  import sys
5
5
  from pathlib import Path
6
6
 
7
- from bake.utils.env import _BAKE_REINVOKED
7
+ from bake.utils.settings import ENV__BAKE_REINVOKED, bake_settings
8
8
 
9
9
  logger = logging.getLogger(__name__)
10
10
 
@@ -21,8 +21,11 @@ def _reinvoke_with_detected_python(bakefile_path: Path | None) -> None:
21
21
  Returns:
22
22
  None. Either calls os.execve() (replaces process) or returns normally.
23
23
  """
24
+ # Access via module path so tests can reassign
25
+ # from bake.utils import settings
26
+
24
27
  # 1. Check marker to prevent infinite loops
25
- if os.environ.get(_BAKE_REINVOKED):
28
+ if bake_settings.bake_reinvoked:
26
29
  logger.debug(
27
30
  "Re-invocation marker set, skipping Python check",
28
31
  extra={"sys.executable": sys.executable},
@@ -52,7 +55,7 @@ def _reinvoke_with_detected_python(bakefile_path: Path | None) -> None:
52
55
  extra={"target_python": str(target_python)},
53
56
  )
54
57
  env = os.environ.copy()
55
- env[_BAKE_REINVOKED] = "1"
58
+ env[ENV__BAKE_REINVOKED] = "1"
56
59
 
57
60
  sys.stdout.flush()
58
61
  sys.stderr.flush()
@@ -0,0 +1,77 @@
1
+ from collections.abc import Callable
2
+
3
+ from bake.cli.common.app import (
4
+ BakefileApp,
5
+ add_completion,
6
+ call_app_with_chdir,
7
+ rich_markup_mode,
8
+ show_help_if_no_command,
9
+ )
10
+ from bake.cli.common.context import Context
11
+ from bake.cli.common.obj import BakefileObject, get_bakefile_object
12
+ from bake.cli.common.params import (
13
+ bakebook_name_option,
14
+ chdir_option,
15
+ dry_run_option,
16
+ file_name_option,
17
+ verbosity_option,
18
+ version_option,
19
+ )
20
+ from bake.utils.constants import (
21
+ DEFAULT_BAKEBOOK_NAME,
22
+ DEFAULT_CHDIR,
23
+ DEFAULT_FILE_NAME,
24
+ )
25
+
26
+ from . import uv
27
+ from .add_inline import add_inline
28
+ from .export import export
29
+ from .find_python import find_python
30
+ from .init import init
31
+ from .lint import lint
32
+
33
+
34
+ def bakefile_app_callback_with_obj(obj: BakefileObject) -> Callable[..., None]:
35
+ def bakefile_app_callback(
36
+ ctx: Context,
37
+ _chdir: chdir_option = DEFAULT_CHDIR,
38
+ _file_name: file_name_option = DEFAULT_FILE_NAME,
39
+ _bakebook_name: bakebook_name_option = DEFAULT_BAKEBOOK_NAME,
40
+ _version: version_option = False,
41
+ _verbosity: verbosity_option = 0,
42
+ _dry_run: dry_run_option = False,
43
+ ):
44
+ ctx.obj = obj
45
+ show_help_if_no_command(ctx)
46
+
47
+ return bakefile_app_callback
48
+
49
+
50
+ def main():
51
+ bakefile_obj = get_bakefile_object(rich_markup_mode=rich_markup_mode)
52
+ bakefile_obj.setup_logging()
53
+ bakefile_obj.resolve_bakefile_path()
54
+
55
+ bakefile_app = BakefileApp(
56
+ add_completion=add_completion,
57
+ rich_markup_mode=rich_markup_mode,
58
+ )
59
+
60
+ uv_commands_context_settings = {
61
+ "allow_extra_args": True,
62
+ "ignore_unknown_options": True,
63
+ }
64
+
65
+ callback = bakefile_app_callback_with_obj(obj=bakefile_obj)
66
+ bakefile_app.callback(invoke_without_command=True)(callback)
67
+ bakefile_app.command()(init)
68
+ bakefile_app.command()(add_inline)
69
+ bakefile_app.command()(find_python)
70
+ bakefile_app.command()(lint)
71
+ bakefile_app.command()(export)
72
+ bakefile_app.command(context_settings=uv_commands_context_settings)(uv.sync)
73
+ bakefile_app.command(context_settings=uv_commands_context_settings)(uv.lock)
74
+ bakefile_app.command(context_settings=uv_commands_context_settings)(uv.add)
75
+ bakefile_app.command(context_settings=uv_commands_context_settings)(uv.pip)
76
+ bakefile_app.bakefile_object = bakefile_obj
77
+ call_app_with_chdir(app=bakefile_app, bakefile_path=bakefile_obj.bakefile_path)
@@ -0,0 +1,56 @@
1
+ import os
2
+ import sys
3
+ from collections.abc import Generator
4
+ from contextlib import contextmanager
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import typer
9
+ from typer.core import MarkupMode
10
+
11
+ from bake.cli.common.context import Context
12
+ from bake.ui import console
13
+
14
+ from .obj import BakefileObject
15
+
16
+ rich_markup_mode: MarkupMode = "rich" if not console.out.no_color else None
17
+ add_completion = True
18
+
19
+
20
+ class BakefileApp(typer.Typer):
21
+ bakefile_object: BakefileObject
22
+
23
+
24
+ if sys.version_info >= (3, 11):
25
+ from contextlib import chdir
26
+ else:
27
+
28
+ @contextmanager
29
+ def chdir(path: Path) -> Generator[None, None, None]:
30
+ """Change directory context manager for Python < 3.11 compatibility."""
31
+ original = Path.cwd()
32
+ try:
33
+ os.chdir(path)
34
+ yield
35
+ finally:
36
+ os.chdir(original)
37
+
38
+
39
+ def call_app_with_chdir(
40
+ app: typer.Typer,
41
+ bakefile_path: Path | None,
42
+ *args: Any,
43
+ **kwargs: Any,
44
+ ) -> None:
45
+ if bakefile_path is not None:
46
+ dir_path = bakefile_path.parent
47
+ with chdir(dir_path):
48
+ app(*args, **kwargs)
49
+ else:
50
+ app(*args, **kwargs)
51
+
52
+
53
+ def show_help_if_no_command(ctx: Context) -> None:
54
+ if ctx.invoked_subcommand is None:
55
+ console.echo(ctx.get_help())
56
+ raise typer.Exit(1)
@@ -57,6 +57,7 @@ class Context(typer.Context):
57
57
  stream: bool = True,
58
58
  shell: bool | None = None,
59
59
  echo: bool = True,
60
+ echo_cmd: str | None = None,
60
61
  dry_run: bool | None = None,
61
62
  keep_temp_file: bool = False,
62
63
  env: dict[str, str] | None = None,
@@ -74,6 +75,7 @@ class Context(typer.Context):
74
75
  stream: bool = True,
75
76
  shell: bool | None = None,
76
77
  echo: bool = True,
78
+ echo_cmd: str | None = None,
77
79
  dry_run: bool | None = None,
78
80
  keep_temp_file: bool = False,
79
81
  env: dict[str, str] | None = None,
@@ -90,6 +92,7 @@ class Context(typer.Context):
90
92
  stream: bool = True,
91
93
  shell: bool | None = None,
92
94
  echo: bool = True,
95
+ echo_cmd: str | None = None,
93
96
  dry_run: bool | None = None,
94
97
  keep_temp_file: bool = False,
95
98
  env: dict[str, str] | None = None,
@@ -104,6 +107,7 @@ class Context(typer.Context):
104
107
  stream=stream,
105
108
  shell=shell,
106
109
  echo=echo,
110
+ echo_cmd=echo_cmd,
107
111
  dry_run=self.obj.dry_run if dry_run is None else dry_run,
108
112
  keep_temp_file=keep_temp_file,
109
113
  env=env,
@@ -27,7 +27,6 @@ from bake.utils.constants import (
27
27
  )
28
28
  from bake.utils.exceptions import BakebookError, BakefileNotFoundError
29
29
 
30
- from .callback import validate_file_name
31
30
  from .exception_handler import typer_exception_handler
32
31
  from .params import (
33
32
  bakebook_name_option,
@@ -36,6 +35,7 @@ from .params import (
36
35
  file_name_option,
37
36
  is_chain_commands_option,
38
37
  remaining_args_argument,
38
+ validate_file_name,
39
39
  verbosity_option,
40
40
  )
41
41
 
@@ -112,8 +112,9 @@ class BakefileObject:
112
112
  console.echo(f"Searched in: {self.chdir.resolve()}\n")
113
113
 
114
114
  def setup_logging(self):
115
- level_map = {0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG}
116
- log_level = level_map.get(self.verbosity, logging.WARNING)
115
+ # Verbosity: 0=silent, 1=INFO, 2=DEBUG (CRITICAL+1 silences all logs)
116
+ level_map: dict[int, int] = {0: logging.CRITICAL + 1, 1: logging.INFO, 2: logging.DEBUG}
117
+ log_level = level_map.get(self.verbosity, logging.CRITICAL + 1)
117
118
  setup_logging(level_per_module={"": log_level}, is_pretty_log=True)
118
119
 
119
120
 
@@ -3,10 +3,18 @@ from typing import Annotated
3
3
 
4
4
  import typer
5
5
 
6
- from bake.cli.common.callback import validate_file_name_callback
7
6
  from bake.cli.utils.version import version_callback
8
7
 
9
8
 
9
+ def validate_file_name(value: str) -> str:
10
+ """Validate file name for --file-name option."""
11
+ if "/" in value or "\\" in value:
12
+ raise typer.BadParameter(f"File name must not contain path separators: {value}")
13
+ if not value.endswith(".py"):
14
+ raise typer.BadParameter(f"File name must end with .py: {value}")
15
+ return value
16
+
17
+
10
18
  def verbosity_callback(_ctx: typer.Context, _param: typer.CallbackParam, value: int) -> int:
11
19
  """Validate verbosity level (max 2)."""
12
20
  if value > 2:
@@ -31,7 +39,7 @@ file_name_option = Annotated[
31
39
  "--file-name",
32
40
  "-f",
33
41
  help="Path to bakefile.py",
34
- callback=validate_file_name_callback,
42
+ callback=validate_file_name,
35
43
  ),
36
44
  ]
37
45
  bakebook_name_option = Annotated[
@@ -5,7 +5,6 @@ from pathlib import Path
5
5
  from ruff.__main__ import find_ruff_bin
6
6
  from ty.__main__ import find_ty_bin
7
7
 
8
- from bake.ui import console
9
8
  from bake.ui.run import run
10
9
 
11
10
  logger = logging.getLogger(__name__)
@@ -19,19 +18,19 @@ def run_ruff(
19
18
  only_bakefile: bool = False,
20
19
  check: bool = True,
21
20
  dry_run: bool = False,
21
+ echo: bool = True,
22
22
  ) -> subprocess.CompletedProcess[str]:
23
23
  ruff_bin = find_ruff_bin()
24
24
  target = bakefile_path.name if only_bakefile else "."
25
25
  cmd = [subcommand, *args, target]
26
- display_cmd = "ruff " + " ".join(cmd)
27
- console.cmd(display_cmd)
28
26
  return run(
29
27
  [str(ruff_bin), *cmd],
30
28
  cwd=bakefile_path.parent,
31
29
  capture_output=True,
32
30
  stream=True,
33
31
  check=check,
34
- echo=False,
32
+ echo=echo,
33
+ echo_cmd="ruff " + " ".join(cmd) if echo else None,
35
34
  dry_run=dry_run,
36
35
  )
37
36
 
@@ -42,6 +41,7 @@ def run_ruff_format(
42
41
  only_bakefile: bool = False,
43
42
  check: bool = True,
44
43
  dry_run: bool = False,
44
+ echo: bool = True,
45
45
  ) -> subprocess.CompletedProcess[str]:
46
46
  return run_ruff(
47
47
  bakefile_path=bakefile_path,
@@ -50,6 +50,7 @@ def run_ruff_format(
50
50
  only_bakefile=only_bakefile,
51
51
  check=check,
52
52
  dry_run=dry_run,
53
+ echo=echo,
53
54
  )
54
55
 
55
56
 
@@ -59,6 +60,7 @@ def run_ruff_check(
59
60
  only_bakefile: bool = False,
60
61
  check: bool = True,
61
62
  dry_run: bool = False,
63
+ echo: bool = True,
62
64
  ) -> subprocess.CompletedProcess[str]:
63
65
  return run_ruff(
64
66
  bakefile_path=bakefile_path,
@@ -72,6 +74,7 @@ def run_ruff_check(
72
74
  only_bakefile=only_bakefile,
73
75
  check=check,
74
76
  dry_run=dry_run,
77
+ echo=echo,
75
78
  )
76
79
 
77
80
 
@@ -82,20 +85,20 @@ def run_ty_check(
82
85
  only_bakefile: bool = False,
83
86
  check: bool = True,
84
87
  dry_run: bool = False,
88
+ echo: bool = True,
85
89
  ) -> subprocess.CompletedProcess[str]:
86
90
  ty_bin = find_ty_bin()
87
91
  cmd = ["check", "--error-on-warning", "--python", str(python_path)]
88
92
  if only_bakefile:
89
93
  cmd.append(bakefile_path.name)
90
94
 
91
- display_cmd = "ty " + " ".join(cmd)
92
- console.cmd(display_cmd)
93
95
  return run(
94
96
  [str(ty_bin), *cmd],
95
97
  cwd=bakefile_path.parent,
96
98
  capture_output=True,
97
99
  stream=True,
98
100
  check=check,
99
- echo=False,
101
+ echo=echo,
102
+ echo_cmd="ty " + " ".join(cmd) if echo else None,
100
103
  dry_run=dry_run,
101
104
  )
@@ -0,0 +1,122 @@
1
+ import sys
2
+ import textwrap
3
+ from typing import Any
4
+
5
+ from beautysh import BashFormatter
6
+ from rich.console import Console
7
+
8
+ from bake.utils.settings import bake_settings
9
+
10
+ out = Console(stderr=False)
11
+ err = Console(stderr=True)
12
+
13
+ BOLD_GREEN = "bold green"
14
+ UNICODE_ENCODINGS = {"utf-8", "utf-16", "utf-32", "utf-16-le", "utf-16-be"}
15
+
16
+
17
+ def _supports_unicode() -> bool:
18
+ return sys.stdout.encoding.lower() in UNICODE_ENCODINGS
19
+
20
+
21
+ def _format_prefix(
22
+ console_obj: Console,
23
+ emoji: str | None,
24
+ label: str,
25
+ style: str,
26
+ message: str,
27
+ ) -> str:
28
+ formatted_label = f"[{label}]" if console_obj.no_color or out.color_system is None else label
29
+
30
+ # Strip emoji/unicode in non-UTF contexts (e.g., Windows CI) to avoid encoding issues
31
+ emoji_str = ""
32
+ if emoji and _supports_unicode():
33
+ emoji_str = emoji + " "
34
+
35
+ return f"[{style}]{emoji_str}{formatted_label}[/{style}] {message}"
36
+
37
+
38
+ def prefix_out(
39
+ message: str,
40
+ emoji: str | None = None,
41
+ label: str = "INFO",
42
+ style: str = "bold blue",
43
+ **kwargs,
44
+ ) -> None:
45
+ out.print(_format_prefix(out, emoji=emoji, label=label, style=style, message=message), **kwargs)
46
+
47
+
48
+ def prefix_err(
49
+ message: str,
50
+ emoji: str | None = None,
51
+ label: str = "INFO",
52
+ style: str = "bold blue",
53
+ **kwargs,
54
+ ) -> None:
55
+ err.print(_format_prefix(err, emoji=emoji, label=label, style=style, message=message), **kwargs)
56
+
57
+
58
+ def success(message: str, **kwargs) -> None:
59
+ prefix_out(
60
+ emoji=":white_check_mark:",
61
+ label="SUCCESS",
62
+ style=BOLD_GREEN,
63
+ message=message,
64
+ **kwargs,
65
+ )
66
+
67
+
68
+ def start(message: str, **kwargs) -> None:
69
+ prefix_out(emoji=None, label="START", style="cyan", message=f"{message}...", **kwargs)
70
+
71
+
72
+ def echo(message: Any, **kwargs) -> None:
73
+ out.print(message, **kwargs)
74
+
75
+
76
+ def cmd(cmd_str: str, **kwargs) -> None:
77
+ arrow = "❯" if _supports_unicode() else ">" # noqa: RUF001
78
+ err.print(f"[{BOLD_GREEN}]{arrow}[/{BOLD_GREEN}] [default]{cmd_str}[/default]", **kwargs)
79
+
80
+
81
+ def script_block(title: str, script: str, **kwargs) -> None:
82
+ formatter = BashFormatter()
83
+ formatted, error = formatter.beautify_string(script)
84
+
85
+ if error:
86
+ formatted = textwrap.dedent(script)
87
+
88
+ terminal_width: int = err.size.width
89
+ width = min(70, terminal_width)
90
+ bold_line = "━" * width
91
+ thin_line = "─" * width
92
+
93
+ err.print(bold_line, style=BOLD_GREEN)
94
+ err.print(title, style="bold")
95
+ err.print(thin_line, style=BOLD_GREEN)
96
+ err.print(formatted, highlight=False, **kwargs)
97
+ err.print(bold_line, style=BOLD_GREEN)
98
+
99
+
100
+ def warning(message: str, **kwargs) -> None:
101
+ if bake_settings.github_actions:
102
+ err.print(f"::warning::{message}", **kwargs)
103
+ else:
104
+ prefix_err(
105
+ emoji=":warning-emoji: ",
106
+ label="WARNING",
107
+ style="bold yellow",
108
+ message=message,
109
+ **kwargs,
110
+ )
111
+
112
+
113
+ def error(message: str, **kwargs) -> None:
114
+ if bake_settings.github_actions:
115
+ err.print(f"::error::{message}", **kwargs)
116
+ else:
117
+ prefix_err(emoji=":x:", label="ERROR", style="bold red", message=message, **kwargs)
118
+
119
+
120
+ def github_action_add_mask(value: str, **kwargs) -> None:
121
+ if bake_settings.github_actions:
122
+ out.print(f"::add-mask::{value}", **kwargs)