krz/orgo
Lightning fast org-mode static site generator.
clone: git clone https://gitbay.org/krz/orgo.git
2f14a2076788dd8a1d9e2c4ee571df3b19198dcf
verified · cmc
author: Christian Cleberg <hello@cleberg.net> · 2026-08-11T21:47:34Z
.github/workflows/ci.yml | 4 +- .github/workflows/release.yml | 6 +- CHANGELOG.md | 8 +- Cargo.lock | 2 +- Cargo.toml | 15 ++-- README.md | 36 ++++----- RELEASING.md | 7 +- docs/guide/01-cli.org | 22 +++--- docs/guide/02-configuration.org | 12 +-- docs/guide/03-collections.org | 2 +- docs/guide/04-templates.org | 2 +- docs/guide/05-org-support.org | 16 ++-- docs/guide/06-authoring.org | 8 +- docs/guide/07-incremental.org | 14 ++-- docs/guide/08-workflow.org | 12 +-- docs/guide/09-auditing.org | 12 +-- docs/guide/10-deploying.org | 20 ++--- docs/guide/11-versioning.org | 12 +-- docs/index.org | 12 +-- docs/install.org | 26 +++---- docs/{org-ssg.toml => orgo.toml} | 6 +- docs/quickstart.org | 38 ++++----- docs/style.css | 2 +- docs/templates/base.html | 2 +- fixtures/site/about.org | 2 +- src/config.rs | 20 ++--- src/incremental.rs | 4 +- src/lib.rs | 2 +- src/main.rs | 38 ++++----- src/render.rs | 4 +- src/serve.rs | 2 +- src/site.rs | 4 +- src/template.rs | 10 +-- src/watch.rs | 2 +- syntaxes/Org.sublime-syntax | 2 +- syntaxes/TOML.sublime-syntax | 2 +- tests/config.rs | 102 ++++++++++++------------- tests/constructs.rs | 12 +-- tests/harness.rs | 4 +- tests/incremental.rs | 10 +-- tests/oracle.el | 6 +- tests/oracle.rs | 24 +++--- tests/pipeline.rs | 8 +- tests/serve.rs | 22 +++--- tests/site.rs | 16 ++-- tests/snapshots/oracle__oracle_blocks.snap | 2 +- tests/snapshots/oracle__oracle_core.snap | 2 +- tests/snapshots/oracle__oracle_elements.snap | 2 +- tests/snapshots/oracle__oracle_footnote.snap | 2 +- tests/snapshots/oracle__oracle_headings.snap | 2 +- tests/snapshots/oracle__oracle_images.snap | 2 +- tests/snapshots/oracle__oracle_lists.snap | 2 +- tests/snapshots/oracle__oracle_minimal.snap | 2 +- tests/snapshots/oracle__oracle_table.snap | 2 +- tests/snapshots/oracle__oracle_timestamps.snap | 2 +- tests/snapshots/site__site_guide_html.snap | 4 +- tests/snapshots/site__site_index_html.snap | 4 +- tests/watch.rs | 14 ++-- 58 files changed, 317 insertions(+), 317 deletions(-) @@ -1,6 +1,6 @@ name: CI -# Build, test and lint on both platforms org-ssg is used from, plus a compiler-floor job. +# Build, test and lint on both platforms orgo is used from, plus a compiler-floor job. # # WHAT THIS CATCHES THAT LOCAL WORK DOES NOT: # @@ -96,7 +96,7 @@ jobs: # Build only. The tests pull in dev-dependencies whose own floors move # independently, and chasing those would make this job about someone else's MSRV. # - # The floor is set by dependencies rather than by org-ssg — its own code compiles on + # The floor is set by dependencies rather than by orgo — its own code compiles on # 1.82 — which is precisely why it is checked here instead of reasoned about: a # dependency raising its floor is invisible until someone on an older compiler # tries to build. @@ -2,7 +2,7 @@ name: Release # Build binaries for a tag and attach them to a GitHub release. # -# WHY BINARIES AT ALL, given `cargo install org-ssg` exists: installing from source needs +# WHY BINARIES AT ALL, given `cargo install orgo` exists: installing from source needs # a Rust toolchain and about a minute of compiling syntect. Someone evaluating a site # generator should be able to download one file and point it at their notes. # @@ -74,9 +74,9 @@ jobs: - name: Package shell: bash run: | - staging="org-ssg-${{ github.event.inputs.tag || github.ref_name }}-${{ matrix.target }}" + staging="orgo-${{ github.event.inputs.tag || github.ref_name }}-${{ matrix.target }}" mkdir "$staging" - cp "target/${{ matrix.target }}/release/org-ssg" "$staging/" + cp "target/${{ matrix.target }}/release/orgo" "$staging/" cp README.md LICENSE CHANGELOG.md "$staging/" tar czf "$staging.tar.gz" "$staging" shasum -a 256 "$staging.tar.gz" > "$staging.tar.gz.sha256" @@ -7,7 +7,7 @@ Two conventions worth knowing before reading: - **A cache-format bump is not a change you need to act on.** The incremental cache is versioned and discards itself; a bump means the next build re-renders everything once. -- **Output changes are called out.** org-ssg aims at what Emacs exports from the same +- **Output changes are called out.** orgo aims at what Emacs exports from the same file, so an entry that says "now renders X" means your pages will change. That is the product, not a regression — but it belongs in a changelog rather than a diff you find later. @@ -47,7 +47,7 @@ Release engineering, so that a version number is worth reading. built with `--strict`. Emacs is installed on both, so the oracle suite runs for real instead of skipping. - **A checked MSRV**, 1.88 — which is how it came to be 1.88 rather than the 1.82 - org-ssg's own code needs. The floor comes from dependencies, and nobody finds that out + orgo's own code needs. The floor comes from dependencies, and nobody finds that out by reasoning about it. - **Release binaries** for macOS (arm64, x86_64) and Linux (gnu, musl), built on tag into 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.** ## 0.12.0 - `serve`: a development server with live reload, bound to loopback. -- A documentation site under `docs/`, built by org-ssg itself. +- A documentation site under `docs/`, built by orgo itself. ## 0.11.0 @@ -153,7 +153,7 @@ publishes from the same sources. **All of these change output.** ## 0.5.0 - Parse diagnostics carry `file:line`, and pages render in parallel. -- The corpus audit (`org-ssg audit`) and the `emacs --batch` oracle. +- The corpus audit (`orgo audit`) and the `emacs --batch` oracle. - `#+SLUG:` decides a page's output filename — found by auditing a real corpus, where it affected 169 of 182 URLs. @@ -674,7 +674,7 @@ dependencies = [ ] [[package]] -name = "org-ssg" +name = "orgo" version = "0.19.1" dependencies = [ "anyhow", @@ -1,17 +1,16 @@ [package] -name = "org-ssg" +name = "orgo" version = "0.19.1" edition = "2021" description = "Org-mode static site generator that renders the org element tree straight to HTML" -license = "MIT" +license = "0BSD" readme = "README.md" keywords = ["org-mode", "static-site-generator", "emacs", "html", "blog"] categories = ["command-line-utilities", "text-processing"] -# Set before the first `cargo publish`: crates.io shows it on the crate page, and a -# missing link is the first thing anyone evaluating a generator looks for. -# repository = "https://git.krz.sh/cmc/org-ssg.git/" +repository = "https://github.com/ccleberg/orgo" +homepage = "https://ccleberg.github.io/orgo/" -# The compiler floor, checked in CI rather than assumed. org-ssg's own code needs 1.82 +# The compiler floor, checked in CI rather than assumed. orgo's own code needs 1.82 # (`Option::is_none_or`); the floor is 1.88 because dependencies in Cargo.lock declare it # — `plist` and `time`, both by way of syntect. Raising this is a minor-version change, # never a patch. @@ -23,11 +22,11 @@ rust-version = "1.88" exclude = ["docs/", "target/", "/.github/"] [lib] -name = "org_ssg" +name = "orgo" path = "src/lib.rs" [[bin]] -name = "org-ssg" +name = "orgo" path = "src/main.rs" [dependencies] @@ -1,4 +1,4 @@ -# org-ssg +# orgo An org-mode static site generator, in Rust. Org is treated as the *source language*, not 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 deliverable, even while the corpus is small enough that a full rebuild is instant. **Full documentation is in [`docs/`](docs/)** — a site written in org and built by -org-ssg itself. Build and read it with: +orgo itself. Build and read it with: ```bash cargo run -- serve docs -o docs/_site @@ -33,23 +33,23 @@ Or skip the scaffolding entirely — point it at any directory of `.org` files: cargo run -- build ~/notes -o _site ``` -**Zero configuration is a supported path, not a demo.** With no `org-ssg.toml`, no -templates and no org-ssg-specific markup in your files, you get a complete site: pages, +**Zero configuration is a supported path, not a demo.** With no `orgo.toml`, no +templates and no orgo-specific markup in your files, you get a complete site: pages, navigation, syntax-highlighted code and the stylesheet to colour it. Configuration changes what you get; it is never what makes it work. Discovery skips what should not be published — dot-directories such as `.git`, the config file, the templates directory, and the output directory when it sits inside the source, so -`org-ssg build . -o _site` does the obvious thing. +`orgo build . -o _site` does the obvious thing. ## Configuration -Everything is optional. `org-ssg init` writes a fully commented `org-ssg.toml`; every +Everything is optional. `orgo init` writes a fully commented `orgo.toml`; every value below is the default. ```toml [site] -title = "org-ssg site" +title = "orgo site" base_url = "" # absolute URL, no trailing slash; needed for feeds/canonical links description = "" language = "en" @@ -224,7 +224,7 @@ group. **A feed is a listing page with an XML template**, not a separate feature — templates are loaded by full filename and any extension, so `output = "feed.xml"` with -`template = "feed.xml"` is all it takes. `org-ssg init` writes a working RSS template. +`template = "feed.xml"` is all it takes. `orgo init` writes a working RSS template. A feed is read away from the site that served it, so relative links in one are simply broken. 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 | Stage | Module | Notes | |---|---|---| -| config | `src/config.rs` | `org-ssg.toml`: site metadata, nav mode, templates, theme. A hash input. | +| config | `src/config.rs` | `orgo.toml`: site metadata, nav mode, templates, theme. A hash input. | | PARSE | `src/parser.rs` | Hand-written recursive descent: line lexer → element builder → inline tokenizer. | | audit | `src/audit.rs` | Phase 0 corpus audit: construct frequencies against the IN/OUT line. | | model | `src/model.rs` | The org element tree — Elements (block) vs Objects (inline). | @@ -424,7 +424,7 @@ feature):** The config component folds in a **site-structure hash** (every page's `(path, title)`), because the shared nav bar is global chrome — a title change or a page add/remove alters the nav on every page and so must re-render them all (otherwise byte-equivalence breaks). -- **Persisted cache manifest** (`<out>/.org-ssg-cache.json`, JSON), carrying per-page +- **Persisted cache manifest** (`<out>/.orgo-cache.json`, JSON), carrying per-page records, the config/template hashes, and the serialized dependency graph, tagged with `CACHE_FORMAT_VERSION`. A version mismatch, a missing file, or a corrupt file all fall 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 Two rules decide what counts as a change, and they are not the same rules the build uses to find content: -- **A build input is a change.** Editing `org-ssg.toml` or a template rebuilds, even +- **A build input is a change.** Editing `orgo.toml` or a template rebuilds, even though discovery skips both as non-content. The question is "would this change the site?", not "is this a page?". - **Our own output is not.** `watch . -o _site` puts the output inside the source, so a @@ -544,7 +544,7 @@ Emacs does. The audit runs against any corpus — point it at your own notes before trusting this tool with them. The numbers below come from a 179-file site published today by weblorg, a wrapper around org's own HTML exporter, which makes it both a realistic workload and a -directly comparable incumbent. With collections configured, org-ssg now reproduces +directly comparable incumbent. With collections configured, orgo now reproduces **all 182 of that site's URLs**. ``` @@ -560,7 +560,7 @@ whole out-of-scope tail is 8 uses: four `#+TBLFM:` in a post *about* org-mode, t **`#+SLUG:` was a hole big enough to sink the project.** 178 of 179 files set it, and the published URL comes from it, not from the filename: `2018-11-28-aes-encryption.org` is -served at `blog/aes-encryption.html`. org-ssg derived output paths from source filenames, +served at `blog/aes-encryption.html`. orgo derived output paths from source filenames, so **169 of 179 pages would have been published at the wrong URL** — every inbound link and every search result, broken, by a tool that reported a clean build. Output paths now come from `#+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 Three invariants are asserted outright, and all three hold — heading structure, list nesting, and source-block text match Emacs exactly. -**No bugs in org-ssg.** Every remaining divergence is a deliberate choice to emit better +**No bugs in orgo.** Every remaining divergence is a deliberate choice to emit better HTML than org does: -| | org-ssg | Emacs | why | +| | orgo | Emacs | why | |---|---|---|---| | emphasis | `<em>`/`<strong>` | `<i>`/`<b>` | semantic, not presentational | | 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: | Stable | Detail | |---|---| -| `org-ssg.toml` keys | Names, types and meaning. New keys are minor releases; removing one is major. | +| `orgo.toml` keys | Names, types and meaning. New keys are minor releases; removing one is major. | | Template context | `page`, `site`, `nav`, `root`, `pages`, `group`, `groups`, `paginator`, `stylesheet`, and the `absolute` / `rfc822` / `truncate` filters. | | CLI | Command names, flags, and exit codes. | | 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: - **The incremental cache.** Versioned, discarded on mismatch, never a correctness dependency. It changes whenever it needs to, in any release. -- **Rendered HTML details.** org-ssg tracks what Emacs exports from the same file, and +- **Rendered HTML details.** orgo tracks what Emacs exports from the same file, and closing a gap changes markup. Changes that affect output are called out in [CHANGELOG.md](CHANGELOG.md) — the class names the documentation names (`post-list`, `figure-number`, `section-number-N`, `footnote-ref`) are the ones to write CSS against. - **The Rust API.** The crate is published so the binary can be installed with `cargo install`; the library exists to serve it, and its types move as the tool does. -The **MSRV is 1.88**, checked in CI on every change. org-ssg's own code compiles on +The **MSRV is 1.88**, checked in CI on every change. orgo's own code compiles on 1.82; the floor comes from dependencies. Raising it is a minor version, never a patch. ## Dependencies @@ -7,14 +7,15 @@ reports `0.17.0` is the kind of mistake nobody notices for months. ## Before the first publish -`repository` in `Cargo.toml` is commented out, because a wrong URL on a crates.io page is -worse than none. Set it, then: - ```bash cargo login # a crates.io token, once per machine cargo publish --dry-run ``` +`repository` and `homepage` in `Cargo.toml` point at GitHub and at the documentation site +on Pages. If git.krz.sh becomes the primary remote, `repository` should follow it — +crates.io shows that link on the crate page, and it should lead somewhere you read. + ## Every release 1. **Write the changelog entry first.** [CHANGELOG.md](CHANGELOG.md) names behaviour, not @@ -7,7 +7,7 @@ =build=, =serve= and =watch= all take the same pair: #+BEGIN_SRC sh -org-ssg <command> <SOURCE> -o <OUTPUT> +orgo <command> <SOURCE> -o <OUTPUT> #+END_SRC *SOURCE is the URL root*, not "the project". =SOURCE/blog/post.org= is published at @@ -22,7 +22,7 @@ consumes its own output. * build #+BEGIN_SRC sh -org-ssg build <INPUT> -o <OUTPUT> [--no-cache] [--strict] [--drafts] [--config FILE] +orgo build <INPUT> -o <OUTPUT> [--no-cache] [--strict] [--drafts] [--config FILE] #+END_SRC If =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 | =--no-cache= | Ignore the incremental cache and re-render every page. | | =--strict= | Broken internal links and parse diagnostics become a non-zero exit. | | =--drafts= | Include pages marked =#+DRAFT:=. | -| =--config FILE= | Use this config instead of =org-ssg.toml= in the source directory. | +| =--config FILE= | Use this config instead of =orgo.toml= in the source directory. | The summary line reports what happened: @@ -52,13 +52,13 @@ Without it, a broken link is a warning and the build succeeds. With it, the buil and names every problem. Use it wherever a bad build should not ship: #+BEGIN_SRC sh -org-ssg build content -o _site --strict +orgo build content -o _site --strict #+END_SRC * serve #+BEGIN_SRC sh -org-ssg serve <INPUT> -o <OUTPUT> [-p PORT] [--host HOST] [--drafts] [--config FILE] +orgo serve <INPUT> -o <OUTPUT> [-p PORT] [--host HOST] [--drafts] [--config FILE] #+END_SRC Builds, watches, serves, and reloads the browser when a rebuild lands. This is the @@ -74,7 +74,7 @@ command to use while writing. laptop, so reaching the local network is something you ask for: #+BEGIN_SRC sh -org-ssg serve content -o _site --host 0.0.0.0 +orgo serve content -o _site --host 0.0.0.0 #+END_SRC The 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 * watch #+BEGIN_SRC sh -org-ssg watch <INPUT> -o <OUTPUT> [--no-cache] [--strict] [--drafts] [--config FILE] +orgo watch <INPUT> -o <OUTPUT> [--no-cache] [--strict] [--drafts] [--config FILE] #+END_SRC Rebuilds 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. * audit #+BEGIN_SRC sh -org-ssg audit <INPUT> +orgo audit <INPUT> #+END_SRC -Reports which org constructs a corpus uses and how they land against what org-ssg +Reports which org constructs a corpus uses and how they land against what orgo supports, plus a census of every keyword, block type, drawer and link scheme seen. Point it at your notes before trusting a tool with them. See [[file:../guide/09-auditing.org][Auditing a corpus]]. @@ -105,7 +105,7 @@ private notes is safe to share. * init #+BEGIN_SRC sh -org-ssg init [DIRECTORY] +orgo init [DIRECTORY] #+END_SRC Scaffolds 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 * clean #+BEGIN_SRC sh -org-ssg clean <OUTPUT> +orgo clean <OUTPUT> #+END_SRC Removes the output directory, including the incremental cache manifest inside it. You @@ -1,8 +1,8 @@ #+TITLE: Configuration -#+DESCRIPTION: Every setting in org-ssg.toml, what it changes, and what it costs. +#+DESCRIPTION: Every setting in orgo.toml, what it changes, and what it costs. #+LEDE: All of it optional. A missing config is a valid config. -org-ssg looks for =org-ssg.toml= in the source directory. Pass a different path with +orgo looks for =orgo.toml= in the source directory. Pass a different path with =--config=. Every field has a default, so a directory of org files with no config still builds a complete site. @@ -14,7 +14,7 @@ lose an afternoon. #+BEGIN_SRC toml [site] -title = "org-ssg site" +title = "orgo site" base_url = "" description = "" language = "en" @@ -47,7 +47,7 @@ Plus any number of =[[collections]]= blocks, documented in [[file:03-collections | Key | Default | Meaning | |-----+---------+---------| -| =title= | ="org-ssg site"= | Site name. Available as ={{ site.title }}=. | +| =title= | ="orgo site"= | Site name. Available as ={{ site.title }}=. | | =base_url= | ="" | Absolute origin, *no trailing slash*. | | =description= | ="" | Available as ={{ site.description }}=. | | =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. Highlighting emits *CSS classes*, never inline styles, so themes live in a stylesheet. Each build writes =syntax.css= into the output and every page links it. -org-ssg bundles TOML and Org on top of syntect's built-in languages. Anything else +orgo bundles TOML and Org on top of syntect's built-in languages. Anything else missing is a file away: put a =.sublime-syntax= definition in =syntaxes_dir= and it is loaded. A definition that fails to parse is reported and skipped, because one bad file should not stop a site from building. @@ -258,6 +258,6 @@ Off is spelled =nil=, =false=, =no=, =0= or =off=; anything else is on. * Configuration is a cache input -The resolved config is hashed into every page's render key, so editing =org-ssg.toml= +The resolved config is hashed into every page's render key, so editing =orgo.toml= re-renders exactly the pages it affects — which for most settings is all of them. You never need =--no-cache= after a config change. @@ -230,7 +230,7 @@ title = "Feed" #+END_SRC This needs =site.base_url=, because a feed with relative links is invalid everywhere it -is read. =org-ssg init= writes this template and leaves the collection commented out +is read. =orgo init= writes this template and leaves the collection commented out until there is a base URL to make absolute links from. * Every setting @@ -122,7 +122,7 @@ Empty on generated pages, which build their content from =pages= or =groups= ins | =keywords= | *Every* =#+KEYWORD:=, by lowercased name. | =page.keywords= is the escape hatch: =#+CUSTOM_THING: x= is -={{ page.keywords.custom_thing }}=, so your own metadata works without org-ssg knowing it +={{ page.keywords.custom_thing }}=, so your own metadata works without orgo knowing it exists. ** site @@ -2,7 +2,7 @@ #+DESCRIPTION: Exactly which org syntax is handled, which is not, and how the rest degrades. #+LEDE: A deliberate subset, with the boundary enforced by tests rather than by hope. -org-ssg parses a defined slice of org. The boundary is not aspirational: every supported +orgo parses a defined slice of org. The boundary is not aspirational: every supported construct has a golden-file test, and every excluded one has a test asserting how it degrades. That is what stops the parser drifting toward all-of-org. @@ -37,7 +37,7 @@ machine-readable =datetime=. *** Text conversions -Org rewrites some prose on export, and so does org-ssg: +Org rewrites some prose on export, and so does orgo: | Written | Published | |---------+-----------| @@ -94,7 +94,7 @@ An =html= export block passes through verbatim; every other backend is dropped, emitting LaTeX into an HTML page is worse than emitting nothing. *Any other name is a special block*: =#+BEGIN_NOTE= becomes =<div class="note">= holding -*parsed org*, which is what makes the convention usable without org-ssg knowing the word +*parsed org*, which is what makes the convention usable without orgo knowing the word "note". A =COMMENT= block is not published. *** Which languages highlight @@ -108,7 +108,7 @@ Recognised, among others: =bash= / =sh=, =c=, =c++=, =css=, =clojure=, =diff=, = =makefile=, =markdown=, =matlab=, =objective-c=, =ocaml=, =perl=, =php=, =python=, =r=, =ruby=, =rust=, =scala=, =sql=, =tcl=, =xml=, =yaml=. -org-ssg adds two syntect does not ship: *TOML* and *Org*. Both are what this project's +orgo adds two syntect does not ship: *TOML* and *Org*. Both are what this project's own documentation needed on its first page — every config example is TOML, and a tool for org users gets written about in org — so they are compiled into the binary and work with no setup. @@ -121,7 +121,7 @@ to parse is reported and skipped rather than failing the build. *** The comma escape A line inside a block that would otherwise look like document structure is written with a -leading comma — =,* heading=, =,#+KEYWORD:= — and org-ssg removes exactly one comma on +leading comma — =,* heading=, =,#+KEYWORD:= — and orgo removes exactly one comma on output, as Emacs does. Every org example in this documentation relies on it. The escape is not optional politeness: an unescaped =*= at column zero *ends the block*, @@ -169,7 +169,7 @@ broken. | =#+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:=. | Every other =#+KEYWORD:= is available to templates as -={{ page.keywords.that_keyword }}=, so metadata org-ssg has never heard of still reaches +={{ page.keywords.that_keyword }}=, so metadata orgo has never heard of still reaches your layout. * Not supported, and what happens instead @@ -219,7 +219,7 @@ source-block text are asserted to match exactly. The rest differs deliberately: -| | org-ssg | Emacs | +| | orgo | Emacs | |-+---------+-------| | emphasis | =<em>= / =<strong>= | =<i>= / =<b>= | | captioned image | =<figure>= / =<figcaption>= | =<p>= + "Figure 1: …" | @@ -229,6 +229,6 @@ The rest differs deliberately: | code | =<pre><code>= | =<pre>= | One genuine semantic difference: org treats a single blank line between a =1.= list and a -following =-= list as *one* list, keeping the first item's bullet type. org-ssg starts a +following =-= list as *one* list, keeping the first item's bullet type. orgo starts a second list. That was kept on measurement — the pattern occurred zero times across a 179-file reference corpus — rather than on taste. @@ -39,7 +39,7 @@ That is deliberate: it is what that link would be on the published site, and bet now than by a reader. #+BEGIN_SRC sh -org-ssg serve content -o _site --drafts +orgo serve content -o _site --drafts #+END_SRC The 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: {% if page.keywords.subtitle %}<p class="subtitle">{{ page.keywords.subtitle }}</p>{% endif %} #+END_SRC -Nothing needs to be registered, and org-ssg needs no release to support a keyword you +Nothing needs to be registered, and orgo needs no release to support a keyword you invented. * Assets @@ -138,9 +138,9 @@ Four things are *never* published: - Dot-entries such as =.git= and =.env=. A source directory is often a repository, and publishing its history next to the homepage is a real way to leak a project. -- =org-ssg.toml=, which is a build input. +- =orgo.toml=, which is a build input. - The templates directory, likewise. -- The output directory, when it lives inside the source — so =org-ssg build . -o _site= +- The output directory, when it lives inside the source — so =orgo build . -o _site= does the obvious thing rather than copying its own output back into itself. Note that excluding dot-entries also means =.well-known/= cannot be published. @@ -2,7 +2,7 @@ #+DESCRIPTION: How the cache decides what to re-render, and why that shape is the architecture. #+LEDE: Editing one post rebuilds four pages, whatever the size of the site. -Incremental rebuilding is not an optimisation bolted on to org-ssg; it is the constraint +Incremental rebuilding is not an optimisation bolted on to orgo; it is the constraint the data model was built around. Parsing is a pure function of one file's bytes, link resolution reports the edges it used, and rendering is a pure function of a resolved document. 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. * What you observe #+BEGIN_EXAMPLE -$ org-ssg build content -o _site +$ orgo build content -o _site built 182 page(s) (182 rendered, 0 cached) ... -$ org-ssg build content -o _site +$ orgo build content -o _site built 182 page(s) (0 rendered, 182 cached) ... -$ vim content/blog/post.org && org-ssg build content -o _site +$ vim content/blog/post.org && orgo build content -o _site built 182 page(s) (4 rendered, 178 cached) ... #+END_EXAMPLE @@ -35,7 +35,7 @@ Every page has a key composed from four hashes: |-----------+--------------| | content | The source file's bytes change. | | resolved links | A link's target moves, is renamed, or disappears. | -| config | =org-ssg.toml= changes, or the shared chrome does. | +| config | =orgo.toml= changes, or the shared chrome does. | | templates | *This page's* layout changes, or something that layout extends or includes. | If 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 %}= followed, so it is treated as depending on every template. Over-invalidating costs time; under-invalidating publishes a stale page. -The cache lives in =<output>/.org-ssg-cache.json= and is tagged with a format version. A +The cache lives in =<output>/.orgo-cache.json= and is tagged with a format version. A version mismatch, a missing file or a corrupt file all fall back to a full rebuild — the cache is an optimisation, never a correctness dependency. There is a test for each of those three fallbacks. @@ -124,5 +124,5 @@ correctly on their own. It exists to answer "is the cache lying to me?" — and is, that is a bug worth reporting, with the two builds' output to compare. #+BEGIN_SRC sh -org-ssg build content -o _site --no-cache +orgo build content -o _site --no-cache #+END_SRC @@ -5,7 +5,7 @@ * serve #+BEGIN_SRC sh -org-ssg serve content -o _site +orgo serve content -o _site #+END_SRC Builds, 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 * watch #+BEGIN_SRC sh -org-ssg watch content -o _site +orgo watch content -o _site #+END_SRC The 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 The rule for what triggers one is deliberately *not* the rule the build uses to find content — the question is "would this change the site?", not "is this a page?". -*Triggers a rebuild:* any =.org= file, any asset, =org-ssg.toml=, and anything in the +*Triggers a rebuild:* any =.org= file, any asset, =orgo.toml=, and anything in the templates directory. The last two are skipped by the build when looking for content, but both change the output. @@ -86,7 +86,7 @@ blog/post.org changed: 2 rendered, 180 cached * Where native watching is unavailable -Some network and container filesystems have no event API. org-ssg falls back to polling +Some network and container filesystems have no event API. orgo falls back to polling every two seconds and says so, rather than failing: #+BEGIN_EXAMPLE @@ -106,10 +106,10 @@ note: native file watching unavailable (...); polling every 2s #+BEGIN_SRC sh # One terminal, left running. -org-ssg serve content -o _site --drafts +orgo serve content -o _site --drafts # Write. The browser keeps up. # Before publishing, check what a real build says. -org-ssg build content -o _site --strict +orgo build content -o _site --strict #+END_SRC @@ -3,7 +3,7 @@ #+LEDE: Construct frequencies, unknown-name census, and no document text in the output. #+BEGIN_SRC sh -org-ssg audit ~/notes +orgo audit ~/notes #+END_SRC The 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: 1. *Coverage.* Of the constructs this corpus uses, which are supported? A construct that is common here and unsupported is a problem with the tool's scope, not with your writing. -2. *Blind spots.* Which names appear that org-ssg has no opinion about at all? These are +2. *Blind spots.* Which names appear that orgo has no opinion about at all? These are the dangerous ones — not "known unsupported" but unknown. * Reading the output @@ -40,7 +40,7 @@ KEYWORDS - =IN= is supported; =OUT= is excluded by design and degrades as described in [[file:05-org-support.org][Org support]]. - The *coverage* line is the number to look at first. -- =???= marks a name org-ssg does not recognise at all — in this example =#+SLUG:=, from +- =???= marks a name orgo does not recognise at all — in this example =#+SLUG:=, from a run made before it was supported. Four censuses follow the construct table: every distinct =#+KEYWORD:=, block type, @@ -80,13 +80,13 @@ green run; it simply measures one thing less. #+BEGIN_SRC sh # What is in there? -org-ssg audit ~/notes +orgo audit ~/notes # Build it and see what the builder itself complains about. -org-ssg build ~/notes -o /tmp/preview --strict +orgo build ~/notes -o /tmp/preview --strict # Look at the result. -org-ssg serve ~/notes -o /tmp/preview +orgo serve ~/notes -o /tmp/preview #+END_SRC =--strict= surfaces broken internal links and malformed constructs as failures rather @@ -5,7 +5,7 @@ * The production build #+BEGIN_SRC sh -org-ssg build content -o _site --strict +orgo build content -o _site --strict #+END_SRC Two differences from the build you run while writing: @@ -34,12 +34,12 @@ No trailing slash. * One thing to exclude -The build writes =.org-ssg-cache.json= into the output directory. It is a dot-file, so +The build writes =.orgo-cache.json= into the output directory. It is a dot-file, so most static hosts ignore it, but it is not part of the site — exclude it if your host uploads everything: #+BEGIN_SRC sh -rsync -a --delete --exclude '.org-ssg-cache.json' _site/ user@host:/var/www/site/ +rsync -a --delete --exclude '.orgo-cache.json' _site/ user@host:/var/www/site/ #+END_SRC Keeping the cache *between* deploys, where the CI runner can see it, is what makes CI @@ -57,7 +57,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo install --path . - - run: org-ssg build content -o _site --strict + - run: orgo build content -o _site --strict - uses: actions/upload-artifact@v4 with: name: site @@ -69,7 +69,7 @@ failed build. ** Caching between runs -Cache =_site/.org-ssg-cache.json= *and* =_site= together, or not at all. The manifest +Cache =_site/.orgo-cache.json= *and* =_site= together, or not at all. The manifest describes files it expects to find; a cache without its outputs simply triggers a full rebuild, which is correct but pointless. @@ -78,16 +78,16 @@ builds is rarely worth the configuration. * Static hosts -Nothing here is org-ssg-specific; a built site is ordinary static files. +Nothing here is orgo-specific; a built site is ordinary static files. - *Netlify, Vercel, Cloudflare Pages*: publish directory =_site=, build command - =cargo install --path . && org-ssg build content -o _site --strict=. + =cargo install --path . && orgo build content -o _site --strict=. - *GitHub Pages*: upload =_site= as the Pages artifact. - *Any web server*: copy =_site= to the document root. ** URLs end in .html -org-ssg writes =blog/post.html= and links to it that way, so the site works with no +orgo writes =blog/post.html= and links to it that way, so the site works with no server configuration at all — including opening it from a filesystem path. If 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 * Checking a build before shipping #+BEGIN_SRC sh -org-ssg build content -o _site --strict -org-ssg serve content -o _site +orgo build content -o _site --strict +orgo serve content -o _site #+END_SRC Serving the production build locally is the last check worth doing: it catches a missing @@ -11,7 +11,7 @@ Changing any of this incompatibly requires a major version. | Stable | What that covers | |--------+------------------| -| =org-ssg.toml= keys | Their names, types and meaning. | +| =orgo.toml= keys | Their names, types and meaning. | | Template context | =page=, =site=, =nav=, =root=, =pages=, =group=, =groups=, =paginator=, =stylesheet=, and the =absolute=, =rfc822= and =truncate= filters. | | The CLI | Command names, flags and exit codes. | | 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. ** The incremental cache -=<output>/.org-ssg-cache.json= is versioned and discards itself on a mismatch. A cache +=<output>/.orgo-cache.json= is versioned and discards itself on a mismatch. A cache format bump means one full rebuild, and nothing else. It is never a correctness dependency: a missing, stale or corrupt cache produces exactly the same site, more slowly. ** Rendered HTML details -org-ssg aims at what Emacs exports from the same file, and closing a gap changes markup. +orgo aims at what Emacs exports from the same file, and closing a gap changes markup. That is the product working rather than a regression — but it is called out in the changelog every time, because your stylesheet is downstream of it. @@ -50,7 +50,7 @@ exists to serve the binary, and its types move as the tool does. * The compiler floor The MSRV is *1.88*, checked in CI on every change rather than assumed — which is how it -came to be 1.88 rather than the 1.82 org-ssg's own code needs. The floor is set by +came to be 1.88 rather than the 1.82 orgo's own code needs. The floor is set by dependencies, and a dependency raising its own is invisible until someone on an older compiler tries to build. @@ -59,8 +59,8 @@ Raising it is a minor version, never a patch. * Upgrading #+BEGIN_SRC sh -cargo install org-ssg # or download a release binary -org-ssg build content -o _site --no-cache --strict +cargo install orgo # or download a release binary +orgo build content -o _site --no-cache --strict #+END_SRC =--no-cache= makes the first build after an upgrade a full one, so you are comparing the @@ -1,9 +1,9 @@ -#+TITLE: org-ssg +#+TITLE: orgo #+DESCRIPTION: An org-mode static site generator in Rust, where the org element tree is the document model. #+LEDE: Org is the source language, not an inconvenient input to be normalised into markdown. #+OPTIONS: toc:nil -org-ssg turns a directory of =.org= files into a static website. It treats org as the +orgo turns a directory of =.org= files into a static website. It treats org as the *source language*: the org element tree — headings, drawers, blocks, links with their org-specific semantics — /is/ the document model, and that tree is rendered straight to HTML. 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 Point it at a directory of org files and you get a complete site: pages, navigation, syntax-highlighted code, and the stylesheet that colours it. Nothing about your files has -to change, and no =org-ssg.toml= is required. +to change, and no =orgo.toml= is required. #+BEGIN_SRC sh -org-ssg build ~/notes -o _site +orgo build ~/notes -o _site #+END_SRC Configuration 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]]. ** It tells you what your corpus actually uses #+BEGIN_SRC sh -org-ssg audit ~/notes +orgo audit ~/notes #+END_SRC The 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. * Status -This documentation site is itself an org-ssg site — the sources are in =docs/= and it is +This documentation site is itself an orgo site — the sources are in =docs/= and it is built with the command in [[file:quickstart.org][Quick start]]. If a feature is described here, it is being used to render the page describing it. @@ -1,5 +1,5 @@ #+TITLE: Install -#+DESCRIPTION: Build org-ssg from source, put it on your PATH, and check that it works. +#+DESCRIPTION: Build orgo from source, put it on your PATH, and check that it works. #+LEDE: One Rust toolchain, one command, no runtime dependencies. * Requirements @@ -13,15 +13,15 @@ * From source #+BEGIN_SRC sh -git clone <repository-url> org-ssg -cd org-ssg +git clone <repository-url> orgo +cd orgo cargo build --release #+END_SRC -The binary lands at =target/release/org-ssg=. Copy it somewhere on your =PATH=: +The binary lands at =target/release/orgo=. Copy it somewhere on your =PATH=: #+BEGIN_SRC sh -cp target/release/org-ssg ~/.local/bin/ +cp target/release/orgo ~/.local/bin/ #+END_SRC Or let cargo do it, which puts it in =~/.cargo/bin=: @@ -33,7 +33,7 @@ cargo install --path . * Running without installing Every command in this documentation works through cargo if you would rather not install -anything. Replace =org-ssg= with =cargo run --= and add =--release= for a fast build: +anything. Replace =orgo= with =cargo run --= and add =--release= for a fast build: #+BEGIN_SRC sh cargo run --release -- build my-site -o _site @@ -45,9 +45,9 @@ syntax highlighting dominates and is not optimised in a debug profile. * Check that it works #+BEGIN_SRC sh -org-ssg --version -org-ssg init /tmp/org-ssg-check -org-ssg build /tmp/org-ssg-check -o /tmp/org-ssg-check/_site +orgo --version +orgo init /tmp/orgo-check +orgo build /tmp/orgo-check -o /tmp/orgo-check/_site #+END_SRC You should see a line reporting the pages built: @@ -56,10 +56,10 @@ You should see a line reporting the pages built: built 5 page(s) (5 rendered, 0 cached), copied 0 asset(s) ... (0 unresolved link(s), 0 diagnostic(s)) #+END_EXAMPLE -Open =/tmp/org-ssg-check/_site/index.html= in a browser, or serve it properly: +Open =/tmp/orgo-check/_site/index.html= in a browser, or serve it properly: #+BEGIN_SRC sh -org-ssg serve /tmp/org-ssg-check -o /tmp/org-ssg-check/_site +orgo serve /tmp/orgo-check -o /tmp/orgo-check/_site #+END_SRC * Running the test suite @@ -82,13 +82,13 @@ still gets a green test run — it simply measures one thing less. * Upgrading -org-ssg stores an incremental cache in =<output>/.org-ssg-cache.json=, tagged with a +orgo stores an incremental cache in =<output>/.orgo-cache.json=, tagged with a format version. A newer binary that changes how output is produced bumps that version, and a version it does not recognise is discarded in favour of a full rebuild. You never need to clear the cache by hand after an upgrade — but if you want to: #+BEGIN_SRC sh -org-ssg clean _site +orgo clean _site #+END_SRC * Next similarity index 89% rename from docs/org-ssg.toml rename to docs/orgo.toml @@ -1,10 +1,10 @@ -# Configuration for the org-ssg documentation site. +# Configuration for the orgo documentation site. # -# This site is built by org-ssg itself, so this file doubles as a worked example: every +# This site is built by orgo itself, so this file doubles as a worked example: every # setting here is one the docs describe, used the way the docs recommend. [site] -title = "org-ssg" +title = "orgo" description = "An org-mode static site generator, in Rust." language = "en" # Left empty so the docs build with relative URLs and open from the filesystem. Set it to @@ -5,8 +5,8 @@ * Two commands #+BEGIN_SRC sh -org-ssg init my-site -org-ssg serve my-site -o _site +orgo init my-site +orgo serve my-site -o _site #+END_SRC Open [[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. #+BEGIN_EXAMPLE my-site/ - org-ssg.toml every setting, at its default, commented + orgo.toml every setting, at its default, commented index.org the home page blog/first-post.org a post, to show the collection working templates/ @@ -35,7 +35,7 @@ You do not need =init=, a config file, or templates. Every command takes the sam paths: #+BEGIN_SRC sh -org-ssg serve <SOURCE> -o <OUTPUT> +orgo serve <SOURCE> -o <OUTPUT> #+END_SRC ** SOURCE is the URL root @@ -49,14 +49,14 @@ repository around it: #+BEGIN_SRC sh cd ~/my-site -org-ssg serve content -o _site # → /blog/post.html +orgo serve content -o _site # → /blog/post.html #+END_SRC Pointing one level too high still builds, which is what makes it worth saying out loud. It just builds the wrong site: #+BEGIN_SRC sh -org-ssg serve . -o _site # → /content/blog/post.html +orgo serve . -o _site # → /content/blog/post.html #+END_SRC Every 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. | Your files | Command | |------------+---------| -| =~/notes/*.org= | =org-ssg serve ~/notes -o /tmp/notes-site= | -| =my-site/content/**/*.org= | =cd my-site && org-ssg serve content -o _site= | -| =my-site/*.org= at the top level | =cd my-site && org-ssg serve . -o _site= | +| =~/notes/*.org= | =orgo serve ~/notes -o /tmp/notes-site= | +| =my-site/content/**/*.org= | =cd my-site && orgo serve content -o _site= | +| =my-site/*.org= at the top level | =cd my-site && orgo serve . -o _site= | | Org files scattered in a code repo | Do not. Copy or symlink the ones you publish into one directory. | ** OUTPUT can live inside the source -=org-ssg serve . -o _site= is fine: the output directory is recognised and skipped, so +=orgo serve . -o _site= is fine: the output directory is recognised and skipped, so the build never copies its own output back into itself. Nothing dot-prefixed is published either, so =.git= stays out of a site built from a repository root. ** Where config and templates go -Both live in the *source* directory — =SOURCE/org-ssg.toml= and =SOURCE/templates/= — and +Both live in the *source* directory — =SOURCE/orgo.toml= and =SOURCE/templates/= — and neither is published. If you would rather keep the config elsewhere, name it: #+BEGIN_SRC sh -org-ssg serve content -o _site --config config/org-ssg.toml +orgo serve content -o _site --config config/orgo.toml #+END_SRC ** A worked example @@ -95,13 +95,13 @@ A repository laid out as =content/= (org files), =theme/= (unrelated), =build.py cd ~/my-site # What is actually in there, before trusting anything with it. -org-ssg audit content +orgo audit content # Build it somewhere disposable and look. -org-ssg serve content -o /tmp/preview +orgo serve content -o /tmp/preview # Happy with it? Build for real, failing on broken links. -org-ssg build content -o _site --strict +orgo build content -o _site --strict #+END_SRC The 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 you work with =--drafts=: #+BEGIN_SRC sh -org-ssg serve my-site -o _site --drafts +orgo serve my-site -o _site --drafts #+END_SRC * Change the design @@ -193,7 +193,7 @@ still looking at it. The full list of variables is in [[file:guide/04-templates. * Add a blog index -Listing pages have no source file; they are declared in =org-ssg.toml=: +Listing pages have no source file; they are declared in =orgo.toml=: #+BEGIN_SRC toml [[collections]] @@ -212,7 +212,7 @@ settings. See [[file:guide/03-collections.org][Collections]]. * Build for real #+BEGIN_SRC sh -org-ssg build my-site -o _site --strict +orgo build my-site -o _site --strict #+END_SRC =--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 * Next - [[file:guide/01-cli.org][Command reference]] — every command and flag. -- [[file:guide/02-configuration.org][Configuration]] — every setting in =org-ssg.toml=. +- [[file:guide/02-configuration.org][Configuration]] — every setting in =orgo.toml=. - [[file:guide/05-org-support.org][Org support]] — exactly which org syntax is handled. @@ -74,7 +74,7 @@ code { } /* Code blocks are dark in both colour schemes, matching the syntect theme named in - org-ssg.toml. One generated stylesheet cannot follow prefers-color-scheme, so the page + orgo.toml. One generated stylesheet cannot follow prefers-color-scheme, so the page commits to the theme's palette instead of leaving highlighted code unreadable in one of the two. */ pre { @@ -45,7 +45,7 @@ {%- endif %} {% block content %}{{ body | safe }}{% endblock %}</main> <footer class="site"> -Built with org-ssg — these docs are an org-ssg site. +Built with orgo — these docs are an orgo site. </footer> </body> </html> @@ -1,3 +1,3 @@ #+TITLE: About -This site is built by org-ssg. Back to [[file:index.org][home]]. +This site is built by orgo. Back to [[file:index.org][home]]. @@ -1,4 +1,4 @@ -//! User-facing build configuration (`org-ssg.toml`). +//! User-facing build configuration (`orgo.toml`). //! //! Everything here was once a constant in the source: the page layout, the nav rule, the //! highlighting theme. That made the generator produce exactly one kind of site — a @@ -7,10 +7,10 @@ //! Two properties matter beyond the settings themselves: //! //! 1. **Absent config is a valid config.** Every field has a default, so a directory of -//! `.org` files with no `org-ssg.toml` still builds. Configuration is how you change +//! `.org` files with no `orgo.toml` still builds. Configuration is how you change //! the output, never how you make it work at all. //! 2. **Config is a hash input** (spec §4.1). [`Config`] serializes deterministically and -//! its hash is folded into every page's render key, so editing `org-ssg.toml` re-renders +//! its hash is folded into every page's render key, so editing `orgo.toml` re-renders //! exactly the pages it affects — which for most settings is all of them. use anyhow::{Context, Result}; @@ -18,7 +18,7 @@ use camino::{Utf8Path, Utf8PathBuf}; use serde::{Deserialize, Serialize}; /// The config file's name, looked for in the source directory. -pub const CONFIG_FILE: &str = "org-ssg.toml"; +pub const CONFIG_FILE: &str = "orgo.toml"; /// Resolved build configuration. Serialized into the config hash, so field order and /// defaults are part of the cache contract. @@ -319,7 +319,7 @@ pub struct Site { impl Default for Site { fn default() -> Self { Site { - title: "org-ssg site".to_string(), + title: "orgo site".to_string(), base_url: String::new(), description: String::new(), language: "en".to_string(), @@ -405,7 +405,7 @@ impl Default for Highlight { } impl Config { - /// Load `org-ssg.toml` from `dir`, or return defaults if there is none. + /// Load `orgo.toml` from `dir`, or return defaults if there is none. /// /// A *missing* config is normal and silent. A *malformed* one is an error: someone /// who wrote a config meant it, and silently building the default site would hide @@ -533,13 +533,13 @@ impl Config { } } -/// The starter config written by `org-ssg init`, and the documentation of record for +/// The starter config written by `orgo init`, and the documentation of record for /// what is configurable. Every value shown is the default, so deleting any line is safe. -pub const STARTER_CONFIG: &str = r#"# org-ssg configuration. Every setting here is optional and shown at its default, +pub const STARTER_CONFIG: &str = r#"# orgo configuration. Every setting here is optional and shown at its default, # so you can delete any line you do not need — or the whole file. [site] -title = "org-ssg site" +title = "orgo site" # Absolute base URL, no trailing slash. Needed for feeds and canonical links, which # cannot be relative — set it and uncomment the [[collections]] feed block below. base_url = "" @@ -574,7 +574,7 @@ expose_page_list = false # A syntect theme name: InspiredGitHub, Solarized (dark), base16-ocean.dark, # base16-eighties.dark, base16-mocha.dark, base16-ocean.light. theme = "InspiredGitHub" -# Extra .sublime-syntax files for languages neither syntect nor org-ssg bundles. +# Extra .sublime-syntax files for languages neither syntect nor orgo bundles. syntaxes_dir = "syntaxes" [build] @@ -35,7 +35,7 @@ pub const CACHE_FORMAT_VERSION: u32 = 7; pub type Hash = ContentHash; /// The resolved global build config is [`crate::config::Config`]; its hash is a -/// component of every page's render key (spec §4.1), so editing `org-ssg.toml` +/// component of every page's render key (spec §4.1), so editing `orgo.toml` /// invalidates the pages it affects. pub use crate::config::Config as BuildConfig; @@ -199,7 +199,7 @@ pub struct Manifest { /// The cache-manifest file lives inside the output directory (spec §4.5: an on-disk /// cache dir). `clean` removes the output directory, taking the cache with it. pub fn manifest_path(out: &Utf8Path) -> Utf8PathBuf { - out.join(".org-ssg-cache.json") + out.join(".orgo-cache.json") } /// Load the manifest, returning `None` on ANY of: missing file, read/parse error, or a @@ -1,4 +1,4 @@ -//! org-ssg — an org-mode static site generator. +//! orgo — an org-mode static site generator. //! //! Org is the source language, not an input to be normalized into markdown. The org //! element tree ([`model`]) *is* the document model; we render it straight to HTML. @@ -6,15 +6,15 @@ use anyhow::{Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use clap::{Parser, Subcommand}; -use org_ssg::parser::parse; -use org_ssg::config::{self, Config}; -use org_ssg::render::{self, render, Html, SyntectHighlighter}; -use org_ssg::resolve::ResolvedDoc; -use org_ssg::site::{build_site, BuildOptions, SYNTAX_STYLESHEET}; -use org_ssg::template::{PageContext, RenderContext, SiteContext, Templater}; +use orgo::parser::parse; +use orgo::config::{self, Config}; +use orgo::render::{self, render, Html, SyntectHighlighter}; +use orgo::resolve::ResolvedDoc; +use orgo::site::{build_site, BuildOptions, SYNTAX_STYLESHEET}; +use orgo::template::{PageContext, RenderContext, SiteContext, Templater}; #[derive(Parser)] -#[command(name = "org-ssg", version, about = "Org-mode static site generator")] +#[command(name = "orgo", version, about = "Org-mode static site generator")] struct Cli { #[command(subcommand)] command: Command, @@ -36,7 +36,7 @@ enum Command { /// Treat broken links and parse diagnostics as errors (spec §4.3.4). #[arg(long)] strict: bool, - /// Config file to use, overriding `org-ssg.toml` in the source directory. + /// Config file to use, overriding `orgo.toml` in the source directory. #[arg(long, value_name = "FILE")] config: Option<Utf8PathBuf>, /// Include pages marked `#+DRAFT:`. @@ -57,7 +57,7 @@ enum Command { /// Treat broken links and parse diagnostics as errors. #[arg(long)] strict: bool, - /// Config file to use, overriding `org-ssg.toml` in the source directory. + /// Config file to use, overriding `orgo.toml` in the source directory. #[arg(long, value_name = "FILE")] config: Option<Utf8PathBuf>, /// Include pages marked `#+DRAFT:`. Handy while writing one. @@ -81,7 +81,7 @@ enum Command { /// Include pages marked `#+DRAFT:`. #[arg(long)] drafts: bool, - /// Config file to use, overriding `org-ssg.toml` in the source directory. + /// Config file to use, overriding `orgo.toml` in the source directory. #[arg(long, value_name = "FILE")] config: Option<Utf8PathBuf>, }, @@ -151,7 +151,7 @@ fn main() -> Result<()> { strict, config, drafts, - } => org_ssg::watch::run( + } => orgo::watch::run( &input, &output, &BuildOptions { @@ -162,8 +162,8 @@ fn main() -> Result<()> { }, ), Command::Audit { input } => { - let result = org_ssg::audit::audit(&input)?; - print!("{}", org_ssg::audit::report(&result)); + let result = orgo::audit::audit(&input)?; + print!("{}", orgo::audit::report(&result)); Ok(()) } Command::Serve { @@ -173,7 +173,7 @@ fn main() -> Result<()> { host, drafts, config, - } => org_ssg::serve::run( + } => orgo::serve::run( &input, &output, &BuildOptions { @@ -201,8 +201,8 @@ fn main() -> Result<()> { /// Scaffold a working site. Writes only files that do not already exist, so running it /// in a directory that has content is safe and additive rather than destructive. fn init(dir: &Utf8Path) -> Result<()> { - use org_ssg::config::{CONFIG_FILE, STARTER_CONFIG}; - use org_ssg::template::{ + use orgo::config::{CONFIG_FILE, STARTER_CONFIG}; + use orgo::template::{ starter_template, STARTER_FEED_TEMPLATE, STARTER_LIST_TEMPLATE, STARTER_TAGS_TEMPLATE, }; @@ -234,7 +234,7 @@ fn init(dir: &Utf8Path) -> Result<()> { "#+FILETAGS: :example:\n", "\n", "Posts in this directory are collected into /blog/ by the [[collections]] block\n", - "in org-ssg.toml, newest first.\n", + "in orgo.toml, newest first.\n", ); let files: [(Utf8PathBuf, &str); 7] = [ @@ -260,7 +260,7 @@ fn init(dir: &Utf8Path) -> Result<()> { for path in &created { println!("created {path}"); } - println!("\nNext: org-ssg build {dir} -o _site"); + println!("\nNext: orgo build {dir} -o _site"); Ok(()) } @@ -321,7 +321,7 @@ fn build_file(input: &Utf8Path, output: &Utf8Path) -> Result<()> { word_count: 0, reading_time: 0, keywords: Default::default(), - toc: org_ssg::util::table_of_contents(&resolved.document.root), + toc: orgo::util::table_of_contents(&resolved.document.root), }; let mut ctx = RenderContext::new(&site, &page_ctx, &[], SYNTAX_STYLESHEET, ""); ctx.body = &fragment; @@ -45,7 +45,7 @@ const CLASS_STYLE: ClassStyle = ClassStyle::Spaced; /// Syntax definitions syntect does not bundle, compiled into the binary. /// -/// Both are gaps this project hits on its own first page: every `org-ssg.toml` example is +/// Both are gaps this project hits on its own first page: every `orgo.toml` example is /// TOML, and a tool for org users is going to be written about in org. Embedding them /// rather than shipping files means they work with no setup, which is the same promise /// the rest of the zero-config path makes. @@ -928,7 +928,7 @@ fn strip_special_column(table: &crate::model::Table) -> crate::model::Table { /// Split text into alternating prose and LaTeX spans, `(text, is_latex)`. /// -/// org-ssg does not typeset LaTeX — it passes it through for MathJax or a reader's eyes — +/// orgo does not typeset LaTeX — it passes it through for MathJax or a reader's eyes — /// but it must know where a fragment *is*, because the export-time text conversions would /// otherwise rewrite the mathematics: `x^2` inside `$…$` is not a superscript to be /// marked up, and `--` inside one is a minus sign twice. @@ -30,7 +30,7 @@ use crate::site::{build_site, BuildOptions}; /// Where the browser subscribes for reload events. Namespaced so it cannot collide with /// a real page. -pub const RELOAD_PATH: &str = "/__org-ssg/reload"; +pub const RELOAD_PATH: &str = "/__orgo/reload"; /// How long a poll waits before answering "nothing yet". Long enough that an idle tab is /// nearly silent, short enough to stay under any proxy or browser idle timeout. @@ -62,7 +62,7 @@ pub struct BuildOptions { pub no_cache: bool, /// Treat broken internal links as a build error rather than a warning (spec §4.3.4). pub strict: bool, - /// Explicit config file, overriding `org-ssg.toml` in the source directory. + /// Explicit config file, overriding `orgo.toml` in the source directory. pub config_path: Option<Utf8PathBuf>, /// Include pages marked `#+DRAFT:`, overriding `build.drafts` when set. pub drafts: bool, @@ -1368,7 +1368,7 @@ fn collect_assets( /// directory (build input, not content) and the output directory when it lives inside /// the source. /// -/// The output case is not a corner case — `org-ssg build . -o _site` is the obvious +/// The output case is not a corner case — `orgo build . -o _site` is the obvious /// thing to type, and without this the build copies its own output back into itself, /// growing `_site/_site/_site/…` on every run. fn excluded_dirs(src: &Utf8Path, config: &Config, out: Option<&Utf8Path>) -> Vec<Utf8PathBuf> { @@ -375,7 +375,7 @@ impl<'a> RenderContext<'a> { } } -/// The starter tag-index template written by `org-ssg init`: shows how `groups` is +/// The starter tag-index template written by `orgo init`: shows how `groups` is /// iterated, and how a group page is linked. pub const STARTER_TAGS_TEMPLATE: &str = r#"<!DOCTYPE html> <html lang="{{ site.language }}"> @@ -410,7 +410,7 @@ pub const STARTER_TAGS_TEMPLATE: &str = r#"<!DOCTYPE html> </html> "#; -/// The starter listing template written by `org-ssg init`: a blog index, showing how a +/// The starter listing template written by `orgo init`: a blog index, showing how a /// collection's `pages` are iterated. pub const STARTER_LIST_TEMPLATE: &str = r#"<!DOCTYPE html> <html lang="{{ site.language }}"> @@ -486,7 +486,7 @@ fn add_filters(env: &mut Environment<'static>, base_url: &str) { return Err(minijinja::Error::new( minijinja::ErrorKind::InvalidOperation, "the `absolute` filter needs site.base_url, which is empty; \ - set it in org-ssg.toml (e.g. base_url = \"https://example.com\")", + set it in orgo.toml (e.g. base_url = \"https://example.com\")", )); } if path.starts_with("http://") || path.starts_with("https://") { @@ -538,7 +538,7 @@ fn add_filters(env: &mut Environment<'static>, base_url: &str) { }); } -/// The starter RSS feed written by `org-ssg init`. A listing page with an XML template: +/// The starter RSS feed written by `orgo init`. A listing page with an XML template: /// no feed-specific machinery, just `absolute` and `rfc822` doing what syndication needs. /// /// 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 { out } -/// The starter layout written by `org-ssg init`: the built-in template, on disk, ready +/// The starter layout written by `orgo init`: the built-in template, on disk, ready /// to edit. pub fn starter_template() -> &'static str { BASE_TEMPLATE @@ -6,7 +6,7 @@ //! //! Two things matter more than the watching itself: //! -//! 1. **Not watching our own output.** `org-ssg watch . -o _site` puts the output inside +//! 1. **Not watching our own output.** `orgo watch . -o _site` puts the output inside //! the source. Rebuilding writes files, writing files raises events, and events //! trigger a rebuild — a loop that never stops and never idles. [`ChangeFilter`] is //! what prevents it, and it is a pure function precisely so it can be tested without @@ -5,7 +5,7 @@ # needs it. # # This highlights org as *source text you are reading about*, which is a different job -# from parsing it: org-ssg's own parser (src/parser.rs) is what turns org into a +# from parsing it: orgo's own parser (src/parser.rs) is what turns org into a # document. Where the two could disagree, this one stays conservative — a highlighter # that colours something wrongly is a cosmetic bug, and one that swallows a line is not. name: Org @@ -1,7 +1,7 @@ %YAML 1.2 --- # TOML, for syntect. Not one of the definitions syntect bundles, and the first thing a -# config-heavy site needs — every org-ssg.toml example in the documentation is one. +# config-heavy site needs — every orgo.toml example in the documentation is one. # # Scope names are the standard TextMate ones, so any syntect theme colours this without # knowing it exists. @@ -2,7 +2,7 @@ //! generator for one site or for anyone's. //! //! The theme running through these tests is that **the zero-config path has to work**. -//! A directory of `.org` files with no `org-ssg.toml`, no templates and no knowledge of +//! A directory of `.org` files with no `orgo.toml`, no templates and no knowledge of //! this tool must build into a real site; configuration is how you change the output, //! never how you make it work at all. @@ -10,15 +10,15 @@ use std::sync::atomic::{AtomicU32, Ordering}; use camino::Utf8PathBuf; -use org_ssg::config::{Config, NavMode}; -use org_ssg::site::{build_site, BuildOptions}; +use orgo::config::{Config, NavMode}; +use orgo::site::{build_site, BuildOptions}; fn tmpdir(tag: &str) -> Utf8PathBuf { static N: AtomicU32 = AtomicU32::new(0); let n = N.fetch_add(1, Ordering::Relaxed); let base = Utf8PathBuf::from_path_buf(std::env::temp_dir()) .expect("utf-8 temp dir") - .join(format!("org-ssg-cfg-{}-{tag}-{n}", std::process::id())); + .join(format!("orgo-cfg-{}-{tag}-{n}", std::process::id())); let _ = std::fs::remove_dir_all(&base); std::fs::create_dir_all(&base).unwrap(); base @@ -36,7 +36,7 @@ fn write_site(src: &Utf8PathBuf) { .unwrap(); } -fn build(src: &Utf8PathBuf, out: &Utf8PathBuf) -> org_ssg::site::SiteReport { +fn build(src: &Utf8PathBuf, out: &Utf8PathBuf) -> orgo::site::SiteReport { build_site(src, out, &BuildOptions::default()).expect("build") } @@ -81,9 +81,9 @@ fn a_malformed_config_is_an_error_but_a_missing_one_is_not() { assert_eq!(Config::load(&src).unwrap(), Config::default()); - std::fs::write(src.join("org-ssg.toml"), "[site\ntitle = broken").unwrap(); + std::fs::write(src.join("orgo.toml"), "[site\ntitle = broken").unwrap(); let err = Config::load(&src).expect_err("malformed config must fail"); - assert!(format!("{err:#}").contains("org-ssg.toml"), "names the file: {err:#}"); + assert!(format!("{err:#}").contains("orgo.toml"), "names the file: {err:#}"); } /// 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() { let root = tmpdir("unknownkey"); let src = root.join("src"); std::fs::create_dir_all(&src).unwrap(); - std::fs::write(src.join("org-ssg.toml"), "[site]\ntittle = \"typo\"\n").unwrap(); + std::fs::write(src.join("orgo.toml"), "[site]\ntittle = \"typo\"\n").unwrap(); let err = Config::load(&src).expect_err("unknown key must fail"); assert!( @@ -126,7 +126,7 @@ fn nav_modes_select_different_pages() { std::fs::create_dir_all(&src).unwrap(); write_site(&src); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), format!("[nav]\nmode = \"{mode}\"\n"), ) .unwrap(); @@ -155,7 +155,7 @@ fn explicit_nav_uses_the_configured_order() { std::fs::create_dir_all(&src).unwrap(); write_site(&src); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[nav]\nmode = \"explicit\"\npages = [\"blog/post.org\", \"index.org\"]\n", ) .unwrap(); @@ -178,7 +178,7 @@ fn explicit_nav_rejects_a_page_that_does_not_exist() { std::fs::create_dir_all(&src).unwrap(); write_site(&src); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[nav]\nmode = \"explicit\"\npages = [\"nope.org\"]\n", ) .unwrap(); @@ -304,7 +304,7 @@ fn the_page_list_is_opt_in_and_widens_invalidation() { std::fs::create_dir_all(src.join("templates")).unwrap(); write_site(&src); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[templates]\nexpose_page_list = true\n", ) .unwrap(); @@ -352,7 +352,7 @@ fn heading_offset_shifts_content_headings_below_the_page_title() { "a level-1 org heading renders as <h2> by default" ); - std::fs::write(src.join("org-ssg.toml"), "[html]\nheading_offset = 0\n").unwrap(); + std::fs::write(src.join("orgo.toml"), "[html]\nheading_offset = 0\n").unwrap(); let out2 = root.join("out2"); build(&src, &out2); assert!( @@ -369,7 +369,7 @@ fn an_unknown_highlight_theme_is_rejected_with_the_available_ones() { let src = root.join("src"); std::fs::create_dir_all(&src).unwrap(); write_site(&src); - std::fs::write(src.join("org-ssg.toml"), "[highlight]\ntheme = \"nope\"\n").unwrap(); + std::fs::write(src.join("orgo.toml"), "[highlight]\ntheme = \"nope\"\n").unwrap(); let err = build_site(&src, &root.join("out"), &BuildOptions::default()) .expect_err("unknown theme must fail"); @@ -385,7 +385,7 @@ fn an_unknown_highlight_theme_is_rejected_with_the_available_ones() { // Discovery // --------------------------------------------------------------------------- -/// `org-ssg build . -o _site` is the obvious thing to type. Without excluding the output +/// `orgo build . -o _site` is the obvious thing to type. Without excluding the output /// directory, the build copies its own output back into itself, growing `_site/_site/…` /// on every run. #[test] @@ -421,7 +421,7 @@ fn dot_directories_and_build_inputs_are_never_published() { write_site(&src); std::fs::write(src.join(".git/config"), "[remote]\nurl = private\n").unwrap(); std::fs::write(src.join(".env"), "SECRET=hunter2\n").unwrap(); - std::fs::write(src.join("org-ssg.toml"), "[site]\ntitle = \"T\"\n").unwrap(); + std::fs::write(src.join("orgo.toml"), "[site]\ntitle = \"T\"\n").unwrap(); std::fs::write(src.join("templates/base.html"), "<html>{{ body | safe }}</html>").unwrap(); std::fs::write(src.join("style.css"), "body{}\n").unwrap(); let out = root.join("out"); @@ -430,7 +430,7 @@ fn dot_directories_and_build_inputs_are_never_published() { assert!(!out.join(".git").exists(), ".git must never be published"); assert!(!out.join(".env").exists(), "dotfiles must never be published"); assert!( - !out.join("org-ssg.toml").exists(), + !out.join("orgo.toml").exists(), "the config is a build input, not content" ); assert!( @@ -472,7 +472,7 @@ fn write_blog(src: &Utf8PathBuf, extra_config: &str) { ) .unwrap(); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), format!( "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\n\ template = \"list.html\"\ntitle = \"Blog\"\n{extra_config}" @@ -758,12 +758,12 @@ fn a_feed_is_just_a_listing_page_with_an_xml_template() { <pubDate>{{ p.date_iso }}</pubDate></item>{% endfor %}</channel></rss>", ) .unwrap(); - let mut config = std::fs::read_to_string(src.join("org-ssg.toml")).unwrap(); + let mut config = std::fs::read_to_string(src.join("orgo.toml")).unwrap(); config.push_str( "\n[[collections]]\nsource = \"blog\"\noutput = \"feed.xml\"\n\ template = \"feed.xml\"\ntitle = \"Feed\"\n", ); - std::fs::write(src.join("org-ssg.toml"), config).unwrap(); + std::fs::write(src.join("orgo.toml"), config).unwrap(); let out = root.join("out"); build(&src, &out); @@ -833,16 +833,16 @@ fn colliding_collection_outputs_are_rejected() { std::fs::create_dir_all(&src).unwrap(); write_blog(&src, ""); - let mut config = std::fs::read_to_string(src.join("org-ssg.toml")).unwrap(); + let mut config = std::fs::read_to_string(src.join("orgo.toml")).unwrap(); config.push_str("\n[[collections]]\nsource = \"\"\noutput = \"blog/index.html\"\n"); - std::fs::write(src.join("org-ssg.toml"), &config).unwrap(); + std::fs::write(src.join("orgo.toml"), &config).unwrap(); let err = build_site(&src, &root.join("out"), &BuildOptions::default()) .expect_err("two collections writing one file must fail"); assert!(format!("{err:#}").contains("blog/index.html"), "{err:#}"); // And a listing that would overwrite a real page. std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[[collections]]\nsource = \"blog\"\noutput = \"index.html\"\ntemplate = \"list.html\"\n", ) .unwrap(); @@ -859,7 +859,7 @@ fn a_missing_collection_template_names_the_ones_that_exist() { std::fs::create_dir_all(&src).unwrap(); write_blog(&src, ""); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\ntemplate = \"nope.html\"\n", ) .unwrap(); @@ -912,7 +912,7 @@ fn write_tagged_blog(src: &Utf8PathBuf, extra: &str) { ) .unwrap(); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), format!( "[[collections]]\nsource = \"blog\"\ngroup_by = \"tags\"\n\ output = \"tags/{{tag}}.html\"\ntemplate = \"tag.html\"\ntitle = \"Tagged: {{tag}}\"\n\ @@ -1044,7 +1044,7 @@ fn a_collection_can_group_by_any_keyword() { ) .unwrap(); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[[collections]]\nsource = \"blog\"\ngroup_by = \"category\"\n\ output = \"cat/{tag}.html\"\ntemplate = \"tag.html\"\ntitle = \"{tag}\"\n", ) @@ -1081,7 +1081,7 @@ fn grouping_without_a_placeholder_is_rejected() { std::fs::create_dir_all(&src).unwrap(); write_tagged_blog(&src, ""); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[[collections]]\nsource = \"blog\"\ngroup_by = \"tags\"\n\ output = \"tags/all.html\"\ntemplate = \"tag.html\"\n", ) @@ -1149,7 +1149,7 @@ fn write_paginated_blog(src: &Utf8PathBuf, count: usize, extra: &str) { ) .unwrap(); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), format!( "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\n\ template = \"list.html\"\ntitle = \"Blog\"\n{extra}" @@ -1275,7 +1275,7 @@ fn groups_paginate_independently() { .unwrap(); } std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[[collections]]\nsource = \"blog\"\ngroup_by = \"tags\"\n\ output = \"tags/{tag}.html\"\ntemplate = \"list.html\"\ntitle = \"{tag}\"\n\ paginate = 2\npaginate_output = \"tags/{tag}/page/{n}.html\"\n", @@ -1317,7 +1317,7 @@ fn pagination_placeholders_are_validated() { // Grouped without {tag} in the page pattern. std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[[collections]]\nsource = \"blog\"\ngroup_by = \"tags\"\n\ output = \"tags/{tag}.html\"\ntemplate = \"list.html\"\n\ paginate = 2\npaginate_output = \"tags/page/{n}.html\"\n", @@ -1380,7 +1380,7 @@ fn write_feed_site(src: &Utf8PathBuf, base_url: &str) { ) .unwrap(); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), format!( "[site]\nbase_url = \"{base_url}\"\n\n\ [[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() { .expect_err("absolute with no base_url must fail"); let message = format!("{err:#}"); assert!(message.contains("base_url"), "names the setting: {message}"); - assert!(message.contains("org-ssg.toml"), "names where to set it: {message}"); + assert!(message.contains("orgo.toml"), "names where to set it: {message}"); assert!(message.contains("feed.xml"), "names the template: {message}"); } @@ -1484,7 +1484,7 @@ fn the_default_layout_emits_a_canonical_link_only_with_a_base_url() { std::fs::create_dir_all(&src).unwrap(); write_site(&src); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), format!("[site]\nbase_url = \"{base}\"\n"), ) .unwrap(); @@ -1509,7 +1509,7 @@ fn changing_base_url_re_renders_the_site() { std::fs::create_dir_all(&src).unwrap(); write_site(&src); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[site]\nbase_url = \"https://example.com\"\n", ) .unwrap(); @@ -1518,7 +1518,7 @@ fn changing_base_url_re_renders_the_site() { assert!(build(&src, &out).rendered.is_empty(), "unchanged rebuild renders nothing"); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[site]\nbase_url = \"https://moved.example\"\n", ) .unwrap(); @@ -1556,7 +1556,7 @@ fn write_excerpt_site(src: &Utf8PathBuf, extra_config: &str) { ) .unwrap(); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), format!( "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\n\ template = \"list.html\"\ntitle = \"Blog\"\n{extra_config}" @@ -1731,9 +1731,9 @@ fn a_link_to_a_draft_is_reported_as_broken() { /// "no" has to mean no. #[test] fn draft_truthiness_is_forgiving_but_respects_an_explicit_negative() { - use org_ssg::model::Keywords; + use orgo::model::Keywords; let draft = |value: &str| { - org_ssg::util::is_draft(&Keywords { + orgo::util::is_draft(&Keywords { entries: vec![("DRAFT".to_string(), value.to_string())], }) }; @@ -1744,7 +1744,7 @@ fn draft_truthiness_is_forgiving_but_respects_an_explicit_negative() { assert!(!draft(no), "{no:?} should mean published"); } assert!( - !org_ssg::util::is_draft(&Keywords::default()), + !orgo::util::is_draft(&Keywords::default()), "no keyword at all means published" ); } @@ -1773,7 +1773,7 @@ fn write_toc_site(src: &Utf8PathBuf, options: &str, config: &str) { <nav>{{ walk(page.toc) }}</nav>{{ body | safe }}</body></html>", ) .unwrap(); - std::fs::write(src.join("org-ssg.toml"), config).unwrap(); + std::fs::write(src.join("orgo.toml"), config).unwrap(); } /// 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() { * One\n** A\n** B\n* Two\n** C\n*** Deep\n* Three\n", ) .unwrap(); - std::fs::write(src.join("org-ssg.toml"), "").unwrap(); + std::fs::write(src.join("orgo.toml"), "").unwrap(); let out = root.join("out"); build(&src, &out); @@ -1914,8 +1914,8 @@ fn section_numbering_resets_at_each_level() { /// `#+OPTIONS:` is a space-separated list of switches, and org spells "off" several ways. #[test] fn export_options_parse_as_org_writes_them() { - use org_ssg::model::Keywords; - use org_ssg::util::option_enabled; + use orgo::model::Keywords; + use orgo::util::option_enabled; let keywords = |v: &str| Keywords { entries: vec![("OPTIONS".to_string(), v.to_string())], }; @@ -1955,7 +1955,7 @@ fn write_two_layouts(src: &Utf8PathBuf, config: &str) { <p>Reply by email</p></body></html>", ) .unwrap(); - std::fs::write(src.join("org-ssg.toml"), config).unwrap(); + std::fs::write(src.join("orgo.toml"), config).unwrap(); } /// 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() { build(&src, &out); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[[pages]]\nmatch = \"blog\"\ntemplate = \"post.html\"\n", ) .unwrap(); @@ -2142,7 +2142,7 @@ fn adding_a_pages_rule_rerenders_the_pages_it_covers() { #[test] fn a_pages_rule_without_a_template_is_rejected() { let mut config = Config::default(); - config.pages.push(org_ssg::config::PageRule { + config.pages.push(orgo::config::PageRule { pattern: Utf8PathBuf::from("blog"), template: String::new(), }); @@ -2211,7 +2211,7 @@ fn same_day_entries_sort_by_time_of_day() { ) .unwrap(); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[[collections]]\nsource = \"blog\"\noutput = \"blog/index.html\"\n\ template = \"list.html\"\ntitle = \"Blog\"\nsort = \"date\"\norder = \"desc\"\n", ) @@ -2249,7 +2249,7 @@ fn an_asset_root_publishes_to_the_site_root() { std::fs::write(root.join("theme/static/robots.txt"), "User-agent: *\n").unwrap(); std::fs::write(root.join("theme/static/img/logo.svg"), "<svg/>").unwrap(); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[build]\nassets = [\"../theme/static\"]\n", ) .unwrap(); @@ -2280,7 +2280,7 @@ fn two_assets_claiming_one_url_is_an_error() { std::fs::create_dir_all(root.join("static")).unwrap(); std::fs::write(root.join("static/style.css"), "body{color:red}").unwrap(); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[build]\nassets = [\"../static\"]\n", ) .unwrap(); @@ -2301,7 +2301,7 @@ fn a_missing_asset_root_is_an_error() { std::fs::create_dir_all(&src).unwrap(); write_site(&src); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[build]\nassets = [\"../nope\"]\n", ) .unwrap(); @@ -2330,7 +2330,7 @@ fn a_collection_can_carry_its_entries_rendered_bodies() { ) .unwrap(); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[[collections]]\nsource = \"blog\"\noutput = \"feed.xml\"\n\ template = \"feed.xml\"\ntitle = \"Feed\"\ninclude_content = true\n", ) @@ -13,10 +13,10 @@ use camino::Utf8PathBuf; -use org_ssg::model::Document; -use org_ssg::parser::parse; -use org_ssg::render::{render, Html, SyntectHighlighter}; -use org_ssg::resolve::ResolvedDoc; +use orgo::model::Document; +use orgo::parser::parse; +use orgo::render::{render, Html, SyntectHighlighter}; +use orgo::resolve::ResolvedDoc; fn parse_fixture(name: &str) -> Document { let path = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")) @@ -160,7 +160,7 @@ fn highlighting_emits_classes_not_inline_styles() { "highlighting must not emit inline styles:\n{html}" ); assert!( - org_ssg::render::syntax_css("InspiredGitHub") + orgo::render::syntax_css("InspiredGitHub") .expect("a built-in theme") .contains(".storage"), "the generated stylesheet must define the emitted classes" @@ -559,7 +559,7 @@ fn special_strings_leave_code_alone() { ); } -/// `#+OPTIONS: -:nil` is how a document opts out, and org-ssg honours org's own switch +/// `#+OPTIONS: -:nil` is how a document opts out, and orgo honours org's own switch /// rather than inventing one. #[test] fn a_document_can_turn_special_strings_off() { @@ -5,8 +5,8 @@ //! - an `insta` JSON snapshot of a hand-built element-tree value, standing in for the //! element-tree snapshots the parser will produce from Phase 1 onward. -use org_ssg::model::{Link, LinkTarget, Object}; -use org_ssg::parser::content_hash; +use orgo::model::{Link, LinkTarget, Object}; +use orgo::parser::content_hash; #[test] fn content_hash_is_deterministic_blake3() { @@ -16,8 +16,8 @@ use std::sync::atomic::{AtomicU32, Ordering}; use camino::Utf8PathBuf; -use org_ssg::incremental::{manifest_path, Manifest, CACHE_FORMAT_VERSION}; -use org_ssg::site::{build_site, BuildOptions}; +use orgo::incremental::{manifest_path, Manifest, CACHE_FORMAT_VERSION}; +use orgo::site::{build_site, BuildOptions}; /// A fresh, empty temp directory unique to this process + call. fn tmpdir(tag: &str) -> Utf8PathBuf { @@ -25,7 +25,7 @@ fn tmpdir(tag: &str) -> Utf8PathBuf { let n = N.fetch_add(1, Ordering::Relaxed); let base = Utf8PathBuf::from_path_buf(std::env::temp_dir()) .expect("utf-8 temp dir") - .join(format!("org-ssg-it-{}-{tag}-{n}", std::process::id())); + .join(format!("orgo-it-{}-{tag}-{n}", std::process::id())); if base.exists() { std::fs::remove_dir_all(&base).unwrap(); } @@ -47,7 +47,7 @@ fn output_files(out: &Utf8PathBuf) -> BTreeMap<String, Vec<u8>> { continue; } let path = Utf8PathBuf::from_path_buf(entry.path().to_owned()).unwrap(); - if path.file_name() == Some(".org-ssg-cache.json") { + if path.file_name() == Some(".orgo-cache.json") { continue; } let rel = path.strip_prefix(out).unwrap().to_string(); @@ -476,7 +476,7 @@ fn editing_one_template_rebuilds_only_the_pages_that_use_it() { ) .unwrap(); std::fs::write( - src.join("org-ssg.toml"), + src.join("orgo.toml"), "[[pages]]\nmatch = \"blog\"\ntemplate = \"post.html\"\n", ) .unwrap(); @@ -1,4 +1,4 @@ -;;; oracle.el --- ground-truth HTML export for the org-ssg differential tests -*- lexical-binding: t -*- +;;; oracle.el --- ground-truth HTML export for the orgo differential tests -*- lexical-binding: t -*- ;; Exports the org file named by $ORG_ORACLE_INPUT to HTML on stdout, using org's own ;; exporter — the same one weblorg wraps to publish the corpus this project targets. @@ -16,7 +16,7 @@ ;; learn what stock org does — normalizing that away would be marking our own homework. (setq org-export-with-toc nil ; we emit no table of contents org-export-with-section-numbers nil ; we do not number headings - ;; org-html-toplevel-hlevel is left at its default of 2. org-ssg's own default + ;; org-html-toplevel-hlevel is left at its default of 2. orgo's own default ;; heading_offset is 1, which produces the same <h2>, so both sides now agree ;; without the oracle being told to. org-html-htmlize-output-type nil ; plain <pre>, not htmlize spans: we highlight @@ -24,7 +24,7 @@ ; the meaningful part org-html-head-include-default-style nil org-html-head-include-scripts nil - ;; Fixtures link to ids that live in org-ssg's own symbol table, not in an + ;; Fixtures link to ids that live in orgo's own symbol table, not in an ;; `org-id' database. Without this, org aborts the whole export on the first one. org-export-with-broken-links t make-backup-files nil) @@ -1,6 +1,6 @@ //! The `emacs --batch` ground-truth oracle (spec §5, Phase 0). //! -//! Every other test in this suite checks org-ssg against org-ssg: a snapshot says our +//! Every other test in this suite checks orgo against orgo: a snapshot says our //! output has not *changed*, never that it is *right*. Those two questions are different, //! and only one of them matters to someone whose site is currently published by Emacs. //! This file answers the second by exporting the same fixture with org's own HTML @@ -26,9 +26,9 @@ use std::process::Command; use camino::Utf8PathBuf; -use org_ssg::parser::parse; -use org_ssg::render::{render, Html, SyntectHighlighter}; -use org_ssg::resolve::ResolvedDoc; +use orgo::parser::parse; +use orgo::render::{render, Html, SyntectHighlighter}; +use orgo::resolve::ResolvedDoc; fn manifest_dir() -> Utf8PathBuf { Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")) @@ -62,7 +62,7 @@ fn org_export(fixture: &str) -> String { String::from_utf8(output.stdout).expect("emacs emits UTF-8") } -/// Render a fixture with org-ssg. +/// Render a fixture with orgo. fn our_export(fixture: &str) -> String { let path = manifest_dir().join("fixtures").join(fixture); let source = std::fs::read_to_string(&path).expect("read fixture"); @@ -252,12 +252,12 @@ fn decode_entities(s: &str) -> String { // Divergence report // --------------------------------------------------------------------------- -/// One divergence org-ssg makes on purpose, so the report can separate "we chose this" +/// One divergence orgo makes on purpose, so the report can separate "we chose this" /// from "we got this wrong". /// /// Without this split the agreement percentage is noise: the timestamps fixture sat at /// 40% while being entirely correct, because org writes `<2024-01-15 Mon>` as text and -/// org-ssg writes a `<time datetime>` element. A number that cannot fall when a real +/// orgo writes a `<time datetime>` element. A number that cannot fall when a real /// defect appears is not measuring anything. struct Deliberate { name: &'static str, @@ -335,7 +335,7 @@ const DELIBERATE: &[Deliberate] = &[ }, in_notes_only: false, }, - // Org emits a `<colgroup>` of empty `<col>`s to carry column alignment; org-ssg + // Org emits a `<colgroup>` of empty `<col>`s to carry column alignment; orgo // leaves alignment to the stylesheet. Deliberate { name: "no-colgroup", @@ -511,7 +511,7 @@ fn align(ours: &[String], theirs: &[String]) -> Vec<Op> { } /// A unified diff of the two skeletons, with hunks that are deliberate collapsed to a -/// named line. `-` is org-ssg, `+` is Emacs. +/// named line. `-` is orgo, `+` is Emacs. /// /// The number that matters is the last one: *unexplained* lines. Agreement can be low /// while unexplained is zero, and that is a passing state. @@ -566,7 +566,7 @@ fn divergence(ours: &[String], theirs: &[String]) -> String { "agreement: {agreed}/{total} skeleton lines ({pct:.1}%)\n\ deliberate: {deliberate} line(s){}\n\ unexplained: {unexplained} line(s)\n\ - (- org-ssg, + emacs, ~ a difference we mean to have)\n\n{body}", + (- orgo, + emacs, ~ a difference we mean to have)\n\n{body}", if rules.is_empty() { String::new() } else { @@ -575,7 +575,7 @@ fn divergence(ours: &[String], theirs: &[String]) -> String { ) } -/// Snapshot the divergence between org-ssg and Emacs for one fixture. +/// Snapshot the divergence between orgo and Emacs for one fixture. fn compare(fixture: &str) -> Option<String> { if !emacs_available() { eprintln!("skipping oracle comparison for {fixture}: no emacs on PATH"); @@ -612,7 +612,7 @@ oracle_test!(oracle_elements, "elements.org"); /// /// The percentages above are context, not a target — the timestamps fixture agrees on /// 40% of its lines and is entirely correct, because org writes a date as text where -/// org-ssg writes `<time datetime>`. What must hold is that nothing diverges for a +/// orgo writes `<time datetime>`. What must hold is that nothing diverges for a /// reason nobody has written down. A new unexplained line means either a defect to fix /// or a decision to record in `DELIBERATE`. #[test] @@ -6,10 +6,10 @@ use camino::Utf8PathBuf; -use org_ssg::model::Document; -use org_ssg::parser::parse; -use org_ssg::render::{render, Html, SyntectHighlighter}; -use org_ssg::resolve::ResolvedDoc; +use orgo::model::Document; +use orgo::parser::parse; +use orgo::render::{render, Html, SyntectHighlighter}; +use orgo::resolve::ResolvedDoc; fn parse_fixture(name: &str) -> Document { let path = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")) @@ -12,15 +12,15 @@ use std::time::{Duration, Instant}; use camino::{Utf8Path, Utf8PathBuf}; -use org_ssg::serve::{inject_reload_script, resolve, since_parameter}; -use org_ssg::site::BuildOptions; +use orgo::serve::{inject_reload_script, resolve, since_parameter}; +use orgo::site::BuildOptions; fn tmpdir(tag: &str) -> Utf8PathBuf { static N: AtomicU32 = AtomicU32::new(0); let n = N.fetch_add(1, Ordering::Relaxed); let base = Utf8PathBuf::from_path_buf(std::env::temp_dir()) .expect("utf-8 temp dir") - .join(format!("org-ssg-serve-{}-{tag}-{n}", std::process::id())); + .join(format!("orgo-serve-{}-{tag}-{n}", std::process::id())); let _ = std::fs::remove_dir_all(&base); std::fs::create_dir_all(&base).unwrap(); base @@ -107,10 +107,10 @@ fn plus_is_not_decoded_as_a_space() { #[test] fn the_poll_parameter_is_read_from_the_query() { - assert_eq!(since_parameter("/__org-ssg/reload?since=7"), 7); - assert_eq!(since_parameter("/__org-ssg/reload?x=1&since=42"), 42); - assert_eq!(since_parameter("/__org-ssg/reload"), 0, "absent means start from zero"); - assert_eq!(since_parameter("/__org-ssg/reload?since=nope"), 0, "unparseable means zero"); + assert_eq!(since_parameter("/__orgo/reload?since=7"), 7); + assert_eq!(since_parameter("/__orgo/reload?x=1&since=42"), 42); + assert_eq!(since_parameter("/__orgo/reload"), 0, "absent means start from zero"); + assert_eq!(since_parameter("/__orgo/reload?since=nope"), 0, "unparseable means zero"); } // --------------------------------------------------------------------------- @@ -167,7 +167,7 @@ fn start_server(src: &Utf8Path, out: &Utf8Path) -> u16 { let port = 20000 + ((std::process::id() % 10000) as u16) + NEXT.fetch_add(1, Ordering::Relaxed) as u16; let (s, o) = (src.to_owned(), out.to_owned()); std::thread::spawn(move || { - let _ = org_ssg::serve::run(&s, &o, &BuildOptions::default(), "127.0.0.1", port); + let _ = orgo::serve::run(&s, &o, &BuildOptions::default(), "127.0.0.1", port); }); let deadline = Instant::now() + Duration::from_secs(20); while Instant::now() < deadline { @@ -194,16 +194,16 @@ fn serving_a_site_reloads_the_browser_when_a_source_changes() { let home = get(port, "/", Duration::from_secs(5)).expect("a response"); assert!(home.contains("200 OK"), "{home}"); assert!(home.contains("First version."), "the page is served: {home}"); - assert!(home.contains("__org-ssg/reload"), "with the reload script: {home}"); + assert!(home.contains("__orgo/reload"), "with the reload script: {home}"); assert!( - !std::fs::read_to_string(out.join("index.html")).unwrap().contains("__org-ssg"), + !std::fs::read_to_string(out.join("index.html")).unwrap().contains("__orgo"), "but the file on disk stays clean" ); // A poll for a generation we already have must block, not answer immediately. let poller = std::thread::spawn(move || { let started = Instant::now(); - let body = get(port, "/__org-ssg/reload?since=0", Duration::from_secs(30)); + let body = get(port, "/__orgo/reload?since=0", Duration::from_secs(30)); (started.elapsed(), body) }); std::thread::sleep(Duration::from_millis(400)); @@ -6,11 +6,11 @@ use camino::Utf8PathBuf; -use org_ssg::index::{SymbolTable, TargetId}; -use org_ssg::parser::parse; -use org_ssg::render::{render, Html, SyntectHighlighter}; -use org_ssg::resolve::{resolve, ResolvedDoc}; -use org_ssg::site::{render_site, BuiltPage}; +use orgo::index::{SymbolTable, TargetId}; +use orgo::parser::parse; +use orgo::render::{render, Html, SyntectHighlighter}; +use orgo::resolve::{resolve, ResolvedDoc}; +use orgo::site::{render_site, BuiltPage}; fn fixtures() -> Utf8PathBuf { Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures") @@ -169,13 +169,13 @@ fn links_resolve_through_the_slug() { /// be impossible by construction rather than by convention. #[test] fn slugs_cannot_escape_the_output_directory() { - use org_ssg::model::Keywords; + use orgo::model::Keywords; let source = Utf8PathBuf::from("blog/post.org"); let slugged = |value: &str| { let keywords = Keywords { entries: vec![("SLUG".to_string(), value.to_string())], }; - org_ssg::util::output_path(&source, &keywords).to_string() + orgo::util::output_path(&source, &keywords).to_string() }; assert_eq!(slugged("../../etc/passwd"), "blog/etc-passwd.html"); assert_eq!(slugged("/absolute"), "blog/absolute.html"); @@ -190,7 +190,7 @@ fn slugs_cannot_escape_the_output_directory() { /// invisible in the source filenames, so the build refuses rather than picking a winner. #[test] fn colliding_slugs_are_a_build_error() { - let dir = std::env::temp_dir().join(format!("org-ssg-slug-{}", std::process::id())); + let dir = std::env::temp_dir().join(format!("orgo-slug-{}", std::process::id())); let dir = Utf8PathBuf::from_path_buf(dir).expect("utf-8 temp dir"); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); @@ -5,7 +5,7 @@ expression: report agreement: 67/77 skeleton lines (87.0%) deliberate: 11 line(s) — pre-code ×6, semantic-emphasis ×4, verse-trailing-break ×1 unexplained: 0 line(s) -(- org-ssg, + emacs, ~ a difference we mean to have) +(- orgo, + emacs, ~ a difference we mean to have) <h2> "Quote" @@ -5,7 +5,7 @@ expression: report agreement: 51/58 skeleton lines (87.9%) deliberate: 7 line(s) — list-per-bullet-type ×2, pre-code ×2, semantic-emphasis ×2 unexplained: 0 line(s) -(- org-ssg, + emacs, ~ a difference we mean to have) +(- orgo, + emacs, ~ a difference we mean to have) <p> "Intro paragraph with a bare URL" @@ -5,7 +5,7 @@ expression: report agreement: 64/82 skeleton lines (78.0%) deliberate: 26 line(s) — footnote-anchor-naming ×1, footnote-section-shape ×2, id-link-resolution ×1, no-colgroup ×1, pre-code ×2 unexplained: 0 line(s) -(- org-ssg, + emacs, ~ a difference we mean to have) +(- orgo, + emacs, ~ a difference we mean to have) <h2> "Code and tables" @@ -5,7 +5,7 @@ expression: report agreement: 30/53 skeleton lines (56.6%) deliberate: 29 line(s) — footnote-anchor-naming ×3, footnote-section-shape ×6, semantic-emphasis ×2 unexplained: 0 line(s) -(- org-ssg, + emacs, ~ a difference we mean to have) +(- orgo, + emacs, ~ a difference we mean to have) <p> "Text with a reference." @@ -5,7 +5,7 @@ expression: report agreement: 28/30 skeleton lines (93.3%) deliberate: 2 line(s) — semantic-time ×2 unexplained: 0 line(s) -(- org-ssg, + emacs, ~ a difference we mean to have) +(- orgo, + emacs, ~ a difference we mean to have) <h2> ~ semantic-time (1 line(s)) @@ -5,7 +5,7 @@ expression: report agreement: 30/42 skeleton lines (71.4%) deliberate: 14 line(s) — figure-element ×8, semantic-emphasis ×2 unexplained: 0 line(s) -(- org-ssg, + emacs, ~ a difference we mean to have) +(- orgo, + emacs, ~ a difference we mean to have) <h2> "Bare image" @@ -5,7 +5,7 @@ expression: report agreement: 109/111 skeleton lines (98.2%) deliberate: 2 line(s) — semantic-emphasis ×2 unexplained: 0 line(s) -(- org-ssg, + emacs, ~ a difference we mean to have) +(- orgo, + emacs, ~ a difference we mean to have) <h2> "Nesting" @@ -5,7 +5,7 @@ expression: report agreement: 38/42 skeleton lines (90.5%) deliberate: 4 line(s) — semantic-emphasis ×4 unexplained: 0 line(s) -(- org-ssg, + emacs, ~ a difference we mean to have) +(- orgo, + emacs, ~ a difference we mean to have) <p> "A single paragraph of preamble text before any heading." @@ -5,7 +5,7 @@ expression: report agreement: 30/34 skeleton lines (88.2%) deliberate: 4 line(s) — no-colgroup ×1 unexplained: 0 line(s) -(- org-ssg, + emacs, ~ a difference we mean to have) +(- orgo, + emacs, ~ a difference we mean to have) <table> ~ no-colgroup (4 line(s)) @@ -5,7 +5,7 @@ expression: report agreement: 25/62 skeleton lines (40.3%) deliberate: 37 line(s) — semantic-time ×5 unexplained: 0 line(s) -(- org-ssg, + emacs, ~ a difference we mean to have) +(- orgo, + emacs, ~ a difference we mean to have) <h2> "Single" @@ -7,12 +7,12 @@ expression: "page(&pages, \"guide.org\").html" <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> -<title>Guide · org-ssg site</title> +<title>Guide · orgo site</title> <link rel="stylesheet" href="syntax.css"> </head> <body> <header> -<a class="site-title" href="index.html">org-ssg site</a> +<a class="site-title" href="index.html">orgo site</a> <nav> <a href="about.html">About</a> <a href="#">Guide</a> @@ -7,12 +7,12 @@ expression: "page(&pages, \"index.org\").html" <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> -<title>Home · org-ssg site</title> +<title>Home · orgo site</title> <link rel="stylesheet" href="syntax.css"> </head> <body> <header> -<a class="site-title" href="index.html">org-ssg site</a> +<a class="site-title" href="index.html">orgo site</a> <nav> <a href="about.html">About</a> <a href="guide.html">Guide</a> @@ -1,6 +1,6 @@ //! `watch`: the change filter, and one end-to-end run against real filesystem events. //! -//! The filter carries the weight here. `org-ssg watch . -o _site` puts the output inside +//! The filter carries the weight here. `orgo watch . -o _site` puts the output inside //! the source, so a rebuild writes files, writing files raises events, and events trigger //! a rebuild — a loop that never stops. That it is a pure function is what makes the //! guarantee testable without waiting on a filesystem. @@ -10,15 +10,15 @@ use std::time::{Duration, Instant}; use camino::{Utf8Path, Utf8PathBuf}; -use org_ssg::site::{build_site, BuildOptions}; -use org_ssg::watch::ChangeFilter; +use orgo::site::{build_site, BuildOptions}; +use orgo::watch::ChangeFilter; fn tmpdir(tag: &str) -> Utf8PathBuf { static N: AtomicU32 = AtomicU32::new(0); let n = N.fetch_add(1, Ordering::Relaxed); let base = Utf8PathBuf::from_path_buf(std::env::temp_dir()) .expect("utf-8 temp dir") - .join(format!("org-ssg-watch-{}-{tag}-{n}", std::process::id())); + .join(format!("orgo-watch-{}-{tag}-{n}", std::process::id())); let _ = std::fs::remove_dir_all(&base); std::fs::create_dir_all(&base).unwrap(); base @@ -39,7 +39,7 @@ fn changes_under_the_output_directory_are_ignored() { let filter = ChangeFilter::new(&src, &out); assert!(!filter.is_relevant(Utf8Path::new("_site/index.html"))); assert!(!filter.is_relevant(Utf8Path::new("_site/blog/post.html"))); - assert!(!filter.is_relevant(Utf8Path::new("_site/.org-ssg-cache.json"))); + assert!(!filter.is_relevant(Utf8Path::new("_site/.orgo-cache.json"))); assert!(filter.is_relevant(Utf8Path::new("index.org")), "real sources still count"); } @@ -68,7 +68,7 @@ fn build_inputs_trigger_a_rebuild_even_though_discovery_skips_them() { std::fs::create_dir_all(&src).unwrap(); let filter = ChangeFilter::new(&src, &root.join("out")); - assert!(filter.is_relevant(Utf8Path::new("org-ssg.toml"))); + assert!(filter.is_relevant(Utf8Path::new("orgo.toml"))); assert!(filter.is_relevant(Utf8Path::new("templates/base.html"))); assert!(filter.is_relevant(Utf8Path::new("templates/feed.xml"))); 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() { let (src_t, out_t) = (src.clone(), out.clone()); let handle = std::thread::spawn(move || { - let _ = org_ssg::watch::run(&src_t, &out_t, &BuildOptions::default()); + let _ = orgo::watch::run(&src_t, &out_t, &BuildOptions::default()); }); // Give the watcher a moment to register before making the change it should see.