bakefile 0.0.4__py3-none-any.whl → 0.0.5__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.
Files changed (71) hide show
  1. bake/__init__.py +9 -0
  2. bake/bakebook/bakebook.py +85 -0
  3. bake/bakebook/decorator.py +50 -0
  4. bake/bakebook/get.py +175 -0
  5. bake/cli/bake/__init__.py +3 -0
  6. bake/cli/bake/__main__.py +5 -0
  7. bake/cli/bake/main.py +74 -0
  8. bake/cli/bake/reinvocation.py +63 -0
  9. bake/cli/bakefile/__init__.py +3 -0
  10. bake/cli/bakefile/__main__.py +5 -0
  11. bake/cli/bakefile/add_inline.py +29 -0
  12. bake/cli/bakefile/find_python.py +18 -0
  13. bake/cli/bakefile/init.py +56 -0
  14. bake/cli/bakefile/lint.py +77 -0
  15. bake/cli/bakefile/main.py +41 -0
  16. bake/cli/bakefile/uv.py +146 -0
  17. bake/cli/common/app.py +54 -0
  18. bake/cli/common/callback.py +13 -0
  19. bake/cli/common/context.py +145 -0
  20. bake/cli/common/exception_handler.py +57 -0
  21. bake/cli/common/obj.py +214 -0
  22. bake/cli/common/params.py +72 -0
  23. bake/cli/utils/__init__.py +0 -0
  24. bake/cli/utils/version.py +18 -0
  25. bake/manage/__init__.py +0 -0
  26. bake/manage/add_inline.py +71 -0
  27. bake/manage/find_python.py +210 -0
  28. bake/manage/lint.py +101 -0
  29. bake/manage/run_uv.py +88 -0
  30. bake/manage/write_bakefile.py +20 -0
  31. bake/py.typed +0 -0
  32. bake/samples/__init__.py +0 -0
  33. bake/samples/simple.py +9 -0
  34. bake/ui/__init__.py +10 -0
  35. bake/ui/console.py +58 -0
  36. bake/ui/logger/__init__.py +33 -0
  37. bake/ui/logger/capsys.py +158 -0
  38. bake/ui/logger/setup.py +53 -0
  39. bake/ui/logger/utils.py +215 -0
  40. bake/ui/run/__init__.py +11 -0
  41. bake/ui/run/run.py +541 -0
  42. bake/ui/run/script.py +74 -0
  43. bake/ui/run/splitter.py +237 -0
  44. bake/ui/run/uv.py +83 -0
  45. bake/ui/style.py +2 -0
  46. bake/utils/__init__.py +11 -0
  47. bake/utils/constants.py +21 -0
  48. {bakefile → bake/utils}/env.py +3 -1
  49. bake/utils/exceptions.py +17 -0
  50. {bakefile-0.0.4.dist-info → bakefile-0.0.5.dist-info}/METADATA +14 -2
  51. bakefile-0.0.5.dist-info/RECORD +61 -0
  52. {bakefile-0.0.4.dist-info → bakefile-0.0.5.dist-info}/WHEEL +1 -1
  53. bakefile-0.0.5.dist-info/entry_points.txt +5 -0
  54. bakelib/__init__.py +4 -0
  55. bakelib/space/__init__.py +0 -0
  56. bakelib/space/base.py +73 -0
  57. bakelib/space/python.py +42 -0
  58. bakelib/space/utils.py +55 -0
  59. bakefile/__init__.py +0 -13
  60. bakefile/cli/bake/__init__.py +0 -3
  61. bakefile/cli/bake/main.py +0 -127
  62. bakefile/cli/bake/resolve_bakebook.py +0 -103
  63. bakefile/cli/bake/utils.py +0 -25
  64. bakefile/cli/bakefile.py +0 -19
  65. bakefile/cli/utils/version.py +0 -9
  66. bakefile/exceptions.py +0 -9
  67. bakefile-0.0.4.dist-info/RECORD +0 -16
  68. bakefile-0.0.4.dist-info/entry_points.txt +0 -4
  69. {bakefile/cli/utils → bake/bakebook}/__init__.py +0 -0
  70. {bakefile → bake}/cli/__init__.py +0 -0
  71. /bakefile/py.typed → /bake/cli/common/__init__.py +0 -0
@@ -0,0 +1,237 @@
1
+ import os
2
+ import select
3
+ import subprocess
4
+ import sys
5
+ import threading
6
+ import time
7
+
8
+ # No PTY locks needed - each thread reads from its own PTY fd independently
9
+ # Locks were causing race conditions where threads waited while their process exited
10
+
11
+
12
+ class OutputSplitter:
13
+ def __init__(
14
+ self,
15
+ stream: bool = True,
16
+ capture: bool = True,
17
+ pty_fd: int | None = None,
18
+ encoding: str | None = None,
19
+ ):
20
+ self._stream = stream
21
+ self._capture = capture
22
+ self._pty_fd = pty_fd
23
+ self._encoding = encoding
24
+ self._stdout_data = b""
25
+ self._stderr_data = b""
26
+
27
+ def _read_stream(self, stream, target, output_list):
28
+ for line in iter(stream.readline, b""):
29
+ if self._stream:
30
+ target.buffer.write(line)
31
+ target.buffer.flush()
32
+ if self._capture:
33
+ output_list.append(line)
34
+ stream.close()
35
+
36
+ def _handle_data(self, data: bytes, target, output_list) -> bool:
37
+ """Handle data chunk: return False if data is empty (EOF)."""
38
+ if not data:
39
+ return False
40
+ if self._stream:
41
+ target.buffer.write(data)
42
+ target.buffer.flush()
43
+ if self._capture:
44
+ output_list.append(data)
45
+ return True
46
+
47
+ def _read_pty(self, pty_fd: int, target, output_list, proc: subprocess.Popen):
48
+ """Read from PTY file descriptor in chunks and stream to output."""
49
+ import fcntl
50
+
51
+ try:
52
+ while True:
53
+ # Try immediate non-blocking read first (catches fast-exiting processes)
54
+ try:
55
+ # Set non-blocking mode
56
+ flags = fcntl.fcntl(pty_fd, fcntl.F_GETFL)
57
+ fcntl.fcntl(pty_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
58
+
59
+ data = os.read(pty_fd, 4096)
60
+ if not self._handle_data(data, target, output_list):
61
+ break
62
+
63
+ # Restore blocking mode
64
+ fcntl.fcntl(pty_fd, fcntl.F_SETFL, flags)
65
+ except BlockingIOError:
66
+ # No data available yet, restore blocking mode and wait with select
67
+ fcntl.fcntl(pty_fd, fcntl.F_SETFL, flags)
68
+
69
+ # Wait for data to be available
70
+ ready, _, _ = select.select([pty_fd], [], [], 0.1)
71
+
72
+ if ready:
73
+ data = os.read(pty_fd, 4096)
74
+ if not self._handle_data(data, target, output_list):
75
+ break
76
+
77
+ # Check if process exited after reading data
78
+ if proc.poll() is not None:
79
+ self._drain_pty(pty_fd, target, output_list)
80
+ break
81
+ finally:
82
+ os.close(pty_fd)
83
+
84
+ def _read_pty_data(self, pty_fd: int, target, output_list) -> bool:
85
+ """Read and handle available PTY data. Returns False on EOF/error."""
86
+ try:
87
+ data = os.read(pty_fd, 4096)
88
+ return self._handle_data(data, target, output_list)
89
+ except OSError:
90
+ return False
91
+
92
+ def _try_select_read(self, pty_fd: int, timeout: float) -> tuple[bool, bool]:
93
+ """Try to read using select.select().
94
+
95
+ Returns:
96
+ (success, has_data): success if select worked, has_data if ready
97
+ """
98
+ try:
99
+ ready, _, _ = select.select([pty_fd], [], [], timeout)
100
+ return True, bool(ready)
101
+ except OSError:
102
+ # On Windows, select.select() raises OSError for non-socket file descriptors
103
+ return False, False
104
+
105
+ def _read_and_handle(self, pty_fd: int, target, output_list) -> bool:
106
+ """Read from PTY and handle data.
107
+
108
+ Returns:
109
+ True if data was handled, False if EOF/error
110
+ """
111
+ try:
112
+ data = os.read(pty_fd, 4096)
113
+ return self._handle_data(data, target, output_list)
114
+ except OSError:
115
+ return False
116
+
117
+ def _handle_data_ready(self, pty_fd: int, target, output_list) -> bool:
118
+ """Handle data ready from select.
119
+
120
+ Returns:
121
+ True if should continue draining, False if done
122
+ """
123
+ return self._read_and_handle(pty_fd, target, output_list)
124
+
125
+ def _handle_timeout(
126
+ self,
127
+ pty_fd: int,
128
+ target,
129
+ output_list,
130
+ select_works: bool,
131
+ consecutive_timeouts: int,
132
+ ) -> tuple[bool, int]:
133
+ """Handle timeout when no data ready.
134
+
135
+ Returns:
136
+ (should_continue, new_timeout_count)
137
+ """
138
+ # Try direct read after 2 consecutive timeouts or if select doesn't work
139
+ if not select_works or consecutive_timeouts >= 2:
140
+ if not self._read_and_handle(pty_fd, target, output_list):
141
+ return False, 0
142
+ return True, 0 # Got data, reset timeout counter
143
+ return True, consecutive_timeouts + 1
144
+
145
+ def _drain_pty(self, pty_fd: int, target, output_list):
146
+ """Drain remaining data from PTY after process exits.
147
+
148
+ We need to handle OS timing: proc.poll() may return exit code before the
149
+ PTY buffer is fully flushed. We use select to wait for data with increasing
150
+ timeouts, and also try direct reads as a fallback in case select doesn't
151
+ detect readiness (e.g., in tests with mocked os.read or on Windows with
152
+ non-socket file descriptors).
153
+ """
154
+ time.sleep(0.005)
155
+
156
+ timeout = 0.05
157
+ consecutive_timeouts = 0
158
+ max_timeouts = 4
159
+ select_works = True
160
+
161
+ try:
162
+ while consecutive_timeouts < max_timeouts:
163
+ # Check if data is ready via select
164
+ if select_works:
165
+ select_works, ready = self._try_select_read(pty_fd, timeout)
166
+ else:
167
+ ready = False
168
+
169
+ if ready:
170
+ # Data ready - read and handle
171
+ if not self._handle_data_ready(pty_fd, target, output_list):
172
+ return
173
+ consecutive_timeouts = 0
174
+ timeout = 0.02
175
+ continue
176
+
177
+ # No data ready - increment timeout and try direct read
178
+ timeout = min(timeout * 1.5, 0.2)
179
+
180
+ should_continue, consecutive_timeouts = self._handle_timeout(
181
+ pty_fd, target, output_list, select_works, consecutive_timeouts
182
+ )
183
+ if not should_continue:
184
+ return
185
+ except OSError:
186
+ pass
187
+
188
+ def attach(self, proc: subprocess.Popen):
189
+ threads = []
190
+
191
+ # Handle PTY stdout (for color-preserving output on Unix)
192
+ if self._pty_fd is not None:
193
+ stdout_list = []
194
+ t = threading.Thread(
195
+ target=self._read_pty, args=(self._pty_fd, sys.stdout, stdout_list, proc)
196
+ )
197
+ t.daemon = True
198
+ t.start()
199
+ threads.append((t, stdout_list, "stdout"))
200
+
201
+ # Handle regular stdout
202
+ elif proc.stdout:
203
+ stdout_list = []
204
+ t = threading.Thread(
205
+ target=self._read_stream, args=(proc.stdout, sys.stdout, stdout_list)
206
+ )
207
+ t.daemon = True
208
+ t.start()
209
+ threads.append((t, stdout_list, "stdout"))
210
+
211
+ # Handle stderr (regular pipe)
212
+ if proc.stderr:
213
+ stderr_list = []
214
+ t = threading.Thread(
215
+ target=self._read_stream, args=(proc.stderr, sys.stderr, stderr_list)
216
+ )
217
+ t.daemon = True
218
+ t.start()
219
+ threads.append((t, stderr_list, "stderr"))
220
+
221
+ return threads
222
+
223
+ def finalize(self, threads):
224
+ for t, data_list, name in threads:
225
+ t.join()
226
+ if name == "stdout":
227
+ self._stdout_data = b"".join(data_list)
228
+ else:
229
+ self._stderr_data = b"".join(data_list)
230
+
231
+ @property
232
+ def stdout(self) -> bytes:
233
+ return self._stdout_data
234
+
235
+ @property
236
+ def stderr(self) -> bytes:
237
+ return self._stderr_data
bake/ui/run/uv.py ADDED
@@ -0,0 +1,83 @@
1
+ import subprocess
2
+ from pathlib import Path
3
+ from typing import Literal, overload
4
+
5
+ from uv import find_uv_bin
6
+
7
+ from bake.ui import console
8
+ from bake.ui.run.run import run
9
+
10
+
11
+ @overload
12
+ def run_uv(
13
+ cmd: list[str] | tuple[str, ...],
14
+ *,
15
+ capture_output: Literal[True] = True,
16
+ check: bool = True,
17
+ cwd: Path | str | None = None,
18
+ stream: bool = False,
19
+ shell: bool | None = None,
20
+ echo: bool = True,
21
+ dry_run: bool = False,
22
+ keep_temp_file: bool = False,
23
+ env: dict[str, str] | None = None,
24
+ _encoding: str | None = None,
25
+ **kwargs,
26
+ ) -> subprocess.CompletedProcess[str]: ...
27
+
28
+
29
+ @overload
30
+ def run_uv(
31
+ cmd: list[str] | tuple[str, ...],
32
+ *,
33
+ capture_output: Literal[False],
34
+ check: bool = True,
35
+ cwd: Path | str | None = None,
36
+ stream: bool = False,
37
+ echo: bool = True,
38
+ dry_run: bool = False,
39
+ keep_temp_file: bool = False,
40
+ env: dict[str, str] | None = None,
41
+ _encoding: str | None = None,
42
+ **kwargs,
43
+ ) -> subprocess.CompletedProcess[None]: ...
44
+
45
+
46
+ def run_uv(
47
+ cmd: list[str] | tuple[str, ...],
48
+ *,
49
+ capture_output: bool = True,
50
+ check: bool = True,
51
+ cwd: Path | str | None = None,
52
+ stream: bool = False,
53
+ echo: bool = True,
54
+ dry_run: bool = False,
55
+ keep_temp_file: bool = False,
56
+ env: dict[str, str] | None = None,
57
+ _encoding: str | None = None,
58
+ **kwargs,
59
+ ) -> subprocess.CompletedProcess[str] | subprocess.CompletedProcess[None]:
60
+ uv_bin = find_uv_bin()
61
+
62
+ # Build display string: "uv" + command parts (no full binary path)
63
+ display_cmd = "uv " + " ".join(cmd)
64
+
65
+ # Echo command to console if requested
66
+ if echo:
67
+ console.cmd(display_cmd)
68
+
69
+ # Call run with full uv binary path, echo=False (already displayed), pass through options
70
+ return run(
71
+ [uv_bin, *cmd],
72
+ capture_output=capture_output,
73
+ check=check,
74
+ cwd=cwd,
75
+ stream=stream,
76
+ shell=False,
77
+ echo=False,
78
+ dry_run=dry_run,
79
+ keep_temp_file=keep_temp_file,
80
+ env=env,
81
+ _encoding=_encoding,
82
+ **kwargs,
83
+ )
bake/ui/style.py ADDED
@@ -0,0 +1,2 @@
1
+ def code(message: str) -> str:
2
+ return f"`[cyan]{message}[/cyan]`"
bake/utils/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ from bake.utils.constants import DEFAULT_BAKEBOOK_NAME, DEFAULT_FILE_NAME
2
+ from bake.utils.env import should_use_colors
3
+ from bake.utils.exceptions import BakebookError, BaseBakefileError
4
+
5
+ __all__ = [
6
+ "DEFAULT_BAKEBOOK_NAME",
7
+ "DEFAULT_FILE_NAME",
8
+ "BakebookError",
9
+ "BaseBakefileError",
10
+ "should_use_colors",
11
+ ]
@@ -0,0 +1,21 @@
1
+ from pathlib import Path
2
+
3
+ # Default value
4
+ DEFAULT_CHDIR = Path(".")
5
+ DEFAULT_FILE_NAME = "bakefile.py"
6
+ DEFAULT_BAKEBOOK_NAME = "bakebook"
7
+ DEFAULT_IS_CHAIN_COMMAND = False
8
+
9
+ # CLI command names
10
+ CMD_BAKE = "bake"
11
+ CMD_BAKEFILE = "bakefile"
12
+ CMD_INIT = "init"
13
+ CMD_ADD_INLINE = "add-inline"
14
+ CMD_LINT = "lint"
15
+
16
+ # Bakefile app command name
17
+ GET_BAKEFILE_OBJECT = "get_bakefile_object"
18
+
19
+ # Others
20
+ BAKEBOOK_NAME_IN_SAMPLES = "__bakebook__"
21
+ BAKE_COMMAND_KWARGS = "_bake_command_kwargs"
@@ -2,7 +2,9 @@ import os
2
2
 
3
3
  ENV_NO_COLOR = "NO_COLOR"
4
4
 
5
+ _BAKE_REINVOKED = "_BAKE_REINVOKED"
6
+
5
7
 
6
8
  def should_use_colors() -> bool:
7
9
  value = os.environ.get(ENV_NO_COLOR)
8
- return not (value == "" or value is None)
10
+ return value == "" or value is None
@@ -0,0 +1,17 @@
1
+ """Custom exceptions for bakefile."""
2
+
3
+
4
+ class BaseBakefileError(Exception):
5
+ """Base exception for all bakefile errors."""
6
+
7
+
8
+ class BakebookError(BaseBakefileError):
9
+ """Exception raised when bakebook cannot be loaded or validated (unexpected error)."""
10
+
11
+
12
+ class BakefileNotFoundError(BakebookError):
13
+ """Exception raised when bakefile.py is not found (expected/suppressable error)."""
14
+
15
+
16
+ class PythonNotFoundError(BaseBakefileError):
17
+ """Exception raised when Python executable cannot be found or created."""
@@ -1,12 +1,24 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bakefile
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: Add your description here
5
5
  Author: Wisaroot Lertthaweedech
6
6
  Author-email: Wisaroot Lertthaweedech <l.wisaroot@gmail.com>
7
+ Requires-Dist: beautysh>=6.4.2
8
+ Requires-Dist: click>=8.3.1
9
+ Requires-Dist: loguru>=0.7.3
10
+ Requires-Dist: orjson>=3.11.5
11
+ Requires-Dist: pydantic-settings>=2.0.0
7
12
  Requires-Dist: pydantic>=2.12.5
8
- Requires-Dist: typer>=0.0.1
13
+ Requires-Dist: rich>=14.2.0
14
+ Requires-Dist: ruff>=0.14.10
15
+ Requires-Dist: tomli>=2.0.0 ; python_full_version < '3.11'
16
+ Requires-Dist: ty>=0.0.8
17
+ Requires-Dist: typer>=0.21.0
18
+ Requires-Dist: uv>=0.9.20
19
+ Requires-Dist: pathspec>=1.0.3 ; extra == 'lib'
9
20
  Requires-Python: >=3.10
21
+ Provides-Extra: lib
10
22
  Description-Content-Type: text/markdown
11
23
 
12
24
  [![tests](https://img.shields.io/github/actions/workflow/status/wislertt/bakefile/cd.yml?branch=main&label=tests&logo=github)](https://github.com/wislertt/bakefile/actions/workflows/cd.yml)
@@ -0,0 +1,61 @@
1
+ bake/__init__.py,sha256=SG-g8MVoBbXURlAP-OhtjMUMnRPTfi9lQ9WQD0EX7IE,338
2
+ bake/bakebook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ bake/bakebook/bakebook.py,sha256=o1sp8Wewxdqr7k12qT-Q4ZrKQ7c7jDliiNCYrVG93NA,2939
4
+ bake/bakebook/decorator.py,sha256=t2I6Pf1vdTk7k8lTzZCqq71-hzcXFvFYbuFV_Wvg45Q,1545
5
+ bake/bakebook/get.py,sha256=fx5WV66OIBrywPN0thePXAJk2gt7wvTVAxYQqZ_PMao,5807
6
+ bake/cli/__init__.py,sha256=da1PTClDMl-IBkrSvq6JC1lnS-K_BASzCvxVhNxN5Ls,13
7
+ bake/cli/bake/__init__.py,sha256=CJokSP1t1KXaIqjkjFQ8_gbaSZ9RRB4YdemIoSTqRcI,56
8
+ bake/cli/bake/__main__.py,sha256=5Ui5_OD1-oG1ou6boak63EPQDmRsW9sGOVVDgXJaNec,133
9
+ bake/cli/bake/main.py,sha256=Lr6eyfaf4-zDRrF0GK9bjjFWop66ydewV5tNUluDd9M,2262
10
+ bake/cli/bake/reinvocation.py,sha256=Ifqc8ZAM7NMyrKU2jkmw1agCEI-Sx7yvLLBiFRi23_0,1985
11
+ bake/cli/bakefile/__init__.py,sha256=_zD3rXQHLr6EWHADdPLAmnc2A5C3dhmBuvP5uJ-_A58,60
12
+ bake/cli/bakefile/__main__.py,sha256=FVntzkZdzdygSWjMzyneXCXsM-MDTPmC3GUk4JZiYFU,137
13
+ bake/cli/bakefile/add_inline.py,sha256=V98T50SLMPqnWVtyEO_6hL17r4n3ZtkSC8NSEqdyHzc,919
14
+ bake/cli/bakefile/find_python.py,sha256=J2HDs_nfNODqCHBZCNM64ESB4kVZK-C04i-KNmVUoSs,539
15
+ bake/cli/bakefile/init.py,sha256=0QuvADFOZZUBN2BUJfK90aEY1oUzoSNVRiljlUSjLu0,1825
16
+ bake/cli/bakefile/lint.py,sha256=DJkIJNBOef6JvgwQ3iL9jTrLqgUyn66Mhv6cuAgqXk0,2509
17
+ bake/cli/bakefile/main.py,sha256=5O08RTx8tKHTMio8-RdEsLC6bUD7AKQ6rPNLBZqQiJ0,1352
18
+ bake/cli/bakefile/uv.py,sha256=PMFG3BdofzGWkor4fMEi3GE4G7hGtclCgPm2xlaPDso,4013
19
+ bake/cli/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ bake/cli/common/app.py,sha256=ViRqRX2DsLoKAunGKd6vOiwk4Xqmug1Gkux4_1RZX_U,1444
21
+ bake/cli/common/callback.py,sha256=NmrZUl5eRr95nluomTwcKjTU7dSKjWcQVli5VEdZk-4,439
22
+ bake/cli/common/context.py,sha256=p3_YCExH4NIIgB4ZTax2fYFOjI__Ig4FffbqegtPE8Q,3699
23
+ bake/cli/common/exception_handler.py,sha256=2vLbqMeZlLxKqNWUkTs3cA-8l6IjK0dU3SyZlRb96YI,1759
24
+ bake/cli/common/obj.py,sha256=O23DCxdoAV7Pujxk_ylrSKHL-7DSS1-7BZuxpa0Hv5M,7016
25
+ bake/cli/common/params.py,sha256=rhLa34SY92nXfUaKo0SQMKK__xRnrmHejHa25tRyKdg,2002
26
+ bake/cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ bake/cli/utils/version.py,sha256=aiweLD0vDezBlJAcCC99oMms71WGD9CWSJuZ4i3VLHA,390
28
+ bake/manage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ bake/manage/add_inline.py,sha256=yefHmF33ghCB8NZ-v61ybeVsaeE8iDFvfRGeTAKg4I8,2245
30
+ bake/manage/find_python.py,sha256=oVmd8KaSsgDQWHuGZpYiQx-DHn50P9EkRi6-YIad99E,7165
31
+ bake/manage/lint.py,sha256=OqwYFF8GGvzHGVPuJcWMRAv5esXEIX4nQXdGcChnkqA,2394
32
+ bake/manage/run_uv.py,sha256=QzlKeVpr20dXNDcwUgyJqnXT4MofRqK-6XkWpzBbUhE,3234
33
+ bake/manage/write_bakefile.py,sha256=efGViLk7sh-QX9Mox7yQw_A1Tp7EOuc_vmSTbFmXUm0,736
34
+ bake/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
+ bake/samples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
+ bake/samples/simple.py,sha256=tTL2OVqQXMYwIlgd1K5gtUKDzxx2CDEb24JLzXkadYg,192
37
+ bake/ui/__init__.py,sha256=DeuVKFwrmhEcwcZnstItPecnvYugpLcPM_a7YEhnqD8,187
38
+ bake/ui/console.py,sha256=C5wrbsOc-wwcx0hGmCozHvGCNTWgGhsh-5vxl880xS4,1689
39
+ bake/ui/logger/__init__.py,sha256=bup2cssTHhergh47s6uYbGtY2dJNxlKKH6otBc4ECFM,728
40
+ bake/ui/logger/capsys.py,sha256=KZL6k7Werp_8styfJKfIvQyv0-gJq54vY3hSJFIacEM,5267
41
+ bake/ui/logger/setup.py,sha256=OrX9UiY0iBGfWWfhMJCdfqCRJsL5yC3rIdIEOn7rveo,1377
42
+ bake/ui/logger/utils.py,sha256=dcppxoS_pX92AFcHIerJGI2_JBHBNghRQmQqlZmmj2Q,7218
43
+ bake/ui/run/__init__.py,sha256=RLQN4f6mY2wyWtqe2e9B1jKwR5P9IyBcZGLdOwj91Ds,336
44
+ bake/ui/run/run.py,sha256=5ve7qbl37hRTpbLFf-wDtmG3RuVKYPlSUOsRlSZ9nUU,17390
45
+ bake/ui/run/script.py,sha256=fk7KiDklYDYpFGkH3wu-hZGI4OnvgcB8z5jtNt41Hg0,2263
46
+ bake/ui/run/splitter.py,sha256=92h4KaomqoYsxi9xZoLoSvk-7JdBF0YL-QNPpVaXqEA,8139
47
+ bake/ui/run/uv.py,sha256=3NpnjgAwQNijJiUT_H6U-3mTHQgBZPlJbNWEeYCZY1g,2077
48
+ bake/ui/style.py,sha256=v9dferzV317Acb0GHpVK_niCj_s2HtL-yiToBZtXky4,70
49
+ bake/utils/__init__.py,sha256=GUu_xlJy3RAHo6UcZXu2x4khxGqLHMA9Zos4hDiQIY8,326
50
+ bake/utils/constants.py,sha256=mRq5IpgOTdlHOTWPq5dx0A-LwhiFkWgYHfr8cLWG7rY,471
51
+ bake/utils/env.py,sha256=bzNdH_2bTJebQaw7D0uVJv-vzZ-uYl0pCAS8oQONVsA,190
52
+ bake/utils/exceptions.py,sha256=pwsQnKH5ljMNxmqEREutXa7TohiBHATHg_D5kQUPT30,519
53
+ bakelib/__init__.py,sha256=sZeRiNINWL8xI3b1MxkGyF3f2lKMjyhjKt7qyCCAufs,126
54
+ bakelib/space/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
+ bakelib/space/base.py,sha256=HzJYzwbeX5YN6Esmxt4fSDRGJNUk4OfZ3Zf9m0bNsX4,2103
56
+ bakelib/space/python.py,sha256=rlEJW5y1q00lGMHwYXicwTt1a4NUXfSei-T_pg88oMA,1223
57
+ bakelib/space/utils.py,sha256=SfNRFeAm35zfF01-fdxKXTlw2uL6DyU0TJXDFkUG7Zk,1471
58
+ bakefile-0.0.5.dist-info/WHEEL,sha256=XjEbIc5-wIORjWaafhI6vBtlxDBp7S9KiujWF1EM7Ak,79
59
+ bakefile-0.0.5.dist-info/entry_points.txt,sha256=Ecvvh7BYHCPJ0UdntrDc3Od6AZdRPXN5Z7o_7ok_0Qw,107
60
+ bakefile-0.0.5.dist-info/METADATA,sha256=vBmhYvnlTTuO2x4IpgNudcEBITAy23VZGvmYahT99kY,2302
61
+ bakefile-0.0.5.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.18
2
+ Generator: uv 0.9.25
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ bake = bake.cli.bake:main
3
+ bakefile = bake.cli.bakefile:main
4
+ bf = bake.cli.bakefile:main
5
+
bakelib/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from bakelib.space.base import BaseSpace
2
+ from bakelib.space.python import PythonSpace
3
+
4
+ __all__ = ["BaseSpace", "PythonSpace"]
File without changes
bakelib/space/base.py ADDED
@@ -0,0 +1,73 @@
1
+ from typing import Annotated
2
+
3
+ import typer
4
+
5
+ from bake import Bakebook, Context, command
6
+ from bake.ui import console
7
+
8
+ from .utils import remove_git_clean_candidates
9
+
10
+
11
+ class BaseSpace(Bakebook):
12
+ @command(help="Run linters and formatters")
13
+ def lint(self, ctx: Context) -> None:
14
+ ctx.run(
15
+ [
16
+ "bunx",
17
+ "prettier@latest",
18
+ "--write",
19
+ "**/*.{js,jsx,ts,tsx,css,json,json5,yaml,yml,md}",
20
+ ]
21
+ )
22
+
23
+ @command(help="Run unit tests")
24
+ def test(self, ctx: Context) -> None:
25
+ _ = ctx
26
+ console.error("No implementation")
27
+ raise typer.Exit(1)
28
+
29
+ @command(help="Clean gitignored files with optional exclusions")
30
+ def clean(
31
+ self,
32
+ ctx: Context,
33
+ exclude_patterns: Annotated[
34
+ list[str] | None,
35
+ typer.Option(
36
+ "--exclude-patterns",
37
+ "-e",
38
+ help="Patterns to exclude",
39
+ ),
40
+ ] = None,
41
+ use_default_excludes: Annotated[
42
+ bool,
43
+ typer.Option(
44
+ "--no-default-excludes",
45
+ help="Do not apply default exclude patterns",
46
+ is_flag=True,
47
+ ),
48
+ ] = False,
49
+ ) -> None:
50
+ results = ctx.run("git clean -fdX -n", stream=False, dry_run=False, echo=True)
51
+
52
+ exclude_patterns: set[str] = set(exclude_patterns if exclude_patterns else [])
53
+
54
+ if not use_default_excludes:
55
+ exclude_patterns |= {".env", ".cache"}
56
+
57
+ console.err.print(f"Exclude pattens: {exclude_patterns}")
58
+
59
+ remove_git_clean_candidates(
60
+ git_clean_dry_run_output=results.stdout,
61
+ exclude_patterns=exclude_patterns,
62
+ dry_run=ctx.dry_run,
63
+ )
64
+
65
+ @command(help="Clean all gitignored files")
66
+ def clean_all(self, ctx: Context) -> None:
67
+ ctx.run("git clean -fdX")
68
+
69
+ @command(help="Setup development environment")
70
+ def setup_dev(self, ctx: Context) -> None:
71
+ _ = ctx
72
+ console.error("No implementation")
73
+ raise typer.Exit(1)
@@ -0,0 +1,42 @@
1
+ from bake import Context
2
+
3
+ from .base import BaseSpace
4
+
5
+
6
+ class PythonSpace(BaseSpace):
7
+ def lint(self, ctx: Context) -> None:
8
+ super().lint(ctx=ctx)
9
+
10
+ ctx.run(
11
+ [
12
+ "uv",
13
+ "run",
14
+ "toml-sort",
15
+ "--sort-inline-arrays",
16
+ "--in-place",
17
+ "--sort-first=project,dependency-groups",
18
+ "pyproject.toml",
19
+ ]
20
+ )
21
+ ctx.run(["uv", "run", "ruff", "format", "--exit-non-zero-on-format", "."])
22
+ ctx.run(["uv", "run", "ruff", "check", "--fix", "--exit-non-zero-on-fix", "."])
23
+ ctx.run(["uv", "run", "ty", "check", "--error-on-warning", "."])
24
+ ctx.run(["uv", "run", "deptry", "."])
25
+
26
+ def test(self, ctx: Context) -> None:
27
+ ctx.run(
28
+ [
29
+ "uv",
30
+ "run",
31
+ "pytest",
32
+ "tests/",
33
+ "--cov=src",
34
+ "--cov-report=html",
35
+ "--cov-report=term-missing",
36
+ "--cov-report=xml",
37
+ ]
38
+ )
39
+
40
+ def setup_dev(self, ctx: Context) -> None:
41
+ super().clean(ctx=ctx)
42
+ ctx.run("uv sync --all-extras --all-groups --frozen")
bakelib/space/utils.py ADDED
@@ -0,0 +1,55 @@
1
+ import shutil
2
+ from pathlib import Path
3
+
4
+ import pathspec
5
+ from pathspec.patterns.gitignore.basic import GitIgnoreBasicPattern
6
+
7
+ from bake.ui import console
8
+
9
+
10
+ def _skip_msg(path: Path, suffix: str, dry_run: bool) -> None:
11
+ verb = "Would skip" if dry_run else "Skipping"
12
+ console.echo(f"[yellow]~[/yellow] {verb} {suffix}{path}")
13
+
14
+
15
+ def _remove_msg(path: Path, dry_run: bool) -> None:
16
+ verb = "Would remove" if dry_run else "Removing"
17
+ console.echo(f"[red]-[/red] [dim]{verb}[/dim] {path}")
18
+
19
+
20
+ def _should_remove_path(path: Path, dry_run: bool) -> None:
21
+ _remove_msg(path, dry_run)
22
+ if dry_run:
23
+ return
24
+
25
+ if path.is_dir():
26
+ shutil.rmtree(path)
27
+ else:
28
+ path.unlink(missing_ok=True)
29
+
30
+
31
+ def remove_git_clean_candidates(
32
+ git_clean_dry_run_output: str, exclude_patterns: set[str], dry_run: bool
33
+ ) -> None:
34
+ spec = pathspec.PathSpec.from_lines(
35
+ GitIgnoreBasicPattern,
36
+ exclude_patterns,
37
+ )
38
+
39
+ for line in git_clean_dry_run_output.splitlines():
40
+ line = line.strip()
41
+ if not line.startswith("Would remove "):
42
+ continue
43
+
44
+ rel_path = line.removeprefix("Would remove ").strip()
45
+ path = Path(rel_path)
46
+
47
+ if spec.match_file(rel_path):
48
+ _skip_msg(path, "", dry_run)
49
+ continue
50
+
51
+ if path.is_dir() and (path / ".git").exists():
52
+ _skip_msg(path, "git repository ", dry_run)
53
+ continue
54
+
55
+ _should_remove_path(path, dry_run)