scriptcast 0.1.0__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.
- scriptcast/__init__.py +0 -0
- scriptcast/__main__.py +371 -0
- scriptcast/assets/__init__.py +0 -0
- scriptcast/assets/fonts/DMSans-Regular.ttf +0 -0
- scriptcast/assets/fonts/Pacifico.ttf +0 -0
- scriptcast/assets/themes/aurora.sh +20 -0
- scriptcast/assets/themes/dark.sh +19 -0
- scriptcast/assets/themes/light.sh +19 -0
- scriptcast/config.py +199 -0
- scriptcast/directives.py +444 -0
- scriptcast/export.py +595 -0
- scriptcast/generator.py +265 -0
- scriptcast/recorder.py +212 -0
- scriptcast/shell/__init__.py +20 -0
- scriptcast/shell/adapter.py +13 -0
- scriptcast/shell/bash.py +11 -0
- scriptcast/shell/zsh.py +11 -0
- scriptcast-0.1.0.dist-info/METADATA +21 -0
- scriptcast-0.1.0.dist-info/RECORD +33 -0
- scriptcast-0.1.0.dist-info/WHEEL +5 -0
- scriptcast-0.1.0.dist-info/entry_points.txt +2 -0
- scriptcast-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_cli.py +304 -0
- tests/test_config.py +400 -0
- tests/test_directives.py +606 -0
- tests/test_export.py +986 -0
- tests/test_generator.py +434 -0
- tests/test_integration.py +97 -0
- tests/test_recorder.py +462 -0
- tests/test_registry.py +57 -0
- tests/test_shell.py +34 -0
- tests/test_theme.py +204 -0
tests/test_cli.py
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# tests/test_cli.py
|
|
2
|
+
import json
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from click.testing import CliRunner
|
|
9
|
+
|
|
10
|
+
from scriptcast.__main__ import cli
|
|
11
|
+
|
|
12
|
+
requires_agg = pytest.mark.skipif(shutil.which("agg") is None, reason="agg not installed")
|
|
13
|
+
|
|
14
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
def _minimal_cast(tmp_path: Path, name: str = "demo") -> Path:
|
|
17
|
+
cast = tmp_path / f"{name}.cast"
|
|
18
|
+
cast.write_text(json.dumps({"version": 2, "width": 80, "height": 24}) + "\n")
|
|
19
|
+
return cast
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _minimal_sc(tmp_path: Path, name: str = "demo") -> Path:
|
|
23
|
+
sc = tmp_path / f"{name}.sc"
|
|
24
|
+
sc.write_text(
|
|
25
|
+
json.dumps({"version": 1, "width": 80, "height": 24, "directive-prefix": "SC"}) + "\n"
|
|
26
|
+
)
|
|
27
|
+
return sc
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _sh(tmp_path: Path, content: str = ": SC scene demo\necho hello\n") -> Path:
|
|
31
|
+
script = tmp_path / "demo.sh"
|
|
32
|
+
script.write_text(content)
|
|
33
|
+
return script
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ── no-args / help ─────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
def test_no_args_prints_help():
|
|
39
|
+
runner = CliRunner()
|
|
40
|
+
result = runner.invoke(cli, [])
|
|
41
|
+
assert result.exit_code == 0
|
|
42
|
+
assert "Usage" in result.output
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ── error cases ────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
def test_nonexistent_file_errors(tmp_path):
|
|
48
|
+
runner = CliRunner()
|
|
49
|
+
result = runner.invoke(cli, [str(tmp_path / "nope.sh")])
|
|
50
|
+
assert result.exit_code != 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_unsupported_extension_errors(tmp_path):
|
|
54
|
+
f = tmp_path / "demo.txt"
|
|
55
|
+
f.write_text("hello\n")
|
|
56
|
+
runner = CliRunner()
|
|
57
|
+
result = runner.invoke(cli, [str(f)])
|
|
58
|
+
assert result.exit_code != 0
|
|
59
|
+
assert "Unsupported" in result.output
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_extra_args_after_input_errors(tmp_path):
|
|
63
|
+
script = _sh(tmp_path)
|
|
64
|
+
runner = CliRunner()
|
|
65
|
+
result = runner.invoke(cli, [str(script), "extra"])
|
|
66
|
+
assert result.exit_code != 0
|
|
67
|
+
assert "Unexpected" in result.output
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_cast_input_with_no_export_errors(tmp_path):
|
|
71
|
+
cast = _minimal_cast(tmp_path)
|
|
72
|
+
runner = CliRunner()
|
|
73
|
+
result = runner.invoke(cli, ["--no-export", str(cast)])
|
|
74
|
+
assert result.exit_code != 0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ── .sh input (full pipeline) ──────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
@requires_agg
|
|
80
|
+
def test_sh_input_end_to_end(tmp_path):
|
|
81
|
+
"""scriptcast demo.sh records, generates, exports."""
|
|
82
|
+
script = _sh(tmp_path)
|
|
83
|
+
runner = CliRunner()
|
|
84
|
+
result = runner.invoke(cli, ["--output-dir", str(tmp_path), str(script)])
|
|
85
|
+
assert result.exit_code == 0, result.output
|
|
86
|
+
assert (tmp_path / "demo.cast").exists()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_sh_input_no_export_produces_cast(tmp_path):
|
|
90
|
+
"""scriptcast demo.sh --no-export produces .cast but no export."""
|
|
91
|
+
script = _sh(tmp_path)
|
|
92
|
+
runner = CliRunner()
|
|
93
|
+
result = runner.invoke(cli, ["--output-dir", str(tmp_path), "--no-export", str(script)])
|
|
94
|
+
assert result.exit_code == 0, result.output
|
|
95
|
+
assert (tmp_path / "demo.cast").exists()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@requires_agg
|
|
99
|
+
def test_sh_input_split_scenes(tmp_path):
|
|
100
|
+
script = _sh(tmp_path, content=(
|
|
101
|
+
": SC scene alpha\necho a\n"
|
|
102
|
+
": SC scene beta\necho b\n"
|
|
103
|
+
))
|
|
104
|
+
runner = CliRunner()
|
|
105
|
+
result = runner.invoke(
|
|
106
|
+
cli, ["--output-dir", str(tmp_path), "--split-scenes", str(script)]
|
|
107
|
+
)
|
|
108
|
+
assert result.exit_code == 0, result.output
|
|
109
|
+
assert (tmp_path / "alpha.cast").exists()
|
|
110
|
+
assert (tmp_path / "beta.cast").exists()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@requires_agg
|
|
114
|
+
def test_sh_input_single_cast_default(tmp_path):
|
|
115
|
+
script = _sh(tmp_path, content=(
|
|
116
|
+
": SC scene alpha\necho a\n"
|
|
117
|
+
": SC scene beta\necho b\n"
|
|
118
|
+
))
|
|
119
|
+
runner = CliRunner()
|
|
120
|
+
result = runner.invoke(cli, ["--output-dir", str(tmp_path), str(script)])
|
|
121
|
+
assert result.exit_code == 0, result.output
|
|
122
|
+
assert (tmp_path / "demo.cast").exists()
|
|
123
|
+
assert not (tmp_path / "alpha.cast").exists()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_sh_input_directive_prefix_stored_in_sc(tmp_path):
|
|
127
|
+
script = _sh(tmp_path, content="echo hello\n")
|
|
128
|
+
runner = CliRunner()
|
|
129
|
+
result = runner.invoke(
|
|
130
|
+
cli,
|
|
131
|
+
["--directive-prefix", "DEMO", "--output-dir", str(tmp_path), "--no-export", str(script)],
|
|
132
|
+
)
|
|
133
|
+
assert result.exit_code == 0, result.output
|
|
134
|
+
header = json.loads((tmp_path / "demo.sc").read_text().splitlines()[0])
|
|
135
|
+
assert header["directive-prefix"] == "DEMO"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ── .sc input ──────────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
def test_sc_input_generates_and_exports(tmp_path):
|
|
141
|
+
sc = _minimal_sc(tmp_path)
|
|
142
|
+
cast = tmp_path / "demo.cast"
|
|
143
|
+
runner = CliRunner()
|
|
144
|
+
with patch("scriptcast.__main__.generate_export", return_value=cast) as mock_exp, \
|
|
145
|
+
patch("scriptcast.__main__.apply_scriptcast_watermark"):
|
|
146
|
+
result = runner.invoke(cli, ["--output-dir", str(tmp_path), str(sc)])
|
|
147
|
+
assert result.exit_code == 0, result.output
|
|
148
|
+
mock_exp.assert_called_once()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_sc_input_no_export_produces_cast(tmp_path):
|
|
152
|
+
sc = _minimal_sc(tmp_path)
|
|
153
|
+
runner = CliRunner()
|
|
154
|
+
result = runner.invoke(cli, ["--output-dir", str(tmp_path), "--no-export", str(sc)])
|
|
155
|
+
assert result.exit_code == 0, result.output
|
|
156
|
+
assert (tmp_path / "demo.cast").exists()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ── .cast input ────────────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
def test_cast_input_exports(tmp_path):
|
|
162
|
+
cast = _minimal_cast(tmp_path)
|
|
163
|
+
runner = CliRunner()
|
|
164
|
+
with patch("scriptcast.__main__.generate_export", return_value=cast), \
|
|
165
|
+
patch("scriptcast.__main__.apply_scriptcast_watermark"):
|
|
166
|
+
result = runner.invoke(cli, ["--output-dir", str(tmp_path), str(cast)])
|
|
167
|
+
assert result.exit_code == 0, result.output
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ── format flag ────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
def test_format_png_accepted(tmp_path):
|
|
173
|
+
cast = _minimal_cast(tmp_path)
|
|
174
|
+
runner = CliRunner()
|
|
175
|
+
with patch("scriptcast.__main__.generate_export", return_value=cast), \
|
|
176
|
+
patch("scriptcast.__main__.apply_scriptcast_watermark"):
|
|
177
|
+
result = runner.invoke(cli, ["--format", "png", str(cast)])
|
|
178
|
+
assert result.exit_code == 0, result.output
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_format_gif_accepted(tmp_path):
|
|
182
|
+
cast = _minimal_cast(tmp_path)
|
|
183
|
+
runner = CliRunner()
|
|
184
|
+
with patch("scriptcast.__main__.generate_export", return_value=cast), \
|
|
185
|
+
patch("scriptcast.__main__.apply_scriptcast_watermark"):
|
|
186
|
+
result = runner.invoke(cli, ["--format", "gif", str(cast)])
|
|
187
|
+
assert result.exit_code == 0, result.output
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_format_apng_rejected(tmp_path):
|
|
191
|
+
cast = _minimal_cast(tmp_path)
|
|
192
|
+
runner = CliRunner()
|
|
193
|
+
result = runner.invoke(cli, ["--format", "apng", str(cast)])
|
|
194
|
+
assert result.exit_code != 0
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ── install subcommand ─────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
def test_install_command_exists():
|
|
200
|
+
assert "install" in cli.commands
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def test_install_command_has_prefix_option():
|
|
204
|
+
param_names = [p.name for p in cli.commands["install"].params]
|
|
205
|
+
assert "prefix" in param_names
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_install_downloads_agg_and_fonts(tmp_path):
|
|
209
|
+
import io
|
|
210
|
+
import zipfile
|
|
211
|
+
from unittest.mock import MagicMock
|
|
212
|
+
|
|
213
|
+
buf = io.BytesIO()
|
|
214
|
+
with zipfile.ZipFile(buf, "w") as zf:
|
|
215
|
+
zf.writestr("fonts/ttf/JetBrainsMono-Regular.ttf", b"fake-ttf-data")
|
|
216
|
+
zip_bytes = buf.getvalue()
|
|
217
|
+
|
|
218
|
+
def fake_urlretrieve(url, dest):
|
|
219
|
+
dest = Path(dest)
|
|
220
|
+
if "agg" in url:
|
|
221
|
+
dest.write_bytes(b"fake-agg-binary")
|
|
222
|
+
else:
|
|
223
|
+
dest.write_bytes(zip_bytes)
|
|
224
|
+
|
|
225
|
+
fake_release = json.dumps({"assets": [
|
|
226
|
+
{"name": "JetBrainsMono-2.304.zip",
|
|
227
|
+
"browser_download_url": "https://example.com/JetBrainsMono-2.304.zip"},
|
|
228
|
+
]}).encode()
|
|
229
|
+
mock_resp = MagicMock()
|
|
230
|
+
mock_resp.read.return_value = fake_release
|
|
231
|
+
mock_resp.__enter__ = lambda s: s
|
|
232
|
+
mock_resp.__exit__ = MagicMock(return_value=False)
|
|
233
|
+
|
|
234
|
+
runner = CliRunner()
|
|
235
|
+
with patch("urllib.request.urlretrieve", side_effect=fake_urlretrieve), \
|
|
236
|
+
patch("urllib.request.urlopen", return_value=mock_resp):
|
|
237
|
+
result = runner.invoke(cli, ["install", "--prefix", str(tmp_path)])
|
|
238
|
+
|
|
239
|
+
assert result.exit_code == 0, result.output
|
|
240
|
+
assert (tmp_path / ".agg-real").exists()
|
|
241
|
+
assert (tmp_path / "agg").exists()
|
|
242
|
+
assert (tmp_path / "fonts" / "JetBrainsMono-Regular.ttf").exists()
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# ── removed subcommands ────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
def test_record_subcommand_removed():
|
|
248
|
+
assert "record" not in cli.commands
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def test_generate_subcommand_removed():
|
|
252
|
+
assert "generate" not in cli.commands
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_export_subcommand_removed():
|
|
256
|
+
assert "export" not in cli.commands
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# ── --xtrace-log flag ──────────────────────────────────────────────────────
|
|
260
|
+
|
|
261
|
+
def test_xtrace_log_creates_xtrace_file(tmp_path):
|
|
262
|
+
script = _sh(tmp_path)
|
|
263
|
+
runner = CliRunner()
|
|
264
|
+
result = runner.invoke(
|
|
265
|
+
cli, ["--output-dir", str(tmp_path), "--no-export", "--xtrace-log", str(script)]
|
|
266
|
+
)
|
|
267
|
+
assert result.exit_code == 0, result.output
|
|
268
|
+
assert (tmp_path / "demo.xtrace").exists()
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def test_xtrace_log_with_sc_input_errors(tmp_path):
|
|
272
|
+
sc = _minimal_sc(tmp_path)
|
|
273
|
+
runner = CliRunner()
|
|
274
|
+
result = runner.invoke(cli, ["--xtrace-log", "--no-export", str(sc)])
|
|
275
|
+
assert result.exit_code != 0
|
|
276
|
+
assert "xtrace-log" in result.output.lower() or "xtrace" in result.output.lower()
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def test_xtrace_log_with_cast_input_errors(tmp_path):
|
|
280
|
+
cast = _minimal_cast(tmp_path)
|
|
281
|
+
runner = CliRunner()
|
|
282
|
+
with patch("scriptcast.__main__.generate_export", return_value=cast), \
|
|
283
|
+
patch("scriptcast.__main__.apply_scriptcast_watermark"):
|
|
284
|
+
result = runner.invoke(cli, ["--xtrace-log", str(cast)])
|
|
285
|
+
assert result.exit_code != 0
|
|
286
|
+
assert "xtrace" in result.output.lower()
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def test_export_on_frame_callback_is_passed(tmp_path):
|
|
290
|
+
"""generate_export is called with a callable on_frame argument."""
|
|
291
|
+
cast = _minimal_cast(tmp_path)
|
|
292
|
+
runner = CliRunner()
|
|
293
|
+
received = {}
|
|
294
|
+
|
|
295
|
+
def fake_generate_export(cast_path, frame_config, format, on_frame=None):
|
|
296
|
+
received["on_frame"] = on_frame
|
|
297
|
+
return cast
|
|
298
|
+
|
|
299
|
+
with patch("scriptcast.__main__.generate_export", side_effect=fake_generate_export), \
|
|
300
|
+
patch("scriptcast.__main__.apply_scriptcast_watermark"):
|
|
301
|
+
result = runner.invoke(cli, [str(cast)])
|
|
302
|
+
|
|
303
|
+
assert result.exit_code == 0, result.output
|
|
304
|
+
assert callable(received.get("on_frame"))
|
tests/test_config.py
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
# tests/test_config.py
|
|
2
|
+
from scriptcast.config import ScriptcastConfig
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_defaults():
|
|
6
|
+
c = ScriptcastConfig()
|
|
7
|
+
assert c.type_speed == 40
|
|
8
|
+
assert c.cmd_wait == 80
|
|
9
|
+
assert c.input_wait == 80
|
|
10
|
+
assert c.exit_wait == 120
|
|
11
|
+
assert c.enter_wait == 80
|
|
12
|
+
assert c.width == 100
|
|
13
|
+
assert c.height == 28
|
|
14
|
+
assert c.terminal_theme == "dark" # was c.theme
|
|
15
|
+
assert c.prompt == "$ "
|
|
16
|
+
assert c.directive_prefix == "SC"
|
|
17
|
+
assert c.trace_prefix == "+"
|
|
18
|
+
assert c.split_scenes is False
|
|
19
|
+
|
|
20
|
+
def test_apply_set_int():
|
|
21
|
+
c = ScriptcastConfig()
|
|
22
|
+
c.apply("set", ["type_speed", "60"])
|
|
23
|
+
assert c.type_speed == 60
|
|
24
|
+
|
|
25
|
+
def test_apply_set_str():
|
|
26
|
+
c = ScriptcastConfig()
|
|
27
|
+
c.apply("set", ["terminal-theme", "light"]) # dash form; was "theme"
|
|
28
|
+
assert c.terminal_theme == "light" # was c.theme
|
|
29
|
+
|
|
30
|
+
def test_apply_old_theme_key_ignored():
|
|
31
|
+
c = ScriptcastConfig()
|
|
32
|
+
c.apply("set", ["theme", "light"]) # old key — no longer valid
|
|
33
|
+
assert c.terminal_theme == "dark" # unchanged
|
|
34
|
+
|
|
35
|
+
def test_apply_unknown_key_ignored():
|
|
36
|
+
c = ScriptcastConfig()
|
|
37
|
+
c.apply("set", ["nonexistent", "value"]) # must not raise
|
|
38
|
+
|
|
39
|
+
def test_apply_non_set_directive_ignored():
|
|
40
|
+
c = ScriptcastConfig()
|
|
41
|
+
c.apply("scene", ["Intro"]) # must not raise
|
|
42
|
+
|
|
43
|
+
def test_copy_is_independent():
|
|
44
|
+
c = ScriptcastConfig()
|
|
45
|
+
c2 = c.copy()
|
|
46
|
+
c2.type_speed = 999
|
|
47
|
+
assert c.type_speed == 40
|
|
48
|
+
|
|
49
|
+
def test_enter_wait_default():
|
|
50
|
+
assert ScriptcastConfig().enter_wait == 80
|
|
51
|
+
|
|
52
|
+
def test_input_wait_default_is_cmd_wait():
|
|
53
|
+
assert ScriptcastConfig().input_wait == 80
|
|
54
|
+
|
|
55
|
+
def test_split_scenes_default():
|
|
56
|
+
assert ScriptcastConfig().split_scenes is False
|
|
57
|
+
|
|
58
|
+
def test_apply_enter_wait():
|
|
59
|
+
c = ScriptcastConfig()
|
|
60
|
+
c.apply("set", ["enter_wait", "200"])
|
|
61
|
+
assert c.enter_wait == 200
|
|
62
|
+
|
|
63
|
+
def test_apply_split_scenes_true():
|
|
64
|
+
c = ScriptcastConfig()
|
|
65
|
+
c.apply("set", ["split_scenes", "true"])
|
|
66
|
+
assert c.split_scenes is True
|
|
67
|
+
|
|
68
|
+
def test_apply_split_scenes_false():
|
|
69
|
+
c = ScriptcastConfig()
|
|
70
|
+
c.split_scenes = True
|
|
71
|
+
c.apply("set", ["split_scenes", "false"])
|
|
72
|
+
assert c.split_scenes is False
|
|
73
|
+
|
|
74
|
+
def test_word_speed_default_is_none():
|
|
75
|
+
assert ScriptcastConfig().word_speed is None
|
|
76
|
+
|
|
77
|
+
def test_apply_word_speed():
|
|
78
|
+
c = ScriptcastConfig()
|
|
79
|
+
c.apply("set", ["word_speed", "80"])
|
|
80
|
+
assert c.word_speed == 80
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_theme_config_defaults():
|
|
84
|
+
from scriptcast.config import ThemeConfig
|
|
85
|
+
c = ThemeConfig()
|
|
86
|
+
assert c.frame_bar_title == "Terminal"
|
|
87
|
+
# Individual padding sides
|
|
88
|
+
assert c.padding_top == 14
|
|
89
|
+
assert c.padding_right == 14
|
|
90
|
+
assert c.padding_bottom == 14
|
|
91
|
+
assert c.padding_left == 14
|
|
92
|
+
assert c.radius == 12
|
|
93
|
+
assert c.border_color == "ffffff30"
|
|
94
|
+
assert c.border_width == 1
|
|
95
|
+
assert c.background == "1e1b4b,0d3b66"
|
|
96
|
+
# Individual margin sides
|
|
97
|
+
assert c.margin_top is None
|
|
98
|
+
assert c.margin_right is None
|
|
99
|
+
assert c.margin_bottom is None
|
|
100
|
+
assert c.margin_left is None
|
|
101
|
+
assert c.shadow is True
|
|
102
|
+
assert c.shadow_color == "0000004d"
|
|
103
|
+
assert c.shadow_radius == 20
|
|
104
|
+
assert c.shadow_offset_y == 21
|
|
105
|
+
assert c.watermark is None
|
|
106
|
+
assert c.watermark_color == "ffffff"
|
|
107
|
+
assert c.watermark_size is None
|
|
108
|
+
assert c.scriptcast_watermark is True
|
|
109
|
+
assert c.frame is True
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_theme_config_custom():
|
|
113
|
+
from scriptcast.config import ThemeConfig
|
|
114
|
+
c = ThemeConfig(frame_bar_title="Demo", background="#1a1a2e,#16213e", watermark="hello")
|
|
115
|
+
assert c.frame_bar_title == "Demo"
|
|
116
|
+
assert c.background == "#1a1a2e,#16213e"
|
|
117
|
+
assert c.watermark == "hello"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_theme_config_custom_padding():
|
|
121
|
+
from scriptcast.config import ThemeConfig
|
|
122
|
+
c = ThemeConfig(padding_top=10, padding_right=20, padding_bottom=30, padding_left=20)
|
|
123
|
+
assert c.padding_top == 10
|
|
124
|
+
assert c.padding_bottom == 30
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_theme_config_custom_margin():
|
|
128
|
+
from scriptcast.config import ThemeConfig
|
|
129
|
+
c = ThemeConfig(margin_top=40, margin_bottom=80)
|
|
130
|
+
assert c.margin_top == 40
|
|
131
|
+
assert c.margin_bottom == 80
|
|
132
|
+
assert c.margin_left is None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_theme_config_frame_default_true():
|
|
136
|
+
from scriptcast.config import ThemeConfig
|
|
137
|
+
assert ThemeConfig().frame is True
|
|
138
|
+
|
|
139
|
+
def test_theme_config_frame_true():
|
|
140
|
+
from scriptcast.config import ThemeConfig
|
|
141
|
+
assert ThemeConfig(frame=True).frame is True
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_theme_config_scriptcast_watermark_default():
|
|
145
|
+
from scriptcast.config import ThemeConfig
|
|
146
|
+
assert ThemeConfig().scriptcast_watermark is True
|
|
147
|
+
|
|
148
|
+
def test_theme_config_scriptcast_watermark_disabled():
|
|
149
|
+
from scriptcast.config import ThemeConfig
|
|
150
|
+
assert ThemeConfig(scriptcast_watermark=False).scriptcast_watermark is False
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_theme_config_apply_string():
|
|
154
|
+
from scriptcast.config import ThemeConfig
|
|
155
|
+
tc = ThemeConfig()
|
|
156
|
+
tc.apply("background", "ff0000,0000ff")
|
|
157
|
+
assert tc.background == "ff0000,0000ff"
|
|
158
|
+
|
|
159
|
+
def test_theme_config_apply_int():
|
|
160
|
+
from scriptcast.config import ThemeConfig
|
|
161
|
+
tc = ThemeConfig()
|
|
162
|
+
tc.apply("radius", "20")
|
|
163
|
+
assert tc.radius == 20
|
|
164
|
+
|
|
165
|
+
def test_theme_config_apply_bool_true():
|
|
166
|
+
from scriptcast.config import ThemeConfig
|
|
167
|
+
tc = ThemeConfig(shadow=False)
|
|
168
|
+
tc.apply("shadow", "true")
|
|
169
|
+
assert tc.shadow is True
|
|
170
|
+
|
|
171
|
+
def test_theme_config_apply_bool_false():
|
|
172
|
+
from scriptcast.config import ThemeConfig
|
|
173
|
+
tc = ThemeConfig(frame=True)
|
|
174
|
+
tc.apply("frame", "false")
|
|
175
|
+
assert tc.frame is False
|
|
176
|
+
|
|
177
|
+
def test_theme_config_apply_bool_scriptcast_watermark():
|
|
178
|
+
from scriptcast.config import ThemeConfig
|
|
179
|
+
tc = ThemeConfig(scriptcast_watermark=True)
|
|
180
|
+
tc.apply("scriptcast-watermark", "false")
|
|
181
|
+
assert tc.scriptcast_watermark is False
|
|
182
|
+
|
|
183
|
+
def test_theme_config_apply_margin_shorthand_one():
|
|
184
|
+
from scriptcast.config import ThemeConfig
|
|
185
|
+
tc = ThemeConfig()
|
|
186
|
+
tc.apply("margin", "60")
|
|
187
|
+
assert (tc.margin_top, tc.margin_right, tc.margin_bottom, tc.margin_left) == (60, 60, 60, 60)
|
|
188
|
+
|
|
189
|
+
def test_theme_config_apply_margin_shorthand_three():
|
|
190
|
+
from scriptcast.config import ThemeConfig
|
|
191
|
+
tc = ThemeConfig()
|
|
192
|
+
tc.apply("margin", "32 32 64")
|
|
193
|
+
assert tc.margin_top == 32
|
|
194
|
+
assert tc.margin_right == 32
|
|
195
|
+
assert tc.margin_bottom == 64
|
|
196
|
+
assert tc.margin_left == 32
|
|
197
|
+
|
|
198
|
+
def test_theme_config_apply_padding_shorthand():
|
|
199
|
+
from scriptcast.config import ThemeConfig
|
|
200
|
+
tc = ThemeConfig()
|
|
201
|
+
tc.apply("padding", "10 20")
|
|
202
|
+
assert (tc.padding_top, tc.padding_bottom) == (10, 10)
|
|
203
|
+
assert (tc.padding_left, tc.padding_right) == (20, 20)
|
|
204
|
+
|
|
205
|
+
def test_theme_config_apply_margin_individual():
|
|
206
|
+
from scriptcast.config import ThemeConfig
|
|
207
|
+
tc = ThemeConfig()
|
|
208
|
+
tc.apply("margin-bottom", "120")
|
|
209
|
+
assert tc.margin_bottom == 120
|
|
210
|
+
assert tc.margin_top is None # unchanged
|
|
211
|
+
|
|
212
|
+
def test_theme_config_apply_frame_bar_bool():
|
|
213
|
+
from scriptcast.config import ThemeConfig
|
|
214
|
+
tc = ThemeConfig(frame_bar=True)
|
|
215
|
+
tc.apply("frame-bar", "false")
|
|
216
|
+
assert tc.frame_bar is False
|
|
217
|
+
|
|
218
|
+
def test_theme_config_apply_frame_bar_title():
|
|
219
|
+
from scriptcast.config import ThemeConfig
|
|
220
|
+
tc = ThemeConfig()
|
|
221
|
+
tc.apply("frame-bar-title", "My Terminal")
|
|
222
|
+
assert tc.frame_bar_title == "My Terminal"
|
|
223
|
+
|
|
224
|
+
def test_theme_config_apply_nullable_none():
|
|
225
|
+
from scriptcast.config import ThemeConfig
|
|
226
|
+
tc = ThemeConfig(background="ff0000")
|
|
227
|
+
tc.apply("background", "none")
|
|
228
|
+
assert tc.background is None
|
|
229
|
+
|
|
230
|
+
def test_theme_config_apply_unknown_key_ignored():
|
|
231
|
+
from scriptcast.config import ThemeConfig
|
|
232
|
+
tc = ThemeConfig()
|
|
233
|
+
tc.apply("nonexistent-key", "value") # must not raise
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def test_scriptcast_config_has_theme():
|
|
237
|
+
from scriptcast.config import ScriptcastConfig, ThemeConfig
|
|
238
|
+
sc = ScriptcastConfig()
|
|
239
|
+
assert isinstance(sc.theme, ThemeConfig)
|
|
240
|
+
|
|
241
|
+
def test_scriptcast_config_theme_defaults():
|
|
242
|
+
from scriptcast.config import ScriptcastConfig
|
|
243
|
+
sc = ScriptcastConfig()
|
|
244
|
+
assert sc.theme.frame is True
|
|
245
|
+
assert sc.theme.background == "1e1b4b,0d3b66"
|
|
246
|
+
|
|
247
|
+
def test_scriptcast_config_apply_theme_key():
|
|
248
|
+
from scriptcast.config import ScriptcastConfig
|
|
249
|
+
sc = ScriptcastConfig()
|
|
250
|
+
sc.apply("set", ["theme-radius", "20"])
|
|
251
|
+
assert sc.theme.radius == 20
|
|
252
|
+
|
|
253
|
+
def test_scriptcast_config_apply_theme_background():
|
|
254
|
+
from scriptcast.config import ScriptcastConfig
|
|
255
|
+
sc = ScriptcastConfig()
|
|
256
|
+
sc.apply("set", ["theme-background", "ff0000,0000ff"])
|
|
257
|
+
assert sc.theme.background == "ff0000,0000ff"
|
|
258
|
+
|
|
259
|
+
def test_scriptcast_config_apply_theme_bool():
|
|
260
|
+
from scriptcast.config import ScriptcastConfig
|
|
261
|
+
sc = ScriptcastConfig()
|
|
262
|
+
sc.apply("set", ["theme-frame", "false"])
|
|
263
|
+
assert sc.theme.frame is False
|
|
264
|
+
|
|
265
|
+
def test_scriptcast_config_apply_theme_margin():
|
|
266
|
+
from scriptcast.config import ScriptcastConfig
|
|
267
|
+
sc = ScriptcastConfig()
|
|
268
|
+
sc.apply("set", ["theme-margin", "32 32 64"])
|
|
269
|
+
assert sc.theme.margin_bottom == 64
|
|
270
|
+
|
|
271
|
+
def test_scriptcast_config_apply_terminal_theme_not_delegated():
|
|
272
|
+
"""terminal-theme must NOT be routed to ThemeConfig; stays on ScriptcastConfig."""
|
|
273
|
+
from scriptcast.config import ScriptcastConfig
|
|
274
|
+
sc = ScriptcastConfig()
|
|
275
|
+
sc.apply("set", ["terminal-theme", "light"])
|
|
276
|
+
assert sc.terminal_theme == "light"
|
|
277
|
+
|
|
278
|
+
def test_copy_deep_copies_theme():
|
|
279
|
+
from scriptcast.config import ScriptcastConfig
|
|
280
|
+
sc = ScriptcastConfig()
|
|
281
|
+
sc2 = sc.copy()
|
|
282
|
+
sc2.theme.radius = 999
|
|
283
|
+
assert sc.theme.radius == 12 # unchanged
|
|
284
|
+
|
|
285
|
+
def test_copy_is_still_independent_for_scalar():
|
|
286
|
+
from scriptcast.config import ScriptcastConfig
|
|
287
|
+
sc = ScriptcastConfig()
|
|
288
|
+
sc2 = sc.copy()
|
|
289
|
+
sc2.type_speed = 999
|
|
290
|
+
assert sc.type_speed == 40
|
|
291
|
+
|
|
292
|
+
def test_cr_delay_default():
|
|
293
|
+
c = ScriptcastConfig()
|
|
294
|
+
assert c.cr_delay == 0
|
|
295
|
+
|
|
296
|
+
def test_apply_set_cr_delay():
|
|
297
|
+
c = ScriptcastConfig()
|
|
298
|
+
c.apply("set", ["cr-delay", "80"])
|
|
299
|
+
assert c.cr_delay == 80
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
# ── extract_config_prefix ─────────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
def test_extract_config_prefix_empty():
|
|
305
|
+
from scriptcast.config import extract_config_prefix
|
|
306
|
+
assert extract_config_prefix("") == ""
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def test_extract_config_prefix_blank_and_comments():
|
|
310
|
+
from scriptcast.config import extract_config_prefix
|
|
311
|
+
src = "# comment\n\n# another\n"
|
|
312
|
+
assert extract_config_prefix(src) == src
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def test_extract_config_prefix_var_assignment():
|
|
316
|
+
from scriptcast.config import extract_config_prefix
|
|
317
|
+
src = "GREEN='\\033[32m'\nRESET='\\033[0m'\n"
|
|
318
|
+
assert extract_config_prefix(src) == src
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def test_extract_config_prefix_sc_set():
|
|
322
|
+
from scriptcast.config import extract_config_prefix
|
|
323
|
+
src = ": SC set width 80\n: SC set height 24\n"
|
|
324
|
+
assert extract_config_prefix(src) == src
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def test_extract_config_prefix_stops_at_scene():
|
|
328
|
+
from scriptcast.config import extract_config_prefix
|
|
329
|
+
src = ": SC set width 80\n: SC scene main\necho hi\n"
|
|
330
|
+
assert extract_config_prefix(src) == ": SC set width 80\n"
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def test_extract_config_prefix_stops_at_real_command():
|
|
334
|
+
from scriptcast.config import extract_config_prefix
|
|
335
|
+
src = ": SC set width 80\necho hello\n"
|
|
336
|
+
assert extract_config_prefix(src) == ": SC set width 80\n"
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def test_extract_config_prefix_stops_at_filter():
|
|
340
|
+
from scriptcast.config import extract_config_prefix
|
|
341
|
+
src = ": SC set width 80\n: SC filter foo\necho hi\n"
|
|
342
|
+
assert extract_config_prefix(src) == ": SC set width 80\n"
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def test_extract_config_prefix_stops_at_type():
|
|
346
|
+
from scriptcast.config import extract_config_prefix
|
|
347
|
+
src = ": SC set width 80\n: SC type hello\n"
|
|
348
|
+
assert extract_config_prefix(src) == ": SC set width 80\n"
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def test_extract_config_prefix_record_pause_block_collected():
|
|
352
|
+
from scriptcast.config import extract_config_prefix
|
|
353
|
+
src = (
|
|
354
|
+
": SC record pause\n"
|
|
355
|
+
"GREEN='\\033[32m'\n"
|
|
356
|
+
": SC record resume\n"
|
|
357
|
+
": SC set prompt \"${GREEN} > \"\n"
|
|
358
|
+
)
|
|
359
|
+
assert extract_config_prefix(src) == src
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def test_extract_config_prefix_record_pause_real_cmd_inside_collected():
|
|
363
|
+
"""Commands inside a record pause block are still collected (explicitly hidden)."""
|
|
364
|
+
from scriptcast.config import extract_config_prefix
|
|
365
|
+
src = (
|
|
366
|
+
": SC record pause\n"
|
|
367
|
+
"some_real_cmd arg\n"
|
|
368
|
+
": SC record resume\n"
|
|
369
|
+
": SC set width 80\n"
|
|
370
|
+
)
|
|
371
|
+
assert extract_config_prefix(src) == src
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def test_extract_config_prefix_stops_after_record_resume_at_scene():
|
|
375
|
+
from scriptcast.config import extract_config_prefix
|
|
376
|
+
src = (
|
|
377
|
+
": SC record pause\n"
|
|
378
|
+
"GREEN='\\033[32m'\n"
|
|
379
|
+
": SC record resume\n"
|
|
380
|
+
": SC scene main\n"
|
|
381
|
+
"echo hi\n"
|
|
382
|
+
)
|
|
383
|
+
expected = (
|
|
384
|
+
": SC record pause\n"
|
|
385
|
+
"GREEN='\\033[32m'\n"
|
|
386
|
+
": SC record resume\n"
|
|
387
|
+
)
|
|
388
|
+
assert extract_config_prefix(src) == expected
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def test_extract_config_prefix_custom_directive_prefix():
|
|
392
|
+
from scriptcast.config import extract_config_prefix
|
|
393
|
+
src = ": DEMO set width 80\n: DEMO scene main\necho hi\n"
|
|
394
|
+
assert extract_config_prefix(src, directive_prefix="DEMO") == ": DEMO set width 80\n"
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def test_extract_config_prefix_export_var_assignment():
|
|
398
|
+
from scriptcast.config import extract_config_prefix
|
|
399
|
+
src = "export FOO=bar\n: SC scene main\n"
|
|
400
|
+
assert extract_config_prefix(src, directive_prefix="SC") == "export FOO=bar\n"
|