revision-engine 0.2.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 (86) hide show
  1. revision_engine-0.2.2/.gitattributes +4 -0
  2. revision_engine-0.2.2/.github/dependabot.yml +10 -0
  3. revision_engine-0.2.2/.github/workflows/demo.yml +36 -0
  4. revision_engine-0.2.2/.github/workflows/test.yml +178 -0
  5. revision_engine-0.2.2/.gitignore +9 -0
  6. revision_engine-0.2.2/LICENSE +21 -0
  7. revision_engine-0.2.2/PKG-INFO +134 -0
  8. revision_engine-0.2.2/README.md +116 -0
  9. revision_engine-0.2.2/RELEASING.md +130 -0
  10. revision_engine-0.2.2/ci-constraints-requirements.txt +13 -0
  11. revision_engine-0.2.2/examples/minimal/docs/revision/chapters/01-intro/lessons/01-hello/anchors.yaml +14 -0
  12. revision_engine-0.2.2/examples/minimal/docs/revision/chapters/01-intro/lessons/01-hello/cues.json +34 -0
  13. revision_engine-0.2.2/examples/minimal/docs/revision/chapters/01-intro/lessons/01-hello/diagrams/flow.dot +8 -0
  14. revision_engine-0.2.2/examples/minimal/docs/revision/chapters/01-intro/lessons/01-hello/lesson.md +26 -0
  15. revision_engine-0.2.2/examples/minimal/docs/revision/chapters/01-intro/lessons/01-hello/media/narration.wav +0 -0
  16. revision_engine-0.2.2/examples/minimal/docs/revision/chapters/01-intro/lessons/01-hello/narration.yaml +9 -0
  17. revision_engine-0.2.2/examples/minimal/docs/revision/chapters/01-intro/lessons/02-quiet/anchors.yaml +3 -0
  18. revision_engine-0.2.2/examples/minimal/docs/revision/chapters/01-intro/lessons/02-quiet/lesson.md +7 -0
  19. revision_engine-0.2.2/examples/minimal/docs/revision/curriculum.yaml +10 -0
  20. revision_engine-0.2.2/examples/minimal/revision.yaml +10 -0
  21. revision_engine-0.2.2/examples/minimal/src/hello.c +15 -0
  22. revision_engine-0.2.2/pyproject.toml +35 -0
  23. revision_engine-0.2.2/revision_engine/__init__.py +3 -0
  24. revision_engine-0.2.2/revision_engine/anchors.py +163 -0
  25. revision_engine-0.2.2/revision_engine/build.py +268 -0
  26. revision_engine-0.2.2/revision_engine/cli.py +159 -0
  27. revision_engine-0.2.2/revision_engine/config.py +159 -0
  28. revision_engine-0.2.2/revision_engine/diagrams.py +108 -0
  29. revision_engine-0.2.2/revision_engine/errors.py +18 -0
  30. revision_engine-0.2.2/revision_engine/highlight.py +62 -0
  31. revision_engine-0.2.2/revision_engine/intrinsics/__init__.py +34 -0
  32. revision_engine-0.2.2/revision_engine/intrinsics/arm.yaml +22 -0
  33. revision_engine-0.2.2/revision_engine/intrinsics/intel.yaml +42 -0
  34. revision_engine-0.2.2/revision_engine/intrinsics/riscv.yaml +28 -0
  35. revision_engine-0.2.2/revision_engine/lesson.py +64 -0
  36. revision_engine-0.2.2/revision_engine/markdown_ext.py +285 -0
  37. revision_engine-0.2.2/revision_engine/narration.py +276 -0
  38. revision_engine-0.2.2/revision_engine/render.py +119 -0
  39. revision_engine-0.2.2/revision_engine/serve.py +153 -0
  40. revision_engine-0.2.2/revision_engine/sources.py +61 -0
  41. revision_engine-0.2.2/revision_engine/static/app.js +292 -0
  42. revision_engine-0.2.2/revision_engine/static/styles.css +160 -0
  43. revision_engine-0.2.2/revision_engine/templates/lesson.html.j2 +89 -0
  44. revision_engine-0.2.2/revision_engine/templates/redirect.html.j2 +5 -0
  45. revision_engine-0.2.2/tests/__init__.py +0 -0
  46. revision_engine-0.2.2/tests/browser/__init__.py +0 -0
  47. revision_engine-0.2.2/tests/browser/conftest.py +54 -0
  48. revision_engine-0.2.2/tests/browser/marks.py +89 -0
  49. revision_engine-0.2.2/tests/browser/test_diagram_links.py +140 -0
  50. revision_engine-0.2.2/tests/browser/test_layout.py +62 -0
  51. revision_engine-0.2.2/tests/browser/test_media_collapse.py +164 -0
  52. revision_engine-0.2.2/tests/browser/test_media_error.py +202 -0
  53. revision_engine-0.2.2/tests/browser/test_narrow_media.py +148 -0
  54. revision_engine-0.2.2/tests/browser/test_nested_intrinsic.py +149 -0
  55. revision_engine-0.2.2/tests/browser/test_pages.py +225 -0
  56. revision_engine-0.2.2/tests/browser/test_quiz_order.py +109 -0
  57. revision_engine-0.2.2/tests/ci_pins.py +97 -0
  58. revision_engine-0.2.2/tests/conftest.py +37 -0
  59. revision_engine-0.2.2/tests/golden/minimal.json +12 -0
  60. revision_engine-0.2.2/tests/golden_digest.py +86 -0
  61. revision_engine-0.2.2/tests/require_golden.py +57 -0
  62. revision_engine-0.2.2/tests/test_anchors.py +225 -0
  63. revision_engine-0.2.2/tests/test_browser_marks.py +158 -0
  64. revision_engine-0.2.2/tests/test_build.py +184 -0
  65. revision_engine-0.2.2/tests/test_ci_constraints.py +208 -0
  66. revision_engine-0.2.2/tests/test_cli.py +320 -0
  67. revision_engine-0.2.2/tests/test_config.py +81 -0
  68. revision_engine-0.2.2/tests/test_determinism.py +23 -0
  69. revision_engine-0.2.2/tests/test_diagrams.py +222 -0
  70. revision_engine-0.2.2/tests/test_errors.py +12 -0
  71. revision_engine-0.2.2/tests/test_golden.py +43 -0
  72. revision_engine-0.2.2/tests/test_golden_digest.py +89 -0
  73. revision_engine-0.2.2/tests/test_highlight.py +52 -0
  74. revision_engine-0.2.2/tests/test_intrinsics.py +95 -0
  75. revision_engine-0.2.2/tests/test_lesson.py +42 -0
  76. revision_engine-0.2.2/tests/test_markdown_ext.py +528 -0
  77. revision_engine-0.2.2/tests/test_narration.py +278 -0
  78. revision_engine-0.2.2/tests/test_release.py +634 -0
  79. revision_engine-0.2.2/tests/test_release_doc.py +135 -0
  80. revision_engine-0.2.2/tests/test_require_golden.py +80 -0
  81. revision_engine-0.2.2/tests/test_serve.py +89 -0
  82. revision_engine-0.2.2/tests/test_sources.py +54 -0
  83. revision_engine-0.2.2/tests/test_static.py +21 -0
  84. revision_engine-0.2.2/tests/tools/make_fixture_audio.py +18 -0
  85. revision_engine-0.2.2/tools/__init__.py +0 -0
  86. revision_engine-0.2.2/tools/release.py +324 -0
@@ -0,0 +1,4 @@
1
+ * text=auto eol=lf
2
+ *.wav binary
3
+ *.mp3 binary
4
+ *.png binary
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ versioning-strategy: "increase-if-necessary"
8
+ groups:
9
+ rendered-output:
10
+ patterns: ["*"]
@@ -0,0 +1,36 @@
1
+ name: demo-pages
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ permissions:
6
+ contents: read
7
+ pages: write
8
+ id-token: write
9
+ concurrency:
10
+ group: pages
11
+ cancel-in-progress: true
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+ - run: sudo apt-get update && sudo apt-get install -y graphviz
21
+ - run: python -m pip install -e .
22
+ # tests/test_ci_constraints.py::test_demo_workflow_checks_before_building pins this step before rv2 build.
23
+ - run: rv2 check --strict --root examples/minimal
24
+ - run: rv2 build --root examples/minimal --output public
25
+ - uses: actions/upload-pages-artifact@v3
26
+ with:
27
+ path: public
28
+ deploy:
29
+ needs: build
30
+ runs-on: ubuntu-latest
31
+ environment:
32
+ name: github-pages
33
+ url: ${{ steps.deployment.outputs.page_url }}
34
+ steps:
35
+ - id: deployment
36
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,178 @@
1
+ name: test
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ permissions:
7
+ contents: read
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-24.04
11
+ env:
12
+ RV_REQUIRE_GOLDEN: "1"
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.12"
18
+ - run: sudo apt-get update && sudo apt-get install -y graphviz
19
+ - run: python -m pip install -e ".[dev]" -c ci-constraints-requirements.txt
20
+ - run: python -m playwright install --with-deps chromium
21
+ - run: python -m pytest -vv --browser chromium
22
+ browsers:
23
+ runs-on: ubuntu-24.04
24
+ strategy:
25
+ fail-fast: false
26
+ matrix:
27
+ browser: [firefox, webkit]
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+ - uses: actions/setup-python@v5
31
+ with:
32
+ python-version: "3.12"
33
+ - run: sudo apt-get update && sudo apt-get install -y graphviz
34
+ - run: python -m pip install -e ".[dev]" -c ci-constraints-requirements.txt
35
+ - run: python -m playwright install --with-deps ${{ matrix.browser }}
36
+ - run: python -m playwright --version
37
+ - run: python -m pytest -vv -rxXfE tests/browser --browser ${{ matrix.browser }}
38
+ # Tags and releases a merged version bump (RELEASING.md step 4). A job here, not a
39
+ # workflow_run workflow, so it sees the push's `before` and runs only after the `test` and
40
+ # `browsers` jobs pass; the upload jobs below run after it.
41
+ release:
42
+ needs: [test, browsers]
43
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mtibbits/reVision'
44
+ runs-on: ubuntu-24.04
45
+ outputs:
46
+ version: ${{ steps.decide.outputs.version }}
47
+ permissions:
48
+ contents: write
49
+ pull-requests: read
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+ with:
53
+ fetch-depth: 0
54
+ - uses: actions/setup-python@v5
55
+ with:
56
+ python-version: "3.12"
57
+ - id: decide
58
+ env:
59
+ BEFORE: ${{ github.event.before }}
60
+ run: python tools/release.py decide --before "$BEFORE" --after "$GITHUB_SHA"
61
+ - if: steps.decide.outputs.tag == 'true'
62
+ env:
63
+ VERSION: ${{ steps.decide.outputs.version }}
64
+ run: |
65
+ git -c user.name="github-actions[bot]" -c user.email="41898282+github-actions[bot]@users.noreply.github.com" tag -a "v$VERSION" -m "reVision engine v$VERSION" "$GITHUB_SHA"
66
+ git push origin "refs/tags/v$VERSION"
67
+ - id: exists
68
+ if: steps.decide.outputs.release == 'true'
69
+ env:
70
+ VERSION: ${{ steps.decide.outputs.version }}
71
+ GH_TOKEN: ${{ github.token }}
72
+ run: |
73
+ gh release list -R "$GITHUB_REPOSITORY" --limit 1000 --json tagName > "$RUNNER_TEMP/releases.json"
74
+ python tools/release.py exists --version "$VERSION" --releases "$RUNNER_TEMP/releases.json"
75
+ - if: steps.decide.outputs.release == 'true' && steps.exists.outputs.exists == 'false'
76
+ env:
77
+ VERSION: ${{ steps.decide.outputs.version }}
78
+ GH_TOKEN: ${{ github.token }}
79
+ run: |
80
+ gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/pulls" > "$RUNNER_TEMP/pulls.json"
81
+ python tools/release.py notes --version "$VERSION" --sha "$GITHUB_SHA" --pulls "$RUNNER_TEMP/pulls.json" > "$RUNNER_TEMP/notes.md"
82
+ gh release create "v$VERSION" -R "$GITHUB_REPOSITORY" --verify-tag --title "reVision engine v$VERSION" --notes-file "$RUNNER_TEMP/notes.md"
83
+ # Upload the release (RELEASING.md step 4). Jobs here, not a tag-triggered workflow: the
84
+ # release job's tag push uses GITHUB_TOKEN, which starts no workflow.
85
+ dist:
86
+ needs: release
87
+ if: needs.release.outputs.version != ''
88
+ runs-on: ubuntu-24.04
89
+ outputs:
90
+ wheel-sha256: ${{ steps.digests.outputs.wheel }}
91
+ sdist-sha256: ${{ steps.digests.outputs.sdist }}
92
+ steps:
93
+ - uses: actions/checkout@v4
94
+ with:
95
+ ref: refs/tags/v${{ needs.release.outputs.version }}
96
+ persist-credentials: false
97
+ - uses: actions/setup-python@v5
98
+ with:
99
+ python-version: "3.12"
100
+ - run: python -m pip install build -c ci-constraints-requirements.txt
101
+ - run: python -m build
102
+ - env:
103
+ VERSION: ${{ needs.release.outputs.version }}
104
+ run: python tools/release.py dist --version "$VERSION" --dir dist
105
+ - id: digests
106
+ env:
107
+ VERSION: ${{ needs.release.outputs.version }}
108
+ run: |
109
+ wheel=$(sha256sum "dist/revision_engine-$VERSION-py3-none-any.whl")
110
+ sdist=$(sha256sum "dist/revision_engine-$VERSION.tar.gz")
111
+ echo "wheel=${wheel%% *}" >> "$GITHUB_OUTPUT"
112
+ echo "sdist=${sdist%% *}" >> "$GITHUB_OUTPUT"
113
+ - uses: actions/upload-artifact@v4
114
+ with:
115
+ name: dist
116
+ path: dist/
117
+ if-no-files-found: error
118
+ overwrite: true
119
+ testpypi:
120
+ needs: dist
121
+ runs-on: ubuntu-24.04
122
+ environment: testpypi
123
+ permissions:
124
+ id-token: write
125
+ steps:
126
+ - uses: actions/download-artifact@v4
127
+ with:
128
+ name: dist
129
+ path: dist/
130
+ - uses: pypa/gh-action-pypi-publish@release/v1
131
+ with:
132
+ repository-url: https://test.pypi.org/legacy/
133
+ # No token here: --extra-index-url lets TestPyPI, where anyone can upload anything, supply a
134
+ # dependency, so code from this install never runs where an id-token can be minted.
135
+ smoke:
136
+ needs: [release, testpypi]
137
+ runs-on: ubuntu-24.04
138
+ steps:
139
+ - uses: actions/checkout@v4
140
+ with:
141
+ ref: refs/tags/v${{ needs.release.outputs.version }}
142
+ persist-credentials: false
143
+ - uses: actions/setup-python@v5
144
+ with:
145
+ python-version: "3.12"
146
+ - env:
147
+ VERSION: ${{ needs.release.outputs.version }}
148
+ run: |
149
+ python -m venv "$RUNNER_TEMP/smoke"
150
+ for attempt in 1 2 3 4 5 6 7 8 9 10; do
151
+ "$RUNNER_TEMP/smoke/bin/python" -m pip install --no-cache-dir --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ -c ci-constraints-requirements.txt "revision-engine==$VERSION" && break
152
+ if [ "$attempt" = 10 ]; then exit 1; fi
153
+ sleep 30
154
+ done
155
+ cd "$RUNNER_TEMP"
156
+ out=$("$RUNNER_TEMP/smoke/bin/rv2" --version)
157
+ echo "$out"
158
+ test "$out" = "rv2 $VERSION"
159
+ # Uploads only the files dist hashed: code that smoke ran from TestPyPI must not choose them.
160
+ pypi:
161
+ needs: [release, dist, smoke]
162
+ runs-on: ubuntu-24.04
163
+ environment: pypi
164
+ permissions:
165
+ id-token: write
166
+ steps:
167
+ - uses: actions/download-artifact@v4
168
+ with:
169
+ name: dist
170
+ path: dist/
171
+ - env:
172
+ VERSION: ${{ needs.release.outputs.version }}
173
+ WHEEL_SHA256: ${{ needs.dist.outputs.wheel-sha256 }}
174
+ SDIST_SHA256: ${{ needs.dist.outputs.sdist-sha256 }}
175
+ run: |
176
+ test "$(ls -A dist | wc -l)" = 2
177
+ printf '%s %s\n' "$WHEEL_SHA256" "dist/revision_engine-$VERSION-py3-none-any.whl" "$SDIST_SHA256" "dist/revision_engine-$VERSION.tar.gz" | sha256sum --strict -c
178
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .pytest_cache/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .venv/
8
+ public/
9
+ test-results/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matthew Tibbits
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.5
2
+ Name: revision-engine
3
+ Version: 0.2.2
4
+ Summary: reVision: a static-site build tool for teaching code by example.
5
+ Project-URL: Homepage, https://github.com/mtibbits/reVision
6
+ Author: Matthew Tibbits
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.12
10
+ Requires-Dist: jinja2>=3.1
11
+ Requires-Dist: markdown-it-py>=3.0
12
+ Requires-Dist: pygments>=2.17
13
+ Requires-Dist: pyyaml>=6.0
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest-playwright>=0.5; extra == 'dev'
16
+ Requires-Dist: pytest>=8.0; extra == 'dev'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # reVision²
20
+
21
+ A static-site build tool for teaching code by example. A reader sees a real source
22
+ file on the left and a lesson about it on the right. Phrases in the lesson box the
23
+ code they describe. A narration track drives the same boxes on a timeline.
24
+
25
+ Install the engine as in [step 1](#add-revision-to-a-repository) below, add a `revision.yaml` and a
26
+ `docs/revision/` folder to the repository you want to teach, and run `rv2 build`.
27
+ Revision control, revisited.
28
+
29
+ ## Add reVision to a repository
30
+
31
+ 1. Install the engine: `pip install revision-engine`. To pin a release tag instead:
32
+ `pip install "revision-engine @ git+https://github.com/mtibbits/reVision@v0.2.2"`, which needs `git`.
33
+ Graphviz (`dot`) must be on the PATH for diagrams.
34
+ 2. Create `revision.yaml` at the repository root:
35
+ ```yaml
36
+ site: { title: My project by example, base_url: "" }
37
+ repo: { url: https://github.com/you/project, host: github } # or host: gitlab
38
+ content: docs/revision
39
+ output: public
40
+ ```
41
+ 3. Create `docs/revision/curriculum.yaml` and one lesson folder holding `lesson.md`
42
+ and `anchors.yaml`. Copy `examples/minimal/docs/revision/` from this repository to start.
43
+ 4. `rv2 check` to validate, `rv2 serve` to preview, `rv2 build` to write `public/`.
44
+ `--root` must be the repository's git root; the code pane's "open at commit" link is built from it.
45
+
46
+ ## Commands
47
+
48
+ | Command | What it does |
49
+ |---|---|
50
+ | `rv2 build` | Writes the site into the output folder. No network. Same commit, same bytes. |
51
+ | `rv2 check [--strict] [--list-unlinked]` | Runs every validation without writing. Use it as a pre-commit hook or CI gate. `--strict` exits 1 if any warning is reported; `--list-unlinked` lists each inline code span (not fenced blocks) that matches no vendor intrinsic table, per lesson, without changing the exit code. |
52
+ | `rv2 serve` | Builds to a temp folder, serves on localhost, rebuilds on change. |
53
+ | `rv2 narrate LESSON [--model voice.onnx]` | Synthesizes `narration.yaml` with Piper into audio plus `cues.json`. Commit both. |
54
+
55
+ For `narrate`, `pip install piper-tts` and download a voice (`.onnx` plus its `.onnx.json`) from
56
+ [rhasspy/piper-voices](https://huggingface.co/rhasspy/piper-voices); the example lesson uses
57
+ `en_US-lessac-medium`. Without `--model` the voice comes from `--voice`, then the lesson's `narration.yaml`
58
+ `voice`, then its front matter `voice`, then the project's `narration.voice`, and the model is `<voice>.onnx`
59
+ directly in `$RV2_VOICES` or `~/piper-voices`. A voice is a name, not a path: it may use only ASCII letters,
60
+ digits, `_`, `-` and `.`, not starting with `-` or `.`, and `rv2 narrate` refuses anything else. To use a model
61
+ file somewhere else, pass `--model`. `--silent` writes a silent placeholder track with plausible timings when
62
+ Piper is unavailable.
63
+
64
+ `revision` is an alias for `rv2`.
65
+
66
+ ## Writing a lesson
67
+
68
+ - `lesson.md` starts with front matter: `title`, `file` (the source file shown), and optionally
69
+ `summary`, `start` (the anchor boxed on load), `media`, and `voice` (a voice name, not a path; see
70
+ `narrate`).
71
+ - `anchors.yaml` names code regions. Each anchor uses one form: `from`/`to` (a line containing
72
+ `from`, then the first later line matching the `to` regex), `match` (one line), or
73
+ `lines: 88-92`. Add `variant: true` and a `label` to list it in the Variants panel.
74
+ - `within: <anchor>` scopes `match`, `from` and `to` to another anchor's lines, for files where the
75
+ same body appears twice (aligned and unaligned variants).
76
+ - In the body, `[phrase](@anchor)` boxes a region, `[name](!intrinsic)` links a SIMD intrinsic to
77
+ its vendor documentation (Intel and Arm names to their intrinsic's page, RISC-V Vector names to
78
+ their section of the v1.0 intrinsic reference; a bare `` `name` `` in inline code auto-links
79
+ too, except inside another link's text, where it stays plain inline code; a code span in an
80
+ anchor phrase still links), `![Alt](diagrams/x.dot)` inlines a Graphviz diagram (give a node
81
+ `id="anchor-name"` to make it hover and click like a phrase; a `URL`, `href` or HTML-label `HREF`
82
+ on such a node is removed with a warning, because a click on it pins the anchor), and a fenced
83
+ block with the language `quiz` adds a page-local knowledge check.
84
+ - In a quiz, a question's `q` and its `choices` accept one kind of markup: a backtick code span,
85
+ which renders as inline code (not linked to vendor documentation). Anything else, raw HTML and
86
+ backslashes included, shows as written, so a backslash does not stop a backtick from opening a
87
+ code span. YAML does not allow an unquoted value to begin with a backtick, so single-quote a `q`
88
+ or choice that starts with one (`` q: '`vaddq_f32` adds how many lanes?' ``); unquoted, the
89
+ build fails with "quiz block is not valid YAML". Use single quotes, not double: inside double
90
+ quotes YAML reads a backslash as an escape, so `` `\n` `` would lose its backslash. Inside single
91
+ quotes, write an apostrophe twice (`''`).
92
+ - A quiz block's only top-level key is `questions`. A question accepts `q`, `choices`, `answer`
93
+ (the index of the right choice, counting from 0) and `shuffle`; any other key fails the build
94
+ and is named in the error, so a misspelled `shufle: false` cannot leave a question shuffled.
95
+ - Each time the page loads, a question's choices appear in a random order, so the right answer is
96
+ not always in the same place; grading follows the choice, not its position. Add `shuffle: false`
97
+ to a question to keep its choices in the order written, for example numeric ranges or "all of
98
+ the above". A question with two choices (True/False) always keeps its order, so `shuffle: true`
99
+ on one is a build warning. With JavaScript off, choices appear in the order written.
100
+ - `narration.yaml` is a list of segments: `text` plus what to `show`, and optionally a `diagram`
101
+ and `node` to bring into view. Timings are derived by `rv2 narrate`, never typed. If the words
102
+ change and `narrate` is not rerun, `build` fails with the diff.
103
+
104
+ ## Deploy
105
+
106
+ <!-- tests/test_release_doc.py::test_deploy_runs_the_strict_check_before_building checks that each platform's recipe below runs the strict check before building. -->
107
+ GitHub Actions: install Python and Graphviz, install the engine as in
108
+ [step 1](#add-revision-to-a-repository), run `rv2 check --strict` so any warning fails the job before anything is published, then
109
+ `rv2 build`, and upload `public/` with `actions/upload-pages-artifact`. GitLab CI: install Python,
110
+ Graphviz and the engine the same way and run `rv2 check --strict`, then `rv2 build`, in a `pages`
111
+ job with `public/` as its artifact, on an image that has `git`.
112
+ `.github/workflows/demo.yml` in this repository builds `examples/minimal/` that way.
113
+
114
+ ## Developing the engine
115
+
116
+ ```bash
117
+ python -m venv .venv && source .venv/Scripts/activate # or .venv/bin/activate
118
+ python -m pip install -e ".[dev]" -c ci-constraints-requirements.txt && python -m playwright install chromium
119
+ python -m pytest --browser chromium
120
+ python -m playwright install firefox webkit
121
+ python -m pytest tests/browser --browser firefox --browser webkit
122
+ ```
123
+
124
+ The golden test compares the built example site byte-for-byte, except that each diagram is reduced to its nodes (ids, anchor stamps, labels) and edges (endpoints, labels), so Graphviz layout and fonts do not matter.
125
+ That reduction was verified identical on Graphviz 16.1.0 (Windows) and on Ubuntu 22.04's `graphviz` 2.42.2 package (which reports itself as 2.43.0); a Graphviz that changes node ids, anchor stamps, edges, or node and edge labels fails the test (graph and cluster labels and link targets are not compared).
126
+ CI runs on the pinned `ubuntu-24.04` image and installs its `graphviz` package (2.42.2). Its `test` job sets `RV_REQUIRE_GOLDEN=1` and runs the whole suite in Chromium, so a skipped or missing golden test fails the job; the gate's message is the last line of the log. A separate `browsers` job runs `tests/browser` in Firefox and WebKit without the golden gate; a test that cannot pass in one engine carries `@pytest.mark.xfail_browser(...)` naming its tracking issue, and is a strict xfail there. On a push to `main` that bumps the version, a `release` job tags and releases it after both pass, and later jobs upload it to TestPyPI, install it from there, and upload it to PyPI; see RELEASING.md step 4.
127
+ <!-- tests/test_ci_constraints.py checks that every README line installing .[dev], here and in the fence above, contains INSTALL_CMD from tests/ci_pins.py. -->
128
+ CI installs the runtime dependencies whose output the golden test hashes (Pygments, markdown-it-py, Jinja2, PyYAML and their dependencies) at the versions pinned in `ci-constraints-requirements.txt`; `pyproject.toml` keeps floors only, so installing the engine in a content repository is unaffected. To match CI locally, run `python -m pip install -e ".[dev]" -c ci-constraints-requirements.txt`; after an intentional rendering change, regenerate with `RV_UPDATE_GOLDEN=1 python -m pytest tests/test_golden.py`. `.github/dependabot.yml` asks Dependabot to propose pin bumps; a bump that changes output reddens the golden test on its own PR, where the manifest is regenerated after review.
129
+
130
+ To cut a release, follow [RELEASING.md](https://github.com/mtibbits/reVision/blob/main/RELEASING.md).
131
+
132
+ ## License
133
+
134
+ MIT.
@@ -0,0 +1,116 @@
1
+ # reVision²
2
+
3
+ A static-site build tool for teaching code by example. A reader sees a real source
4
+ file on the left and a lesson about it on the right. Phrases in the lesson box the
5
+ code they describe. A narration track drives the same boxes on a timeline.
6
+
7
+ Install the engine as in [step 1](#add-revision-to-a-repository) below, add a `revision.yaml` and a
8
+ `docs/revision/` folder to the repository you want to teach, and run `rv2 build`.
9
+ Revision control, revisited.
10
+
11
+ ## Add reVision to a repository
12
+
13
+ 1. Install the engine: `pip install revision-engine`. To pin a release tag instead:
14
+ `pip install "revision-engine @ git+https://github.com/mtibbits/reVision@v0.2.2"`, which needs `git`.
15
+ Graphviz (`dot`) must be on the PATH for diagrams.
16
+ 2. Create `revision.yaml` at the repository root:
17
+ ```yaml
18
+ site: { title: My project by example, base_url: "" }
19
+ repo: { url: https://github.com/you/project, host: github } # or host: gitlab
20
+ content: docs/revision
21
+ output: public
22
+ ```
23
+ 3. Create `docs/revision/curriculum.yaml` and one lesson folder holding `lesson.md`
24
+ and `anchors.yaml`. Copy `examples/minimal/docs/revision/` from this repository to start.
25
+ 4. `rv2 check` to validate, `rv2 serve` to preview, `rv2 build` to write `public/`.
26
+ `--root` must be the repository's git root; the code pane's "open at commit" link is built from it.
27
+
28
+ ## Commands
29
+
30
+ | Command | What it does |
31
+ |---|---|
32
+ | `rv2 build` | Writes the site into the output folder. No network. Same commit, same bytes. |
33
+ | `rv2 check [--strict] [--list-unlinked]` | Runs every validation without writing. Use it as a pre-commit hook or CI gate. `--strict` exits 1 if any warning is reported; `--list-unlinked` lists each inline code span (not fenced blocks) that matches no vendor intrinsic table, per lesson, without changing the exit code. |
34
+ | `rv2 serve` | Builds to a temp folder, serves on localhost, rebuilds on change. |
35
+ | `rv2 narrate LESSON [--model voice.onnx]` | Synthesizes `narration.yaml` with Piper into audio plus `cues.json`. Commit both. |
36
+
37
+ For `narrate`, `pip install piper-tts` and download a voice (`.onnx` plus its `.onnx.json`) from
38
+ [rhasspy/piper-voices](https://huggingface.co/rhasspy/piper-voices); the example lesson uses
39
+ `en_US-lessac-medium`. Without `--model` the voice comes from `--voice`, then the lesson's `narration.yaml`
40
+ `voice`, then its front matter `voice`, then the project's `narration.voice`, and the model is `<voice>.onnx`
41
+ directly in `$RV2_VOICES` or `~/piper-voices`. A voice is a name, not a path: it may use only ASCII letters,
42
+ digits, `_`, `-` and `.`, not starting with `-` or `.`, and `rv2 narrate` refuses anything else. To use a model
43
+ file somewhere else, pass `--model`. `--silent` writes a silent placeholder track with plausible timings when
44
+ Piper is unavailable.
45
+
46
+ `revision` is an alias for `rv2`.
47
+
48
+ ## Writing a lesson
49
+
50
+ - `lesson.md` starts with front matter: `title`, `file` (the source file shown), and optionally
51
+ `summary`, `start` (the anchor boxed on load), `media`, and `voice` (a voice name, not a path; see
52
+ `narrate`).
53
+ - `anchors.yaml` names code regions. Each anchor uses one form: `from`/`to` (a line containing
54
+ `from`, then the first later line matching the `to` regex), `match` (one line), or
55
+ `lines: 88-92`. Add `variant: true` and a `label` to list it in the Variants panel.
56
+ - `within: <anchor>` scopes `match`, `from` and `to` to another anchor's lines, for files where the
57
+ same body appears twice (aligned and unaligned variants).
58
+ - In the body, `[phrase](@anchor)` boxes a region, `[name](!intrinsic)` links a SIMD intrinsic to
59
+ its vendor documentation (Intel and Arm names to their intrinsic's page, RISC-V Vector names to
60
+ their section of the v1.0 intrinsic reference; a bare `` `name` `` in inline code auto-links
61
+ too, except inside another link's text, where it stays plain inline code; a code span in an
62
+ anchor phrase still links), `![Alt](diagrams/x.dot)` inlines a Graphviz diagram (give a node
63
+ `id="anchor-name"` to make it hover and click like a phrase; a `URL`, `href` or HTML-label `HREF`
64
+ on such a node is removed with a warning, because a click on it pins the anchor), and a fenced
65
+ block with the language `quiz` adds a page-local knowledge check.
66
+ - In a quiz, a question's `q` and its `choices` accept one kind of markup: a backtick code span,
67
+ which renders as inline code (not linked to vendor documentation). Anything else, raw HTML and
68
+ backslashes included, shows as written, so a backslash does not stop a backtick from opening a
69
+ code span. YAML does not allow an unquoted value to begin with a backtick, so single-quote a `q`
70
+ or choice that starts with one (`` q: '`vaddq_f32` adds how many lanes?' ``); unquoted, the
71
+ build fails with "quiz block is not valid YAML". Use single quotes, not double: inside double
72
+ quotes YAML reads a backslash as an escape, so `` `\n` `` would lose its backslash. Inside single
73
+ quotes, write an apostrophe twice (`''`).
74
+ - A quiz block's only top-level key is `questions`. A question accepts `q`, `choices`, `answer`
75
+ (the index of the right choice, counting from 0) and `shuffle`; any other key fails the build
76
+ and is named in the error, so a misspelled `shufle: false` cannot leave a question shuffled.
77
+ - Each time the page loads, a question's choices appear in a random order, so the right answer is
78
+ not always in the same place; grading follows the choice, not its position. Add `shuffle: false`
79
+ to a question to keep its choices in the order written, for example numeric ranges or "all of
80
+ the above". A question with two choices (True/False) always keeps its order, so `shuffle: true`
81
+ on one is a build warning. With JavaScript off, choices appear in the order written.
82
+ - `narration.yaml` is a list of segments: `text` plus what to `show`, and optionally a `diagram`
83
+ and `node` to bring into view. Timings are derived by `rv2 narrate`, never typed. If the words
84
+ change and `narrate` is not rerun, `build` fails with the diff.
85
+
86
+ ## Deploy
87
+
88
+ <!-- tests/test_release_doc.py::test_deploy_runs_the_strict_check_before_building checks that each platform's recipe below runs the strict check before building. -->
89
+ GitHub Actions: install Python and Graphviz, install the engine as in
90
+ [step 1](#add-revision-to-a-repository), run `rv2 check --strict` so any warning fails the job before anything is published, then
91
+ `rv2 build`, and upload `public/` with `actions/upload-pages-artifact`. GitLab CI: install Python,
92
+ Graphviz and the engine the same way and run `rv2 check --strict`, then `rv2 build`, in a `pages`
93
+ job with `public/` as its artifact, on an image that has `git`.
94
+ `.github/workflows/demo.yml` in this repository builds `examples/minimal/` that way.
95
+
96
+ ## Developing the engine
97
+
98
+ ```bash
99
+ python -m venv .venv && source .venv/Scripts/activate # or .venv/bin/activate
100
+ python -m pip install -e ".[dev]" -c ci-constraints-requirements.txt && python -m playwright install chromium
101
+ python -m pytest --browser chromium
102
+ python -m playwright install firefox webkit
103
+ python -m pytest tests/browser --browser firefox --browser webkit
104
+ ```
105
+
106
+ The golden test compares the built example site byte-for-byte, except that each diagram is reduced to its nodes (ids, anchor stamps, labels) and edges (endpoints, labels), so Graphviz layout and fonts do not matter.
107
+ That reduction was verified identical on Graphviz 16.1.0 (Windows) and on Ubuntu 22.04's `graphviz` 2.42.2 package (which reports itself as 2.43.0); a Graphviz that changes node ids, anchor stamps, edges, or node and edge labels fails the test (graph and cluster labels and link targets are not compared).
108
+ CI runs on the pinned `ubuntu-24.04` image and installs its `graphviz` package (2.42.2). Its `test` job sets `RV_REQUIRE_GOLDEN=1` and runs the whole suite in Chromium, so a skipped or missing golden test fails the job; the gate's message is the last line of the log. A separate `browsers` job runs `tests/browser` in Firefox and WebKit without the golden gate; a test that cannot pass in one engine carries `@pytest.mark.xfail_browser(...)` naming its tracking issue, and is a strict xfail there. On a push to `main` that bumps the version, a `release` job tags and releases it after both pass, and later jobs upload it to TestPyPI, install it from there, and upload it to PyPI; see RELEASING.md step 4.
109
+ <!-- tests/test_ci_constraints.py checks that every README line installing .[dev], here and in the fence above, contains INSTALL_CMD from tests/ci_pins.py. -->
110
+ CI installs the runtime dependencies whose output the golden test hashes (Pygments, markdown-it-py, Jinja2, PyYAML and their dependencies) at the versions pinned in `ci-constraints-requirements.txt`; `pyproject.toml` keeps floors only, so installing the engine in a content repository is unaffected. To match CI locally, run `python -m pip install -e ".[dev]" -c ci-constraints-requirements.txt`; after an intentional rendering change, regenerate with `RV_UPDATE_GOLDEN=1 python -m pytest tests/test_golden.py`. `.github/dependabot.yml` asks Dependabot to propose pin bumps; a bump that changes output reddens the golden test on its own PR, where the manifest is regenerated after review.
111
+
112
+ To cut a release, follow [RELEASING.md](https://github.com/mtibbits/reVision/blob/main/RELEASING.md).
113
+
114
+ ## License
115
+
116
+ MIT.