scriptcast 0.2.0__tar.gz → 0.3.0__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.
- {scriptcast-0.2.0 → scriptcast-0.3.0}/.github/workflows/publish-pages.yml +2 -1
- {scriptcast-0.2.0 → scriptcast-0.3.0}/CHANGELOG.md +6 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/PKG-INFO +1 -1
- scriptcast-0.3.0/assets/demo.png +0 -0
- scriptcast-0.3.0/assets/showcase-aurora.png +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/assets/showcase-dark.png +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/assets/showcase-light.png +0 -0
- scriptcast-0.3.0/assets/tutorial.png +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/examples/demo.sh +0 -1
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/__main__.py +1 -1
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/assets/themes/aurora.sh +1 -1
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/assets/themes/dark.sh +1 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/assets/themes/light.sh +1 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/recorder.py +8 -2
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/shell/adapter.py +6 -0
- scriptcast-0.3.0/scriptcast/shell/zsh.py +72 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/tests/test_directives.py +1 -1
- {scriptcast-0.2.0 → scriptcast-0.3.0}/tests/test_recorder.py +53 -0
- scriptcast-0.3.0/tests/test_shell.py +94 -0
- scriptcast-0.2.0/assets/demo.png +0 -0
- scriptcast-0.2.0/assets/showcase-aurora.png +0 -0
- scriptcast-0.2.0/assets/tutorial.png +0 -0
- scriptcast-0.2.0/scriptcast/shell/zsh.py +0 -11
- scriptcast-0.2.0/tests/test_shell.py +0 -34
- {scriptcast-0.2.0 → scriptcast-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/.github/pull_request_template.md +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/.github/workflows/ci.yml +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/.github/workflows/publish-pypi.yml +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/.gitignore +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/CODE_OF_CONDUCT.md +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/CONTRIBUTING.md +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/DIRECTIVES.md +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/Makefile +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/README.md +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/cliff.toml +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/examples/.gitignore +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/examples/fake-db +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/examples/fake-myapp +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/examples/showcase.sh +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/examples/tutorial.sh +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/pyproject.toml +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/__init__.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/assets/__init__.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/assets/fonts/DMSans-Regular.ttf +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/assets/fonts/Pacifico.ttf +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/config.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/directives.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/export.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/generator.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/shell/__init__.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/scriptcast/shell/bash.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/tests/__init__.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/tests/test_cli.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/tests/test_config.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/tests/test_export.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/tests/test_generator.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/tests/test_integration.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/tests/test_registry.py +0 -0
- {scriptcast-0.2.0 → scriptcast-0.3.0}/tests/test_theme.py +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: Deploy GitHub Pages
|
|
3
3
|
on:
|
|
4
4
|
workflow_run:
|
|
5
|
-
workflows: ["
|
|
5
|
+
workflows: ["Publish to PyPI"]
|
|
6
6
|
types: [completed]
|
|
7
7
|
|
|
8
8
|
# Allows you to run this workflow manually from the Actions tab
|
|
@@ -24,6 +24,7 @@ jobs:
|
|
|
24
24
|
# Build job
|
|
25
25
|
build:
|
|
26
26
|
runs-on: ubuntu-latest
|
|
27
|
+
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
|
|
27
28
|
steps:
|
|
28
29
|
- name: Checkout
|
|
29
30
|
uses: actions/checkout@v4
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scriptcast
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Generate terminal demos (asciinema casts & GIFs) from simple shell-like scripts.
|
|
5
5
|
Project-URL: Homepage, https://dacrystal.github.io/scriptcast
|
|
6
6
|
Project-URL: Source Code, https://github.com/dacrystal/scriptcast
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -227,7 +227,7 @@ def cli(
|
|
|
227
227
|
out_dir = Path(output_dir) if output_dir else in_path.parent
|
|
228
228
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
229
229
|
resolved_shell = shell or _default_shell()
|
|
230
|
-
theme_path = _resolve_theme(theme
|
|
230
|
+
theme_path = _resolve_theme(theme or "aurora")
|
|
231
231
|
|
|
232
232
|
config = build_config(
|
|
233
233
|
script_path=in_path if suffix != ".cast" else None,
|
|
@@ -15,7 +15,7 @@ from pathlib import Path
|
|
|
15
15
|
|
|
16
16
|
from .config import ScriptcastConfig
|
|
17
17
|
from .directives import ScEvent, build_directives
|
|
18
|
-
from .shell import get_adapter
|
|
18
|
+
from .shell import ShellAdapter, get_adapter
|
|
19
19
|
|
|
20
20
|
logger = logging.getLogger(__name__)
|
|
21
21
|
|
|
@@ -89,10 +89,16 @@ def _postprocess(
|
|
|
89
89
|
raw_text: str,
|
|
90
90
|
trace_prefix: str = "+",
|
|
91
91
|
directive_prefix: str = "SC",
|
|
92
|
+
adapter: ShellAdapter | None = None,
|
|
92
93
|
) -> str:
|
|
93
94
|
"""Convert raw .log text to JSONL .sc body (no header line)."""
|
|
94
95
|
directives = build_directives(directive_prefix, trace_prefix)
|
|
95
96
|
events = _parse_raw(raw_text, trace_prefix, directive_prefix)
|
|
97
|
+
if adapter is not None:
|
|
98
|
+
events = [
|
|
99
|
+
ScEvent(e.ts, e.type, adapter.unescape_xtrace(e.text)) if e.type == "dir" else e
|
|
100
|
+
for e in events
|
|
101
|
+
]
|
|
96
102
|
for d in directives:
|
|
97
103
|
events = d.post(events)
|
|
98
104
|
return _serialise(events)
|
|
@@ -184,7 +190,7 @@ def record(
|
|
|
184
190
|
xtrace_path = sc_path.with_suffix('.xtrace')
|
|
185
191
|
xtrace_path.write_text(raw_text)
|
|
186
192
|
logger.info("Saved: %s", xtrace_path)
|
|
187
|
-
clean_text = _postprocess(raw_text, config.trace_prefix, config.directive_prefix)
|
|
193
|
+
clean_text = _postprocess(raw_text, config.trace_prefix, config.directive_prefix, adapter)
|
|
188
194
|
logger.debug("Post-processed to %d events", clean_text.count("\n"))
|
|
189
195
|
|
|
190
196
|
header = json.dumps({
|
|
@@ -11,3 +11,9 @@ class ShellAdapter(ABC):
|
|
|
11
11
|
def tracing_preamble(self, trace_prefix: str) -> str:
|
|
12
12
|
"""Return shell code to enable tracing with the given prefix."""
|
|
13
13
|
...
|
|
14
|
+
|
|
15
|
+
def unescape_xtrace(self, text: str) -> str:
|
|
16
|
+
"""Decode shell-specific quoting in a directive text from xtrace output.
|
|
17
|
+
Default implementation is identity (correct for bash).
|
|
18
|
+
"""
|
|
19
|
+
return text
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# scriptcast/shell/zsh.py
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from .adapter import ShellAdapter
|
|
5
|
+
|
|
6
|
+
_ANSI_C_RE = re.compile(r"\$'((?:[^'\\]|\\.)*)'")
|
|
7
|
+
_SIMPLE_ESCAPES = {
|
|
8
|
+
'n': '\n', 'r': '\r', 't': '\t', 'a': '\a',
|
|
9
|
+
'b': '\b', 'f': '\f', 'v': '\v', "'": "'", '\\': '\\',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _decode_ansi_c_body(body: str) -> str:
|
|
14
|
+
"""Decode the interior of a $'...' ANSI-C quoted string as produced by zsh xtrace."""
|
|
15
|
+
out: list[str] = []
|
|
16
|
+
i = 0
|
|
17
|
+
while i < len(body):
|
|
18
|
+
ch = body[i]
|
|
19
|
+
if ch != '\\' or i + 1 >= len(body):
|
|
20
|
+
out.append(ch)
|
|
21
|
+
i += 1
|
|
22
|
+
continue
|
|
23
|
+
nxt = body[i + 1]
|
|
24
|
+
if nxt in _SIMPLE_ESCAPES:
|
|
25
|
+
out.append(_SIMPLE_ESCAPES[nxt])
|
|
26
|
+
i += 2
|
|
27
|
+
elif nxt in ('e', 'E'):
|
|
28
|
+
out.append('\x1b')
|
|
29
|
+
i += 2
|
|
30
|
+
elif nxt in ('C', 'c') and i + 3 < len(body) and body[i + 2] == '-':
|
|
31
|
+
# \C-X → Ctrl+X; e.g. \C-[ → chr(91-64) = chr(27) = ESC
|
|
32
|
+
code = ord(body[i + 3].upper()) - 64
|
|
33
|
+
if 0 <= code <= 127:
|
|
34
|
+
out.append(chr(code))
|
|
35
|
+
i += 4
|
|
36
|
+
else:
|
|
37
|
+
out.append('\\')
|
|
38
|
+
out.append(nxt)
|
|
39
|
+
i += 2
|
|
40
|
+
elif nxt == 'x':
|
|
41
|
+
hex_str = body[i + 2:i + 4]
|
|
42
|
+
if len(hex_str) == 2 and all(c in '0123456789abcdefABCDEF' for c in hex_str):
|
|
43
|
+
out.append(chr(int(hex_str, 16)))
|
|
44
|
+
i += 4
|
|
45
|
+
else:
|
|
46
|
+
out.append('\\')
|
|
47
|
+
out.append(nxt)
|
|
48
|
+
i += 2
|
|
49
|
+
elif nxt in '01234567':
|
|
50
|
+
j = i + 1
|
|
51
|
+
while j < len(body) and j < i + 4 and body[j] in '01234567':
|
|
52
|
+
j += 1
|
|
53
|
+
out.append(chr(int(body[i + 1:j], 8)))
|
|
54
|
+
i = j
|
|
55
|
+
else:
|
|
56
|
+
out.append('\\')
|
|
57
|
+
out.append(nxt)
|
|
58
|
+
i += 2
|
|
59
|
+
return ''.join(out)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ZshAdapter(ShellAdapter):
|
|
63
|
+
@property
|
|
64
|
+
def name(self) -> str:
|
|
65
|
+
return "zsh"
|
|
66
|
+
|
|
67
|
+
def tracing_preamble(self, trace_prefix: str) -> str:
|
|
68
|
+
return f'PS4="{trace_prefix} "\nsetopt xtrace\n'
|
|
69
|
+
|
|
70
|
+
def unescape_xtrace(self, text: str) -> str:
|
|
71
|
+
"""Expand $'...' ANSI-C spans in zsh xtrace directive text."""
|
|
72
|
+
return _ANSI_C_RE.sub(lambda m: _decode_ansi_c_body(m.group(1)), text)
|
|
@@ -29,7 +29,7 @@ def test_sc_event_fields():
|
|
|
29
29
|
def test_sc_event_is_frozen():
|
|
30
30
|
e = ScEvent(ts=1.0, type="cmd", text="echo hi")
|
|
31
31
|
with pytest.raises(dataclasses.FrozenInstanceError):
|
|
32
|
-
e.ts = 2.0
|
|
32
|
+
e.ts = 2.0
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def test_directive_pre_passthrough():
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
4
|
import shutil
|
|
5
|
+
from unittest.mock import MagicMock
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
5
8
|
|
|
6
9
|
from scriptcast.config import ScriptcastConfig
|
|
7
10
|
from scriptcast.directives import ScEvent
|
|
@@ -460,3 +463,53 @@ def test_record_no_xtrace_log_by_default(tmp_path):
|
|
|
460
463
|
xtrace_path = tmp_path / "demo.xtrace"
|
|
461
464
|
assert not xtrace_path.exists()
|
|
462
465
|
|
|
466
|
+
|
|
467
|
+
def test_postprocess_applies_unescape_to_dir_events():
|
|
468
|
+
"""adapter.unescape_xtrace is called on dir events, not cmd or out events."""
|
|
469
|
+
adapter = MagicMock()
|
|
470
|
+
adapter.unescape_xtrace.side_effect = lambda t: t.replace("BEFORE", "AFTER")
|
|
471
|
+
|
|
472
|
+
raw = (
|
|
473
|
+
"1.0 + : SC scene main\n"
|
|
474
|
+
"1.1 + echo hi\n"
|
|
475
|
+
"1.2 hello\n"
|
|
476
|
+
)
|
|
477
|
+
_postprocess(raw, adapter=adapter)
|
|
478
|
+
|
|
479
|
+
# called once for the dir event "scene main", not for cmd or out
|
|
480
|
+
assert adapter.unescape_xtrace.call_count == 1
|
|
481
|
+
adapter.unescape_xtrace.assert_called_with("scene main")
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def test_postprocess_unescape_transforms_dir_text():
|
|
485
|
+
"""unescape result is used as the directive text in the .sc output."""
|
|
486
|
+
adapter = MagicMock()
|
|
487
|
+
adapter.unescape_xtrace.return_value = "set prompt \x1b[92m> \x1b[0m"
|
|
488
|
+
|
|
489
|
+
raw = "1.0 + : SC set prompt $'\\C-[[92m> \\C-[[0m'\n"
|
|
490
|
+
sc_body = _postprocess(raw, adapter=adapter)
|
|
491
|
+
|
|
492
|
+
events = [json.loads(line) for line in sc_body.strip().splitlines()]
|
|
493
|
+
assert events[0][1] == "dir"
|
|
494
|
+
assert events[0][2] == "set prompt \x1b[92m> \x1b[0m"
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def test_record_zsh_prompt_esc_bytes(tmp_path):
|
|
498
|
+
"""zsh $'...' prompt survives record → .sc with correct ESC bytes."""
|
|
499
|
+
zsh = shutil.which("zsh")
|
|
500
|
+
if zsh is None:
|
|
501
|
+
pytest.skip("zsh not available")
|
|
502
|
+
|
|
503
|
+
script = tmp_path / "t.sh"
|
|
504
|
+
# $'\x1b[92m> \x1b[0m' expands to ESC bytes; zsh xtrace uses $'\C-[...'
|
|
505
|
+
script.write_text(": SC set prompt $'\\x1b[92m> \\x1b[0m'\n")
|
|
506
|
+
sc_path = tmp_path / "t.sc"
|
|
507
|
+
record(script, sc_path, ScriptcastConfig(), zsh)
|
|
508
|
+
|
|
509
|
+
lines = sc_path.read_text().splitlines()
|
|
510
|
+
dir_events = [
|
|
511
|
+
json.loads(ln) for ln in lines[1:]
|
|
512
|
+
if json.loads(ln)[1] == "dir" and json.loads(ln)[2].startswith("set prompt")
|
|
513
|
+
]
|
|
514
|
+
assert dir_events, "no 'set prompt' dir event found in .sc"
|
|
515
|
+
assert dir_events[0][2] == "set prompt \x1b[92m> \x1b[0m"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# tests/test_shell.py
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from scriptcast.shell import get_adapter
|
|
5
|
+
from scriptcast.shell.bash import BashAdapter
|
|
6
|
+
from scriptcast.shell.zsh import ZshAdapter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_get_bash():
|
|
10
|
+
assert isinstance(get_adapter("bash"), BashAdapter)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_get_zsh():
|
|
14
|
+
assert isinstance(get_adapter("zsh"), ZshAdapter)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_get_full_path():
|
|
18
|
+
assert isinstance(get_adapter("/bin/bash"), BashAdapter)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_get_unsupported_raises():
|
|
22
|
+
with pytest.raises(ValueError, match="Unsupported shell"):
|
|
23
|
+
get_adapter("fish")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_bash_preamble_contains_set_x():
|
|
27
|
+
p = BashAdapter().tracing_preamble("+")
|
|
28
|
+
assert "set -x" in p
|
|
29
|
+
assert 'PS4="+ "' in p
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_bash_preamble_custom_prefix():
|
|
33
|
+
p = BashAdapter().tracing_preamble(">>")
|
|
34
|
+
assert 'PS4=">> "' in p
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_zsh_preamble_contains_xtrace():
|
|
38
|
+
p = ZshAdapter().tracing_preamble("+")
|
|
39
|
+
assert "setopt xtrace" in p
|
|
40
|
+
assert 'PS4="+ "' in p
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_bash_unescape_xtrace_is_identity():
|
|
44
|
+
adapter = BashAdapter()
|
|
45
|
+
text = "set prompt $'\\C-[[92m> \\C-[[0m'"
|
|
46
|
+
assert adapter.unescape_xtrace(text) == text
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_zsh_unescape_ctrl_bracket_to_esc():
|
|
50
|
+
# \C-[ is zsh's notation for ESC (Ctrl+[, chr 27)
|
|
51
|
+
result = ZshAdapter().unescape_xtrace("set prompt $'\\C-[[92m> \\C-[[0m'")
|
|
52
|
+
assert result == "set prompt \x1b[92m> \x1b[0m"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_zsh_unescape_octal():
|
|
56
|
+
result = ZshAdapter().unescape_xtrace("set prompt $'\\033[92m> \\033[0m'")
|
|
57
|
+
assert result == "set prompt \x1b[92m> \x1b[0m"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_zsh_unescape_hex():
|
|
61
|
+
result = ZshAdapter().unescape_xtrace("set prompt $'\\x1b[92m> \\x1b[0m'")
|
|
62
|
+
assert result == "set prompt \x1b[92m> \x1b[0m"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_zsh_unescape_escape_letter():
|
|
66
|
+
result = ZshAdapter().unescape_xtrace("set prompt $'\\e[92m> \\e[0m'")
|
|
67
|
+
assert result == "set prompt \x1b[92m> \x1b[0m"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_zsh_unescape_standard_escapes():
|
|
71
|
+
result = ZshAdapter().unescape_xtrace("$'\\n\\t\\r\\\\'")
|
|
72
|
+
assert result == "\n\t\r\\"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_zsh_unescape_no_dollar_quote_unchanged():
|
|
76
|
+
text = "set prompt '\\033[92m> \\033[0m'"
|
|
77
|
+
assert ZshAdapter().unescape_xtrace(text) == text
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_zsh_unescape_multiple_spans():
|
|
81
|
+
result = ZshAdapter().unescape_xtrace("$'\\C-['foo$'\\C-['")
|
|
82
|
+
assert result == "\x1bfoo\x1b"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_zsh_unescape_unknown_escape_passthrough():
|
|
86
|
+
# \q is not a known escape — passes through unchanged
|
|
87
|
+
result = ZshAdapter().unescape_xtrace("$'\\q'")
|
|
88
|
+
assert result == "\\q"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_zsh_unescape_ctrl_out_of_range_passthrough():
|
|
92
|
+
# \C-? would give chr(-1) — should passthrough, not crash
|
|
93
|
+
result = ZshAdapter().unescape_xtrace("$'\\C-?'")
|
|
94
|
+
assert result == "\\C-?"
|
scriptcast-0.2.0/assets/demo.png
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# scriptcast/shell/zsh.py
|
|
2
|
-
from .adapter import ShellAdapter
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class ZshAdapter(ShellAdapter):
|
|
6
|
-
@property
|
|
7
|
-
def name(self) -> str:
|
|
8
|
-
return "zsh"
|
|
9
|
-
|
|
10
|
-
def tracing_preamble(self, trace_prefix: str) -> str:
|
|
11
|
-
return f'PS4="{trace_prefix} "\nsetopt xtrace\n'
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# tests/test_shell.py
|
|
2
|
-
import pytest
|
|
3
|
-
|
|
4
|
-
from scriptcast.shell import get_adapter
|
|
5
|
-
from scriptcast.shell.bash import BashAdapter
|
|
6
|
-
from scriptcast.shell.zsh import ZshAdapter
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def test_get_bash():
|
|
10
|
-
assert isinstance(get_adapter("bash"), BashAdapter)
|
|
11
|
-
|
|
12
|
-
def test_get_zsh():
|
|
13
|
-
assert isinstance(get_adapter("zsh"), ZshAdapter)
|
|
14
|
-
|
|
15
|
-
def test_get_full_path():
|
|
16
|
-
assert isinstance(get_adapter("/bin/bash"), BashAdapter)
|
|
17
|
-
|
|
18
|
-
def test_get_unsupported_raises():
|
|
19
|
-
with pytest.raises(ValueError, match="Unsupported shell"):
|
|
20
|
-
get_adapter("fish")
|
|
21
|
-
|
|
22
|
-
def test_bash_preamble_contains_set_x():
|
|
23
|
-
p = BashAdapter().tracing_preamble("+")
|
|
24
|
-
assert "set -x" in p
|
|
25
|
-
assert 'PS4="+ "' in p
|
|
26
|
-
|
|
27
|
-
def test_bash_preamble_custom_prefix():
|
|
28
|
-
p = BashAdapter().tracing_preamble(">>")
|
|
29
|
-
assert 'PS4=">> "' in p
|
|
30
|
-
|
|
31
|
-
def test_zsh_preamble_contains_xtrace():
|
|
32
|
-
p = ZshAdapter().tracing_preamble("+")
|
|
33
|
-
assert "setopt xtrace" in p
|
|
34
|
-
assert 'PS4="+ "' in p
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|