ask-shell 0.10.0__tar.gz → 0.10.2__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 (32) hide show
  1. {ask_shell-0.10.0 → ask_shell-0.10.2}/PKG-INFO +1 -1
  2. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/__init__.py +1 -1
  3. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/_run.py +3 -1
  4. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/models.py +31 -11
  5. {ask_shell-0.10.0 → ask_shell-0.10.2}/pyproject.toml +1 -1
  6. {ask_shell-0.10.0 → ask_shell-0.10.2}/.gitignore +0 -0
  7. {ask_shell-0.10.0 → ask_shell-0.10.2}/LICENSE +0 -0
  8. {ask_shell-0.10.0 → ask_shell-0.10.2}/README.md +0 -0
  9. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/__main__.py +0 -0
  10. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/__init__.py +0 -0
  11. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/_run_env.py +0 -0
  12. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/events.py +0 -0
  13. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/global_callbacks.py +0 -0
  14. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/interactive.py +0 -0
  15. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/live_print_context.py +0 -0
  16. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/rich_live.py +0 -0
  17. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/rich_live_callback.py +0 -0
  18. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/rich_progress.py +0 -0
  19. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/rich_run_state.py +0 -0
  20. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/run_pool.py +0 -0
  21. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/_internal/typer_command.py +0 -0
  22. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/ask.py +0 -0
  23. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/console.py +0 -0
  24. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/py.typed +0 -0
  25. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/settings.py +0 -0
  26. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/shell.py +0 -0
  27. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/shell_events.py +0 -0
  28. {ask_shell-0.10.0 → ask_shell-0.10.2}/ask_shell/test_docs.py +0 -0
  29. {ask_shell-0.10.0 → ask_shell-0.10.2}/examples/apply_live/__init__.py +0 -0
  30. {ask_shell-0.10.0 → ask_shell-0.10.2}/examples/apply_live/cli.py +0 -0
  31. {ask_shell-0.10.0 → ask_shell-0.10.2}/examples/apply_live/stream_handler.py +0 -0
  32. {ask_shell-0.10.0 → ask_shell-0.10.2}/scripts/fix_source_links.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ask-shell
3
- Version: 0.10.0
3
+ Version: 0.10.2
4
4
  Summary: CLIs with prompts, shell runs, and testable flows
5
5
  Author-email: EspenAlbert <espen.albert1@gmail.com>
6
6
  License-Expression: MIT
@@ -7,7 +7,7 @@ from ask_shell import ask
7
7
  from ask_shell.settings import AskShellSettings
8
8
  from ask_shell.settings import ShellRunSummary
9
9
 
10
- VERSION = "0.10.0"
10
+ VERSION = "0.10.2"
11
11
  __all__ = [
12
12
  "shell",
13
13
  "console",
@@ -415,10 +415,12 @@ def _execute_run(shell_run: ShellRun) -> ShellRun:
415
415
  config = shell_run.config
416
416
  queue = shell_run._queue
417
417
 
418
+ ctx = shell_run._callback_context
419
+
418
420
  def queue_consumer():
419
421
  for message in queue:
420
422
  try:
421
- shell_run._on_event(message)
423
+ ctx.run(shell_run._on_event, message)
422
424
  except BaseException as e:
423
425
  logger.warning(f"Error processing message '{type(message).__name__}' for {shell_run}: {e!r}")
424
426
  logger.exception(e)
@@ -1,8 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextvars
3
4
  import logging
4
5
  import os
5
6
  import platform
7
+ import re
6
8
  import subprocess
7
9
  import sys
8
10
  from concurrent.futures import Future
@@ -124,6 +126,23 @@ def _resolve_binary(binary_name: str, cwd: Path) -> Path:
124
126
  raise ValueError(f"Binary or non-executable '{binary_name}' not found. {install}")
125
127
 
126
128
 
129
+ class _ShellTokenSplit(NamedTuple):
130
+ first: str
131
+ remainder: str
132
+
133
+
134
+ _FIRST_SHELL_TOKEN = re.compile(r"(?P<first>\S+)(?P<remainder>.*)", re.DOTALL)
135
+
136
+
137
+ def _split_first_shell_token(shell_input: str) -> _ShellTokenSplit:
138
+ stripped = shell_input.lstrip()
139
+ if not stripped:
140
+ raise ValueError("shell_input must not be empty")
141
+ match = _FIRST_SHELL_TOKEN.match(stripped)
142
+ assert match is not None
143
+ return _ShellTokenSplit(match.group("first"), match.group("remainder"))
144
+
145
+
127
146
  class ShellConfig(Entity):
128
147
  """
129
148
  >>> ShellConfig("some_script").print_prefix
@@ -201,16 +220,14 @@ class ShellConfig(Entity):
201
220
 
202
221
  @property
203
222
  def binary_file_args(self) -> ShellInput:
204
- match self.shell_input.split():
205
- case [file_or_binary, *args] if os.sep in file_or_binary:
206
- file_or_binary_path = Path(file_or_binary).resolve()
207
- if is_executable(file_or_binary_path):
208
- return ShellInput("", file_or_binary_path, args)
209
- return ShellInput(file_or_binary, None, args)
210
- case [file_or_binary, *args]:
211
- return ShellInput(file_or_binary, None, args)
212
- case _:
213
- raise ValueError("shell_input must not be empty")
223
+ split = _split_first_shell_token(self.shell_input)
224
+ args = split.remainder.strip().split()
225
+ if os.sep in split.first:
226
+ file_or_binary_path = Path(split.first).resolve()
227
+ if is_executable(file_or_binary_path):
228
+ return ShellInput("", file_or_binary_path, args)
229
+ return ShellInput(split.first, None, args)
230
+ return ShellInput(split.first, None, args)
214
231
 
215
232
  def _infer_print_prefix(self, parsed_input: ShellInput):
216
233
  cwd = self.cwd
@@ -240,8 +257,10 @@ class ShellConfig(Entity):
240
257
  raise ValueError(f"cwd {self.cwd} does not exist")
241
258
  parsed_input = self.binary_file_args
242
259
  if not self.skip_binary_check and parsed_input.is_binary_call:
260
+ # ponytail: first-token only; full shlex if quoted binary paths matter
261
+ remainder = _split_first_shell_token(self.shell_input).remainder
243
262
  binary_path = _resolve_binary(parsed_input.binary_name, self.cwd)
244
- self.shell_input = f"{binary_path} {' '.join(parsed_input.args)}"
263
+ self.shell_input = f"{binary_path}{remainder}"
245
264
  if self.is_binary_call is None:
246
265
  self.is_binary_call = parsed_input.is_binary_call
247
266
  if self.print_prefix is None:
@@ -293,6 +312,7 @@ class ShellRun:
293
312
  _stderr_log_path: Path | None = field(init=False, default=None)
294
313
  _queue: ShellRunQueueT = field(init=False, default_factory=ClosableQueue)
295
314
  _lock: RLock = field(init=False, default_factory=RLock)
315
+ _callback_context: contextvars.Context = field(default_factory=contextvars.copy_context, init=False)
296
316
 
297
317
  @property
298
318
  def has_started(self) -> bool:
@@ -2,7 +2,7 @@
2
2
 
3
3
  [project]
4
4
  name = "ask-shell"
5
- version = "0.10.0"
5
+ version = "0.10.2"
6
6
  description = "CLIs with prompts, shell runs, and testable flows"
7
7
  requires-python = ">=3.13"
8
8
  license = "MIT"
File without changes
File without changes
File without changes
File without changes