krz/orgo

Lightning fast org-mode static site generator. fast go org-mode static-site-generator

Commit 2f14a20767

2f14a2076788dd8a1d9e2c4ee571df3b19198dcf

parent: 2ff6947870

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-11 21:47 UTC

Rename to orgo

org-ssg described the project and named nothing. `orgo` is four letters, sits in
the same sound-space as hugo and zola without borrowing from either, and is free
on crates.io — which almost nothing short is.

The rename reaches further than the crate: the config file is `orgo.toml` and
the cache is `.orgo-cache.json`. Both are better done now, while the only site
using them is one I can rename in the same afternoon.

Layout: unified · split

.github/workflows/ci.yml +2 −2
@@ -1,6 +1,6 @@
1name: CI 1name: CI
2 2
3# Build, test and lint on both platforms org-ssg is used from, plus a compiler-floor job. 3# Build, test and lint on both platforms orgo is used from, plus a compiler-floor job.
4# 4#
5# WHAT THIS CATCHES THAT LOCAL WORK DOES NOT: 5# WHAT THIS CATCHES THAT LOCAL WORK DOES NOT:
6# 6#
@@ -96,7 +96,7 @@ jobs:
96 # Build only. The tests pull in dev-dependencies whose own floors move 96 # Build only. The tests pull in dev-dependencies whose own floors move
97 # independently, and chasing those would make this job about someone else's MSRV. 97 # independently, and chasing those would make this job about someone else's MSRV.
98 # 98 #
99 # The floor is set by dependencies rather than by org-ssg — its own code compiles on 99 # The floor is set by dependencies rather than by orgo — its own code compiles on
100 # 1.82 — which is precisely why it is checked here instead of reasoned about: a 100 # 1.82 — which is precisely why it is checked here instead of reasoned about: a
101 # dependency raising its floor is invisible until someone on an older compiler 101 # dependency raising its floor is invisible until someone on an older compiler
102 # tries to build. 102 # tries to build.
.github/workflows/release.yml +3 −3
@@ -2,7 +2,7 @@ name: Release
2 2
3# Build binaries for a tag and attach them to a GitHub release. 3# Build binaries for a tag and attach them to a GitHub release.
4# 4#
5# WHY BINARIES AT ALL, given `cargo install org-ssg` exists: installing from source needs 5# WHY BINARIES AT ALL, given `cargo install orgo` exists: installing from source needs
6# a Rust toolchain and about a minute of compiling syntect. Someone evaluating a site 6# a Rust toolchain and about a minute of compiling syntect. Someone evaluating a site
7# generator should be able to download one file and point it at their notes. 7# generator should be able to download one file and point it at their notes.
8# 8#
@@ -74,9 +74,9 @@ jobs:
74 - name: Package 74 - name: Package
75 shell: bash 75 shell: bash
76 run: | 76 run: |
77 staging="org-ssg-${{ github.event.inputs.tag || github.ref_name }}-${{ matrix.target }}" 77 staging="orgo-${{ github.event.inputs.tag || github.ref_name }}-${{ matrix.target }}"
78 mkdir "$staging" 78 mkdir "$staging"
79 cp "target/${{ matrix.target }}/release/org-ssg" "$staging/" 79 cp "target/${{ matrix.target }}/release/orgo" "$staging/"
80 cp README.md LICENSE CHANGELOG.md "$staging/" 80 cp README.md LICENSE CHANGELOG.md "$staging/"
81 tar czf "$staging.tar.gz" "$staging" 81 tar czf "$staging.tar.gz" "$staging"
82 shasum -a 256 "$staging.tar.gz" > "$staging.tar.gz.sha256" 82 shasum -a 256 "$staging.tar.gz" > "$staging.tar.gz.sha256"
CHANGELOG.md +4 −4
@@ -7,7 +7,7 @@ Two conventions worth knowing before reading:
7 7
8- **A cache-format bump is not a change you need to act on.** The incremental cache is 8- **A cache-format bump is not a change you need to act on.** The incremental cache is
9 versioned and discards itself; a bump means the next build re-renders everything once. 9 versioned and discards itself; a bump means the next build re-renders everything once.
10- **Output changes are called out.** org-ssg aims at what Emacs exports from the same 10- **Output changes are called out.** orgo aims at what Emacs exports from the same
11 file, so an entry that says "now renders X" means your pages will change. That is the 11 file, so an entry that says "now renders X" means your pages will change. That is the
12 product, not a regression — but it belongs in a changelog rather than a diff you find 12 product, not a regression — but it belongs in a changelog rather than a diff you find
13 later. 13 later.
@@ -47,7 +47,7 @@ Release engineering, so that a version number is worth reading.
47 built with `--strict`. Emacs is installed on both, so the oracle suite runs for real 47 built with `--strict`. Emacs is installed on both, so the oracle suite runs for real
48 instead of skipping. 48 instead of skipping.
49- **A checked MSRV**, 1.88 — which is how it came to be 1.88 rather than the 1.82 49- **A checked MSRV**, 1.88 — which is how it came to be 1.88 rather than the 1.82
50 org-ssg's own code needs. The floor comes from dependencies, and nobody finds that out 50 orgo's own code needs. The floor comes from dependencies, and nobody finds that out
51 by reasoning about it. 51 by reasoning about it.
52- **Release binaries** for macOS (arm64, x86_64) and Linux (gnu, musl), built on tag into 52- **Release binaries** for macOS (arm64, x86_64) and Linux (gnu, musl), built on tag into
53 a draft release. The tag is checked against `Cargo.toml` before anything is built. 53 a draft release. The tag is checked against `Cargo.toml` before anything is built.
@@ -117,7 +117,7 @@ publishes from the same sources. **All of these change output.**
117## 0.12.0 117## 0.12.0
118 118
119- `serve`: a development server with live reload, bound to loopback. 119- `serve`: a development server with live reload, bound to loopback.
120- A documentation site under `docs/`, built by org-ssg itself. 120- A documentation site under `docs/`, built by orgo itself.
121 121
122## 0.11.0 122## 0.11.0
123 123
@@ -153,7 +153,7 @@ publishes from the same sources. **All of these change output.**
153## 0.5.0 153## 0.5.0
154 154
155- Parse diagnostics carry `file:line`, and pages render in parallel. 155- Parse diagnostics carry `file:line`, and pages render in parallel.
156- The corpus audit (`org-ssg audit`) and the `emacs --batch` oracle. 156- The corpus audit (`orgo audit`) and the `emacs --batch` oracle.
157- `#+SLUG:` decides a page's output filename — found by auditing a real corpus, where it 157- `#+SLUG:` decides a page's output filename — found by auditing a real corpus, where it
158 affected 169 of 182 URLs. 158 affected 169 of 182 URLs.
159 159
Cargo.lock +1 −1
@@ -674,7 +674,7 @@ dependencies = [
674] 674]
675 675
676[[package]] 676[[package]]
677name = "org-ssg" 677name = "orgo"
678version = "0.19.1" 678version = "0.19.1"
679dependencies = [ 679dependencies = [
680 "anyhow", 680 "anyhow",
Cargo.toml +7 −8
@@ -1,17 +1,16 @@
1[package] 1[package]
2name = "org-ssg" 2name = "orgo"
3version = "0.19.1" 3version = "0.19.1"
4edition = "2021" 4edition = "2021"
5description = "Org-mode static site generator that renders the org element tree straight to HTML" 5description = "Org-mode static site generator that renders the org element tree straight to HTML"
6license = "MIT" 6license = "0BSD"
7readme = "README.md" 7readme = "README.md"
8keywords = ["org-mode", "static-site-generator", "emacs", "html", "blog"] 8keywords = ["org-mode", "static-site-generator", "emacs", "html", "blog"]
9categories = ["command-line-utilities", "text-processing"] 9categories = ["command-line-utilities", "text-processing"]
10# Set before the first `cargo publish`: crates.io shows it on the crate page, and a 10repository = "https://github.com/ccleberg/orgo"
11# missing link is the first thing anyone evaluating a generator looks for. 11homepage = "https://ccleberg.github.io/orgo/"
12# repository = "https://git.krz.sh/cmc/org-ssg.git/"
13 12
14# The compiler floor, checked in CI rather than assumed. org-ssg's own code needs 1.82 13# The compiler floor, checked in CI rather than assumed. orgo's own code needs 1.82
15# (`Option::is_none_or`); the floor is 1.88 because dependencies in Cargo.lock declare it 14# (`Option::is_none_or`); the floor is 1.88 because dependencies in Cargo.lock declare it
16# — `plist` and `time`, both by way of syntect. Raising this is a minor-version change, 15# — `plist` and `time`, both by way of syntect. Raising this is a minor-version change,
17# never a patch. 16# never a patch.
@@ -23,11 +22,11 @@ rust-version = "1.88"
23exclude = ["docs/", "target/", "/.github/"] 22exclude = ["docs/", "target/", "/.github/"]
24 23
25[lib] 24[lib]
26name = "org_ssg" 25name = "orgo"
27path = "src/lib.rs" 26path = "src/lib.rs"
28 27
29[[bin]] 28[[bin]]
30name = "org-ssg" 29name = "orgo"
31path = "src/main.rs" 30path = "src/main.rs"
32 31
33[dependencies] 32[dependencies]
README.md +18 −18
@@ -1,4 +1,4 @@
1# org-ssg 1# orgo
2 2
3An org-mode static site generator, in Rust. Org is treated as the *source language*, 3An org-mode static site generator, in Rust. Org is treated as the *source language*,
4not an inconvenient input to be normalized into markdown. The org element tree — 4not an inconvenient input to be normalized into markdown. The org element tree —
@@ -14,7 +14,7 @@ it imposes on the data model — pure, hashable, dependency-tracked units — is
14deliverable, even while the corpus is small enough that a full rebuild is instant. 14deliverable, even while the corpus is small enough that a full rebuild is instant.
15 15
16**Full documentation is in [`docs/`](docs/)** — a site written in org and built by 16**Full documentation is in [`docs/`](docs/)** — a site written in org and built by
17org-ssg itself. Build and read it with: 17orgo itself. Build and read it with:
18 18
19```bash 19```bash
20cargo run -- serve docs -o docs/_site 20cargo run -- serve docs -o docs/_site
@@ -33,23 +33,23 @@ Or skip the scaffolding entirely — point it at any directory of `.org` files:
33cargo run -- build ~/notes -o _site 33cargo run -- build ~/notes -o _site
34``` 34```
35 35
36**Zero configuration is a supported path, not a demo.** With no `org-ssg.toml`, no 36**Zero configuration is a supported path, not a demo.** With no `orgo.toml`, no
37templates and no org-ssg-specific markup in your files, you get a complete site: pages, 37templates and no orgo-specific markup in your files, you get a complete site: pages,
38navigation, syntax-highlighted code and the stylesheet to colour it. Configuration 38navigation, syntax-highlighted code and the stylesheet to colour it. Configuration
39changes what you get; it is never what makes it work. 39changes what you get; it is never what makes it work.
40 40
41Discovery skips what should not be published — dot-directories such as `.git`, the config 41Discovery skips what should not be published — dot-directories such as `.git`, the config
42file, the templates directory, and the output directory when it sits inside the source, so 42file, the templates directory, and the output directory when it sits inside the source, so
43`org-ssg build . -o _site` does the obvious thing. 43`orgo build . -o _site` does the obvious thing.
44 44
45## Configuration 45## Configuration
46 46
47Everything is optional. `org-ssg init` writes a fully commented `org-ssg.toml`; every 47Everything is optional. `orgo init` writes a fully commented `orgo.toml`; every
48value below is the default. 48value below is the default.
49 49
50```toml 50```toml
51[site] 51[site]
52title = "org-ssg site" 52title = "orgo site"
53base_url = "" # absolute URL, no trailing slash; needed for feeds/canonical links 53base_url = "" # absolute URL, no trailing slash; needed for feeds/canonical links
54description = "" 54description = ""
55language = "en" 55language = "en"
@@ -224,7 +224,7 @@ group.
224 224
225**A feed is a listing page with an XML template**, not a separate feature — templates are 225**A feed is a listing page with an XML template**, not a separate feature — templates are
226loaded by full filename and any extension, so `output = "feed.xml"` with 226loaded by full filename and any extension, so `output = "feed.xml"` with
227`template = "feed.xml"` is all it takes. `org-ssg init` writes a working RSS template. 227`template = "feed.xml"` is all it takes. `orgo init` writes a working RSS template.
228 228
229A feed is read away from the site that served it, so relative links in one are simply 229A feed is read away from the site that served it, so relative links in one are simply
230broken. Set `site.base_url` and use the `absolute` filter: 230broken. Set `site.base_url` and use the `absolute` filter:
@@ -319,7 +319,7 @@ is the only inherently global stage — it is where the link dependency graph is
319 319
320| Stage | Module | Notes | 320| Stage | Module | Notes |
321|---|---|---| 321|---|---|---|
322| config | `src/config.rs` | `org-ssg.toml`: site metadata, nav mode, templates, theme. A hash input. | 322| config | `src/config.rs` | `orgo.toml`: site metadata, nav mode, templates, theme. A hash input. |
323| PARSE | `src/parser.rs` | Hand-written recursive descent: line lexer → element builder → inline tokenizer. | 323| PARSE | `src/parser.rs` | Hand-written recursive descent: line lexer → element builder → inline tokenizer. |
324| audit | `src/audit.rs` | Phase 0 corpus audit: construct frequencies against the IN/OUT line. | 324| audit | `src/audit.rs` | Phase 0 corpus audit: construct frequencies against the IN/OUT line. |
325| model | `src/model.rs` | The org element tree — Elements (block) vs Objects (inline). | 325| model | `src/model.rs` | The org element tree — Elements (block) vs Objects (inline). |
@@ -424,7 +424,7 @@ feature):**
424 The config component folds in a **site-structure hash** (every page's `(path, title)`), 424 The config component folds in a **site-structure hash** (every page's `(path, title)`),
425 because the shared nav bar is global chrome — a title change or a page add/remove alters 425 because the shared nav bar is global chrome — a title change or a page add/remove alters
426 the nav on every page and so must re-render them all (otherwise byte-equivalence breaks). 426 the nav on every page and so must re-render them all (otherwise byte-equivalence breaks).
427- **Persisted cache manifest** (`<out>/.org-ssg-cache.json`, JSON), carrying per-page 427- **Persisted cache manifest** (`<out>/.orgo-cache.json`, JSON), carrying per-page
428 records, the config/template hashes, and the serialized dependency graph, tagged with 428 records, the config/template hashes, and the serialized dependency graph, tagged with
429 `CACHE_FORMAT_VERSION`. A version mismatch, a missing file, or a corrupt file all fall 429 `CACHE_FORMAT_VERSION`. A version mismatch, a missing file, or a corrupt file all fall
430 back to a clean full rebuild — the cache is an optimization, never a correctness 430 back to a clean full rebuild — the cache is an optimization, never a correctness
@@ -523,7 +523,7 @@ it over the original and touches the directory, which is one edit and several ev
523Two rules decide what counts as a change, and they are not the same rules the build uses 523Two rules decide what counts as a change, and they are not the same rules the build uses
524to find content: 524to find content:
525 525
526- **A build input is a change.** Editing `org-ssg.toml` or a template rebuilds, even 526- **A build input is a change.** Editing `orgo.toml` or a template rebuilds, even
527 though discovery skips both as non-content. The question is "would this change the 527 though discovery skips both as non-content. The question is "would this change the
528 site?", not "is this a page?". 528 site?", not "is this a page?".
529- **Our own output is not.** `watch . -o _site` puts the output inside the source, so a 529- **Our own output is not.** `watch . -o _site` puts the output inside the source, so a
@@ -544,7 +544,7 @@ Emacs does.
544The audit runs against any corpus — point it at your own notes before trusting this tool 544The audit runs against any corpus — point it at your own notes before trusting this tool
545with them. The numbers below come from a 179-file site published today by weblorg, a 545with them. The numbers below come from a 179-file site published today by weblorg, a
546wrapper around org's own HTML exporter, which makes it both a realistic workload and a 546wrapper around org's own HTML exporter, which makes it both a realistic workload and a
547directly comparable incumbent. With collections configured, org-ssg now reproduces 547directly comparable incumbent. With collections configured, orgo now reproduces
548**all 182 of that site's URLs**. 548**all 182 of that site's URLs**.
549 549
550``` 550```
@@ -560,7 +560,7 @@ whole out-of-scope tail is 8 uses: four `#+TBLFM:` in a post *about* org-mode, t
560 560
561**`#+SLUG:` was a hole big enough to sink the project.** 178 of 179 files set it, and the 561**`#+SLUG:` was a hole big enough to sink the project.** 178 of 179 files set it, and the
562published URL comes from it, not from the filename: `2018-11-28-aes-encryption.org` is 562published URL comes from it, not from the filename: `2018-11-28-aes-encryption.org` is
563served at `blog/aes-encryption.html`. org-ssg derived output paths from source filenames, 563served at `blog/aes-encryption.html`. orgo derived output paths from source filenames,
564so **169 of 179 pages would have been published at the wrong URL** — every inbound link and 564so **169 of 179 pages would have been published at the wrong URL** — every inbound link and
565every search result, broken, by a tool that reported a clean build. Output paths now come 565every search result, broken, by a tool that reported a clean build. Output paths now come
566from `#+SLUG:` when present ([`util::output_path`](src/util.rs)); slugs are sanitized so an 566from `#+SLUG:` when present ([`util::output_path`](src/util.rs)); slugs are sanitized so an
@@ -588,10 +588,10 @@ report gets reviewed and shows up as a diff, where a permanently red test gets i
588Three invariants are asserted outright, and all three hold — heading structure, list 588Three invariants are asserted outright, and all three hold — heading structure, list
589nesting, and source-block text match Emacs exactly. 589nesting, and source-block text match Emacs exactly.
590 590
591**No bugs in org-ssg.** Every remaining divergence is a deliberate choice to emit better 591**No bugs in orgo.** Every remaining divergence is a deliberate choice to emit better
592HTML than org does: 592HTML than org does:
593 593
594| | org-ssg | Emacs | why | 594| | orgo | Emacs | why |
595|---|---|---|---| 595|---|---|---|---|
596| emphasis | `<em>`/`<strong>` | `<i>`/`<b>` | semantic, not presentational | 596| emphasis | `<em>`/`<strong>` | `<i>`/`<b>` | semantic, not presentational |
597| captioned image | `<figure>`/`<figcaption>` | `<p>` + `"Figure 1: …"` | real figure semantics | 597| captioned image | `<figure>`/`<figcaption>` | `<p>` + `"Figure 1: …"` | real figure semantics |
@@ -695,7 +695,7 @@ a major version — is what you actually build a site against:
695 695
696| Stable | Detail | 696| Stable | Detail |
697|---|---| 697|---|---|
698| `org-ssg.toml` keys | Names, types and meaning. New keys are minor releases; removing one is major. | 698| `orgo.toml` keys | Names, types and meaning. New keys are minor releases; removing one is major. |
699| Template context | `page`, `site`, `nav`, `root`, `pages`, `group`, `groups`, `paginator`, `stylesheet`, and the `absolute` / `rfc822` / `truncate` filters. | 699| Template context | `page`, `site`, `nav`, `root`, `pages`, `group`, `groups`, `paginator`, `stylesheet`, and the `absolute` / `rfc822` / `truncate` filters. |
700| CLI | Command names, flags, and exit codes. | 700| CLI | Command names, flags, and exit codes. |
701| URLs | How a source path becomes an output path, including `#+SLUG:`. A generator that moves your URLs breaks every link anyone has to you. | 701| URLs | How a source path becomes an output path, including `#+SLUG:`. A generator that moves your URLs breaks every link anyone has to you. |
@@ -704,14 +704,14 @@ Explicitly **not stable**, so that the above can be:
704 704
705- **The incremental cache.** Versioned, discarded on mismatch, never a correctness 705- **The incremental cache.** Versioned, discarded on mismatch, never a correctness
706 dependency. It changes whenever it needs to, in any release. 706 dependency. It changes whenever it needs to, in any release.
707- **Rendered HTML details.** org-ssg tracks what Emacs exports from the same file, and 707- **Rendered HTML details.** orgo tracks what Emacs exports from the same file, and
708 closing a gap changes markup. Changes that affect output are called out in 708 closing a gap changes markup. Changes that affect output are called out in
709 [CHANGELOG.md](CHANGELOG.md) — the class names the documentation names (`post-list`, 709 [CHANGELOG.md](CHANGELOG.md) — the class names the documentation names (`post-list`,
710 `figure-number`, `section-number-N`, `footnote-ref`) are the ones to write CSS against. 710 `figure-number`, `section-number-N`, `footnote-ref`) are the ones to write CSS against.
711- **The Rust API.** The crate is published so the binary can be installed with 711- **The Rust API.** The crate is published so the binary can be installed with
712 `cargo install`; the library exists to serve it, and its types move as the tool does. 712 `cargo install`; the library exists to serve it, and its types move as the tool does.
713 713
714The **MSRV is 1.88**, checked in CI on every change. org-ssg's own code compiles on 714The **MSRV is 1.88**, checked in CI on every change. orgo's own code compiles on
7151.82; the floor comes from dependencies. Raising it is a minor version, never a patch. 7151.82; the floor comes from dependencies. Raising it is a minor version, never a patch.
716 716
717## Dependencies 717## Dependencies
RELEASING.md +4 −3
@@ -7,14 +7,15 @@ reports `0.17.0` is the kind of mistake nobody notices for months.
7 7
8## Before the first publish 8## Before the first publish
9 9
10`repository` in `Cargo.toml` is commented out, because a wrong URL on a crates.io page is
11worse than none. Set it, then:
12
13```bash 10```bash
14cargo login # a crates.io token, once per machine 11cargo login # a crates.io token, once per machine
15cargo publish --dry-run 12cargo publish --dry-run
16``` 13```
17 14
15`repository` and `homepage` in `Cargo.toml` point at GitHub and at the documentation site
16on Pages. If git.krz.sh becomes the primary remote, `repository` should follow it —
17crates.io shows that link on the crate page, and it should lead somewhere you read.
18
18## Every release 19## Every release
19 20
201. **Write the changelog entry first.** [CHANGELOG.md](CHANGELOG.md) names behaviour, not 211. **Write the changelog entry first.** [CHANGELOG.md](CHANGELOG.md) names behaviour, not
docs/guide/01-cli.org +11 −11
@@ -7,7 +7,7 @@
7=build=, =serve= and =watch= all take the same pair: 7=build=, =serve= and =watch= all take the same pair:
8 8
9#+BEGIN_SRC sh 9#+BEGIN_SRC sh
10org-ssg <command> <SOURCE> -o <OUTPUT> 10orgo <command> <SOURCE> -o <OUTPUT>
11#+END_SRC 11#+END_SRC
12 12
13*SOURCE is the URL root*, not "the project". =SOURCE/blog/post.org= is published at 13*SOURCE is the URL root*, not "the project". =SOURCE/blog/post.org= is published at
@@ -22,7 +22,7 @@ consumes its own output.
22* build 22* build
23 23
24#+BEGIN_SRC sh 24#+BEGIN_SRC sh
25org-ssg build <INPUT> -o <OUTPUT> [--no-cache] [--strict] [--drafts] [--config FILE] 25orgo build <INPUT> -o <OUTPUT> [--no-cache] [--strict] [--drafts] [--config FILE]
26#+END_SRC 26#+END_SRC
27 27
28If =INPUT= is a directory, it is walked and built into a linked site at =OUTPUT=. If it 28If =INPUT= is a directory, it is walked and built into a linked site at =OUTPUT=. If it
@@ -35,7 +35,7 @@ though with no other documents to resolve against, internal links keep a best-ef
35| =--no-cache= | Ignore the incremental cache and re-render every page. | 35| =--no-cache= | Ignore the incremental cache and re-render every page. |
36| =--strict= | Broken internal links and parse diagnostics become a non-zero exit. | 36| =--strict= | Broken internal links and parse diagnostics become a non-zero exit. |
37| =--drafts= | Include pages marked =#+DRAFT:=. | 37| =--drafts= | Include pages marked =#+DRAFT:=. |
38| =--config FILE= | Use this config instead of =org-ssg.toml= in the source directory. | 38| =--config FILE= | Use this config instead of =orgo.toml= in the source directory. |
39 39
40The summary line reports what happened: 40The summary line reports what happened:
41 41
@@ -52,13 +52,13 @@ Without it, a broken link is a warning and the build succeeds. With it, the buil
52and names every problem. Use it wherever a bad build should not ship: 52and names every problem. Use it wherever a bad build should not ship:
53 53
54#+BEGIN_SRC sh 54#+BEGIN_SRC sh
55org-ssg build content -o _site --strict 55orgo build content -o _site --strict
56#+END_SRC 56#+END_SRC
57 57
58* serve 58* serve
59 59
60#+BEGIN_SRC sh 60#+BEGIN_SRC sh
61org-ssg serve <INPUT> -o <OUTPUT> [-p PORT] [--host HOST] [--drafts] [--config FILE] 61orgo serve <INPUT> -o <OUTPUT> [-p PORT] [--host HOST] [--drafts] [--config FILE]
62#+END_SRC 62#+END_SRC
63 63
64Builds, watches, serves, and reloads the browser when a rebuild lands. This is the 64Builds, watches, serves, and reloads the browser when a rebuild lands. This is the
@@ -74,7 +74,7 @@ command to use while writing.
74laptop, so reaching the local network is something you ask for: 74laptop, so reaching the local network is something you ask for:
75 75
76#+BEGIN_SRC sh 76#+BEGIN_SRC sh
77org-ssg serve content -o _site --host 0.0.0.0 77orgo serve content -o _site --host 0.0.0.0
78#+END_SRC 78#+END_SRC
79 79
80The live-reload script is injected into responses and never written to disk, so what you 80The live-reload script is injected into responses and never written to disk, so what you
@@ -83,7 +83,7 @@ deploy stays clean. Details in [[file:../guide/08-workflow.org][Watching and ser
83* watch 83* watch
84 84
85#+BEGIN_SRC sh 85#+BEGIN_SRC sh
86org-ssg watch <INPUT> -o <OUTPUT> [--no-cache] [--strict] [--drafts] [--config FILE] 86orgo watch <INPUT> -o <OUTPUT> [--no-cache] [--strict] [--drafts] [--config FILE]
87#+END_SRC 87#+END_SRC
88 88
89Rebuilds on filesystem events with no server — for when something else is already serving 89Rebuilds on filesystem events with no server — for when something else is already serving
@@ -92,10 +92,10 @@ the output, or you just want the build to keep up as you write.
92* audit 92* audit
93 93
94#+BEGIN_SRC sh 94#+BEGIN_SRC sh
95org-ssg audit <INPUT> 95orgo audit <INPUT>
96#+END_SRC 96#+END_SRC
97 97
98Reports which org constructs a corpus uses and how they land against what org-ssg 98Reports which org constructs a corpus uses and how they land against what orgo
99supports, plus a census of every keyword, block type, drawer and link scheme seen. Point 99supports, plus a census of every keyword, block type, drawer and link scheme seen. Point
100it at your notes before trusting a tool with them. See [[file:../guide/09-auditing.org][Auditing a corpus]]. 100it at your notes before trusting a tool with them. See [[file:../guide/09-auditing.org][Auditing a corpus]].
101 101
@@ -105,7 +105,7 @@ private notes is safe to share.
105* init 105* init
106 106
107#+BEGIN_SRC sh 107#+BEGIN_SRC sh
108org-ssg init [DIRECTORY] 108orgo init [DIRECTORY]
109#+END_SRC 109#+END_SRC
110 110
111Scaffolds a working site: a fully commented config, an editable copy of the built-in 111Scaffolds a working site: a fully commented config, an editable copy of the built-in
@@ -118,7 +118,7 @@ that already has content — it fills in what is missing and leaves the rest alo
118* clean 118* clean
119 119
120#+BEGIN_SRC sh 120#+BEGIN_SRC sh
121org-ssg clean <OUTPUT> 121orgo clean <OUTPUT>
122#+END_SRC 122#+END_SRC
123 123
124Removes the output directory, including the incremental cache manifest inside it. You 124Removes the output directory, including the incremental cache manifest inside it. You
docs/guide/02-configuration.org +6 −6
@@ -1,8 +1,8 @@
1#+TITLE: Configuration 1#+TITLE: Configuration
2#+DESCRIPTION: Every setting in org-ssg.toml, what it changes, and what it costs. 2#+DESCRIPTION: Every setting in orgo.toml, what it changes, and what it costs.
3#+LEDE: All of it optional. A missing config is a valid config. 3#+LEDE: All of it optional. A missing config is a valid config.
4 4
5org-ssg looks for =org-ssg.toml= in the source directory. Pass a different path with 5orgo looks for =orgo.toml= in the source directory. Pass a different path with
6=--config=. Every field has a default, so a directory of org files with no config still 6=--config=. Every field has a default, so a directory of org files with no config still
7builds a complete site. 7builds a complete site.
8 8
@@ -14,7 +14,7 @@ lose an afternoon.
14 14
15#+BEGIN_SRC toml 15#+BEGIN_SRC toml
16[site] 16[site]
17title = "org-ssg site" 17title = "orgo site"
18base_url = "" 18base_url = ""
19description = "" 19description = ""
20language = "en" 20language = "en"
@@ -47,7 +47,7 @@ Plus any number of =[[collections]]= blocks, documented in [[file:03-collections
47 47
48| Key | Default | Meaning | 48| Key | Default | Meaning |
49|-----+---------+---------| 49|-----+---------+---------|
50| =title= | ="org-ssg site"= | Site name. Available as ={{ site.title }}=. | 50| =title= | ="orgo site"= | Site name. Available as ={{ site.title }}=. |
51| =base_url= | ="" | Absolute origin, *no trailing slash*. | 51| =base_url= | ="" | Absolute origin, *no trailing slash*. |
52| =description= | ="" | Available as ={{ site.description }}=. | 52| =description= | ="" | Available as ={{ site.description }}=. |
53| =language= | ="en"= | Goes in =<html lang>= in the built-in layout. | 53| =language= | ="en"= | Goes in =<html lang>= in the built-in layout. |
@@ -181,7 +181,7 @@ An unknown name is an error listing the valid ones.
181Highlighting emits *CSS classes*, never inline styles, so themes live in a stylesheet. 181Highlighting emits *CSS classes*, never inline styles, so themes live in a stylesheet.
182Each build writes =syntax.css= into the output and every page links it. 182Each build writes =syntax.css= into the output and every page links it.
183 183
184org-ssg bundles TOML and Org on top of syntect's built-in languages. Anything else 184orgo bundles TOML and Org on top of syntect's built-in languages. Anything else
185missing is a file away: put a =.sublime-syntax= definition in =syntaxes_dir= and it is 185missing is a file away: put a =.sublime-syntax= definition in =syntaxes_dir= and it is
186loaded. A definition that fails to parse is reported and skipped, because one bad file 186loaded. A definition that fails to parse is reported and skipped, because one bad file
187should not stop a site from building. 187should not stop a site from building.
@@ -258,6 +258,6 @@ Off is spelled =nil=, =false=, =no=, =0= or =off=; anything else is on.
258 258
259* Configuration is a cache input 259* Configuration is a cache input
260 260
261The resolved config is hashed into every page's render key, so editing =org-ssg.toml= 261The resolved config is hashed into every page's render key, so editing =orgo.toml=
262re-renders exactly the pages it affects — which for most settings is all of them. You 262re-renders exactly the pages it affects — which for most settings is all of them. You
263never need =--no-cache= after a config change. 263never need =--no-cache= after a config change.
docs/guide/03-collections.org +1 −1
@@ -230,7 +230,7 @@ title = "Feed"
230#+END_SRC 230#+END_SRC
231 231
232This needs =site.base_url=, because a feed with relative links is invalid everywhere it 232This needs =site.base_url=, because a feed with relative links is invalid everywhere it
233is read. =org-ssg init= writes this template and leaves the collection commented out 233is read. =orgo init= writes this template and leaves the collection commented out
234until there is a base URL to make absolute links from. 234until there is a base URL to make absolute links from.
235 235
236* Every setting 236* Every setting
docs/guide/04-templates.org +1 −1
@@ -122,7 +122,7 @@ Empty on generated pages, which build their content from =pages= or =groups= ins
122| =keywords= | *Every* =#+KEYWORD:=, by lowercased name. | 122| =keywords= | *Every* =#+KEYWORD:=, by lowercased name. |
123 123
124=page.keywords= is the escape hatch: =#+CUSTOM_THING: x= is 124=page.keywords= is the escape hatch: =#+CUSTOM_THING: x= is
125={{ page.keywords.custom_thing }}=, so your own metadata works without org-ssg knowing it 125={{ page.keywords.custom_thing }}=, so your own metadata works without orgo knowing it
126exists. 126exists.
127 127
128** site 128** site
docs/guide/05-org-support.org +8 −8
@@ -2,7 +2,7 @@
2#+DESCRIPTION: Exactly which org syntax is handled, which is not, and how the rest degrades. 2#+DESCRIPTION: Exactly which org syntax is handled, which is not, and how the rest degrades.
3#+LEDE: A deliberate subset, with the boundary enforced by tests rather than by hope. 3#+LEDE: A deliberate subset, with the boundary enforced by tests rather than by hope.
4 4
5org-ssg parses a defined slice of org. The boundary is not aspirational: every supported 5orgo parses a defined slice of org. The boundary is not aspirational: every supported
6construct has a golden-file test, and every excluded one has a test asserting how it 6construct has a golden-file test, and every excluded one has a test asserting how it
7degrades. That is what stops the parser drifting toward all-of-org. 7degrades. That is what stops the parser drifting toward all-of-org.
8 8
@@ -37,7 +37,7 @@ machine-readable =datetime=.
37 37
38*** Text conversions 38*** Text conversions
39 39
40Org rewrites some prose on export, and so does org-ssg: 40Org rewrites some prose on export, and so does orgo:
41 41
42| Written | Published | 42| Written | Published |
43|---------+-----------| 43|---------+-----------|
@@ -94,7 +94,7 @@ An =html= export block passes through verbatim; every other backend is dropped,
94emitting LaTeX into an HTML page is worse than emitting nothing. 94emitting LaTeX into an HTML page is worse than emitting nothing.
95 95
96*Any other name is a special block*: =#+BEGIN_NOTE= becomes =<div class="note">= holding 96*Any other name is a special block*: =#+BEGIN_NOTE= becomes =<div class="note">= holding
97*parsed org*, which is what makes the convention usable without org-ssg knowing the word 97*parsed org*, which is what makes the convention usable without orgo knowing the word
98"note". A =COMMENT= block is not published. 98"note". A =COMMENT= block is not published.
99 99
100*** Which languages highlight 100*** Which languages highlight
@@ -108,7 +108,7 @@ Recognised, among others: =bash= / =sh=, =c=, =c++=, =css=, =clojure=, =diff=, =
108=makefile=, =markdown=, =matlab=, =objective-c=, =ocaml=, =perl=, =php=, =python=, =r=, 108=makefile=, =markdown=, =matlab=, =objective-c=, =ocaml=, =perl=, =php=, =python=, =r=,
109=ruby=, =rust=, =scala=, =sql=, =tcl=, =xml=, =yaml=. 109=ruby=, =rust=, =scala=, =sql=, =tcl=, =xml=, =yaml=.
110 110
111org-ssg adds two syntect does not ship: *TOML* and *Org*. Both are what this project's 111orgo adds two syntect does not ship: *TOML* and *Org*. Both are what this project's
112own documentation needed on its first page — every config example is TOML, and a tool for 112own documentation needed on its first page — every config example is TOML, and a tool for
113org users gets written about in org — so they are compiled into the binary and work with 113org users gets written about in org — so they are compiled into the binary and work with
114no setup. 114no setup.
@@ -121,7 +121,7 @@ to parse is reported and skipped rather than failing the build.
121*** The comma escape 121*** The comma escape
122 122
123A line inside a block that would otherwise look like document structure is written with a 123A line inside a block that would otherwise look like document structure is written with a
124leading comma — =,* heading=, =,#+KEYWORD:= — and org-ssg removes exactly one comma on 124leading comma — =,* heading=, =,#+KEYWORD:= — and orgo removes exactly one comma on
125output, as Emacs does. Every org example in this documentation relies on it. 125output, as Emacs does. Every org example in this documentation relies on it.
126 126
127The escape is not optional politeness: an unescaped =*= at column zero *ends the block*, 127The escape is not optional politeness: an unescaped =*= at column zero *ends the block*,
@@ -169,7 +169,7 @@ broken.
169| =#+CAPTION:=, =#+ATTR_HTML:= | Attach to the image *directly* below them — a blank line in between attaches to nothing, as in org. A captioned image is numbered =Figure N:=. | 169| =#+CAPTION:=, =#+ATTR_HTML:= | Attach to the image *directly* below them — a blank line in between attaches to nothing, as in org. A captioned image is numbered =Figure N:=. |
170 170
171Every other =#+KEYWORD:= is available to templates as 171Every other =#+KEYWORD:= is available to templates as
172={{ page.keywords.that_keyword }}=, so metadata org-ssg has never heard of still reaches 172={{ page.keywords.that_keyword }}=, so metadata orgo has never heard of still reaches
173your layout. 173your layout.
174 174
175* Not supported, and what happens instead 175* Not supported, and what happens instead
@@ -219,7 +219,7 @@ source-block text are asserted to match exactly.
219 219
220The rest differs deliberately: 220The rest differs deliberately:
221 221
222| | org-ssg | Emacs | 222| | orgo | Emacs |
223|-+---------+-------| 223|-+---------+-------|
224| emphasis | =<em>= / =<strong>= | =<i>= / =<b>= | 224| emphasis | =<em>= / =<strong>= | =<i>= / =<b>= |
225| captioned image | =<figure>= / =<figcaption>= | =<p>= + "Figure 1: …" | 225| captioned image | =<figure>= / =<figcaption>= | =<p>= + "Figure 1: …" |
@@ -229,6 +229,6 @@ The rest differs deliberately:
229| code | =<pre><code>= | =<pre>= | 229| code | =<pre><code>= | =<pre>= |
230 230
231One genuine semantic difference: org treats a single blank line between a =1.= list and a 231One genuine semantic difference: org treats a single blank line between a =1.= list and a
232following =-= list as *one* list, keeping the first item's bullet type. org-ssg starts a 232following =-= list as *one* list, keeping the first item's bullet type. orgo starts a
233second list. That was kept on measurement — the pattern occurred zero times across a 233second list. That was kept on measurement — the pattern occurred zero times across a
234179-file reference corpus — rather than on taste. 234179-file reference corpus — rather than on taste.
docs/guide/06-authoring.org +4 −4
@@ -39,7 +39,7 @@ That is deliberate: it is what that link would be on the published site, and bet
39now than by a reader. 39now than by a reader.
40 40
41#+BEGIN_SRC sh 41#+BEGIN_SRC sh
42org-ssg serve content -o _site --drafts 42orgo serve content -o _site --drafts
43#+END_SRC 43#+END_SRC
44 44
45The keyword is read forgivingly. =t=, =yes=, =1= and a bare =#+DRAFT:= all mean draft, 45The keyword is read forgivingly. =t=, =yes=, =1= and a bare =#+DRAFT:= all mean draft,
@@ -125,7 +125,7 @@ Every =#+KEYWORD:= reaches templates under its lowercased name:
125{% if page.keywords.subtitle %}<p class="subtitle">{{ page.keywords.subtitle }}</p>{% endif %} 125{% if page.keywords.subtitle %}<p class="subtitle">{{ page.keywords.subtitle }}</p>{% endif %}
126#+END_SRC 126#+END_SRC
127 127
128Nothing needs to be registered, and org-ssg needs no release to support a keyword you 128Nothing needs to be registered, and orgo needs no release to support a keyword you
129invented. 129invented.
130 130
131* Assets 131* Assets
@@ -138,9 +138,9 @@ Four things are *never* published:
138 138
139- Dot-entries such as =.git= and =.env=. A source directory is often a repository, and 139- Dot-entries such as =.git= and =.env=. A source directory is often a repository, and
140 publishing its history next to the homepage is a real way to leak a project. 140 publishing its history next to the homepage is a real way to leak a project.
141- =org-ssg.toml=, which is a build input. 141- =orgo.toml=, which is a build input.
142- The templates directory, likewise. 142- The templates directory, likewise.
143- The output directory, when it lives inside the source — so =org-ssg build . -o _site= 143- The output directory, when it lives inside the source — so =orgo build . -o _site=
144 does the obvious thing rather than copying its own output back into itself. 144 does the obvious thing rather than copying its own output back into itself.
145 145
146Note that excluding dot-entries also means =.well-known/= cannot be published. 146Note that excluding dot-entries also means =.well-known/= cannot be published.
docs/guide/07-incremental.org +7 −7
@@ -2,7 +2,7 @@
2#+DESCRIPTION: How the cache decides what to re-render, and why that shape is the architecture. 2#+DESCRIPTION: How the cache decides what to re-render, and why that shape is the architecture.
3#+LEDE: Editing one post rebuilds four pages, whatever the size of the site. 3#+LEDE: Editing one post rebuilds four pages, whatever the size of the site.
4 4
5Incremental rebuilding is not an optimisation bolted on to org-ssg; it is the constraint 5Incremental rebuilding is not an optimisation bolted on to orgo; it is the constraint
6the data model was built around. Parsing is a pure function of one file's bytes, link 6the data model was built around. Parsing is a pure function of one file's bytes, link
7resolution reports the edges it used, and rendering is a pure function of a resolved 7resolution reports the edges it used, and rendering is a pure function of a resolved
8document. Those properties are what make caching sound — and they are also what make the 8document. Those properties are what make caching sound — and they are also what make the
@@ -14,13 +14,13 @@ invalidates what explains the behaviour you will see.
14* What you observe 14* What you observe
15 15
16#+BEGIN_EXAMPLE 16#+BEGIN_EXAMPLE
17$ org-ssg build content -o _site 17$ orgo build content -o _site
18built 182 page(s) (182 rendered, 0 cached) ... 18built 182 page(s) (182 rendered, 0 cached) ...
19 19
20$ org-ssg build content -o _site 20$ orgo build content -o _site
21built 182 page(s) (0 rendered, 182 cached) ... 21built 182 page(s) (0 rendered, 182 cached) ...
22 22
23$ vim content/blog/post.org && org-ssg build content -o _site 23$ vim content/blog/post.org && orgo build content -o _site
24built 182 page(s) (4 rendered, 178 cached) ... 24built 182 page(s) (4 rendered, 178 cached) ...
25#+END_EXAMPLE 25#+END_EXAMPLE
26 26
@@ -35,7 +35,7 @@ Every page has a key composed from four hashes:
35|-----------+--------------| 35|-----------+--------------|
36| content | The source file's bytes change. | 36| content | The source file's bytes change. |
37| resolved links | A link's target moves, is renamed, or disappears. | 37| resolved links | A link's target moves, is renamed, or disappears. |
38| config | =org-ssg.toml= changes, or the shared chrome does. | 38| config | =orgo.toml= changes, or the shared chrome does. |
39| templates | *This page's* layout changes, or something that layout extends or includes. | 39| templates | *This page's* layout changes, or something that layout extends or includes. |
40 40
41If a page's key matches the cached one and its output file still exists, the file on disk 41If a page's key matches the cached one and its output file still exists, the file on disk
@@ -54,7 +54,7 @@ A template whose include is computed at render time — ={% include chooser %}=
54followed, so it is treated as depending on every template. Over-invalidating costs time; 54followed, so it is treated as depending on every template. Over-invalidating costs time;
55under-invalidating publishes a stale page. 55under-invalidating publishes a stale page.
56 56
57The cache lives in =<output>/.org-ssg-cache.json= and is tagged with a format version. A 57The cache lives in =<output>/.orgo-cache.json= and is tagged with a format version. A
58version mismatch, a missing file or a corrupt file all fall back to a full rebuild — the 58version mismatch, a missing file or a corrupt file all fall back to a full rebuild — the
59cache is an optimisation, never a correctness dependency. There is a test for each of 59cache is an optimisation, never a correctness dependency. There is a test for each of
60those three fallbacks. 60those three fallbacks.
@@ -124,5 +124,5 @@ correctly on their own. It exists to answer "is the cache lying to me?" — and
124is, that is a bug worth reporting, with the two builds' output to compare. 124is, that is a bug worth reporting, with the two builds' output to compare.
125 125
126#+BEGIN_SRC sh 126#+BEGIN_SRC sh
127org-ssg build content -o _site --no-cache 127orgo build content -o _site --no-cache
128#+END_SRC 128#+END_SRC
docs/guide/08-workflow.org +6 −6
@@ -5,7 +5,7 @@
5* serve 5* serve
6 6
7#+BEGIN_SRC sh 7#+BEGIN_SRC sh
8org-ssg serve content -o _site 8orgo serve content -o _site
9#+END_SRC 9#+END_SRC
10 10
11Builds, watches, serves at [[http://127.0.0.1:3000][127.0.0.1:3000]], and reloads the 11Builds, watches, serves at [[http://127.0.0.1:3000][127.0.0.1:3000]], and reloads the
@@ -43,7 +43,7 @@ the build just failed tells you nothing — the error is already on your termina
43* watch 43* watch
44 44
45#+BEGIN_SRC sh 45#+BEGIN_SRC sh
46org-ssg watch content -o _site 46orgo watch content -o _site
47#+END_SRC 47#+END_SRC
48 48
49The same rebuilding without the server, for when something else already serves the output. 49The same rebuilding without the server, for when something else already serves the output.
@@ -54,7 +54,7 @@ Rebuilds are driven by OS filesystem events, so nothing happens while nothing ha
54The rule for what triggers one is deliberately *not* the rule the build uses to find 54The rule for what triggers one is deliberately *not* the rule the build uses to find
55content — the question is "would this change the site?", not "is this a page?". 55content — the question is "would this change the site?", not "is this a page?".
56 56
57*Triggers a rebuild:* any =.org= file, any asset, =org-ssg.toml=, and anything in the 57*Triggers a rebuild:* any =.org= file, any asset, =orgo.toml=, and anything in the
58templates directory. The last two are skipped by the build when looking for content, but 58templates directory. The last two are skipped by the build when looking for content, but
59both change the output. 59both change the output.
60 60
@@ -86,7 +86,7 @@ blog/post.org changed: 2 rendered, 180 cached
86 86
87* Where native watching is unavailable 87* Where native watching is unavailable
88 88
89Some network and container filesystems have no event API. org-ssg falls back to polling 89Some network and container filesystems have no event API. orgo falls back to polling
90every two seconds and says so, rather than failing: 90every two seconds and says so, rather than failing:
91 91
92#+BEGIN_EXAMPLE 92#+BEGIN_EXAMPLE
@@ -106,10 +106,10 @@ note: native file watching unavailable (...); polling every 2s
106 106
107#+BEGIN_SRC sh 107#+BEGIN_SRC sh
108# One terminal, left running. 108# One terminal, left running.
109org-ssg serve content -o _site --drafts 109orgo serve content -o _site --drafts
110 110
111# Write. The browser keeps up. 111# Write. The browser keeps up.
112 112
113# Before publishing, check what a real build says. 113# Before publishing, check what a real build says.
114org-ssg build content -o _site --strict 114orgo build content -o _site --strict
115#+END_SRC 115#+END_SRC
docs/guide/09-auditing.org +6 −6
@@ -3,7 +3,7 @@
3#+LEDE: Construct frequencies, unknown-name census, and no document text in the output. 3#+LEDE: Construct frequencies, unknown-name census, and no document text in the output.
4 4
5#+BEGIN_SRC sh 5#+BEGIN_SRC sh
6org-ssg audit ~/notes 6orgo audit ~/notes
7#+END_SRC 7#+END_SRC
8 8
9The audit answers two questions about a body of org files: 9The audit answers two questions about a body of org files:
@@ -11,7 +11,7 @@ The audit answers two questions about a body of org files:
111. *Coverage.* Of the constructs this corpus uses, which are supported? A construct that 111. *Coverage.* Of the constructs this corpus uses, which are supported? A construct that
12 is common here and unsupported is a problem with the tool's scope, not with your 12 is common here and unsupported is a problem with the tool's scope, not with your
13 writing. 13 writing.
142. *Blind spots.* Which names appear that org-ssg has no opinion about at all? These are 142. *Blind spots.* Which names appear that orgo has no opinion about at all? These are
15 the dangerous ones — not "known unsupported" but unknown. 15 the dangerous ones — not "known unsupported" but unknown.
16 16
17* Reading the output 17* Reading the output
@@ -40,7 +40,7 @@ KEYWORDS
40- =IN= is supported; =OUT= is excluded by design and degrades as described in 40- =IN= is supported; =OUT= is excluded by design and degrades as described in
41 [[file:05-org-support.org][Org support]]. 41 [[file:05-org-support.org][Org support]].
42- The *coverage* line is the number to look at first. 42- The *coverage* line is the number to look at first.
43- =???= marks a name org-ssg does not recognise at all — in this example =#+SLUG:=, from 43- =???= marks a name orgo does not recognise at all — in this example =#+SLUG:=, from
44 a run made before it was supported. 44 a run made before it was supported.
45 45
46Four censuses follow the construct table: every distinct =#+KEYWORD:=, block type, 46Four censuses follow the construct table: every distinct =#+KEYWORD:=, block type,
@@ -80,13 +80,13 @@ green run; it simply measures one thing less.
80 80
81#+BEGIN_SRC sh 81#+BEGIN_SRC sh
82# What is in there? 82# What is in there?
83org-ssg audit ~/notes 83orgo audit ~/notes
84 84
85# Build it and see what the builder itself complains about. 85# Build it and see what the builder itself complains about.
86org-ssg build ~/notes -o /tmp/preview --strict 86orgo build ~/notes -o /tmp/preview --strict
87 87
88# Look at the result. 88# Look at the result.
89org-ssg serve ~/notes -o /tmp/preview 89orgo serve ~/notes -o /tmp/preview
90#+END_SRC 90#+END_SRC
91 91
92=--strict= surfaces broken internal links and malformed constructs as failures rather 92=--strict= surfaces broken internal links and malformed constructs as failures rather
docs/guide/10-deploying.org +10 −10
@@ -5,7 +5,7 @@
5* The production build 5* The production build
6 6
7#+BEGIN_SRC sh 7#+BEGIN_SRC sh
8org-ssg build content -o _site --strict 8orgo build content -o _site --strict
9#+END_SRC 9#+END_SRC
10 10
11Two differences from the build you run while writing: 11Two differences from the build you run while writing:
@@ -34,12 +34,12 @@ No trailing slash.
34 34
35* One thing to exclude 35* One thing to exclude
36 36
37The build writes =.org-ssg-cache.json= into the output directory. It is a dot-file, so 37The build writes =.orgo-cache.json= into the output directory. It is a dot-file, so
38most static hosts ignore it, but it is not part of the site — exclude it if your host 38most static hosts ignore it, but it is not part of the site — exclude it if your host
39uploads everything: 39uploads everything:
40 40
41#+BEGIN_SRC sh 41#+BEGIN_SRC sh
42rsync -a --delete --exclude '.org-ssg-cache.json' _site/ user@host:/var/www/site/ 42rsync -a --delete --exclude '.orgo-cache.json' _site/ user@host:/var/www/site/
43#+END_SRC 43#+END_SRC
44 44
45Keeping the cache *between* deploys, where the CI runner can see it, is what makes CI 45Keeping the cache *between* deploys, where the CI runner can see it, is what makes CI
@@ -57,7 +57,7 @@ jobs:
57 - uses: actions/checkout@v4 57 - uses: actions/checkout@v4
58 - uses: dtolnay/rust-toolchain@stable 58 - uses: dtolnay/rust-toolchain@stable
59 - run: cargo install --path . 59 - run: cargo install --path .
60 - run: org-ssg build content -o _site --strict 60 - run: orgo build content -o _site --strict
61 - uses: actions/upload-artifact@v4 61 - uses: actions/upload-artifact@v4
62 with: 62 with:
63 name: site 63 name: site
@@ -69,7 +69,7 @@ failed build.
69 69
70** Caching between runs 70** Caching between runs
71 71
72Cache =_site/.org-ssg-cache.json= *and* =_site= together, or not at all. The manifest 72Cache =_site/.orgo-cache.json= *and* =_site= together, or not at all. The manifest
73describes files it expects to find; a cache without its outputs simply triggers a full 73describes files it expects to find; a cache without its outputs simply triggers a full
74rebuild, which is correct but pointless. 74rebuild, which is correct but pointless.
75 75
@@ -78,16 +78,16 @@ builds is rarely worth the configuration.
78 78
79* Static hosts 79* Static hosts
80 80
81Nothing here is org-ssg-specific; a built site is ordinary static files. 81Nothing here is orgo-specific; a built site is ordinary static files.
82 82
83- *Netlify, Vercel, Cloudflare Pages*: publish directory =_site=, build command 83- *Netlify, Vercel, Cloudflare Pages*: publish directory =_site=, build command
84 =cargo install --path . && org-ssg build content -o _site --strict=. 84 =cargo install --path . && orgo build content -o _site --strict=.
85- *GitHub Pages*: upload =_site= as the Pages artifact. 85- *GitHub Pages*: upload =_site= as the Pages artifact.
86- *Any web server*: copy =_site= to the document root. 86- *Any web server*: copy =_site= to the document root.
87 87
88** URLs end in .html 88** URLs end in .html
89 89
90org-ssg writes =blog/post.html= and links to it that way, so the site works with no 90orgo writes =blog/post.html= and links to it that way, so the site works with no
91server configuration at all — including opening it from a filesystem path. 91server configuration at all — including opening it from a filesystem path.
92 92
93If you prefer extensionless URLs, that is a server-side rewrite, and you should also set 93If you prefer extensionless URLs, that is a server-side rewrite, and you should also set
@@ -96,8 +96,8 @@ If you prefer extensionless URLs, that is a server-side rewrite, and you should
96* Checking a build before shipping 96* Checking a build before shipping
97 97
98#+BEGIN_SRC sh 98#+BEGIN_SRC sh
99org-ssg build content -o _site --strict 99orgo build content -o _site --strict
100org-ssg serve content -o _site 100orgo serve content -o _site
101#+END_SRC 101#+END_SRC
102 102
103Serving the production build locally is the last check worth doing: it catches a missing 103Serving the production build locally is the last check worth doing: it catches a missing
docs/guide/11-versioning.org +6 −6
@@ -11,7 +11,7 @@ Changing any of this incompatibly requires a major version.
11 11
12| Stable | What that covers | 12| Stable | What that covers |
13|--------+------------------| 13|--------+------------------|
14| =org-ssg.toml= keys | Their names, types and meaning. | 14| =orgo.toml= keys | Their names, types and meaning. |
15| Template context | =page=, =site=, =nav=, =root=, =pages=, =group=, =groups=, =paginator=, =stylesheet=, and the =absolute=, =rfc822= and =truncate= filters. | 15| Template context | =page=, =site=, =nav=, =root=, =pages=, =group=, =groups=, =paginator=, =stylesheet=, and the =absolute=, =rfc822= and =truncate= filters. |
16| The CLI | Command names, flags and exit codes. | 16| The CLI | Command names, flags and exit codes. |
17| URLs | How a source path becomes an output path, =#+SLUG:= included. | 17| URLs | How a source path becomes an output path, =#+SLUG:= included. |
@@ -28,13 +28,13 @@ Three things move freely, so the list above can hold still.
28 28
29** The incremental cache 29** The incremental cache
30 30
31=<output>/.org-ssg-cache.json= is versioned and discards itself on a mismatch. A cache 31=<output>/.orgo-cache.json= is versioned and discards itself on a mismatch. A cache
32format bump means one full rebuild, and nothing else. It is never a correctness 32format bump means one full rebuild, and nothing else. It is never a correctness
33dependency: a missing, stale or corrupt cache produces exactly the same site, more slowly. 33dependency: a missing, stale or corrupt cache produces exactly the same site, more slowly.
34 34
35** Rendered HTML details 35** Rendered HTML details
36 36
37org-ssg aims at what Emacs exports from the same file, and closing a gap changes markup. 37orgo aims at what Emacs exports from the same file, and closing a gap changes markup.
38That is the product working rather than a regression — but it is called out in the 38That is the product working rather than a regression — but it is called out in the
39changelog every time, because your stylesheet is downstream of it. 39changelog every time, because your stylesheet is downstream of it.
40 40
@@ -50,7 +50,7 @@ exists to serve the binary, and its types move as the tool does.
50* The compiler floor 50* The compiler floor
51 51
52The MSRV is *1.88*, checked in CI on every change rather than assumed — which is how it 52The MSRV is *1.88*, checked in CI on every change rather than assumed — which is how it
53came to be 1.88 rather than the 1.82 org-ssg's own code needs. The floor is set by 53came to be 1.88 rather than the 1.82 orgo's own code needs. The floor is set by
54dependencies, and a dependency raising its own is invisible until someone on an older 54dependencies, and a dependency raising its own is invisible until someone on an older
55compiler tries to build. 55compiler tries to build.
56 56
@@ -59,8 +59,8 @@ Raising it is a minor version, never a patch.
59* Upgrading 59* Upgrading
60 60
61#+BEGIN_SRC sh 61#+BEGIN_SRC sh
62cargo install org-ssg # or download a release binary 62cargo install orgo # or download a release binary
63org-ssg build content -o _site --no-cache --strict 63orgo build content -o _site --no-cache --strict
64#+END_SRC 64#+END_SRC
65 65
66=--no-cache= makes the first build after an upgrade a full one, so you are comparing the 66=--no-cache= makes the first build after an upgrade a full one, so you are comparing the
docs/index.org +6 −6
@@ -1,9 +1,9 @@
1#+TITLE: org-ssg 1#+TITLE: orgo
2#+DESCRIPTION: An org-mode static site generator in Rust, where the org element tree is the document model. 2#+DESCRIPTION: An org-mode static site generator in Rust, where the org element tree is the document model.
3#+LEDE: Org is the source language, not an inconvenient input to be normalised into markdown. 3#+LEDE: Org is the source language, not an inconvenient input to be normalised into markdown.
4#+OPTIONS: toc:nil 4#+OPTIONS: toc:nil
5 5
6org-ssg turns a directory of =.org= files into a static website. It treats org as the 6orgo turns a directory of =.org= files into a static website. It treats org as the
7*source language*: the org element tree — headings, drawers, blocks, links with their 7*source language*: the org element tree — headings, drawers, blocks, links with their
8org-specific semantics — /is/ the document model, and that tree is rendered straight to 8org-specific semantics — /is/ the document model, and that tree is rendered straight to
9HTML. There is no markdown-shaped intermediate representation, because the point is to 9HTML. There is no markdown-shaped intermediate representation, because the point is to
@@ -26,10 +26,10 @@ Open [[http://127.0.0.1:3000][127.0.0.1:3000]], edit any =.org= file, and the br
26 26
27Point it at a directory of org files and you get a complete site: pages, navigation, 27Point it at a directory of org files and you get a complete site: pages, navigation,
28syntax-highlighted code, and the stylesheet that colours it. Nothing about your files has 28syntax-highlighted code, and the stylesheet that colours it. Nothing about your files has
29to change, and no =org-ssg.toml= is required. 29to change, and no =orgo.toml= is required.
30 30
31#+BEGIN_SRC sh 31#+BEGIN_SRC sh
32org-ssg build ~/notes -o _site 32orgo build ~/notes -o _site
33#+END_SRC 33#+END_SRC
34 34
35Configuration changes what you get. It is never what makes it work. 35Configuration changes what you get. It is never what makes it work.
@@ -54,7 +54,7 @@ listed in [[file:guide/05-org-support.org][Org support]].
54** It tells you what your corpus actually uses 54** It tells you what your corpus actually uses
55 55
56#+BEGIN_SRC sh 56#+BEGIN_SRC sh
57org-ssg audit ~/notes 57orgo audit ~/notes
58#+END_SRC 58#+END_SRC
59 59
60The audit reports which org constructs appear in a corpus, how often, and whether each is 60The audit reports which org constructs appear in a corpus, how often, and whether each is
@@ -75,6 +75,6 @@ notes stays safe to paste into an issue.
75 75
76* Status 76* Status
77 77
78This documentation site is itself an org-ssg site — the sources are in =docs/= and it is 78This documentation site is itself an orgo site — the sources are in =docs/= and it is
79built with the command in [[file:quickstart.org][Quick start]]. If a feature is described here, it is being used 79built with the command in [[file:quickstart.org][Quick start]]. If a feature is described here, it is being used
80to render the page describing it. 80to render the page describing it.
docs/install.org +13 −13
@@ -1,5 +1,5 @@
1#+TITLE: Install 1#+TITLE: Install
2#+DESCRIPTION: Build org-ssg from source, put it on your PATH, and check that it works. 2#+DESCRIPTION: Build orgo from source, put it on your PATH, and check that it works.
3#+LEDE: One Rust toolchain, one command, no runtime dependencies. 3#+LEDE: One Rust toolchain, one command, no runtime dependencies.
4 4
5* Requirements 5* Requirements
@@ -13,15 +13,15 @@
13* From source 13* From source
14 14
15#+BEGIN_SRC sh 15#+BEGIN_SRC sh
16git clone <repository-url> org-ssg 16git clone <repository-url> orgo
17cd org-ssg 17cd orgo
18cargo build --release 18cargo build --release
19#+END_SRC 19#+END_SRC
20 20
21The binary lands at =target/release/org-ssg=. Copy it somewhere on your =PATH=: 21The binary lands at =target/release/orgo=. Copy it somewhere on your =PATH=:
22 22
23#+BEGIN_SRC sh 23#+BEGIN_SRC sh
24cp target/release/org-ssg ~/.local/bin/ 24cp target/release/orgo ~/.local/bin/
25#+END_SRC 25#+END_SRC
26 26
27Or let cargo do it, which puts it in =~/.cargo/bin=: 27Or let cargo do it, which puts it in =~/.cargo/bin=:
@@ -33,7 +33,7 @@ cargo install --path .
33* Running without installing 33* Running without installing
34 34
35Every command in this documentation works through cargo if you would rather not install 35Every command in this documentation works through cargo if you would rather not install
36anything. Replace =org-ssg= with =cargo run --= and add =--release= for a fast build: 36anything. Replace =orgo= with =cargo run --= and add =--release= for a fast build:
37 37
38#+BEGIN_SRC sh 38#+BEGIN_SRC sh
39cargo run --release -- build my-site -o _site 39cargo run --release -- build my-site -o _site
@@ -45,9 +45,9 @@ syntax highlighting dominates and is not optimised in a debug profile.
45* Check that it works 45* Check that it works
46 46
47#+BEGIN_SRC sh 47#+BEGIN_SRC sh
48org-ssg --version 48orgo --version
49org-ssg init /tmp/org-ssg-check 49orgo init /tmp/orgo-check
50org-ssg build /tmp/org-ssg-check -o /tmp/org-ssg-check/_site 50orgo build /tmp/orgo-check -o /tmp/orgo-check/_site
51#+END_SRC 51#+END_SRC
52 52
53You should see a line reporting the pages built: 53You should see a line reporting the pages built:
@@ -56,10 +56,10 @@ You should see a line reporting the pages built:
56built 5 page(s) (5 rendered, 0 cached), copied 0 asset(s) ... (0 unresolved link(s), 0 diagnostic(s)) 56built 5 page(s) (5 rendered, 0 cached), copied 0 asset(s) ... (0 unresolved link(s), 0 diagnostic(s))
57#+END_EXAMPLE 57#+END_EXAMPLE
58 58
59Open =/tmp/org-ssg-check/_site/index.html= in a browser, or serve it properly: 59Open =/tmp/orgo-check/_site/index.html= in a browser, or serve it properly:
60 60
61#+BEGIN_SRC sh 61#+BEGIN_SRC sh
62org-ssg serve /tmp/org-ssg-check -o /tmp/org-ssg-check/_site 62orgo serve /tmp/orgo-check -o /tmp/orgo-check/_site
63#+END_SRC 63#+END_SRC
64 64
65* Running the test suite 65* Running the test suite
@@ -82,13 +82,13 @@ still gets a green test run — it simply measures one thing less.
82 82
83* Upgrading 83* Upgrading
84 84
85org-ssg stores an incremental cache in =<output>/.org-ssg-cache.json=, tagged with a 85orgo stores an incremental cache in =<output>/.orgo-cache.json=, tagged with a
86format version. A newer binary that changes how output is produced bumps that version, 86format version. A newer binary that changes how output is produced bumps that version,
87and a version it does not recognise is discarded in favour of a full rebuild. You never 87and a version it does not recognise is discarded in favour of a full rebuild. You never
88need to clear the cache by hand after an upgrade — but if you want to: 88need to clear the cache by hand after an upgrade — but if you want to:
89 89
90#+BEGIN_SRC sh 90#+BEGIN_SRC sh
91org-ssg clean _site 91orgo clean _site
92#+END_SRC 92#+END_SRC
93 93
94* Next 94* Next
docs/org-ssg.toml → docs/orgo.toml renamed +3 −3
@@ -1,10 +1,10 @@
1# Configuration for the org-ssg documentation site. 1# Configuration for the orgo documentation site.
2# 2#
3# This site is built by org-ssg itself, so this file doubles as a worked example: every 3# This site is built by orgo itself, so this file doubles as a worked example: every
4# setting here is one the docs describe, used the way the docs recommend. 4# setting here is one the docs describe, used the way the docs recommend.
5 5
6[site] 6[site]
7title = "org-ssg" 7title = "orgo"
8description = "An org-mode static site generator, in Rust." 8description = "An org-mode static site generator, in Rust."
9language = "en" 9language = "en"
10# Left empty so the docs build with relative URLs and open from the filesystem. Set it to 10# Left empty so the docs build with relative URLs and open from the filesystem. Set it to
docs/quickstart.org +19 −19
@@ -5,8 +5,8 @@
5* Two commands 5* Two commands
6 6
7#+BEGIN_SRC sh 7#+BEGIN_SRC sh
8org-ssg init my-site 8orgo init my-site
9org-ssg serve my-site -o _site 9orgo serve my-site -o _site
10#+END_SRC 10#+END_SRC
11 11
12Open [[http://127.0.0.1:3000][127.0.0.1:3000]]. Edit =my-site/index.org= in your editor, save, and the page reloads 12Open [[http://127.0.0.1:3000][127.0.0.1:3000]]. Edit =my-site/index.org= in your editor, save, and the page reloads
@@ -19,7 +19,7 @@ already has content is safe and additive.
19 19
20#+BEGIN_EXAMPLE 20#+BEGIN_EXAMPLE
21my-site/ 21my-site/
22 org-ssg.toml every setting, at its default, commented 22 orgo.toml every setting, at its default, commented
23 index.org the home page 23 index.org the home page
24 blog/first-post.org a post, to show the collection working 24 blog/first-post.org a post, to show the collection working
25 templates/ 25 templates/
@@ -35,7 +35,7 @@ You do not need =init=, a config file, or templates. Every command takes the sam
35paths: 35paths:
36 36
37#+BEGIN_SRC sh 37#+BEGIN_SRC sh
38org-ssg serve <SOURCE> -o <OUTPUT> 38orgo serve <SOURCE> -o <OUTPUT>
39#+END_SRC 39#+END_SRC
40 40
41** SOURCE is the URL root 41** SOURCE is the URL root
@@ -49,14 +49,14 @@ repository around it:
49 49
50#+BEGIN_SRC sh 50#+BEGIN_SRC sh
51cd ~/my-site 51cd ~/my-site
52org-ssg serve content -o _site # → /blog/post.html 52orgo serve content -o _site # → /blog/post.html
53#+END_SRC 53#+END_SRC
54 54
55Pointing one level too high still builds, which is what makes it worth saying out loud. 55Pointing one level too high still builds, which is what makes it worth saying out loud.
56It just builds the wrong site: 56It just builds the wrong site:
57 57
58#+BEGIN_SRC sh 58#+BEGIN_SRC sh
59org-ssg serve . -o _site # → /content/blog/post.html 59orgo serve . -o _site # → /content/blog/post.html
60#+END_SRC 60#+END_SRC
61 61
62Every URL gains a =/content/= prefix, and every non-org file in the repository — 62Every URL gains a =/content/= prefix, and every non-org file in the repository —
@@ -67,24 +67,24 @@ If you see either symptom, you picked the directory above the one you meant.
67 67
68| Your files | Command | 68| Your files | Command |
69|------------+---------| 69|------------+---------|
70| =~/notes/*.org= | =org-ssg serve ~/notes -o /tmp/notes-site= | 70| =~/notes/*.org= | =orgo serve ~/notes -o /tmp/notes-site= |
71| =my-site/content/**/*.org= | =cd my-site && org-ssg serve content -o _site= | 71| =my-site/content/**/*.org= | =cd my-site && orgo serve content -o _site= |
72| =my-site/*.org= at the top level | =cd my-site && org-ssg serve . -o _site= | 72| =my-site/*.org= at the top level | =cd my-site && orgo serve . -o _site= |
73| Org files scattered in a code repo | Do not. Copy or symlink the ones you publish into one directory. | 73| Org files scattered in a code repo | Do not. Copy or symlink the ones you publish into one directory. |
74 74
75** OUTPUT can live inside the source 75** OUTPUT can live inside the source
76 76
77=org-ssg serve . -o _site= is fine: the output directory is recognised and skipped, so 77=orgo serve . -o _site= is fine: the output directory is recognised and skipped, so
78the build never copies its own output back into itself. Nothing dot-prefixed is published 78the build never copies its own output back into itself. Nothing dot-prefixed is published
79either, so =.git= stays out of a site built from a repository root. 79either, so =.git= stays out of a site built from a repository root.
80 80
81** Where config and templates go 81** Where config and templates go
82 82
83Both live in the *source* directory — =SOURCE/org-ssg.toml= and =SOURCE/templates/= — and 83Both live in the *source* directory — =SOURCE/orgo.toml= and =SOURCE/templates/= — and
84neither is published. If you would rather keep the config elsewhere, name it: 84neither is published. If you would rather keep the config elsewhere, name it:
85 85
86#+BEGIN_SRC sh 86#+BEGIN_SRC sh
87org-ssg serve content -o _site --config config/org-ssg.toml 87orgo serve content -o _site --config config/orgo.toml
88#+END_SRC 88#+END_SRC
89 89
90** A worked example 90** A worked example
@@ -95,13 +95,13 @@ A repository laid out as =content/= (org files), =theme/= (unrelated), =build.py
95cd ~/my-site 95cd ~/my-site
96 96
97# What is actually in there, before trusting anything with it. 97# What is actually in there, before trusting anything with it.
98org-ssg audit content 98orgo audit content
99 99
100# Build it somewhere disposable and look. 100# Build it somewhere disposable and look.
101org-ssg serve content -o /tmp/preview 101orgo serve content -o /tmp/preview
102 102
103# Happy with it? Build for real, failing on broken links. 103# Happy with it? Build for real, failing on broken links.
104org-ssg build content -o _site --strict 104orgo build content -o _site --strict
105#+END_SRC 105#+END_SRC
106 106
107The audit reports which org constructs appear, how often, and whether each is supported — 107The audit reports which org constructs appear, how often, and whether each is supported —
@@ -159,7 +159,7 @@ The page is not written, and does not appear in listings or navigation. Preview
159you work with =--drafts=: 159you work with =--drafts=:
160 160
161#+BEGIN_SRC sh 161#+BEGIN_SRC sh
162org-ssg serve my-site -o _site --drafts 162orgo serve my-site -o _site --drafts
163#+END_SRC 163#+END_SRC
164 164
165* Change the design 165* Change the design
@@ -193,7 +193,7 @@ still looking at it. The full list of variables is in [[file:guide/04-templates.
193 193
194* Add a blog index 194* Add a blog index
195 195
196Listing pages have no source file; they are declared in =org-ssg.toml=: 196Listing pages have no source file; they are declared in =orgo.toml=:
197 197
198#+BEGIN_SRC toml 198#+BEGIN_SRC toml
199[[collections]] 199[[collections]]
@@ -212,7 +212,7 @@ settings. See [[file:guide/03-collections.org][Collections]].
212* Build for real 212* Build for real
213 213
214#+BEGIN_SRC sh 214#+BEGIN_SRC sh
215org-ssg build my-site -o _site --strict 215orgo build my-site -o _site --strict
216#+END_SRC 216#+END_SRC
217 217
218=--strict= turns broken internal links and parse diagnostics into a non-zero exit, which 218=--strict= turns broken internal links and parse diagnostics into a non-zero exit, which
@@ -222,5 +222,5 @@ is what you want in CI. Deployment is just copying =_site= somewhere; see
222* Next 222* Next
223 223
224- [[file:guide/01-cli.org][Command reference]] — every command and flag. 224- [[file:guide/01-cli.org][Command reference]] — every command and flag.
225- [[file:guide/02-configuration.org][Configuration]] — every setting in =org-ssg.toml=. 225- [[file:guide/02-configuration.org][Configuration]] — every setting in =orgo.toml=.
226- [[file:guide/05-org-support.org][Org support]] — exactly which org syntax is handled. 226- [[file:guide/05-org-support.org][Org support]] — exactly which org syntax is handled.
docs/style.css +1 −1
@@ -74,7 +74,7 @@ code {
74} 74}
75 75
76/* Code blocks are dark in both colour schemes, matching the syntect theme named in 76/* Code blocks are dark in both colour schemes, matching the syntect theme named in
77 org-ssg.toml. One generated stylesheet cannot follow prefers-color-scheme, so the page 77 orgo.toml. One generated stylesheet cannot follow prefers-color-scheme, so the page
78 commits to the theme's palette instead of leaving highlighted code unreadable in one 78 commits to the theme's palette instead of leaving highlighted code unreadable in one
79 of the two. */ 79 of the two. */
80pre { 80pre {
docs/templates/base.html +1 −1
@@ -45,7 +45,7 @@
45{%- endif %} 45{%- endif %}
46{% block content %}{{ body | safe }}{% endblock %}</main> 46{% block content %}{{ body | safe }}{% endblock %}</main>
47<footer class="site"> 47<footer class="site">
48Built with org-ssg &mdash; these docs are an org-ssg site. 48Built with orgo &mdash; these docs are an orgo site.
49</footer> 49</footer>
50</body> 50</body>
51</html> 51</html>
fixtures/site/about.org +1 −1
@@ -1,3 +1,3 @@
1#+TITLE: About 1#+TITLE: About
2 2
3This site is built by org-ssg. Back to [[file:index.org][home]]. 3This site is built by orgo. Back to [[file:index.org][home]].
src/config.rs +10 −10
@@ -1,4 +1,4 @@
1//! User-facing build configuration (`org-ssg.toml`). 1//! User-facing build configuration (`orgo.toml`).
2//! 2//!
3//! Everything here was once a constant in the source: the page layout, the nav rule, the 3//! Everything here was once a constant in the source: the page layout, the nav rule, the
4//! highlighting theme. That made the generator produce exactly one kind of site — a 4//! highlighting theme. That made the generator produce exactly one kind of site — a
@@ -7,10 +7,10 @@
7//! Two properties matter beyond the settings themselves: 7//! Two properties matter beyond the settings themselves:
8//! 8//!
9//! 1. **Absent config is a valid config.** Every field has a default, so a directory of 9//! 1. **Absent config is a valid config.** Every field has a default, so a directory of
10//! `.org` files with no `org-ssg.toml` still builds. Configuration is how you change 10//! `.org` files with no `orgo.toml` still builds. Configuration is how you change
11//! the output, never how you make it work at all. 11//! the output, never how you make it work at all.
12//! 2. **Config is a hash input** (spec §4.1). [`Config`] serializes deterministically and 12//! 2. **Config is a hash input** (spec §4.1). [`Config`] serializes deterministically and
13//! its hash is folded into every page's render key, so editing `org-ssg.toml` re-renders 13//! its hash is folded into every page's render key, so editing `orgo.toml` re-renders
14//! exactly the pages it affects — which for most settings is all of them. 14//! exactly the pages it affects — which for most settings is all of them.
15 15
16use anyhow::{Context, Result}; 16use anyhow::{Context, Result};
@@ -18,7 +18,7 @@ use camino::{Utf8Path, Utf8PathBuf};
18use serde::{Deserialize, Serialize}; 18use serde::{Deserialize, Serialize};
19 19
20/// The config file's name, looked for in the source directory. 20/// The config file's name, looked for in the source directory.
21pub const CONFIG_FILE: &str = "org-ssg.toml"; 21pub const CONFIG_FILE: &str = "orgo.toml";
22 22
23/// Resolved build configuration. Serialized into the config hash, so field order and 23/// Resolved build configuration. Serialized into the config hash, so field order and
24/// defaults are part of the cache contract. 24/// defaults are part of the cache contract.
@@ -319,7 +319,7 @@ pub struct Site {
319impl Default for Site { 319impl Default for Site {
320 fn default() -> Self { 320 fn default() -> Self {
321 Site { 321 Site {
322 title: "org-ssg site".to_string(), 322 title: "orgo site".to_string(),
323 base_url: String::new(), 323 base_url: String::new(),
324 description: String::new(), 324 description: String::new(),
325 language: "en".to_string(), 325 language: "en".to_string(),
@@ -405,7 +405,7 @@ impl Default for Highlight {
405} 405}
406 406
407impl Config { 407impl Config {
408 /// Load `org-ssg.toml` from `dir`, or return defaults if there is none. 408 /// Load `orgo.toml` from `dir`, or return defaults if there is none.
409 /// 409 ///
410 /// A *missing* config is normal and silent. A *malformed* one is an error: someone 410 /// A *missing* config is normal and silent. A *malformed* one is an error: someone
411 /// who wrote a config meant it, and silently building the default site would hide 411 /// who wrote a config meant it, and silently building the default site would hide
@@ -533,13 +533,13 @@ impl Config {
533 } 533 }
534} 534}
535 535
536/// The starter config written by `org-ssg init`, and the documentation of record for 536/// The starter config written by `orgo init`, and the documentation of record for
537/// what is configurable. Every value shown is the default, so deleting any line is safe. 537/// what is configurable. Every value shown is the default, so deleting any line is safe.
538pub const STARTER_CONFIG: &str = r#"# org-ssg configuration. Every setting here is optional and shown at its default, 538pub const STARTER_CONFIG: &str = r#"# orgo configuration. Every setting here is optional and shown at its default,
539# so you can delete any line you do not need — or the whole file. 539# so you can delete any line you do not need — or the whole file.
540 540
541[site] 541[site]
542title = "org-ssg site" 542title = "orgo site"
543# Absolute base URL, no trailing slash. Needed for feeds and canonical links, which 543# Absolute base URL, no trailing slash. Needed for feeds and canonical links, which
544# cannot be relative — set it and uncomment the [[collections]] feed block below. 544# cannot be relative — set it and uncomment the [[collections]] feed block below.
545base_url = "" 545base_url = ""
@@ -574,7 +574,7 @@ expose_page_list = false
574# A syntect theme name: InspiredGitHub, Solarized (dark), base16-ocean.dark, 574# A syntect theme name: InspiredGitHub, Solarized (dark), base16-ocean.dark,
575# base16-eighties.dark, base16-mocha.dark, base16-ocean.light. 575# base16-eighties.dark, base16-mocha.dark, base16-ocean.light.
576theme = "InspiredGitHub" 576theme = "InspiredGitHub"
577# Extra .sublime-syntax files for languages neither syntect nor org-ssg bundles. 577# Extra .sublime-syntax files for languages neither syntect nor orgo bundles.
578syntaxes_dir = "syntaxes" 578syntaxes_dir = "syntaxes"
579 579
580[build] 580[build]
src/incremental.rs +2 −2
@@ -35,7 +35,7 @@ pub const CACHE_FORMAT_VERSION: u32 = 7;
35pub type Hash = ContentHash; 35pub type Hash = ContentHash;
36 36
37/// The resolved global build config is [`crate::config::Config`]; its hash is a 37/// The resolved global build config is [`crate::config::Config`]; its hash is a
38/// component of every page's render key (spec §4.1), so editing `org-ssg.toml` 38/// component of every page's render key (spec §4.1), so editing `orgo.toml`
39/// invalidates the pages it affects. 39/// invalidates the pages it affects.
40pub use crate::config::Config as BuildConfig; 40pub use crate::config::Config as BuildConfig;
41 41
@@ -199,7 +199,7 @@ pub struct Manifest {
199/// The cache-manifest file lives inside the output directory (spec §4.5: an on-disk 199/// The cache-manifest file lives inside the output directory (spec §4.5: an on-disk
200/// cache dir). `clean` removes the output directory, taking the cache with it. 200/// cache dir). `clean` removes the output directory, taking the cache with it.
201pub fn manifest_path(out: &Utf8Path) -> Utf8PathBuf { 201pub fn manifest_path(out: &Utf8Path) -> Utf8PathBuf {
202 out.join(".org-ssg-cache.json") 202 out.join(".orgo-cache.json")
203} 203}
204 204
205/// Load the manifest, returning `None` on ANY of: missing file, read/parse error, or a 205/// Load the manifest, returning `None` on ANY of: missing file, read/parse error, or a
src/lib.rs +1 −1
@@ -1,4 +1,4 @@
1//! org-ssg — an org-mode static site generator. 1//! orgo — an org-mode static site generator.
2//! 2//!
3//! Org is the source language, not an input to be normalized into markdown. The org 3//! Org is the source language, not an input to be normalized into markdown. The org
4//! element tree ([`model`]) *is* the document model; we render it straight to HTML. 4//! element tree ([`model`]) *is* the document model; we render it straight to HTML.
src/main.rs +19 −19
@@ -6,15 +6,15 @@ use anyhow::{Context, Result};
6use camino::{Utf8Path, Utf8PathBuf}; 6use camino::{Utf8Path, Utf8PathBuf};
7use clap::{Parser, Subcommand}; 7use clap::{Parser, Subcommand};
8 8
9use org_ssg::parser::parse; 9use orgo::parser::parse;
10use org_ssg::config::{self, Config}; 10use orgo::config::{self, Config};
11use org_ssg::render::{self, render, Html, SyntectHighlighter}; 11use orgo::render::{self, render, Html, SyntectHighlighter};
12use org_ssg::resolve::ResolvedDoc; 12use orgo::resolve::ResolvedDoc;
13use org_ssg::site::{build_site, BuildOptions, SYNTAX_STYLESHEET}; 13use orgo::site::{build_site, BuildOptions, SYNTAX_STYLESHEET};
14use org_ssg::template::{PageContext, RenderContext, SiteContext, Templater}; 14use orgo::template::{PageContext, RenderContext, SiteContext, Templater};
15 15
16#[derive(Parser)] 16#[derive(Parser)]
17#[command(name = "org-ssg", version, about = "Org-mode static site generator")] 17#[command(name = "orgo", version, about = "Org-mode static site generator")]
18struct Cli { 18struct Cli {
19 #[command(subcommand)] 19 #[command(subcommand)]
20 command: Command, 20 command: Command,
@@ -36,7 +36,7 @@ enum Command {
36 /// Treat broken links and parse diagnostics as errors (spec §4.3.4). 36 /// Treat broken links and parse diagnostics as errors (spec §4.3.4).
37 #[arg(long)] 37 #[arg(long)]
38 strict: bool, 38 strict: bool,
39 /// Config file to use, overriding `org-ssg.toml` in the source directory. 39 /// Config file to use, overriding `orgo.toml` in the source directory.
40 #[arg(long, value_name = "FILE")] 40 #[arg(long, value_name = "FILE")]
41 config: Option<Utf8PathBuf>, 41 config: Option<Utf8PathBuf>,
42 /// Include pages marked `#+DRAFT:`. 42 /// Include pages marked `#+DRAFT:`.
@@ -57,7 +57,7 @@ enum Command {
57 /// Treat broken links and parse diagnostics as errors. 57 /// Treat broken links and parse diagnostics as errors.
58 #[arg(long)] 58 #[arg(long)]
59 strict: bool, 59 strict: bool,
60 /// Config file to use, overriding `org-ssg.toml` in the source directory. 60 /// Config file to use, overriding `orgo.toml` in the source directory.
61 #[arg(long, value_name = "FILE")] 61 #[arg(long, value_name = "FILE")]
62 config: Option<Utf8PathBuf>, 62 config: Option<Utf8PathBuf>,
63 /// Include pages marked `#+DRAFT:`. Handy while writing one. 63 /// Include pages marked `#+DRAFT:`. Handy while writing one.
@@ -81,7 +81,7 @@ enum Command {
81 /// Include pages marked `#+DRAFT:`. 81 /// Include pages marked `#+DRAFT:`.
82 #[arg(long)] 82 #[arg(long)]
83 drafts: bool, 83 drafts: bool,
84 /// Config file to use, overriding `org-ssg.toml` in the source directory. 84 /// Config file to use, overriding `orgo.toml` in the source directory.
85 #[arg(long, value_name = "FILE")] 85 #[arg(long, value_name = "FILE")]
86 config: Option<Utf8PathBuf>, 86 config: Option<Utf8PathBuf>,
87 }, 87 },
@@ -151,7 +151,7 @@ fn main() -> Result<()> {
151 strict, 151 strict,
152 config, 152 config,
153 drafts, 153 drafts,
154 } => org_ssg::watch::run( 154 } => orgo::watch::run(
155 &input, 155 &input,
156 &output, 156 &output,
157 &BuildOptions { 157 &BuildOptions {
@@ -162,8 +162,8 @@ fn main() -> Result<()> {
162 }, 162 },
163 ), 163 ),
164 Command::Audit { input } => { 164 Command::Audit { input } => {
165 let result = org_ssg::audit::audit(&input)?; 165 let result = orgo::audit::audit(&input)?;
166 print!("{}", org_ssg::audit::report(&result)); 166 print!("{}", orgo::audit::report(&result));
167 Ok(()) 167 Ok(())
168 } 168 }
169 Command::Serve { 169 Command::Serve {
@@ -173,7 +173,7 @@ fn main() -> Result<()> {
173 host, 173 host,
174 drafts, 174 drafts,
175 config, 175 config,
176 } => org_ssg::serve::run( 176 } => orgo::serve::run(
177 &input, 177 &input,
178 &output, 178 &output,
179 &BuildOptions { 179 &BuildOptions {
@@ -201,8 +201,8 @@ fn main() -> Result<()> {
201/// Scaffold a working site. Writes only files that do not already exist, so running it 201/// Scaffold a working site. Writes only files that do not already exist, so running it
202/// in a directory that has content is safe and additive rather than destructive. 202/// in a directory that has content is safe and additive rather than destructive.
203fn init(dir: &Utf8Path) -> Result<()> { 203fn init(dir: &Utf8Path) -> Result<()> {
204 use org_ssg::config::{CONFIG_FILE, STARTER_CONFIG}; 204 use orgo::config::{CONFIG_FILE, STARTER_CONFIG};
205 use org_ssg::template::{ 205 use orgo::template::{
206 starter_template, STARTER_FEED_TEMPLATE, STARTER_LIST_TEMPLATE, STARTER_TAGS_TEMPLATE, 206 starter_template, STARTER_FEED_TEMPLATE, STARTER_LIST_TEMPLATE, STARTER_TAGS_TEMPLATE,
207 }; 207 };
208 208
@@ -234,7 +234,7 @@ fn init(dir: &Utf8Path) -> Result<()> {
234 "#+FILETAGS: :example:\n", 234 "#+FILETAGS: :example:\n",
235 "\n", 235 "\n",
236 "Posts in this directory are collected into /blog/ by the [[collections]] block\n", 236 "Posts in this directory are collected into /blog/ by the [[collections]] block\n",
237 "in org-ssg.toml, newest first.\n", 237 "in orgo.toml, newest first.\n",
238 ); 238 );
239 239
240 let files: [(Utf8PathBuf, &str); 7] = [ 240 let files: [(Utf8PathBuf, &str); 7] = [
@@ -260,7 +260,7 @@ fn init(dir: &Utf8Path) -> Result<()> {
260 for path in &created { 260 for path in &created {
261 println!("created {path}"); 261 println!("created {path}");
262 } 262 }
263 println!("\nNext: org-ssg build {dir} -o _site"); 263 println!("\nNext: orgo build {dir} -o _site");
264 Ok(()) 264 Ok(())
265} 265}
266 266
@@ -321,7 +321,7 @@ fn build_file(input: &Utf8Path, output: &Utf8Path) -> Result<()> {
321 word_count: 0, 321 word_count: 0,
322 reading_time: 0, 322 reading_time: 0,
323 keywords: Default::default(), 323 keywords: Default::default(),
324 toc: org_ssg::util::table_of_contents(&resolved.document.root), 324 toc: orgo::util::table_of_contents(&resolved.document.root),
325 }; 325 };
326 let mut ctx = RenderContext::new(&site, &page_ctx, &[], SYNTAX_STYLESHEET, ""); 326 let mut ctx = RenderContext::new(&site, &page_ctx, &[], SYNTAX_STYLESHEET, "");
327 ctx.body = &fragment; 327 ctx.body = &fragment;
src/render.rs +2 −2
@@ -45,7 +45,7 @@ const CLASS_STYLE: ClassStyle = ClassStyle::Spaced;
45 45
46/// Syntax definitions syntect does not bundle, compiled into the binary. 46/// Syntax definitions syntect does not bundle, compiled into the binary.
47/// 47///
48/// Both are gaps this project hits on its own first page: every `org-ssg.toml` example is 48/// Both are gaps this project hits on its own first page: every `orgo.toml` example is
49/// TOML, and a tool for org users is going to be written about in org. Embedding them 49/// TOML, and a tool for org users is going to be written about in org. Embedding them
50/// rather than shipping files means they work with no setup, which is the same promise 50/// rather than shipping files means they work with no setup, which is the same promise
51/// the rest of the zero-config path makes. 51/// the rest of the zero-config path makes.
@@ -928,7 +928,7 @@ fn strip_special_column(table: &crate::model::Table) -> crate::model::Table {
928 928
929/// Split text into alternating prose and LaTeX spans, `(text, is_latex)`. 929/// Split text into alternating prose and LaTeX spans, `(text, is_latex)`.
930/// 930///
931/// org-ssg does not typeset LaTeX — it passes it through for MathJax or a reader's eyes — 931/// orgo does not typeset LaTeX — it passes it through for MathJax or a reader's eyes —
932/// but it must know where a fragment *is*, because the export-time text conversions would 932/// but it must know where a fragment *is*, because the export-time text conversions would
933/// otherwise rewrite the mathematics: `x^2` inside `$…$` is not a superscript to be 933/// otherwise rewrite the mathematics: `x^2` inside `$…$` is not a superscript to be
934/// marked up, and `--` inside one is a minus sign twice. 934/// marked up, and `--` inside one is a minus sign twice.
src/serve.rs +1 −1
@@ -30,7 +30,7 @@ use crate::site::{build_site, BuildOptions};
30 30
31/// Where the browser subscribes for reload events. Namespaced so it cannot collide with 31/// Where the browser subscribes for reload events. Namespaced so it cannot collide with
32/// a real page. 32/// a real page.
33pub const RELOAD_PATH: &str = "/__org-ssg/reload"; 33pub const RELOAD_PATH: &str = "/__orgo/reload";
34 34
35/// How long a poll waits before answering "nothing yet". Long enough that an idle tab is 35/// How long a poll waits before answering "nothing yet". Long enough that an idle tab is
36/// nearly silent, short enough to stay under any proxy or browser idle timeout. 36/// nearly silent, short enough to stay under any proxy or browser idle timeout.
src/site.rs +2 −2
@@ -62,7 +62,7 @@ pub struct BuildOptions {
62 pub no_cache: bool, 62 pub no_cache: bool,
63 /// Treat broken internal links as a build error rather than a warning (spec §4.3.4). 63 /// Treat broken internal links as a build error rather than a warning (spec §4.3.4).
64 pub strict: bool, 64 pub strict: bool,
65 /// Explicit config file, overriding `org-ssg.toml` in the source directory. 65 /// Explicit config file, overriding `orgo.toml` in the source directory.
66 pub config_path: Option<Utf8PathBuf>, 66 pub config_path: Option<Utf8PathBuf>,
67 /// Include pages marked `#+DRAFT:`, overriding `build.drafts` when set. 67 /// Include pages marked `#+DRAFT:`, overriding `build.drafts` when set.
68 pub drafts: bool, 68 pub drafts: bool,
@@ -1368,7 +1368,7 @@ fn collect_assets(
1368/// directory (build input, not content) and the output directory when it lives inside 1368/// directory (build input, not content) and the output directory when it lives inside
1369/// the source. 1369/// the source.
1370/// 1370///
1371/// The output case is not a corner case — `org-ssg build . -o _site` is the obvious 1371/// The output case is not a corner case — `orgo build . -o _site` is the obvious
1372/// thing to type, and without this the build copies its own output back into itself, 1372/// thing to type, and without this the build copies its own output back into itself,
1373/// growing `_site/_site/_site/…` on every run. 1373/// growing `_site/_site/_site/…` on every run.
1374fn excluded_dirs(src: &Utf8Path, config: &Config, out: Option<&Utf8Path>) -> Vec<Utf8PathBuf> { 1374fn excluded_dirs(src: &Utf8Path, config: &Config, out: Option<&Utf8Path>) -> Vec<Utf8PathBuf> {
src/template.rs +5 −5
@@ -375,7 +375,7 @@ impl<'a> RenderContext<'a> {
375 } 375 }
376} 376}
377 377
378/// The starter tag-index template written by `org-ssg init`: shows how `groups` is 378/// The starter tag-index template written by `orgo init`: shows how `groups` is
379/// iterated, and how a group page is linked. 379/// iterated, and how a group page is linked.
380pub const STARTER_TAGS_TEMPLATE: &str = r#"<!DOCTYPE html> 380pub const STARTER_TAGS_TEMPLATE: &str = r#"<!DOCTYPE html>
381<html lang="{{ site.language }}"> 381<html lang="{{ site.language }}">
@@ -410,7 +410,7 @@ pub const STARTER_TAGS_TEMPLATE: &str = r#"<!DOCTYPE html>
410</html> 410</html>
411"#; 411"#;
412 412
413/// The starter listing template written by `org-ssg init`: a blog index, showing how a 413/// The starter listing template written by `orgo init`: a blog index, showing how a
414/// collection's `pages` are iterated. 414/// collection's `pages` are iterated.
415pub const STARTER_LIST_TEMPLATE: &str = r#"<!DOCTYPE html> 415pub const STARTER_LIST_TEMPLATE: &str = r#"<!DOCTYPE html>
416<html lang="{{ site.language }}"> 416<html lang="{{ site.language }}">
@@ -486,7 +486,7 @@ fn add_filters(env: &mut Environment<'static>, base_url: &str) {
486 return Err(minijinja::Error::new( 486 return Err(minijinja::Error::new(
487 minijinja::ErrorKind::InvalidOperation, 487 minijinja::ErrorKind::InvalidOperation,
488 "the `absolute` filter needs site.base_url, which is empty; \ 488 "the `absolute` filter needs site.base_url, which is empty; \
489 set it in org-ssg.toml (e.g. base_url = \"https://example.com\")", 489 set it in orgo.toml (e.g. base_url = \"https://example.com\")",
490 )); 490 ));
491 } 491 }
492 if path.starts_with("http://") || path.starts_with("https://") { 492 if path.starts_with("http://") || path.starts_with("https://") {
@@ -538,7 +538,7 @@ fn add_filters(env: &mut Environment<'static>, base_url: &str) {
538 }); 538 });
539} 539}
540 540
541/// The starter RSS feed written by `org-ssg init`. A listing page with an XML template: 541/// The starter RSS feed written by `orgo init`. A listing page with an XML template:
542/// no feed-specific machinery, just `absolute` and `rfc822` doing what syndication needs. 542/// no feed-specific machinery, just `absolute` and `rfc822` doing what syndication needs.
543/// 543///
544/// Emitted commented-out guidance rather than a broken feed when `site.base_url` is 544/// Emitted commented-out guidance rather than a broken feed when `site.base_url` is
@@ -667,7 +667,7 @@ fn render_error_detail(error: minijinja::Error) -> String {
667 out 667 out
668} 668}
669 669
670/// The starter layout written by `org-ssg init`: the built-in template, on disk, ready 670/// The starter layout written by `orgo init`: the built-in template, on disk, ready
671/// to edit. 671/// to edit.
672pub fn starter_template() -> &'static str { 672pub fn starter_template() -> &'static str {
673 BASE_TEMPLATE 673 BASE_TEMPLATE
src/watch.rs +1 −1
@@ -6,7 +6,7 @@
6//! 6//!
7//! Two things matter more than the watching itself: 7//! Two things matter more than the watching itself:
8//! 8//!
9//! 1. **Not watching our own output.** `org-ssg watch . -o _site` puts the output inside 9//! 1. **Not watching our own output.** `orgo watch . -o _site` puts the output inside
10//! the source. Rebuilding writes files, writing files raises events, and events 10//! the source. Rebuilding writes files, writing files raises events, and events
11//! trigger a rebuild — a loop that never stops and never idles. [`ChangeFilter`] is 11//! trigger a rebuild — a loop that never stops and never idles. [`ChangeFilter`] is
12//! what prevents it, and it is a pure function precisely so it can be tested without 12//! what prevents it, and it is a pure function precisely so it can be tested without
syntaxes/Org.sublime-syntax +1 −1
@@ -5,7 +5,7 @@
5# needs it. 5# needs it.
6# 6#
7# This highlights org as *source text you are reading about*, which is a different job 7# This highlights org as *source text you are reading about*, which is a different job
8# from parsing it: org-ssg's own parser (src/parser.rs) is what turns org into a 8# from parsing it: orgo's own parser (src/parser.rs) is what turns org into a
9# document. Where the two could disagree, this one stays conservative — a highlighter 9# document. Where the two could disagree, this one stays conservative — a highlighter
10# that colours something wrongly is a cosmetic bug, and one that swallows a line is not. 10# that colours something wrongly is a cosmetic bug, and one that swallows a line is not.
11name: Org 11name: Org
syntaxes/TOML.sublime-syntax +1 −1
@@ -1,7 +1,7 @@
1%YAML 1.2 1%YAML 1.2
2--- 2---
3# TOML, for syntect. Not one of the definitions syntect bundles, and the first thing a 3# TOML, for syntect. Not one of the definitions syntect bundles, and the first thing a
4# config-heavy site needs — every org-ssg.toml example in the documentation is one. 4# config-heavy site needs — every orgo.toml example in the documentation is one.
5# 5#
6# Scope names are the standard TextMate ones, so any syntect theme colours this without 6# Scope names are the standard TextMate ones, so any syntect theme colours this without
7# knowing it exists. 7# knowing it exists.
tests/config.rs +51 −51
@@ -2,7 +2,7 @@
2//! generator for one site or for anyone's. 2//! generator for one site or for anyone's.
3//! 3//!
4//! The theme running through these tests is that **the zero-config path has to work**. 4//! The theme running through these tests is that **the zero-config path has to work**.
5//! A directory of `.org` files with no `org-ssg.toml`, no templates and no knowledge of 5//! A directory of `.org` files with no `orgo.toml`, no templates and no knowledge of
6//! this tool must build into a real site; configuration is how you change the output, 6//! this tool must build into a real site; configuration is how you change the output,
7//! never how you make it work at all. 7//! never how you make it work at all.
8 8
@@ -10,15 +10,15 @@ use std::sync::atomic::{AtomicU32, Ordering};
10 10
11use camino::Utf8PathBuf; 11use camino::Utf8PathBuf;
12 12
13use org_ssg::config::{Config, NavMode}; 13use orgo::config::{Config, NavMode};
14use org_ssg::site::{build_site, BuildOptions}; 14use orgo::site::{build_site, BuildOptions};
15 15
16fn tmpdir(tag: &str) -> Utf8PathBuf { 16fn tmpdir(tag: &str) -> Utf8PathBuf {
17 static N: AtomicU32 = AtomicU32::new(0); 17 static N: AtomicU32 = AtomicU32::new(0);
18 let n = N.fetch_add(1, Ordering::Relaxed); 18 let n = N.fetch_add(1, Ordering::Relaxed);
19 let base = Utf8PathBuf::from_path_buf(std::env::temp_dir()) 19 let base = Utf8PathBuf::from_path_buf(std::env::temp_dir())
20 .expect("utf-8 temp dir") 20 .expect("utf-8 temp dir")
21 .join(format!("org-ssg-cfg-{}-{tag}-{n}", std::process::id())); 21 .join(format!("orgo-cfg-{}-{tag}-{n}", std::process::id()));
22 let _ = std::fs::remove_dir_all(&base); 22 let _ = std::fs::remove_dir_all(&base);
23 std::fs::create_dir_all(&base).unwrap(); 23 std::fs::create_dir_all(&base).unwrap();
24 base 24 base
@@ -36,7 +36,7 @@ fn write_site(src: &Utf8PathBuf) {
36 .unwrap(); 36 .unwrap();
37} 37}
38 38
39fn build(src: &Utf8PathBuf, out: &Utf8PathBuf) -> org_ssg::site::SiteReport { 39fn build(src: &Utf8PathBuf, out: &Utf8PathBuf) -> orgo::site::SiteReport {
40 build_site(src, out, &BuildOptions::default()).expect("build") 40 build_site(src, out, &BuildOptions::default()).expect("build")
41} 41}
42 42
@@ -81,9 +81,9 @@ fn a_malformed_config_is_an_error_but_a_missing_one_is_not() {
81 81
82 assert_eq!(Config::load(&src).unwrap(), Config::default()); 82 assert_eq!(Config::load(&src).unwrap(), Config::default());
83 83
84 std::fs::write(src.join("org-ssg.toml"), "[site\ntitle = broken").unwrap(); 84 std::fs::write(src.join("orgo.toml"), "[site\ntitle = broken").unwrap();
85 let err = Config::load(&src).expect_err("malformed config must fail"); 85 let err = Config::load(&src).expect_err("malformed config must fail");
86 assert!(format!("{err:#}").contains("org-ssg.toml"), "names the file: {err:#}"); 86 assert!(format!("{err:#}").contains("orgo.toml"), "names the file: {err:#}");
87} 87}
88 88
89/// A misspelled key is a silent no-op in most config formats, which is exactly how 89/// A misspelled key is a silent no-op in most config formats, which is exactly how
@@ -93,7 +93,7 @@ fn an_unknown_config_key_is_rejected() {
93 let root = tmpdir("unknownkey"); 93 let root = tmpdir("unknownkey");
94 let src = root.join("src"); 94 let src = root.join("src");
95 std::fs::create_dir_all(&src).unwrap(); 95 std::fs::create_dir_all(&src).unwrap();
96 std::fs::write(src.join("org-ssg.toml"), "[site]\ntittle = \"typo\"\n").unwrap(); 96 std::fs::write(src.join("orgo.toml"), "[site]\ntittle = \"typo\"\n").unwrap();
97 97
98 let err = Config::load(&src).expect_err("unknown key must fail"); 98 let err = Config::load(&src).expect_err("unknown key must fail");
99 assert!( 99 assert!(
@@ -126,7 +126,7 @@ fn nav_modes_select_different_pages() {
126 std::fs::create_dir_all(&src).unwrap(); 126 std::fs::create_dir_all(&src).unwrap();
127 write_site(&src); 127 write_site(&src);
128 std::fs::write( 128 std::fs::write(
129 src.join("org-ssg.toml"), 129 src.join("orgo.toml"),
130 format!("[nav]\nmode = \"{mode}\"\n"), 130 format!("[nav]\nmode = \"{mode}\"\n"),
131 ) 131 )
132 .unwrap(); 132 .unwrap();
@@ -155,7 +155,7 @@ fn explicit_nav_uses_the_configured_order() {
155 std::fs::create_dir_all(&src).unwrap(); 155 std::fs::create_dir_all(&src).unwrap();
156 write_site(&src); 156 write_site(&src);
157 std::fs::write( 157 std::fs::write(
158 src.join("org-ssg.toml"), 158 src.join("orgo.toml"),
159 "[nav]\nmode = \"explicit\"\npages = [\"blog/post.org\", \"index.org\"]\n", 159 "[nav]\nmode = \"explicit\"\npages = [\"blog/post.org\", \"index.org\"]\n",
160 ) 160 )
161 .unwrap(); 161 .unwrap();
@@ -178,7 +178,7 @@ fn explicit_nav_rejects_a_page_that_does_not_exist() {
178 std::fs::create_dir_all(&src).unwrap(); 178 std::fs::create_dir_all(&src).unwrap();
179 write_site(&src); 179 write_site(&src);
180 std::fs::write( 180 std::fs::write(
181 src.join("org-ssg.toml"), 181 src.join("orgo.toml"),
182 "[nav]\nmode = \"explicit\"\npages = [\"nope.org\"]\n", 182 "[nav]\nmode = \"explicit\"\npages = [\"nope.org\"]\n",
183 ) 183 )
184 .unwrap(); 184 .unwrap();
@@ -304,7 +304,7 @@ fn the_page_list_is_opt_in_and_widens_invalidation() {
304 std::fs::create_dir_all(src.join("templates")).unwrap(); 304 std::fs::create_dir_all(src.join("templates")).unwrap();
305 write_site(&src); 305 write_site(&src);
306 std::fs::write( 306 std::fs::write(
307 src.join("org-ssg.toml"), 307 src.join("orgo.toml"),
308 "[templates]\nexpose_page_list = true\n", 308 "[templates]\nexpose_page_list = true\n",
309 ) 309 )
310 .unwrap(); 310 .unwrap();
@@ -352,7 +352,7 @@ fn heading_offset_shifts_content_headings_below_the_page_title() {
352 "a level-1 org heading renders as <h2> by default" 352 "a level-1 org heading renders as <h2> by default"
353 ); 353 );
354 354
355 std::fs::write(src.join("org-ssg.toml"), "[html]\nheading_offset = 0\n").unwrap(); 355 std::fs::write(src.join("orgo.toml"), "[html]\nheading_offset = 0\n").unwrap();
356 let out2 = root.join("out2"); 356 let out2 = root.join("out2");
357 build(&src, &out2); 357 build(&src, &out2);
358 assert!( 358 assert!(
@@ -369,7 +369,7 @@ fn an_unknown_highlight_theme_is_rejected_with_the_available_ones() {
369 let src = root.join("src"); 369 let src = root.join("src");
370 std::fs::create_dir_all(&src).unwrap(); 370 std::fs::create_dir_all(&src).unwrap();
371 write_site(&src); 371 write_site(&src);
372 std::fs::write(src.join("org-ssg.toml"), "[highlight]\ntheme = \"nope\"\n").unwrap(); 372 std::fs::write(src.join("orgo.toml"), "[highlight]\ntheme = \"nope\"\n").unwrap();
373 373
374 let err = build_site(&src, &root.join("out"), &BuildOptions::default()) 374 let err = build_site(&src, &root.join("out"), &BuildOptions::default())
375 .expect_err("unknown theme must fail"); 375 .expect_err("unknown theme must fail");
@@ -385,7 +385,7 @@ fn an_unknown_highlight_theme_is_rejected_with_the_available_ones() {
385// Discovery 385// Discovery
386// --------------------------------------------------------------------------- 386// ---------------------------------------------------------------------------
387 387
388/// `org-ssg build . -o _site` is the obvious thing to type. Without excluding the output 388/// `orgo build . -o _site` is the obvious thing to type. Without excluding the output
389/// directory, the build copies its own output back into itself, growing `_site/_site/…` 389/// directory, the build copies its own output back into itself, growing `_site/_site/…`
390/// on every run. 390/// on every run.
391#[test] 391#[test]
@@ -421,7 +421,7 @@ fn dot_directories_and_build_inputs_are_never_published() {
421 write_site(&src); 421 write_site(&src);
422 std::fs::write(src.join(".git/config"), "[remote]\nurl = private\n").unwrap(); 422 std::fs::write(src.join(".git/config"), "[remote]\nurl = private\n").unwrap();
423 std::fs::write(src.join(".env"), "SECRET=hunter2\n").unwrap(); 423 std::fs::write(src.join(".env"), "SECRET=hunter2\n").unwrap();
424 std::fs::write(src.join("org-ssg.toml"), "[site]\ntitle = \"T\"\n").unwrap(); 424 std::fs::write(src.join("orgo.toml"), "[site]\ntitle = \"T\"\n").unwrap();
425 std::fs::write(src.join("templates/base.html"), "<html>{{ body | safe }}</html>").unwrap(); 425 std::fs::write(src.join("templates/base.html"), "<html>{{ body | safe }}</html>").unwrap();
426 std::fs::write(src.join("style.css"), "body{}\n").unwrap(); 426 std::fs::write(src.join("style.css"), "body{}\n").unwrap();
427 let out = root.join("out"); 427 let out = root.join("out");
@@ -430,7 +430,7 @@ fn dot_directories_and_build_inputs_are_never_published() {
430 assert!(!out.join(".git").exists(), ".git must never be published"); 430 assert!(!out.join(".git").exists(), ".git must never be published");
431 assert!(!out.join(".env").exists(), "dotfiles must never be published"); 431 assert!(!out.join(".env").exists(), "dotfiles must never be published");
432 assert!( 432 assert!(
433 !out.join("org-ssg.toml").exists(), 433 !out.join("orgo.toml").exists(),
434 "the config is a build input, not content" 434 "the config is a build input, not content"
435 ); 435 );
436 assert!( 436 assert!(
@@ -472,7 +472,7 @@ fn write_blog(src: &Utf8PathBuf, extra_config: &str) {
472 ) 472 )
473 .unwrap(); 473 .unwrap();
474 std::fs::write( 474 std::fs::write(
475 src.join("org-ssg.toml"), 475 src.join("orgo.toml"),
476 format!( 476 format!(
477 "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\n\ 477 "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\n\
478 template = \"list.html\"\ntitle = \"Blog\"\n{extra_config}" 478 template = \"list.html\"\ntitle = \"Blog\"\n{extra_config}"
@@ -758,12 +758,12 @@ fn a_feed_is_just_a_listing_page_with_an_xml_template() {
758 <pubDate>{{ p.date_iso }}</pubDate></item>{% endfor %}</channel></rss>", 758 <pubDate>{{ p.date_iso }}</pubDate></item>{% endfor %}</channel></rss>",
759 ) 759 )
760 .unwrap(); 760 .unwrap();
761 let mut config = std::fs::read_to_string(src.join("org-ssg.toml")).unwrap(); 761 let mut config = std::fs::read_to_string(src.join("orgo.toml")).unwrap();
762 config.push_str( 762 config.push_str(
763 "\n[[collections]]\nsource = \"blog\"\noutput = \"feed.xml\"\n\ 763 "\n[[collections]]\nsource = \"blog\"\noutput = \"feed.xml\"\n\
764 template = \"feed.xml\"\ntitle = \"Feed\"\n", 764 template = \"feed.xml\"\ntitle = \"Feed\"\n",
765 ); 765 );
766 std::fs::write(src.join("org-ssg.toml"), config).unwrap(); 766 std::fs::write(src.join("orgo.toml"), config).unwrap();
767 let out = root.join("out"); 767 let out = root.join("out");
768 build(&src, &out); 768 build(&src, &out);
769 769
@@ -833,16 +833,16 @@ fn colliding_collection_outputs_are_rejected() {
833 std::fs::create_dir_all(&src).unwrap(); 833 std::fs::create_dir_all(&src).unwrap();
834 write_blog(&src, ""); 834 write_blog(&src, "");
835 835
836 let mut config = std::fs::read_to_string(src.join("org-ssg.toml")).unwrap(); 836 let mut config = std::fs::read_to_string(src.join("orgo.toml")).unwrap();
837 config.push_str("\n[[collections]]\nsource = \"\"\noutput = \"blog/index.html\"\n"); 837 config.push_str("\n[[collections]]\nsource = \"\"\noutput = \"blog/index.html\"\n");
838 std::fs::write(src.join("org-ssg.toml"), &config).unwrap(); 838 std::fs::write(src.join("orgo.toml"), &config).unwrap();
839 let err = build_site(&src, &root.join("out"), &BuildOptions::default()) 839 let err = build_site(&src, &root.join("out"), &BuildOptions::default())
840 .expect_err("two collections writing one file must fail"); 840 .expect_err("two collections writing one file must fail");
841 assert!(format!("{err:#}").contains("blog/index.html"), "{err:#}"); 841 assert!(format!("{err:#}").contains("blog/index.html"), "{err:#}");
842 842
843 // And a listing that would overwrite a real page. 843 // And a listing that would overwrite a real page.
844 std::fs::write( 844 std::fs::write(
845 src.join("org-ssg.toml"), 845 src.join("orgo.toml"),
846 "[[collections]]\nsource = \"blog\"\noutput = \"index.html\"\ntemplate = \"list.html\"\n", 846 "[[collections]]\nsource = \"blog\"\noutput = \"index.html\"\ntemplate = \"list.html\"\n",
847 ) 847 )
848 .unwrap(); 848 .unwrap();
@@ -859,7 +859,7 @@ fn a_missing_collection_template_names_the_ones_that_exist() {
859 std::fs::create_dir_all(&src).unwrap(); 859 std::fs::create_dir_all(&src).unwrap();
860 write_blog(&src, ""); 860 write_blog(&src, "");
861 std::fs::write( 861 std::fs::write(
862 src.join("org-ssg.toml"), 862 src.join("orgo.toml"),
863 "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\ntemplate = \"nope.html\"\n", 863 "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\ntemplate = \"nope.html\"\n",
864 ) 864 )
865 .unwrap(); 865 .unwrap();
@@ -912,7 +912,7 @@ fn write_tagged_blog(src: &Utf8PathBuf, extra: &str) {
912 ) 912 )
913 .unwrap(); 913 .unwrap();
914 std::fs::write( 914 std::fs::write(
915 src.join("org-ssg.toml"), 915 src.join("orgo.toml"),
916 format!( 916 format!(
917 "[[collections]]\nsource = \"blog\"\ngroup_by = \"tags\"\n\ 917 "[[collections]]\nsource = \"blog\"\ngroup_by = \"tags\"\n\
918 output = \"tags/{{tag}}.html\"\ntemplate = \"tag.html\"\ntitle = \"Tagged: {{tag}}\"\n\ 918 output = \"tags/{{tag}}.html\"\ntemplate = \"tag.html\"\ntitle = \"Tagged: {{tag}}\"\n\
@@ -1044,7 +1044,7 @@ fn a_collection_can_group_by_any_keyword() {
1044 ) 1044 )
1045 .unwrap(); 1045 .unwrap();
1046 std::fs::write( 1046 std::fs::write(
1047 src.join("org-ssg.toml"), 1047 src.join("orgo.toml"),
1048 "[[collections]]\nsource = \"blog\"\ngroup_by = \"category\"\n\ 1048 "[[collections]]\nsource = \"blog\"\ngroup_by = \"category\"\n\
1049 output = \"cat/{tag}.html\"\ntemplate = \"tag.html\"\ntitle = \"{tag}\"\n", 1049 output = \"cat/{tag}.html\"\ntemplate = \"tag.html\"\ntitle = \"{tag}\"\n",
1050 ) 1050 )
@@ -1081,7 +1081,7 @@ fn grouping_without_a_placeholder_is_rejected() {
1081 std::fs::create_dir_all(&src).unwrap(); 1081 std::fs::create_dir_all(&src).unwrap();
1082 write_tagged_blog(&src, ""); 1082 write_tagged_blog(&src, "");
1083 std::fs::write( 1083 std::fs::write(
1084 src.join("org-ssg.toml"), 1084 src.join("orgo.toml"),
1085 "[[collections]]\nsource = \"blog\"\ngroup_by = \"tags\"\n\ 1085 "[[collections]]\nsource = \"blog\"\ngroup_by = \"tags\"\n\
1086 output = \"tags/all.html\"\ntemplate = \"tag.html\"\n", 1086 output = \"tags/all.html\"\ntemplate = \"tag.html\"\n",
1087 ) 1087 )
@@ -1149,7 +1149,7 @@ fn write_paginated_blog(src: &Utf8PathBuf, count: usize, extra: &str) {
1149 ) 1149 )
1150 .unwrap(); 1150 .unwrap();
1151 std::fs::write( 1151 std::fs::write(
1152 src.join("org-ssg.toml"), 1152 src.join("orgo.toml"),
1153 format!( 1153 format!(
1154 "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\n\ 1154 "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\n\
1155 template = \"list.html\"\ntitle = \"Blog\"\n{extra}" 1155 template = \"list.html\"\ntitle = \"Blog\"\n{extra}"
@@ -1275,7 +1275,7 @@ fn groups_paginate_independently() {
1275 .unwrap(); 1275 .unwrap();
1276 } 1276 }
1277 std::fs::write( 1277 std::fs::write(
1278 src.join("org-ssg.toml"), 1278 src.join("orgo.toml"),
1279 "[[collections]]\nsource = \"blog\"\ngroup_by = \"tags\"\n\ 1279 "[[collections]]\nsource = \"blog\"\ngroup_by = \"tags\"\n\
1280 output = \"tags/{tag}.html\"\ntemplate = \"list.html\"\ntitle = \"{tag}\"\n\ 1280 output = \"tags/{tag}.html\"\ntemplate = \"list.html\"\ntitle = \"{tag}\"\n\
1281 paginate = 2\npaginate_output = \"tags/{tag}/page/{n}.html\"\n", 1281 paginate = 2\npaginate_output = \"tags/{tag}/page/{n}.html\"\n",
@@ -1317,7 +1317,7 @@ fn pagination_placeholders_are_validated() {
1317 1317
1318 // Grouped without {tag} in the page pattern. 1318 // Grouped without {tag} in the page pattern.
1319 std::fs::write( 1319 std::fs::write(
1320 src.join("org-ssg.toml"), 1320 src.join("orgo.toml"),
1321 "[[collections]]\nsource = \"blog\"\ngroup_by = \"tags\"\n\ 1321 "[[collections]]\nsource = \"blog\"\ngroup_by = \"tags\"\n\
1322 output = \"tags/{tag}.html\"\ntemplate = \"list.html\"\n\ 1322 output = \"tags/{tag}.html\"\ntemplate = \"list.html\"\n\
1323 paginate = 2\npaginate_output = \"tags/page/{n}.html\"\n", 1323 paginate = 2\npaginate_output = \"tags/page/{n}.html\"\n",
@@ -1380,7 +1380,7 @@ fn write_feed_site(src: &Utf8PathBuf, base_url: &str) {
1380 ) 1380 )
1381 .unwrap(); 1381 .unwrap();
1382 std::fs::write( 1382 std::fs::write(
1383 src.join("org-ssg.toml"), 1383 src.join("orgo.toml"),
1384 format!( 1384 format!(
1385 "[site]\nbase_url = \"{base_url}\"\n\n\ 1385 "[site]\nbase_url = \"{base_url}\"\n\n\
1386 [[collections]]\nsource = \"blog\"\noutput = \"feed.xml\"\n\ 1386 [[collections]]\nsource = \"blog\"\noutput = \"feed.xml\"\n\
@@ -1442,7 +1442,7 @@ fn absolute_without_a_base_url_is_an_error_that_says_what_to_set() {
1442 .expect_err("absolute with no base_url must fail"); 1442 .expect_err("absolute with no base_url must fail");
1443 let message = format!("{err:#}"); 1443 let message = format!("{err:#}");
1444 assert!(message.contains("base_url"), "names the setting: {message}"); 1444 assert!(message.contains("base_url"), "names the setting: {message}");
1445 assert!(message.contains("org-ssg.toml"), "names where to set it: {message}"); 1445 assert!(message.contains("orgo.toml"), "names where to set it: {message}");
1446 assert!(message.contains("feed.xml"), "names the template: {message}"); 1446 assert!(message.contains("feed.xml"), "names the template: {message}");
1447} 1447}
1448 1448
@@ -1484,7 +1484,7 @@ fn the_default_layout_emits_a_canonical_link_only_with_a_base_url() {
1484 std::fs::create_dir_all(&src).unwrap(); 1484 std::fs::create_dir_all(&src).unwrap();
1485 write_site(&src); 1485 write_site(&src);
1486 std::fs::write( 1486 std::fs::write(
1487 src.join("org-ssg.toml"), 1487 src.join("orgo.toml"),
1488 format!("[site]\nbase_url = \"{base}\"\n"), 1488 format!("[site]\nbase_url = \"{base}\"\n"),
1489 ) 1489 )
1490 .unwrap(); 1490 .unwrap();
@@ -1509,7 +1509,7 @@ fn changing_base_url_re_renders_the_site() {
1509 std::fs::create_dir_all(&src).unwrap(); 1509 std::fs::create_dir_all(&src).unwrap();
1510 write_site(&src); 1510 write_site(&src);
1511 std::fs::write( 1511 std::fs::write(
1512 src.join("org-ssg.toml"), 1512 src.join("orgo.toml"),
1513 "[site]\nbase_url = \"https://example.com\"\n", 1513 "[site]\nbase_url = \"https://example.com\"\n",
1514 ) 1514 )
1515 .unwrap(); 1515 .unwrap();
@@ -1518,7 +1518,7 @@ fn changing_base_url_re_renders_the_site() {
1518 assert!(build(&src, &out).rendered.is_empty(), "unchanged rebuild renders nothing"); 1518 assert!(build(&src, &out).rendered.is_empty(), "unchanged rebuild renders nothing");
1519 1519
1520 std::fs::write( 1520 std::fs::write(
1521 src.join("org-ssg.toml"), 1521 src.join("orgo.toml"),
1522 "[site]\nbase_url = \"https://moved.example\"\n", 1522 "[site]\nbase_url = \"https://moved.example\"\n",
1523 ) 1523 )
1524 .unwrap(); 1524 .unwrap();
@@ -1556,7 +1556,7 @@ fn write_excerpt_site(src: &Utf8PathBuf, extra_config: &str) {
1556 ) 1556 )
1557 .unwrap(); 1557 .unwrap();
1558 std::fs::write( 1558 std::fs::write(
1559 src.join("org-ssg.toml"), 1559 src.join("orgo.toml"),
1560 format!( 1560 format!(
1561 "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\n\ 1561 "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\n\
1562 template = \"list.html\"\ntitle = \"Blog\"\n{extra_config}" 1562 template = \"list.html\"\ntitle = \"Blog\"\n{extra_config}"
@@ -1731,9 +1731,9 @@ fn a_link_to_a_draft_is_reported_as_broken() {
1731/// "no" has to mean no. 1731/// "no" has to mean no.
1732#[test] 1732#[test]
1733fn draft_truthiness_is_forgiving_but_respects_an_explicit_negative() { 1733fn draft_truthiness_is_forgiving_but_respects_an_explicit_negative() {
1734 use org_ssg::model::Keywords; 1734 use orgo::model::Keywords;
1735 let draft = |value: &str| { 1735 let draft = |value: &str| {
1736 org_ssg::util::is_draft(&Keywords { 1736 orgo::util::is_draft(&Keywords {
1737 entries: vec![("DRAFT".to_string(), value.to_string())], 1737 entries: vec![("DRAFT".to_string(), value.to_string())],
1738 }) 1738 })
1739 }; 1739 };
@@ -1744,7 +1744,7 @@ fn draft_truthiness_is_forgiving_but_respects_an_explicit_negative() {
1744 assert!(!draft(no), "{no:?} should mean published"); 1744 assert!(!draft(no), "{no:?} should mean published");
1745 } 1745 }
1746 assert!( 1746 assert!(
1747 !org_ssg::util::is_draft(&Keywords::default()), 1747 !orgo::util::is_draft(&Keywords::default()),
1748 "no keyword at all means published" 1748 "no keyword at all means published"
1749 ); 1749 );
1750} 1750}
@@ -1773,7 +1773,7 @@ fn write_toc_site(src: &Utf8PathBuf, options: &str, config: &str) {
1773 <nav>{{ walk(page.toc) }}</nav>{{ body | safe }}</body></html>", 1773 <nav>{{ walk(page.toc) }}</nav>{{ body | safe }}</body></html>",
1774 ) 1774 )
1775 .unwrap(); 1775 .unwrap();
1776 std::fs::write(src.join("org-ssg.toml"), config).unwrap(); 1776 std::fs::write(src.join("orgo.toml"), config).unwrap();
1777} 1777}
1778 1778
1779/// A table of contents is a tree, and reconstructing one from a flat list of levels 1779/// A table of contents is a tree, and reconstructing one from a flat list of levels
@@ -1889,7 +1889,7 @@ fn section_numbering_resets_at_each_level() {
1889 * One\n** A\n** B\n* Two\n** C\n*** Deep\n* Three\n", 1889 * One\n** A\n** B\n* Two\n** C\n*** Deep\n* Three\n",
1890 ) 1890 )
1891 .unwrap(); 1891 .unwrap();
1892 std::fs::write(src.join("org-ssg.toml"), "").unwrap(); 1892 std::fs::write(src.join("orgo.toml"), "").unwrap();
1893 let out = root.join("out"); 1893 let out = root.join("out");
1894 build(&src, &out); 1894 build(&src, &out);
1895 1895
@@ -1914,8 +1914,8 @@ fn section_numbering_resets_at_each_level() {
1914/// `#+OPTIONS:` is a space-separated list of switches, and org spells "off" several ways. 1914/// `#+OPTIONS:` is a space-separated list of switches, and org spells "off" several ways.
1915#[test] 1915#[test]
1916fn export_options_parse_as_org_writes_them() { 1916fn export_options_parse_as_org_writes_them() {
1917 use org_ssg::model::Keywords; 1917 use orgo::model::Keywords;
1918 use org_ssg::util::option_enabled; 1918 use orgo::util::option_enabled;
1919 let keywords = |v: &str| Keywords { 1919 let keywords = |v: &str| Keywords {
1920 entries: vec![("OPTIONS".to_string(), v.to_string())], 1920 entries: vec![("OPTIONS".to_string(), v.to_string())],
1921 }; 1921 };
@@ -1955,7 +1955,7 @@ fn write_two_layouts(src: &Utf8PathBuf, config: &str) {
1955 <p>Reply by email</p></body></html>", 1955 <p>Reply by email</p></body></html>",
1956 ) 1956 )
1957 .unwrap(); 1957 .unwrap();
1958 std::fs::write(src.join("org-ssg.toml"), config).unwrap(); 1958 std::fs::write(src.join("orgo.toml"), config).unwrap();
1959} 1959}
1960 1960
1961/// A section's layout is a property of the section: one rule covers every page under it, 1961/// A section's layout is a property of the section: one rule covers every page under it,
@@ -2124,7 +2124,7 @@ fn adding_a_pages_rule_rerenders_the_pages_it_covers() {
2124 build(&src, &out); 2124 build(&src, &out);
2125 2125
2126 std::fs::write( 2126 std::fs::write(
2127 src.join("org-ssg.toml"), 2127 src.join("orgo.toml"),
2128 "[[pages]]\nmatch = \"blog\"\ntemplate = \"post.html\"\n", 2128 "[[pages]]\nmatch = \"blog\"\ntemplate = \"post.html\"\n",
2129 ) 2129 )
2130 .unwrap(); 2130 .unwrap();
@@ -2142,7 +2142,7 @@ fn adding_a_pages_rule_rerenders_the_pages_it_covers() {
2142#[test] 2142#[test]
2143fn a_pages_rule_without_a_template_is_rejected() { 2143fn a_pages_rule_without_a_template_is_rejected() {
2144 let mut config = Config::default(); 2144 let mut config = Config::default();
2145 config.pages.push(org_ssg::config::PageRule { 2145 config.pages.push(orgo::config::PageRule {
2146 pattern: Utf8PathBuf::from("blog"), 2146 pattern: Utf8PathBuf::from("blog"),
2147 template: String::new(), 2147 template: String::new(),
2148 }); 2148 });
@@ -2211,7 +2211,7 @@ fn same_day_entries_sort_by_time_of_day() {
2211 ) 2211 )
2212 .unwrap(); 2212 .unwrap();
2213 std::fs::write( 2213 std::fs::write(
2214 src.join("org-ssg.toml"), 2214 src.join("orgo.toml"),
2215 "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\n\ 2215 "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\n\
2216 template = \"list.html\"\ntitle = \"Blog\"\nsort = \"date\"\norder = \"desc\"\n", 2216 template = \"list.html\"\ntitle = \"Blog\"\nsort = \"date\"\norder = \"desc\"\n",
2217 ) 2217 )
@@ -2249,7 +2249,7 @@ fn an_asset_root_publishes_to_the_site_root() {
2249 std::fs::write(root.join("theme/static/robots.txt"), "User-agent: *\n").unwrap(); 2249 std::fs::write(root.join("theme/static/robots.txt"), "User-agent: *\n").unwrap();
2250 std::fs::write(root.join("theme/static/img/logo.svg"), "<svg/>").unwrap(); 2250 std::fs::write(root.join("theme/static/img/logo.svg"), "<svg/>").unwrap();
2251 std::fs::write( 2251 std::fs::write(
2252 src.join("org-ssg.toml"), 2252 src.join("orgo.toml"),
2253 "[build]\nassets = [\"../theme/static\"]\n", 2253 "[build]\nassets = [\"../theme/static\"]\n",
2254 ) 2254 )
2255 .unwrap(); 2255 .unwrap();
@@ -2280,7 +2280,7 @@ fn two_assets_claiming_one_url_is_an_error() {
2280 std::fs::create_dir_all(root.join("static")).unwrap(); 2280 std::fs::create_dir_all(root.join("static")).unwrap();
2281 std::fs::write(root.join("static/style.css"), "body{color:red}").unwrap(); 2281 std::fs::write(root.join("static/style.css"), "body{color:red}").unwrap();
2282 std::fs::write( 2282 std::fs::write(
2283 src.join("org-ssg.toml"), 2283 src.join("orgo.toml"),
2284 "[build]\nassets = [\"../static\"]\n", 2284 "[build]\nassets = [\"../static\"]\n",
2285 ) 2285 )
2286 .unwrap(); 2286 .unwrap();
@@ -2301,7 +2301,7 @@ fn a_missing_asset_root_is_an_error() {
2301 std::fs::create_dir_all(&src).unwrap(); 2301 std::fs::create_dir_all(&src).unwrap();
2302 write_site(&src); 2302 write_site(&src);
2303 std::fs::write( 2303 std::fs::write(
2304 src.join("org-ssg.toml"), 2304 src.join("orgo.toml"),
2305 "[build]\nassets = [\"../nope\"]\n", 2305 "[build]\nassets = [\"../nope\"]\n",
2306 ) 2306 )
2307 .unwrap(); 2307 .unwrap();
@@ -2330,7 +2330,7 @@ fn a_collection_can_carry_its_entries_rendered_bodies() {
2330 ) 2330 )
2331 .unwrap(); 2331 .unwrap();
2332 std::fs::write( 2332 std::fs::write(
2333 src.join("org-ssg.toml"), 2333 src.join("orgo.toml"),
2334 "[[collections]]\nsource = \"blog\"\noutput = \"feed.xml\"\n\ 2334 "[[collections]]\nsource = \"blog\"\noutput = \"feed.xml\"\n\
2335 template = \"feed.xml\"\ntitle = \"Feed\"\ninclude_content = true\n", 2335 template = \"feed.xml\"\ntitle = \"Feed\"\ninclude_content = true\n",
2336 ) 2336 )
tests/constructs.rs +6 −6
@@ -13,10 +13,10 @@
13 13
14use camino::Utf8PathBuf; 14use camino::Utf8PathBuf;
15 15
16use org_ssg::model::Document; 16use orgo::model::Document;
17use org_ssg::parser::parse; 17use orgo::parser::parse;
18use org_ssg::render::{render, Html, SyntectHighlighter}; 18use orgo::render::{render, Html, SyntectHighlighter};
19use org_ssg::resolve::ResolvedDoc; 19use orgo::resolve::ResolvedDoc;
20 20
21fn parse_fixture(name: &str) -> Document { 21fn parse_fixture(name: &str) -> Document {
22 let path = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")) 22 let path = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR"))
@@ -160,7 +160,7 @@ fn highlighting_emits_classes_not_inline_styles() {
160 "highlighting must not emit inline styles:\n{html}" 160 "highlighting must not emit inline styles:\n{html}"
161 ); 161 );
162 assert!( 162 assert!(
163 org_ssg::render::syntax_css("InspiredGitHub") 163 orgo::render::syntax_css("InspiredGitHub")
164 .expect("a built-in theme") 164 .expect("a built-in theme")
165 .contains(".storage"), 165 .contains(".storage"),
166 "the generated stylesheet must define the emitted classes" 166 "the generated stylesheet must define the emitted classes"
@@ -559,7 +559,7 @@ fn special_strings_leave_code_alone() {
559 ); 559 );
560} 560}
561 561
562/// `#+OPTIONS: -:nil` is how a document opts out, and org-ssg honours org's own switch 562/// `#+OPTIONS: -:nil` is how a document opts out, and orgo honours org's own switch
563/// rather than inventing one. 563/// rather than inventing one.
564#[test] 564#[test]
565fn a_document_can_turn_special_strings_off() { 565fn a_document_can_turn_special_strings_off() {
tests/harness.rs +2 −2
@@ -5,8 +5,8 @@
5//! - an `insta` JSON snapshot of a hand-built element-tree value, standing in for the 5//! - an `insta` JSON snapshot of a hand-built element-tree value, standing in for the
6//! element-tree snapshots the parser will produce from Phase 1 onward. 6//! element-tree snapshots the parser will produce from Phase 1 onward.
7 7
8use org_ssg::model::{Link, LinkTarget, Object}; 8use orgo::model::{Link, LinkTarget, Object};
9use org_ssg::parser::content_hash; 9use orgo::parser::content_hash;
10 10
11#[test] 11#[test]
12fn content_hash_is_deterministic_blake3() { 12fn content_hash_is_deterministic_blake3() {
tests/incremental.rs +5 −5
@@ -16,8 +16,8 @@ use std::sync::atomic::{AtomicU32, Ordering};
16 16
17use camino::Utf8PathBuf; 17use camino::Utf8PathBuf;
18 18
19use org_ssg::incremental::{manifest_path, Manifest, CACHE_FORMAT_VERSION}; 19use orgo::incremental::{manifest_path, Manifest, CACHE_FORMAT_VERSION};
20use org_ssg::site::{build_site, BuildOptions}; 20use orgo::site::{build_site, BuildOptions};
21 21
22/// A fresh, empty temp directory unique to this process + call. 22/// A fresh, empty temp directory unique to this process + call.
23fn tmpdir(tag: &str) -> Utf8PathBuf { 23fn tmpdir(tag: &str) -> Utf8PathBuf {
@@ -25,7 +25,7 @@ fn tmpdir(tag: &str) -> Utf8PathBuf {
25 let n = N.fetch_add(1, Ordering::Relaxed); 25 let n = N.fetch_add(1, Ordering::Relaxed);
26 let base = Utf8PathBuf::from_path_buf(std::env::temp_dir()) 26 let base = Utf8PathBuf::from_path_buf(std::env::temp_dir())
27 .expect("utf-8 temp dir") 27 .expect("utf-8 temp dir")
28 .join(format!("org-ssg-it-{}-{tag}-{n}", std::process::id())); 28 .join(format!("orgo-it-{}-{tag}-{n}", std::process::id()));
29 if base.exists() { 29 if base.exists() {
30 std::fs::remove_dir_all(&base).unwrap(); 30 std::fs::remove_dir_all(&base).unwrap();
31 } 31 }
@@ -47,7 +47,7 @@ fn output_files(out: &Utf8PathBuf) -> BTreeMap<String, Vec<u8>> {
47 continue; 47 continue;
48 } 48 }
49 let path = Utf8PathBuf::from_path_buf(entry.path().to_owned()).unwrap(); 49 let path = Utf8PathBuf::from_path_buf(entry.path().to_owned()).unwrap();
50 if path.file_name() == Some(".org-ssg-cache.json") { 50 if path.file_name() == Some(".orgo-cache.json") {
51 continue; 51 continue;
52 } 52 }
53 let rel = path.strip_prefix(out).unwrap().to_string(); 53 let rel = path.strip_prefix(out).unwrap().to_string();
@@ -476,7 +476,7 @@ fn editing_one_template_rebuilds_only_the_pages_that_use_it() {
476 ) 476 )
477 .unwrap(); 477 .unwrap();
478 std::fs::write( 478 std::fs::write(
479 src.join("org-ssg.toml"), 479 src.join("orgo.toml"),
480 "[[pages]]\nmatch = \"blog\"\ntemplate = \"post.html\"\n", 480 "[[pages]]\nmatch = \"blog\"\ntemplate = \"post.html\"\n",
481 ) 481 )
482 .unwrap(); 482 .unwrap();
tests/oracle.el +3 −3
@@ -1,4 +1,4 @@
1;;; oracle.el --- ground-truth HTML export for the org-ssg differential tests -*- lexical-binding: t -*- 1;;; oracle.el --- ground-truth HTML export for the orgo differential tests -*- lexical-binding: t -*-
2 2
3;; Exports the org file named by $ORG_ORACLE_INPUT to HTML on stdout, using org's own 3;; Exports the org file named by $ORG_ORACLE_INPUT to HTML on stdout, using org's own
4;; exporter — the same one weblorg wraps to publish the corpus this project targets. 4;; exporter — the same one weblorg wraps to publish the corpus this project targets.
@@ -16,7 +16,7 @@
16;; learn what stock org does — normalizing that away would be marking our own homework. 16;; learn what stock org does — normalizing that away would be marking our own homework.
17(setq org-export-with-toc nil ; we emit no table of contents 17(setq org-export-with-toc nil ; we emit no table of contents
18 org-export-with-section-numbers nil ; we do not number headings 18 org-export-with-section-numbers nil ; we do not number headings
19 ;; org-html-toplevel-hlevel is left at its default of 2. org-ssg's own default 19 ;; org-html-toplevel-hlevel is left at its default of 2. orgo's own default
20 ;; heading_offset is 1, which produces the same <h2>, so both sides now agree 20 ;; heading_offset is 1, which produces the same <h2>, so both sides now agree
21 ;; without the oracle being told to. 21 ;; without the oracle being told to.
22 org-html-htmlize-output-type nil ; plain <pre>, not htmlize spans: we highlight 22 org-html-htmlize-output-type nil ; plain <pre>, not htmlize spans: we highlight
@@ -24,7 +24,7 @@
24 ; the meaningful part 24 ; the meaningful part
25 org-html-head-include-default-style nil 25 org-html-head-include-default-style nil
26 org-html-head-include-scripts nil 26 org-html-head-include-scripts nil
27 ;; Fixtures link to ids that live in org-ssg's own symbol table, not in an 27 ;; Fixtures link to ids that live in orgo's own symbol table, not in an
28 ;; `org-id' database. Without this, org aborts the whole export on the first one. 28 ;; `org-id' database. Without this, org aborts the whole export on the first one.
29 org-export-with-broken-links t 29 org-export-with-broken-links t
30 make-backup-files nil) 30 make-backup-files nil)
tests/oracle.rs +12 −12
@@ -1,6 +1,6 @@
1//! The `emacs --batch` ground-truth oracle (spec §5, Phase 0). 1//! The `emacs --batch` ground-truth oracle (spec §5, Phase 0).
2//! 2//!
3//! Every other test in this suite checks org-ssg against org-ssg: a snapshot says our 3//! Every other test in this suite checks orgo against orgo: a snapshot says our
4//! output has not *changed*, never that it is *right*. Those two questions are different, 4//! output has not *changed*, never that it is *right*. Those two questions are different,
5//! and only one of them matters to someone whose site is currently published by Emacs. 5//! and only one of them matters to someone whose site is currently published by Emacs.
6//! This file answers the second by exporting the same fixture with org's own HTML 6//! This file answers the second by exporting the same fixture with org's own HTML
@@ -26,9 +26,9 @@ use std::process::Command;
26 26
27use camino::Utf8PathBuf; 27use camino::Utf8PathBuf;
28 28
29use org_ssg::parser::parse; 29use orgo::parser::parse;
30use org_ssg::render::{render, Html, SyntectHighlighter}; 30use orgo::render::{render, Html, SyntectHighlighter};
31use org_ssg::resolve::ResolvedDoc; 31use orgo::resolve::ResolvedDoc;
32 32
33fn manifest_dir() -> Utf8PathBuf { 33fn manifest_dir() -> Utf8PathBuf {
34 Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")) 34 Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR"))
@@ -62,7 +62,7 @@ fn org_export(fixture: &str) -> String {
62 String::from_utf8(output.stdout).expect("emacs emits UTF-8") 62 String::from_utf8(output.stdout).expect("emacs emits UTF-8")
63} 63}
64 64
65/// Render a fixture with org-ssg. 65/// Render a fixture with orgo.
66fn our_export(fixture: &str) -> String { 66fn our_export(fixture: &str) -> String {
67 let path = manifest_dir().join("fixtures").join(fixture); 67 let path = manifest_dir().join("fixtures").join(fixture);
68 let source = std::fs::read_to_string(&path).expect("read fixture"); 68 let source = std::fs::read_to_string(&path).expect("read fixture");
@@ -252,12 +252,12 @@ fn decode_entities(s: &str) -> String {
252// Divergence report 252// Divergence report
253// --------------------------------------------------------------------------- 253// ---------------------------------------------------------------------------
254 254
255/// One divergence org-ssg makes on purpose, so the report can separate "we chose this" 255/// One divergence orgo makes on purpose, so the report can separate "we chose this"
256/// from "we got this wrong". 256/// from "we got this wrong".
257/// 257///
258/// Without this split the agreement percentage is noise: the timestamps fixture sat at 258/// Without this split the agreement percentage is noise: the timestamps fixture sat at
259/// 40% while being entirely correct, because org writes `<2024-01-15 Mon>` as text and 259/// 40% while being entirely correct, because org writes `<2024-01-15 Mon>` as text and
260/// org-ssg writes a `<time datetime>` element. A number that cannot fall when a real 260/// orgo writes a `<time datetime>` element. A number that cannot fall when a real
261/// defect appears is not measuring anything. 261/// defect appears is not measuring anything.
262struct Deliberate { 262struct Deliberate {
263 name: &'static str, 263 name: &'static str,
@@ -335,7 +335,7 @@ const DELIBERATE: &[Deliberate] = &[
335 }, 335 },
336 in_notes_only: false, 336 in_notes_only: false,
337 }, 337 },
338 // Org emits a `<colgroup>` of empty `<col>`s to carry column alignment; org-ssg 338 // Org emits a `<colgroup>` of empty `<col>`s to carry column alignment; orgo
339 // leaves alignment to the stylesheet. 339 // leaves alignment to the stylesheet.
340 Deliberate { 340 Deliberate {
341 name: "no-colgroup", 341 name: "no-colgroup",
@@ -511,7 +511,7 @@ fn align(ours: &[String], theirs: &[String]) -> Vec<Op> {
511} 511}
512 512
513/// A unified diff of the two skeletons, with hunks that are deliberate collapsed to a 513/// A unified diff of the two skeletons, with hunks that are deliberate collapsed to a
514/// named line. `-` is org-ssg, `+` is Emacs. 514/// named line. `-` is orgo, `+` is Emacs.
515/// 515///
516/// The number that matters is the last one: *unexplained* lines. Agreement can be low 516/// The number that matters is the last one: *unexplained* lines. Agreement can be low
517/// while unexplained is zero, and that is a passing state. 517/// while unexplained is zero, and that is a passing state.
@@ -566,7 +566,7 @@ fn divergence(ours: &[String], theirs: &[String]) -> String {
566 "agreement: {agreed}/{total} skeleton lines ({pct:.1}%)\n\ 566 "agreement: {agreed}/{total} skeleton lines ({pct:.1}%)\n\
567 deliberate: {deliberate} line(s){}\n\ 567 deliberate: {deliberate} line(s){}\n\
568 unexplained: {unexplained} line(s)\n\ 568 unexplained: {unexplained} line(s)\n\
569 (- org-ssg, + emacs, ~ a difference we mean to have)\n\n{body}", 569 (- orgo, + emacs, ~ a difference we mean to have)\n\n{body}",
570 if rules.is_empty() { 570 if rules.is_empty() {
571 String::new() 571 String::new()
572 } else { 572 } else {
@@ -575,7 +575,7 @@ fn divergence(ours: &[String], theirs: &[String]) -> String {
575 ) 575 )
576} 576}
577 577
578/// Snapshot the divergence between org-ssg and Emacs for one fixture. 578/// Snapshot the divergence between orgo and Emacs for one fixture.
579fn compare(fixture: &str) -> Option<String> { 579fn compare(fixture: &str) -> Option<String> {
580 if !emacs_available() { 580 if !emacs_available() {
581 eprintln!("skipping oracle comparison for {fixture}: no emacs on PATH"); 581 eprintln!("skipping oracle comparison for {fixture}: no emacs on PATH");
@@ -612,7 +612,7 @@ oracle_test!(oracle_elements, "elements.org");
612/// 612///
613/// The percentages above are context, not a target — the timestamps fixture agrees on 613/// The percentages above are context, not a target — the timestamps fixture agrees on
614/// 40% of its lines and is entirely correct, because org writes a date as text where 614/// 40% of its lines and is entirely correct, because org writes a date as text where
615/// org-ssg writes `<time datetime>`. What must hold is that nothing diverges for a 615/// orgo writes `<time datetime>`. What must hold is that nothing diverges for a
616/// reason nobody has written down. A new unexplained line means either a defect to fix 616/// reason nobody has written down. A new unexplained line means either a defect to fix
617/// or a decision to record in `DELIBERATE`. 617/// or a decision to record in `DELIBERATE`.
618#[test] 618#[test]
tests/pipeline.rs +4 −4
@@ -6,10 +6,10 @@
6 6
7use camino::Utf8PathBuf; 7use camino::Utf8PathBuf;
8 8
9use org_ssg::model::Document; 9use orgo::model::Document;
10use org_ssg::parser::parse; 10use orgo::parser::parse;
11use org_ssg::render::{render, Html, SyntectHighlighter}; 11use orgo::render::{render, Html, SyntectHighlighter};
12use org_ssg::resolve::ResolvedDoc; 12use orgo::resolve::ResolvedDoc;
13 13
14fn parse_fixture(name: &str) -> Document { 14fn parse_fixture(name: &str) -> Document {
15 let path = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")) 15 let path = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR"))
tests/serve.rs +11 −11
@@ -12,15 +12,15 @@ use std::time::{Duration, Instant};
12 12
13use camino::{Utf8Path, Utf8PathBuf}; 13use camino::{Utf8Path, Utf8PathBuf};
14 14
15use org_ssg::serve::{inject_reload_script, resolve, since_parameter}; 15use orgo::serve::{inject_reload_script, resolve, since_parameter};
16use org_ssg::site::BuildOptions; 16use orgo::site::BuildOptions;
17 17
18fn tmpdir(tag: &str) -> Utf8PathBuf { 18fn tmpdir(tag: &str) -> Utf8PathBuf {
19 static N: AtomicU32 = AtomicU32::new(0); 19 static N: AtomicU32 = AtomicU32::new(0);
20 let n = N.fetch_add(1, Ordering::Relaxed); 20 let n = N.fetch_add(1, Ordering::Relaxed);
21 let base = Utf8PathBuf::from_path_buf(std::env::temp_dir()) 21 let base = Utf8PathBuf::from_path_buf(std::env::temp_dir())
22 .expect("utf-8 temp dir") 22 .expect("utf-8 temp dir")
23 .join(format!("org-ssg-serve-{}-{tag}-{n}", std::process::id())); 23 .join(format!("orgo-serve-{}-{tag}-{n}", std::process::id()));
24 let _ = std::fs::remove_dir_all(&base); 24 let _ = std::fs::remove_dir_all(&base);
25 std::fs::create_dir_all(&base).unwrap(); 25 std::fs::create_dir_all(&base).unwrap();
26 base 26 base
@@ -107,10 +107,10 @@ fn plus_is_not_decoded_as_a_space() {
107 107
108#[test] 108#[test]
109fn the_poll_parameter_is_read_from_the_query() { 109fn the_poll_parameter_is_read_from_the_query() {
110 assert_eq!(since_parameter("/__org-ssg/reload?since=7"), 7); 110 assert_eq!(since_parameter("/__orgo/reload?since=7"), 7);
111 assert_eq!(since_parameter("/__org-ssg/reload?x=1&since=42"), 42); 111 assert_eq!(since_parameter("/__orgo/reload?x=1&since=42"), 42);
112 assert_eq!(since_parameter("/__org-ssg/reload"), 0, "absent means start from zero"); 112 assert_eq!(since_parameter("/__orgo/reload"), 0, "absent means start from zero");
113 assert_eq!(since_parameter("/__org-ssg/reload?since=nope"), 0, "unparseable means zero"); 113 assert_eq!(since_parameter("/__orgo/reload?since=nope"), 0, "unparseable means zero");
114} 114}
115 115
116// --------------------------------------------------------------------------- 116// ---------------------------------------------------------------------------
@@ -167,7 +167,7 @@ fn start_server(src: &Utf8Path, out: &Utf8Path) -> u16 {
167 let port = 20000 + ((std::process::id() % 10000) as u16) + NEXT.fetch_add(1, Ordering::Relaxed) as u16; 167 let port = 20000 + ((std::process::id() % 10000) as u16) + NEXT.fetch_add(1, Ordering::Relaxed) as u16;
168 let (s, o) = (src.to_owned(), out.to_owned()); 168 let (s, o) = (src.to_owned(), out.to_owned());
169 std::thread::spawn(move || { 169 std::thread::spawn(move || {
170 let _ = org_ssg::serve::run(&s, &o, &BuildOptions::default(), "127.0.0.1", port); 170 let _ = orgo::serve::run(&s, &o, &BuildOptions::default(), "127.0.0.1", port);
171 }); 171 });
172 let deadline = Instant::now() + Duration::from_secs(20); 172 let deadline = Instant::now() + Duration::from_secs(20);
173 while Instant::now() < deadline { 173 while Instant::now() < deadline {
@@ -194,16 +194,16 @@ fn serving_a_site_reloads_the_browser_when_a_source_changes() {
194 let home = get(port, "/", Duration::from_secs(5)).expect("a response"); 194 let home = get(port, "/", Duration::from_secs(5)).expect("a response");
195 assert!(home.contains("200 OK"), "{home}"); 195 assert!(home.contains("200 OK"), "{home}");
196 assert!(home.contains("First version."), "the page is served: {home}"); 196 assert!(home.contains("First version."), "the page is served: {home}");
197 assert!(home.contains("__org-ssg/reload"), "with the reload script: {home}"); 197 assert!(home.contains("__orgo/reload"), "with the reload script: {home}");
198 assert!( 198 assert!(
199 !std::fs::read_to_string(out.join("index.html")).unwrap().contains("__org-ssg"), 199 !std::fs::read_to_string(out.join("index.html")).unwrap().contains("__orgo"),
200 "but the file on disk stays clean" 200 "but the file on disk stays clean"
201 ); 201 );
202 202
203 // A poll for a generation we already have must block, not answer immediately. 203 // A poll for a generation we already have must block, not answer immediately.
204 let poller = std::thread::spawn(move || { 204 let poller = std::thread::spawn(move || {
205 let started = Instant::now(); 205 let started = Instant::now();
206 let body = get(port, "/__org-ssg/reload?since=0", Duration::from_secs(30)); 206 let body = get(port, "/__orgo/reload?since=0", Duration::from_secs(30));
207 (started.elapsed(), body) 207 (started.elapsed(), body)
208 }); 208 });
209 std::thread::sleep(Duration::from_millis(400)); 209 std::thread::sleep(Duration::from_millis(400));
tests/site.rs +8 −8
@@ -6,11 +6,11 @@
6 6
7use camino::Utf8PathBuf; 7use camino::Utf8PathBuf;
8 8
9use org_ssg::index::{SymbolTable, TargetId}; 9use orgo::index::{SymbolTable, TargetId};
10use org_ssg::parser::parse; 10use orgo::parser::parse;
11use org_ssg::render::{render, Html, SyntectHighlighter}; 11use orgo::render::{render, Html, SyntectHighlighter};
12use org_ssg::resolve::{resolve, ResolvedDoc}; 12use orgo::resolve::{resolve, ResolvedDoc};
13use org_ssg::site::{render_site, BuiltPage}; 13use orgo::site::{render_site, BuiltPage};
14 14
15fn fixtures() -> Utf8PathBuf { 15fn fixtures() -> Utf8PathBuf {
16 Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures") 16 Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures")
@@ -169,13 +169,13 @@ fn links_resolve_through_the_slug() {
169/// be impossible by construction rather than by convention. 169/// be impossible by construction rather than by convention.
170#[test] 170#[test]
171fn slugs_cannot_escape_the_output_directory() { 171fn slugs_cannot_escape_the_output_directory() {
172 use org_ssg::model::Keywords; 172 use orgo::model::Keywords;
173 let source = Utf8PathBuf::from("blog/post.org"); 173 let source = Utf8PathBuf::from("blog/post.org");
174 let slugged = |value: &str| { 174 let slugged = |value: &str| {
175 let keywords = Keywords { 175 let keywords = Keywords {
176 entries: vec![("SLUG".to_string(), value.to_string())], 176 entries: vec![("SLUG".to_string(), value.to_string())],
177 }; 177 };
178 org_ssg::util::output_path(&source, &keywords).to_string() 178 orgo::util::output_path(&source, &keywords).to_string()
179 }; 179 };
180 assert_eq!(slugged("../../etc/passwd"), "blog/etc-passwd.html"); 180 assert_eq!(slugged("../../etc/passwd"), "blog/etc-passwd.html");
181 assert_eq!(slugged("/absolute"), "blog/absolute.html"); 181 assert_eq!(slugged("/absolute"), "blog/absolute.html");
@@ -190,7 +190,7 @@ fn slugs_cannot_escape_the_output_directory() {
190/// invisible in the source filenames, so the build refuses rather than picking a winner. 190/// invisible in the source filenames, so the build refuses rather than picking a winner.
191#[test] 191#[test]
192fn colliding_slugs_are_a_build_error() { 192fn colliding_slugs_are_a_build_error() {
193 let dir = std::env::temp_dir().join(format!("org-ssg-slug-{}", std::process::id())); 193 let dir = std::env::temp_dir().join(format!("orgo-slug-{}", std::process::id()));
194 let dir = Utf8PathBuf::from_path_buf(dir).expect("utf-8 temp dir"); 194 let dir = Utf8PathBuf::from_path_buf(dir).expect("utf-8 temp dir");
195 let _ = std::fs::remove_dir_all(&dir); 195 let _ = std::fs::remove_dir_all(&dir);
196 std::fs::create_dir_all(&dir).unwrap(); 196 std::fs::create_dir_all(&dir).unwrap();
tests/snapshots/oracle__oracle_blocks.snap +1 −1
@@ -5,7 +5,7 @@ expression: report
5agreement: 67/77 skeleton lines (87.0%) 5agreement: 67/77 skeleton lines (87.0%)
6deliberate: 11 line(s) — pre-code ×6, semantic-emphasis ×4, verse-trailing-break ×1 6deliberate: 11 line(s) — pre-code ×6, semantic-emphasis ×4, verse-trailing-break ×1
7unexplained: 0 line(s) 7unexplained: 0 line(s)
8(- org-ssg, + emacs, ~ a difference we mean to have) 8(- orgo, + emacs, ~ a difference we mean to have)
9 9
10 <h2> 10 <h2>
11 "Quote" 11 "Quote"
tests/snapshots/oracle__oracle_core.snap +1 −1
@@ -5,7 +5,7 @@ expression: report
5agreement: 51/58 skeleton lines (87.9%) 5agreement: 51/58 skeleton lines (87.9%)
6deliberate: 7 line(s) — list-per-bullet-type ×2, pre-code ×2, semantic-emphasis ×2 6deliberate: 7 line(s) — list-per-bullet-type ×2, pre-code ×2, semantic-emphasis ×2
7unexplained: 0 line(s) 7unexplained: 0 line(s)
8(- org-ssg, + emacs, ~ a difference we mean to have) 8(- orgo, + emacs, ~ a difference we mean to have)
9 9
10 <p> 10 <p>
11 "Intro paragraph with a bare URL" 11 "Intro paragraph with a bare URL"
tests/snapshots/oracle__oracle_elements.snap +1 −1
@@ -5,7 +5,7 @@ expression: report
5agreement: 64/82 skeleton lines (78.0%) 5agreement: 64/82 skeleton lines (78.0%)
6deliberate: 26 line(s) — footnote-anchor-naming ×1, footnote-section-shape ×2, id-link-resolution ×1, no-colgroup ×1, pre-code ×2 6deliberate: 26 line(s) — footnote-anchor-naming ×1, footnote-section-shape ×2, id-link-resolution ×1, no-colgroup ×1, pre-code ×2
7unexplained: 0 line(s) 7unexplained: 0 line(s)
8(- org-ssg, + emacs, ~ a difference we mean to have) 8(- orgo, + emacs, ~ a difference we mean to have)
9 9
10 <h2> 10 <h2>
11 "Code and tables" 11 "Code and tables"
tests/snapshots/oracle__oracle_footnote.snap +1 −1
@@ -5,7 +5,7 @@ expression: report
5agreement: 30/53 skeleton lines (56.6%) 5agreement: 30/53 skeleton lines (56.6%)
6deliberate: 29 line(s) — footnote-anchor-naming ×3, footnote-section-shape ×6, semantic-emphasis ×2 6deliberate: 29 line(s) — footnote-anchor-naming ×3, footnote-section-shape ×6, semantic-emphasis ×2
7unexplained: 0 line(s) 7unexplained: 0 line(s)
8(- org-ssg, + emacs, ~ a difference we mean to have) 8(- orgo, + emacs, ~ a difference we mean to have)
9 9
10 <p> 10 <p>
11 "Text with a reference." 11 "Text with a reference."
tests/snapshots/oracle__oracle_headings.snap +1 −1
@@ -5,7 +5,7 @@ expression: report
5agreement: 28/30 skeleton lines (93.3%) 5agreement: 28/30 skeleton lines (93.3%)
6deliberate: 2 line(s) — semantic-time ×2 6deliberate: 2 line(s) — semantic-time ×2
7unexplained: 0 line(s) 7unexplained: 0 line(s)
8(- org-ssg, + emacs, ~ a difference we mean to have) 8(- orgo, + emacs, ~ a difference we mean to have)
9 9
10 <h2> 10 <h2>
11~ semantic-time (1 line(s)) 11~ semantic-time (1 line(s))
tests/snapshots/oracle__oracle_images.snap +1 −1
@@ -5,7 +5,7 @@ expression: report
5agreement: 30/42 skeleton lines (71.4%) 5agreement: 30/42 skeleton lines (71.4%)
6deliberate: 14 line(s) — figure-element ×8, semantic-emphasis ×2 6deliberate: 14 line(s) — figure-element ×8, semantic-emphasis ×2
7unexplained: 0 line(s) 7unexplained: 0 line(s)
8(- org-ssg, + emacs, ~ a difference we mean to have) 8(- orgo, + emacs, ~ a difference we mean to have)
9 9
10 <h2> 10 <h2>
11 "Bare image" 11 "Bare image"
tests/snapshots/oracle__oracle_lists.snap +1 −1
@@ -5,7 +5,7 @@ expression: report
5agreement: 109/111 skeleton lines (98.2%) 5agreement: 109/111 skeleton lines (98.2%)
6deliberate: 2 line(s) — semantic-emphasis ×2 6deliberate: 2 line(s) — semantic-emphasis ×2
7unexplained: 0 line(s) 7unexplained: 0 line(s)
8(- org-ssg, + emacs, ~ a difference we mean to have) 8(- orgo, + emacs, ~ a difference we mean to have)
9 9
10 <h2> 10 <h2>
11 "Nesting" 11 "Nesting"
tests/snapshots/oracle__oracle_minimal.snap +1 −1
@@ -5,7 +5,7 @@ expression: report
5agreement: 38/42 skeleton lines (90.5%) 5agreement: 38/42 skeleton lines (90.5%)
6deliberate: 4 line(s) — semantic-emphasis ×4 6deliberate: 4 line(s) — semantic-emphasis ×4
7unexplained: 0 line(s) 7unexplained: 0 line(s)
8(- org-ssg, + emacs, ~ a difference we mean to have) 8(- orgo, + emacs, ~ a difference we mean to have)
9 9
10 <p> 10 <p>
11 "A single paragraph of preamble text before any heading." 11 "A single paragraph of preamble text before any heading."
tests/snapshots/oracle__oracle_table.snap +1 −1
@@ -5,7 +5,7 @@ expression: report
5agreement: 30/34 skeleton lines (88.2%) 5agreement: 30/34 skeleton lines (88.2%)
6deliberate: 4 line(s) — no-colgroup ×1 6deliberate: 4 line(s) — no-colgroup ×1
7unexplained: 0 line(s) 7unexplained: 0 line(s)
8(- org-ssg, + emacs, ~ a difference we mean to have) 8(- orgo, + emacs, ~ a difference we mean to have)
9 9
10 <table> 10 <table>
11~ no-colgroup (4 line(s)) 11~ no-colgroup (4 line(s))
tests/snapshots/oracle__oracle_timestamps.snap +1 −1
@@ -5,7 +5,7 @@ expression: report
5agreement: 25/62 skeleton lines (40.3%) 5agreement: 25/62 skeleton lines (40.3%)
6deliberate: 37 line(s) — semantic-time ×5 6deliberate: 37 line(s) — semantic-time ×5
7unexplained: 0 line(s) 7unexplained: 0 line(s)
8(- org-ssg, + emacs, ~ a difference we mean to have) 8(- orgo, + emacs, ~ a difference we mean to have)
9 9
10 <h2> 10 <h2>
11 "Single" 11 "Single"
tests/snapshots/site__site_guide_html.snap +2 −2
@@ -7,12 +7,12 @@ expression: "page(&pages, \"guide.org\").html"
7<head> 7<head>
8<meta charset="utf-8"> 8<meta charset="utf-8">
9<meta name="viewport" content="width=device-width, initial-scale=1"> 9<meta name="viewport" content="width=device-width, initial-scale=1">
10<title>Guide &middot; org-ssg site</title> 10<title>Guide &middot; orgo site</title>
11<link rel="stylesheet" href="syntax.css"> 11<link rel="stylesheet" href="syntax.css">
12</head> 12</head>
13<body> 13<body>
14<header> 14<header>
15<a class="site-title" href="index.html">org-ssg site</a> 15<a class="site-title" href="index.html">orgo site</a>
16<nav> 16<nav>
17<a href="about.html">About</a> 17<a href="about.html">About</a>
18<a href="#">Guide</a> 18<a href="#">Guide</a>
tests/snapshots/site__site_index_html.snap +2 −2
@@ -7,12 +7,12 @@ expression: "page(&pages, \"index.org\").html"
7<head> 7<head>
8<meta charset="utf-8"> 8<meta charset="utf-8">
9<meta name="viewport" content="width=device-width, initial-scale=1"> 9<meta name="viewport" content="width=device-width, initial-scale=1">
10<title>Home &middot; org-ssg site</title> 10<title>Home &middot; orgo site</title>
11<link rel="stylesheet" href="syntax.css"> 11<link rel="stylesheet" href="syntax.css">
12</head> 12</head>
13<body> 13<body>
14<header> 14<header>
15<a class="site-title" href="index.html">org-ssg site</a> 15<a class="site-title" href="index.html">orgo site</a>
16<nav> 16<nav>
17<a href="about.html">About</a> 17<a href="about.html">About</a>
18<a href="guide.html">Guide</a> 18<a href="guide.html">Guide</a>
tests/watch.rs +7 −7
@@ -1,6 +1,6 @@
1//! `watch`: the change filter, and one end-to-end run against real filesystem events. 1//! `watch`: the change filter, and one end-to-end run against real filesystem events.
2//! 2//!
3//! The filter carries the weight here. `org-ssg watch . -o _site` puts the output inside 3//! The filter carries the weight here. `orgo watch . -o _site` puts the output inside
4//! the source, so a rebuild writes files, writing files raises events, and events trigger 4//! the source, so a rebuild writes files, writing files raises events, and events trigger
5//! a rebuild — a loop that never stops. That it is a pure function is what makes the 5//! a rebuild — a loop that never stops. That it is a pure function is what makes the
6//! guarantee testable without waiting on a filesystem. 6//! guarantee testable without waiting on a filesystem.
@@ -10,15 +10,15 @@ use std::time::{Duration, Instant};
10 10
11use camino::{Utf8Path, Utf8PathBuf}; 11use camino::{Utf8Path, Utf8PathBuf};
12 12
13use org_ssg::site::{build_site, BuildOptions}; 13use orgo::site::{build_site, BuildOptions};
14use org_ssg::watch::ChangeFilter; 14use orgo::watch::ChangeFilter;
15 15
16fn tmpdir(tag: &str) -> Utf8PathBuf { 16fn tmpdir(tag: &str) -> Utf8PathBuf {
17 static N: AtomicU32 = AtomicU32::new(0); 17 static N: AtomicU32 = AtomicU32::new(0);
18 let n = N.fetch_add(1, Ordering::Relaxed); 18 let n = N.fetch_add(1, Ordering::Relaxed);
19 let base = Utf8PathBuf::from_path_buf(std::env::temp_dir()) 19 let base = Utf8PathBuf::from_path_buf(std::env::temp_dir())
20 .expect("utf-8 temp dir") 20 .expect("utf-8 temp dir")
21 .join(format!("org-ssg-watch-{}-{tag}-{n}", std::process::id())); 21 .join(format!("orgo-watch-{}-{tag}-{n}", std::process::id()));
22 let _ = std::fs::remove_dir_all(&base); 22 let _ = std::fs::remove_dir_all(&base);
23 std::fs::create_dir_all(&base).unwrap(); 23 std::fs::create_dir_all(&base).unwrap();
24 base 24 base
@@ -39,7 +39,7 @@ fn changes_under_the_output_directory_are_ignored() {
39 let filter = ChangeFilter::new(&src, &out); 39 let filter = ChangeFilter::new(&src, &out);
40 assert!(!filter.is_relevant(Utf8Path::new("_site/index.html"))); 40 assert!(!filter.is_relevant(Utf8Path::new("_site/index.html")));
41 assert!(!filter.is_relevant(Utf8Path::new("_site/blog/post.html"))); 41 assert!(!filter.is_relevant(Utf8Path::new("_site/blog/post.html")));
42 assert!(!filter.is_relevant(Utf8Path::new("_site/.org-ssg-cache.json"))); 42 assert!(!filter.is_relevant(Utf8Path::new("_site/.orgo-cache.json")));
43 assert!(filter.is_relevant(Utf8Path::new("index.org")), "real sources still count"); 43 assert!(filter.is_relevant(Utf8Path::new("index.org")), "real sources still count");
44} 44}
45 45
@@ -68,7 +68,7 @@ fn build_inputs_trigger_a_rebuild_even_though_discovery_skips_them() {
68 std::fs::create_dir_all(&src).unwrap(); 68 std::fs::create_dir_all(&src).unwrap();
69 let filter = ChangeFilter::new(&src, &root.join("out")); 69 let filter = ChangeFilter::new(&src, &root.join("out"));
70 70
71 assert!(filter.is_relevant(Utf8Path::new("org-ssg.toml"))); 71 assert!(filter.is_relevant(Utf8Path::new("orgo.toml")));
72 assert!(filter.is_relevant(Utf8Path::new("templates/base.html"))); 72 assert!(filter.is_relevant(Utf8Path::new("templates/base.html")));
73 assert!(filter.is_relevant(Utf8Path::new("templates/feed.xml"))); 73 assert!(filter.is_relevant(Utf8Path::new("templates/feed.xml")));
74 assert!(filter.is_relevant(Utf8Path::new("style.css")), "assets are copied through"); 74 assert!(filter.is_relevant(Utf8Path::new("style.css")), "assets are copied through");
@@ -149,7 +149,7 @@ fn watching_rebuilds_the_site_when_a_source_file_changes() {
149 149
150 let (src_t, out_t) = (src.clone(), out.clone()); 150 let (src_t, out_t) = (src.clone(), out.clone());
151 let handle = std::thread::spawn(move || { 151 let handle = std::thread::spawn(move || {
152 let _ = org_ssg::watch::run(&src_t, &out_t, &BuildOptions::default()); 152 let _ = orgo::watch::run(&src_t, &out_t, &BuildOptions::default());
153 }); 153 });
154 154
155 // Give the watcher a moment to register before making the change it should see. 155 // Give the watcher a moment to register before making the change it should see.