scriptcast 0.1.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.
Files changed (65) hide show
  1. scriptcast-0.3.0/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  2. scriptcast-0.3.0/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
  3. scriptcast-0.3.0/.github/pull_request_template.md +14 -0
  4. scriptcast-0.3.0/.github/workflows/ci.yml +46 -0
  5. scriptcast-0.3.0/.github/workflows/publish-pages.yml +51 -0
  6. scriptcast-0.3.0/.github/workflows/publish-pypi.yml +26 -0
  7. scriptcast-0.3.0/.gitignore +180 -0
  8. scriptcast-0.3.0/CHANGELOG.md +58 -0
  9. scriptcast-0.3.0/CODE_OF_CONDUCT.md +41 -0
  10. scriptcast-0.3.0/CONTRIBUTING.md +50 -0
  11. scriptcast-0.3.0/DIRECTIVES.md +163 -0
  12. scriptcast-0.3.0/Makefile +42 -0
  13. scriptcast-0.3.0/PKG-INFO +286 -0
  14. scriptcast-0.3.0/assets/demo.png +0 -0
  15. scriptcast-0.3.0/assets/showcase-aurora.png +0 -0
  16. scriptcast-0.3.0/assets/showcase-dark.png +0 -0
  17. scriptcast-0.3.0/assets/showcase-light.png +0 -0
  18. scriptcast-0.3.0/assets/tutorial.png +0 -0
  19. scriptcast-0.3.0/cliff.toml +44 -0
  20. scriptcast-0.3.0/examples/.gitignore +5 -0
  21. scriptcast-0.3.0/examples/demo.sh +13 -0
  22. scriptcast-0.3.0/examples/fake-db +11 -0
  23. scriptcast-0.3.0/examples/fake-myapp +7 -0
  24. scriptcast-0.3.0/examples/showcase.sh +52 -0
  25. scriptcast-0.3.0/examples/tutorial.sh +104 -0
  26. {scriptcast-0.1.0 → scriptcast-0.3.0}/pyproject.toml +25 -7
  27. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/__main__.py +16 -1
  28. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/assets/themes/aurora.sh +1 -1
  29. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/assets/themes/dark.sh +1 -0
  30. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/assets/themes/light.sh +1 -0
  31. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/recorder.py +8 -2
  32. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/shell/adapter.py +6 -0
  33. scriptcast-0.3.0/scriptcast/shell/zsh.py +72 -0
  34. {scriptcast-0.1.0 → scriptcast-0.3.0}/tests/test_cli.py +15 -0
  35. {scriptcast-0.1.0 → scriptcast-0.3.0}/tests/test_directives.py +1 -1
  36. {scriptcast-0.1.0 → scriptcast-0.3.0}/tests/test_recorder.py +53 -0
  37. scriptcast-0.3.0/tests/test_shell.py +94 -0
  38. scriptcast-0.1.0/PKG-INFO +0 -21
  39. scriptcast-0.1.0/scriptcast/shell/zsh.py +0 -11
  40. scriptcast-0.1.0/scriptcast.egg-info/PKG-INFO +0 -21
  41. scriptcast-0.1.0/scriptcast.egg-info/SOURCES.txt +0 -36
  42. scriptcast-0.1.0/scriptcast.egg-info/dependency_links.txt +0 -1
  43. scriptcast-0.1.0/scriptcast.egg-info/entry_points.txt +0 -2
  44. scriptcast-0.1.0/scriptcast.egg-info/requires.txt +0 -10
  45. scriptcast-0.1.0/scriptcast.egg-info/top_level.txt +0 -5
  46. scriptcast-0.1.0/setup.cfg +0 -4
  47. scriptcast-0.1.0/tests/test_shell.py +0 -34
  48. {scriptcast-0.1.0 → scriptcast-0.3.0}/README.md +0 -0
  49. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/__init__.py +0 -0
  50. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/assets/__init__.py +0 -0
  51. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/assets/fonts/DMSans-Regular.ttf +0 -0
  52. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/assets/fonts/Pacifico.ttf +0 -0
  53. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/config.py +0 -0
  54. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/directives.py +0 -0
  55. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/export.py +0 -0
  56. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/generator.py +0 -0
  57. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/shell/__init__.py +0 -0
  58. {scriptcast-0.1.0 → scriptcast-0.3.0}/scriptcast/shell/bash.py +0 -0
  59. {scriptcast-0.1.0 → scriptcast-0.3.0}/tests/__init__.py +0 -0
  60. {scriptcast-0.1.0 → scriptcast-0.3.0}/tests/test_config.py +0 -0
  61. {scriptcast-0.1.0 → scriptcast-0.3.0}/tests/test_export.py +0 -0
  62. {scriptcast-0.1.0 → scriptcast-0.3.0}/tests/test_generator.py +0 -0
  63. {scriptcast-0.1.0 → scriptcast-0.3.0}/tests/test_integration.py +0 -0
  64. {scriptcast-0.1.0 → scriptcast-0.3.0}/tests/test_registry.py +0 -0
  65. {scriptcast-0.1.0 → scriptcast-0.3.0}/tests/test_theme.py +0 -0
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: Bug report
3
+ about: Something isn't working
4
+ labels: bug
5
+ ---
6
+
7
+ ## Describe the bug
8
+
9
+ <!-- A clear description of what the bug is. -->
10
+
11
+ ## Steps to reproduce
12
+
13
+ 1. Script content (`.sh` file):
14
+
15
+ ```sh
16
+ # paste your script here
17
+ ```
18
+
19
+ 2. Command run:
20
+
21
+ ```bash
22
+ scriptcast ...
23
+ ```
24
+
25
+ 3. Expected behaviour:
26
+
27
+ 4. Actual behaviour:
28
+
29
+ ## Environment
30
+
31
+ - scriptcast version: <!-- `pip show scriptcast` -->
32
+ - Python version: <!-- `python --version` -->
33
+ - OS:
34
+ - Shell:
35
+
36
+ ## .sc file (if available)
37
+
38
+ <!-- Attach or paste the `.sc` file generated before the error, if relevant. -->
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest a new directive or capability
4
+ labels: enhancement
5
+ ---
6
+
7
+ ## Use case
8
+
9
+ <!-- What are you trying to do that scriptcast doesn't support today? -->
10
+
11
+ ## Proposed directive syntax
12
+
13
+ <!-- If this is a new directive, show how it would look in a script: -->
14
+
15
+ ```sh
16
+ : SC myfeature arg1 arg2
17
+ ```
18
+
19
+ ## Expected behaviour in the cast
20
+
21
+ <!-- What should the generated cast look like? -->
22
+
23
+ ## Alternatives considered
24
+
25
+ <!-- Any workarounds or alternative approaches you've thought of? -->
@@ -0,0 +1,14 @@
1
+ ## What does this PR do?
2
+
3
+ <!-- One sentence summary. -->
4
+
5
+ ## Checklist
6
+
7
+ - [ ] `make all` passes locally (lint + typecheck + tests)
8
+ - [ ] New behaviour is covered by tests
9
+ - [ ] `DIRECTIVES.md` updated (if directive added or changed)
10
+ - [ ] `CHANGELOG.md` has an entry (or I'm relying on the auto-changelog PR)
11
+
12
+ ## Related issues
13
+
14
+ <!-- Closes #123 -->
@@ -0,0 +1,46 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, dev]
6
+ pull_request:
7
+ branches: [main, dev]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+ - run: pip install ruff
18
+ - run: ruff check .
19
+
20
+ typecheck:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ - uses: actions/setup-python@v5
25
+ with:
26
+ python-version: "3.12"
27
+ - run: pip install -e ".[dev]"
28
+ - run: mypy scriptcast/
29
+
30
+ test:
31
+ runs-on: ubuntu-latest
32
+ strategy:
33
+ matrix:
34
+ python-version: ["3.10", "3.11", "3.12"]
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+ - uses: actions/setup-python@v5
38
+ with:
39
+ python-version: ${{ matrix.python-version }}
40
+ - run: pip install -e ".[dev]"
41
+ - run: pytest --cov=scriptcast --cov-report=xml
42
+ - uses: codecov/codecov-action@v4
43
+ if: matrix.python-version == '3.12'
44
+ with:
45
+ files: coverage.xml
46
+ fail_ci_if_error: false
@@ -0,0 +1,51 @@
1
+ # Sample workflow for building and deploying a Jekyll site to GitHub Pages
2
+ name: Deploy GitHub Pages
3
+ on:
4
+ workflow_run:
5
+ workflows: ["Publish to PyPI"]
6
+ types: [completed]
7
+
8
+ # Allows you to run this workflow manually from the Actions tab
9
+ workflow_dispatch:
10
+
11
+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
12
+ permissions:
13
+ contents: read
14
+ pages: write
15
+ id-token: write
16
+
17
+ # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
18
+ # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
19
+ concurrency:
20
+ group: "pages"
21
+ cancel-in-progress: false
22
+
23
+ jobs:
24
+ # Build job
25
+ build:
26
+ runs-on: ubuntu-latest
27
+ if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
28
+ steps:
29
+ - name: Checkout
30
+ uses: actions/checkout@v4
31
+ - name: Setup Pages
32
+ uses: actions/configure-pages@v5
33
+ - name: Build with Jekyll
34
+ uses: actions/jekyll-build-pages@v1
35
+ with:
36
+ source: ./
37
+ destination: ./_site
38
+ - name: Upload artifact
39
+ uses: actions/upload-pages-artifact@v3
40
+
41
+ # Deployment job
42
+ deploy:
43
+ environment:
44
+ name: github-pages
45
+ url: ${{ steps.deployment.outputs.page_url }}
46
+ runs-on: ubuntu-latest
47
+ needs: build
48
+ steps:
49
+ - name: Deploy to GitHub Pages
50
+ id: deployment
51
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,26 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ id-token: write
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+
20
+ - uses: astral-sh/setup-uv@v5
21
+
22
+ - name: Build package
23
+ run: uv build
24
+
25
+ - name: Publish to PyPI
26
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,180 @@
1
+ # Created by https://www.toptal.com/developers/gitignore/api/python
2
+ # Edit at https://www.toptal.com/developers/gitignore?templates=python
3
+
4
+ ### Python ###
5
+ # Byte-compiled / optimized / DLL files
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+
10
+ # C extensions
11
+ *.so
12
+
13
+ # Distribution / packaging
14
+ .Python
15
+ build/
16
+ develop-eggs/
17
+ dist/
18
+ downloads/
19
+ eggs/
20
+ .eggs/
21
+ lib/
22
+ lib64/
23
+ parts/
24
+ sdist/
25
+ var/
26
+ wheels/
27
+ share/python-wheels/
28
+ *.egg-info/
29
+ .installed.cfg
30
+ *.egg
31
+ MANIFEST
32
+
33
+ # PyInstaller
34
+ # Usually these files are written by a python script from a template
35
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
36
+ *.manifest
37
+ *.spec
38
+
39
+ # Installer logs
40
+ pip-log.txt
41
+ pip-delete-this-directory.txt
42
+
43
+ # Unit test / coverage reports
44
+ htmlcov/
45
+ .tox/
46
+ .nox/
47
+ .coverage
48
+ .coverage.*
49
+ .cache
50
+ nosetests.xml
51
+ coverage.xml
52
+ *.cover
53
+ *.py,cover
54
+ .hypothesis/
55
+ .pytest_cache/
56
+ cover/
57
+
58
+ # Translations
59
+ *.mo
60
+ *.pot
61
+
62
+ # Django stuff:
63
+ *.log
64
+ local_settings.py
65
+ db.sqlite3
66
+ db.sqlite3-journal
67
+
68
+ # Flask stuff:
69
+ instance/
70
+ .webassets-cache
71
+
72
+ # Scrapy stuff:
73
+ .scrapy
74
+
75
+ # Sphinx documentation
76
+ docs/_build/
77
+
78
+ # PyBuilder
79
+ .pybuilder/
80
+ target/
81
+
82
+ # Jupyter Notebook
83
+ .ipynb_checkpoints
84
+
85
+ # IPython
86
+ profile_default/
87
+ ipython_config.py
88
+
89
+ # pyenv
90
+ # For a library or package, you might want to ignore these files since the code is
91
+ # intended to run in multiple environments; otherwise, check them in:
92
+ # .python-version
93
+
94
+ # pipenv
95
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
96
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
97
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
98
+ # install all needed dependencies.
99
+ #Pipfile.lock
100
+
101
+ # poetry
102
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
103
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
104
+ # commonly ignored for libraries.
105
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
106
+ #poetry.lock
107
+
108
+ # pdm
109
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
110
+ #pdm.lock
111
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
112
+ # in version control.
113
+ # https://pdm.fming.dev/#use-with-ide
114
+ .pdm.toml
115
+
116
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
117
+ __pypackages__/
118
+
119
+ # Celery stuff
120
+ celerybeat-schedule
121
+ celerybeat.pid
122
+
123
+ # SageMath parsed files
124
+ *.sage.py
125
+
126
+ # Environments
127
+ .env
128
+ .venv
129
+ env/
130
+ venv/
131
+ ENV/
132
+ env.bak/
133
+ venv.bak/
134
+
135
+ # Spyder project settings
136
+ .spyderproject
137
+ .spyproject
138
+
139
+ # Rope project settings
140
+ .ropeproject
141
+
142
+ # mkdocs documentation
143
+ /site
144
+
145
+ # mypy
146
+ .mypy_cache/
147
+ .dmypy.json
148
+ dmypy.json
149
+
150
+ # Pyre type checker
151
+ .pyre/
152
+
153
+ # pytype static type analyzer
154
+ .pytype/
155
+
156
+ # Cython debug symbols
157
+ cython_debug/
158
+
159
+ # PyCharm
160
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
161
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
162
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
163
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
164
+ #.idea/
165
+
166
+ ### Python Patch ###
167
+ # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
168
+ poetry.toml
169
+
170
+ # ruff
171
+ .ruff_cache/
172
+
173
+ # LSP config files
174
+ pyrightconfig.json
175
+
176
+ # End of https://www.toptal.com/developers/gitignore/api/python
177
+
178
+ # Git worktrees
179
+ .worktrees/
180
+ uv.lock
@@ -0,0 +1,58 @@
1
+ ## [0.3.0] - 2026-04-07
2
+
3
+ ### Added
4
+
5
+ - Fix default theme loading and zsh xtrace ESC byte corruption ([#1](https://github.com/dacrystal/scriptcast/issues/1))
6
+
7
+ ## [0.2.0] - 2026-04-06
8
+
9
+ ### Added
10
+
11
+ - Add --version, ASCII banner, hatch-fancy-pypi-readme, and GitHub Pages workflow
12
+
13
+ ## [0.1.0] - 2026-04-06
14
+
15
+ ### Added
16
+
17
+ - SC mock/expect directives, InputLine event, single-cast default
18
+ - Add expect example to basic.sh; fix recorder cwd and send_user newline
19
+ - JSONL .sc format, streaming generator, expect session improvements
20
+ - FilterDirective subprocess command; add CommentDirective
21
+ - Open source readiness — directive plugin system, CLI fix, CI/CD, community files
22
+ - Typing_word_speed, GIF frame overlay, dev tooling
23
+ - Frame layout system with CLI flags for title bar, shadow, background, and watermark
24
+ - Add theme system, scriptcast watermark, and frame layout refactor
25
+ - SVG-based chrome renderer with APNG output support
26
+ - PIL fallback frame parity — content masking, APNG, GIF palette fix
27
+ - Replace gif subcommand with export — content-first compositor
28
+ - Add terminal content pre-processing pipeline
29
+ - Frame bool, dark theme full config, CLI cleanup, DM Sans watermark
30
+ - CLI simplification
31
+ - Export format png, temp gif, aurora/light themes, aurora FrameConfig defaults
32
+ - Complete basic.sh, README demo, and xtrace quoting fixes
33
+ - Unify config pipeline — ThemeConfig nested in ScriptcastConfig, theme.py deleted
34
+ - *(examples)* Redesign demo — showcase.sh, tutorial.sh, fake-myapp
35
+ - Multi-pass directive pipeline redesign
36
+ - Verbatim xtrace capture + PTY recording
37
+ - Unified CLI — single entry point, --no-export flag, config resolved before recording
38
+ - HelpersDirective + PTY read simplification + three bug fixes
39
+ - --xtrace-log flag + export progress bar + frame-bar-title fix
40
+
41
+ ### Changed
42
+
43
+ - Remove _handle_passthrough; directives own SC syntax matching
44
+ - Replace SVG chrome rendering with PIL-only chrome+mask approach
45
+ - Codebase cleanup and best-practices refactor
46
+
47
+ ### Fixed
48
+
49
+ - CI test failure and ruff lint errors from recent features
50
+ - Ignore missing PIL stubs in mypy (Pillow is optional)
51
+ - Stable GIF palette across frames — eliminates chrome color flickering
52
+ - Reserve palette slots for exact chrome colors in GIF output
53
+ - Frame rendering bug fixes — shadow, border, theme parsing, SVG filter
54
+ - Decode \xNN and \NNN escape sequences in SC directive xtrace lines
55
+ - *(filter)* Apply filter to trace/cmd lines and decouple from ExpectDirective
56
+ - Resolve CI test failures
57
+ - Resolve all mypy errors in scriptcast/
58
+
@@ -0,0 +1,41 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment:
18
+
19
+ * Demonstrating empathy and kindness toward other people
20
+ * Being respectful of differing opinions, viewpoints, and experiences
21
+ * Giving and gracefully accepting constructive feedback
22
+ * Accepting responsibility and apologizing to those affected by our mistakes
23
+ * Focusing on what is best not just for us as individuals, but for the overall community
24
+
25
+ Examples of unacceptable behavior:
26
+
27
+ * The use of sexualized language or imagery, and sexual attention or advances of any kind
28
+ * Trolling, insulting or derogatory comments, and personal or political attacks
29
+ * Public or private harassment
30
+ * Publishing others' private information without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate
32
+
33
+ ## Enforcement
34
+
35
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
36
+ reported to the project maintainer at dacrystal@users.noreply.github.com.
37
+ All complaints will be reviewed and investigated promptly and fairly.
38
+
39
+ ## Attribution
40
+
41
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
@@ -0,0 +1,50 @@
1
+ # Contributing to scriptcast
2
+
3
+ ## Dev Setup
4
+
5
+ ```bash
6
+ git clone https://github.com/dacrystal/scriptcast.git
7
+ cd scriptcast
8
+ pip install -e ".[dev]"
9
+ ```
10
+
11
+ ## Running Tests
12
+
13
+ ```bash
14
+ make test # pytest with coverage
15
+ make lint # ruff
16
+ make typecheck # mypy
17
+ make all # all three
18
+ ```
19
+
20
+ All three must pass before opening a PR. CI enforces this.
21
+
22
+ ## Commit Messages
23
+
24
+ Use conventional commits — the changelog is generated from them:
25
+
26
+ - `feat: add SC highlight directive`
27
+ - `fix: expect session drops last output line`
28
+ - `docs: clarify filter-add chaining`
29
+ - `chore: bump ruff version`
30
+
31
+ ## Writing a Directive
32
+
33
+ See [DIRECTIVES.md](DIRECTIVES.md) for the full guide.
34
+
35
+ ## Pull Request Checklist
36
+
37
+ - [ ] Tests pass locally (`make all`)
38
+ - [ ] New behaviour is covered by tests
39
+ - [ ] CHANGELOG.md has an entry (or the auto-PR from CI will add one)
40
+ - [ ] DIRECTIVES.md updated if you added or changed a directive
41
+
42
+ ## Release Process (maintainers only)
43
+
44
+ 1. Ensure `CHANGELOG.md` is up to date
45
+ 2. Bump version in `pyproject.toml`
46
+ 3. `git tag v0.X.0 && git push --tags`
47
+ 4. The `release.yml` workflow publishes to PyPI and creates a GitHub Release
48
+
49
+ PyPI uses Trusted Publisher (OIDC) — no API key needed. Configure at:
50
+ pypi.org → your project → Publishing → Add publisher (environment: `release`)
@@ -0,0 +1,163 @@
1
+ # Writing Directives
2
+
3
+ Directives are the extension point in scriptcast. Each directive is a Python
4
+ class that participates in one or more of the three pipeline phases:
5
+
6
+ | Phase | Method | When it runs |
7
+ |-------|--------|-------------|
8
+ | Pre | `pre(queue)` | Before the shell script is executed — rewrites script lines |
9
+ | Post | `post(queue)` | After execution — transforms raw xtrace lines into JSONL events |
10
+ | Gen | `gen(event, queue, active, cursor)` | During cast generation — emits asciinema cast lines |
11
+
12
+ A directive can implement any combination of these phases by overriding the
13
+ corresponding method. The base `Directive` class provides no-op defaults for all
14
+ three, so you only override what you need.
15
+
16
+ ## The Directive Base Class
17
+
18
+ ```python
19
+ from scriptcast.directives import Directive
20
+
21
+ class MyDirective(Directive):
22
+ priority: int = 50 # lower = runs earlier in the chain
23
+ handles: str | None = None # gen phase only: directive name in .sc events
24
+ ```
25
+
26
+ ### priority
27
+
28
+ Directives are sorted by `priority` before being placed in the processing chain.
29
+ Lower values run first. Core directive priorities:
30
+
31
+ | Directive | Priority | Notes |
32
+ |-----------|----------|-------|
33
+ | RecordDirective | 10 | Must absorb pause/resume before anything else |
34
+ | MockDirective | 20 | Must absorb mock markers before expect or sc catch them |
35
+ | ExpectDirective | 30 | Owns all lines in an expect session; must precede filter |
36
+ | FilterDirective | 40 | Applies to output lines after expect releases them |
37
+ | CommentDirective | 45 | Must precede ScDirective (catch-all) |
38
+ | SetDirective | 50 | Gen phase only |
39
+ | SleepDirective | 50 | Gen phase only |
40
+ | ScDirective | 99 | Catch-all for any remaining `: SC` line — always last |
41
+
42
+ Pick a priority between 50 and 98 for most new directives.
43
+
44
+ ### handles
45
+
46
+ Used in the gen phase only. Set `handles = "myname"` and your directive's `gen()`
47
+ method will be called when a `directive` event with text `myname ...` appears in the
48
+ `.sc` file. If `handles` is `None`, `gen()` is never called automatically.
49
+
50
+ ## Phase Details
51
+
52
+ ### pre(queue: deque[str]) → list[str] | None
53
+
54
+ Called for each line of the script before it is run. The queue contains the
55
+ remaining unprocessed script lines.
56
+
57
+ - **Return None** if this line is not for you — the next directive gets a chance.
58
+ - **Return a list of strings** (replacement lines) to consume from the queue and
59
+ substitute. You are responsible for `queue.popleft()` on lines you consume.
60
+
61
+ ```python
62
+ def pre(self, queue):
63
+ if not queue[0].startswith(f": {self.dp} mything"):
64
+ return None
65
+ queue.popleft()
66
+ return ["echo 'replaced'\n"]
67
+ ```
68
+
69
+ ### post(queue: deque[tuple[float, str]]) → list[str] | None
70
+
71
+ Called for each (timestamp, content) item in the raw xtrace output. The queue
72
+ contains remaining unprocessed items.
73
+
74
+ - **Return None** if this item is not for you.
75
+ - **Return a list of JSONL strings** (or `[]` to consume without emitting).
76
+ - Use `json.dumps([ts, "cmd"|"output"|"input"|"directive", text])` to produce events.
77
+
78
+ ```python
79
+ import json
80
+
81
+ def post(self, queue):
82
+ ts, content = queue[0]
83
+ if content != f"{self.tp} : {self.dp} mything":
84
+ return None
85
+ queue.popleft()
86
+ return [json.dumps([ts, "directive", "mything"])]
87
+ ```
88
+
89
+ ### gen(event, queue, active, cursor) → tuple[float, list[str]]
90
+
91
+ Called during cast generation when a `directive` event matches `self.handles`.
92
+
93
+ - `event` — `(timestamp: float, "directive", text: str)`
94
+ - `queue` — remaining events (you may peek/consume ahead)
95
+ - `active` — current `ScriptcastConfig` (mutable — you can change timing settings)
96
+ - `cursor` — current time position in the cast (seconds)
97
+ - **Return** `(updated_cursor, list_of_cast_json_lines)`
98
+
99
+ ```python
100
+ import json
101
+
102
+ class MySleepDirective(Directive):
103
+ handles = "mysleep"
104
+
105
+ def gen(self, event, queue, active, cursor):
106
+ _, _, text = event
107
+ parts = text.split()
108
+ if len(parts) >= 2:
109
+ cursor += int(parts[1]) / 1000.0
110
+ return cursor, []
111
+ ```
112
+
113
+ ## Registering a Third-Party Directive
114
+
115
+ In your package's `pyproject.toml`:
116
+
117
+ ```toml
118
+ [project.entry-points."scriptcast.directives"]
119
+ my-directive = "mypkg.directives:MyDirective"
120
+ ```
121
+
122
+ After `pip install` your package alongside scriptcast, your directive is
123
+ automatically loaded and sorted into the processing chain by priority.
124
+
125
+ ## Worked Example: SC pause directive
126
+
127
+ This adds a `SC pause <ms>` recorder directive that inserts a `sleep` event
128
+ into the `.sc` file, causing the generator to pause for the given milliseconds.
129
+
130
+ ```python
131
+ # mypkg/directives.py
132
+ import json
133
+ import re
134
+ from scriptcast.directives import Directive
135
+
136
+
137
+ class PauseDirective(Directive):
138
+ """SC pause <ms> — insert a pause into the cast.
139
+
140
+ Script syntax: : SC pause 500
141
+ Records as: [ts, "directive", "sleep 500"]
142
+ """
143
+ priority = 48 # between CommentDirective (45) and ScDirective (99)
144
+
145
+ def __init__(self, dp="SC", tp="+"):
146
+ super().__init__(dp, tp)
147
+ self._re = re.compile(rf"^{re.escape(tp)} : {re.escape(dp)} pause (\d+)$")
148
+
149
+ def post(self, queue):
150
+ ts, content = queue[0]
151
+ m = self._re.match(content)
152
+ if not m:
153
+ return None
154
+ queue.popleft()
155
+ ms = m.group(1)
156
+ return [json.dumps([ts, "directive", f"sleep {ms}"])]
157
+ ```
158
+
159
+ ```toml
160
+ # mypkg/pyproject.toml
161
+ [project.entry-points."scriptcast.directives"]
162
+ pause = "mypkg.directives:PauseDirective"
163
+ ```