From 18520c040ccf4139a3b7bb3a9f8ba3bfede67aa3 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Tue, 29 Sep 2026 16:48:26 -0500 Subject: [PATCH 1/2] fix(pandoc): keep org file keywords and add no heading drawers The org reader stores #+TITLE, #+DATE and #+DESCRIPTION as document metadata, which the fragment writer never emits, so a reflow deleted them. Shield each contiguous run of org file keywords like a pragma. The org reader also gives every heading an identifier and the org writer prints each as a :PROPERTIES: drawer. Clear identifiers the source did not set with :CUSTOM_ID:, for both the FFI and the CLI reader. --- src/parser/pandoc/mod.rs | 65 +++++++++++++++++++++++++++++++ src/parser/pandoc/shield.rs | 77 +++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/src/parser/pandoc/mod.rs b/src/parser/pandoc/mod.rs index be778735..ff3817b0 100644 --- a/src/parser/pandoc/mod.rs +++ b/src/parser/pandoc/mod.rs @@ -250,6 +250,9 @@ pub fn format_via_pandoc( let json = json_with_backend(&shield.source, format, backend)?; let mut doc: pandoc_ast::Pandoc = serde_json::from_str(&json).map_err(|e| PandocError::Ast(e.to_string()))?; + if shield::is_org_pandoc_format(format) { + drop_generated_org_ids(&mut doc.blocks, &authored_org_ids(input)); + } reflow_pandoc(&mut doc, splitter, reflow_config); let out_json = serde_json::to_string(&doc).map_err(|e| PandocError::Ast(format!("serialize AST: {e}")))?; @@ -259,6 +262,44 @@ pub fn format_via_pandoc( Ok(written) } +/// `CUSTOM_ID` values the org source sets on its headings. +fn authored_org_ids(input: &str) -> std::collections::HashSet { + input + .lines() + .filter_map(|l| l.trim().strip_prefix(":CUSTOM_ID:")) + .map(|id| id.trim().to_string()) + .filter(|id| !id.is_empty()) + .collect() +} + +/// The org reader gives every heading an identifier, and the org writer +/// prints each one as a `:PROPERTIES:` drawer. Keep only the identifiers the +/// source set itself, so a reflow never adds drawers the author did not write. +fn drop_generated_org_ids( + blocks: &mut [pandoc_ast::Block], + authored: &std::collections::HashSet, +) { + use pandoc_ast::Block; + for block in blocks { + match block { + Block::Header(_, attr, _) => { + if !authored.contains(&attr.0) { + attr.0.clear(); + } + } + Block::BlockQuote(inner) | Block::Div(_, inner) | Block::Figure(_, _, inner) => { + drop_generated_org_ids(inner, authored); + } + Block::BulletList(items) | Block::OrderedList(_, items) => { + for item in items { + drop_generated_org_ids(item, authored); + } + } + _ => {} + } + } +} + /// Parser that uses pandoc for universal format support. pub struct PandocParser { /// Pandoc input format (e.g. "latex", "markdown", "org", "rst", "typst") @@ -580,6 +621,30 @@ mod tests { ); } + #[test] + fn format_via_pandoc_org_keeps_keywords_and_adds_no_drawers() { + if !pandoc_default_available() { + return; + } + let src = "#+TITLE: t\n#+DATE: 2026\n#+DESCRIPTION: d\n#+FILETAGS: :x:\n\n* A\n:PROPERTIES:\n:CUSTOM_ID: keep-me\n:END:\n\nOne. Two.\n\n* B\n\nThree. Four.\n"; + let splitter = crate::sentence::unicode::UnicodeSentenceSplitter::new(); + let out = format_via_pandoc( + src, + "org", + PandocBackend::Auto, + &splitter, + &crate::reflow::ReflowConfig::default(), + ) + .unwrap(); + assert!( + out.starts_with("#+TITLE: t\n#+DATE: 2026\n#+DESCRIPTION: d\n#+FILETAGS: :x:\n"), + "{out}" + ); + assert!(out.contains(":CUSTOM_ID: keep-me"), "{out}"); + assert_eq!(out.matches(":CUSTOM_ID:").count(), 1, "{out}"); + assert!(out.contains("Three.\nFour."), "{out}"); + } + #[test] fn refuse_if_comments_missing_catches_restore_miss() { let err = refuse_if_comments_missing( diff --git a/src/parser/pandoc/shield.rs b/src/parser/pandoc/shield.rs index 9f075a19..176fd115 100644 --- a/src/parser/pandoc/shield.rs +++ b/src/parser/pandoc/shield.rs @@ -5,6 +5,12 @@ //! Replace each span with a unique sentinel paragraph, write, then put //! the original text back. Off-region bodies travel inside the sentinel //! so they are not reflowed. +//! +//! Org file keywords (`#+TITLE:`, `#+DATE:`, `#+DESCRIPTION:`, ...) are +//! read into document metadata, which a fragment writer never emits, so +//! each contiguous run of them is shielded the same way. Affiliated +//! keywords (`#+NAME:`, `#+CAPTION:`, `#+ATTR_...:`, `#+RESULTS:`, +//! `#+TBLFM:`) stay in place because they bind to the next element. use std::ops::Range; @@ -61,6 +67,62 @@ pub(crate) fn is_rst_pandoc_format(format: &str) -> bool { matches!(base, "rst" | "rest") } +pub(crate) fn is_org_pandoc_format(format: &str) -> bool { + format.split(['+', '-']).next().unwrap_or(format) == "org" +} + +/// An org file keyword line: `#+KEY:` or `#+KEY: value`, not a block +/// delimiter and not an affiliated keyword that binds to the next element. +fn is_org_file_keyword(text: &str) -> bool { + let Some(rest) = text.strip_prefix("#+") else { + return false; + }; + let Some((key, _)) = rest.split_once(':') else { + return false; + }; + if key.is_empty() + || !key + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-') + { + return false; + } + let key = key.to_ascii_lowercase(); + !(key.starts_with("attr_") + || matches!( + key.as_str(), + "name" | "caption" | "results" | "tblfm" | "header" | "plot" | "label" + )) +} + +/// Each contiguous run of org file keyword lines outside code blocks. +fn org_keyword_spans(input: &str) -> Vec> { + let lines = iter_lines(input); + let mut spans = Vec::new(); + let mut run: Option> = None; + let mut in_code = false; + let mut fence_mark: Option = None; + let mut rst_code_indent: Option = None; + for line in &lines { + let opened_or_closed = toggle_code( + line.text, + &mut in_code, + &mut fence_mark, + &mut rst_code_indent, + ); + if !opened_or_closed && !in_code && is_org_file_keyword(line.text) { + match &mut run { + Some(r) => r.end = line.end, + None => run = Some(line.start..line.end), + } + } else if let Some(r) = run.take() { + spans.push(r); + } + } + spans.extend(run); + spans +} + fn strip_one_trailing_newline(s: &str) -> (&str, bool) { if let Some(stripped) = s.strip_suffix("\r\n") { (stripped, true) @@ -91,6 +153,13 @@ fn overlaps(a: &Range, b: &Range) -> bool { fn collect_spans(input: &str, format: &str) -> Vec> { let mut spans = pragma_spans(input); + if is_org_pandoc_format(format) { + for span in org_keyword_spans(input) { + if !spans.iter().any(|p| overlaps(p, &span)) { + spans.push(span); + } + } + } if is_rst_pandoc_format(format) { for span in rst_comment_spans(input) { if !spans.iter().any(|p| overlaps(p, &span)) { @@ -282,6 +351,14 @@ fn toggle_code( #[cfg(test)] mod tests { + #[test] + fn org_file_keywords_are_one_span_per_run() { + let src = "#+TITLE: t\n#+DATE: d\n\n* A\n\n#+NAME: tbl\n| a |\n#+begin_src sh\n#+TITLE: in code\n#+end_src\n#+STATUS: s\n"; + let spans = org_keyword_spans(src); + let texts: Vec<&str> = spans.iter().map(|r| &src[r.clone()]).collect(); + assert_eq!(texts, vec!["#+TITLE: t\n#+DATE: d\n", "#+STATUS: s\n"]); + } + use super::*; #[test] From e4ec7bb77baa003c00385f2f5f4588f9782bfb36 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 4 Oct 2026 06:53:58 -0500 Subject: [PATCH 2/2] chore(version): v0.11.9 --- CHANGELOG.md | 5 +++++ Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 2 +- conda/recipe.yaml | 2 +- docs/orgmode/howto/ci-enforcement.org | 6 +++--- docs/orgmode/howto/vale-integration.org | 2 +- docs/orgmode/tutorials/quickstart.org | 2 +- docs/source/conf.py | 2 +- editors/obsidian/manifest.json | 2 +- editors/obsidian/package-lock.json | 6 +++--- editors/obsidian/package.json | 2 +- editors/vscode/README.md | 4 ++-- editors/vscode/package.json | 2 +- editors/word/README.md | 4 ++-- editors/word/manifest.xml | 2 +- editors/word/package-lock.json | 6 +++--- editors/word/package.json | 2 +- editors/word/src/taskpane/taskpane.html | 2 +- npm/package.json | 2 +- packages/snapper-wasm/package-lock.json | 4 ++-- packages/snapper-wasm/package.json | 2 +- readme_src.org | 2 +- site-svelte/src/app.html | 2 +- site-svelte/src/routes/+page.svelte | 2 +- site/index.html | 4 ++-- 26 files changed, 40 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00b5d8ec..c386cc99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines. - - - +## v0.11.9 - 2026-10-04 +#### Bug Fixes +- (**pandoc**) keep org file keywords and add no heading drawers - (18520c0) - *HaoZeke* +- (**site**) pin the homepage to v0.11.8 - (783e674) - *HaoZeke* + ## v0.11.8 - 2026-10-03 #### Tests - (**markdown**) accept the line-block terminator on the structure span - (6f3cd8c) - *HaoZeke* diff --git a/Cargo.lock b/Cargo.lock index 0b4ab786..aa651559 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2221,7 +2221,7 @@ checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100" [[package]] name = "snapper-fmt" -version = "0.11.8" +version = "0.11.9" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 2beaddbc..ec287c14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "snapper-fmt" -version = "0.11.8" +version = "0.11.9" edition = "2024" autotests = false rust-version = "1.85" diff --git a/README.md b/README.md index ea03d62e..0ca6ae20 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ Configuration guide (org source in-tree): `docs/orgmode/howto/mcp-integration.or ## Pre-commit hook - repo: https://github.com/TurtleTech-ehf/snapper - rev: v0.11.8 + rev: v0.11.9 hooks: - id: snapper diff --git a/conda/recipe.yaml b/conda/recipe.yaml index b7728eb0..f79e0987 100644 --- a/conda/recipe.yaml +++ b/conda/recipe.yaml @@ -3,7 +3,7 @@ schema_version: 1 context: - version: "0.11.8" + version: "0.11.9" package: name: snapper-fmt diff --git a/docs/orgmode/howto/ci-enforcement.org b/docs/orgmode/howto/ci-enforcement.org index b8388eaf..3f01ac38 100644 --- a/docs/orgmode/howto/ci-enforcement.org +++ b/docs/orgmode/howto/ci-enforcement.org @@ -10,7 +10,7 @@ The easiest way to enforce semantic line breaks in CI. Add to your workflow: #+begin_src yaml -- uses: TurtleTech-ehf/snapper@v0.11.8 +- uses: TurtleTech-ehf/snapper@v0.11.9 with: files: '**/*.org **/*.tex **/*.md' #+end_src @@ -20,7 +20,7 @@ This installs snapper and runs =--check= on the specified files. For GitHub Code Scanning integration (SARIF annotations on PRs): #+begin_src yaml -- uses: TurtleTech-ehf/snapper@v0.11.8 +- uses: TurtleTech-ehf/snapper@v0.11.9 with: files: '**/*.org **/*.tex **/*.md' sarif: 'true' @@ -35,7 +35,7 @@ Add to =.pre-commit-config.yaml=: #+begin_src yaml - repo: https://github.com/TurtleTech-ehf/snapper - rev: v0.11.8 + rev: v0.11.9 hooks: - id: snapper #+end_src diff --git a/docs/orgmode/howto/vale-integration.org b/docs/orgmode/howto/vale-integration.org index a684d022..37e83e98 100644 --- a/docs/orgmode/howto/vale-integration.org +++ b/docs/orgmode/howto/vale-integration.org @@ -70,7 +70,7 @@ A typical workflow: #+begin_src yaml - repo: https://github.com/TurtleTech-ehf/snapper - rev: v0.11.8 + rev: v0.11.9 hooks: - id: snapper - repo: https://github.com/errata-ai/vale diff --git a/docs/orgmode/tutorials/quickstart.org b/docs/orgmode/tutorials/quickstart.org index 4b0c6781..875a56e9 100644 --- a/docs/orgmode/tutorials/quickstart.org +++ b/docs/orgmode/tutorials/quickstart.org @@ -232,7 +232,7 @@ Add to your =.pre-commit-config.yaml=: #+begin_src yaml - repo: https://github.com/TurtleTech-ehf/snapper - rev: v0.11.8 + rev: v0.11.9 hooks: - id: snapper #+end_src diff --git a/docs/source/conf.py b/docs/source/conf.py index d9a2e77b..b6f9ab8a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -3,7 +3,7 @@ project = "snapper" copyright = '2026--present, Rohit Goswami' author = "Rohit Goswami" -release = "0.11.8" +release = "0.11.9" html_logo = "../../branding/logo/snapper_logo.png" extensions = [ diff --git a/editors/obsidian/manifest.json b/editors/obsidian/manifest.json index e7fabfbd..7b081bb5 100644 --- a/editors/obsidian/manifest.json +++ b/editors/obsidian/manifest.json @@ -1,7 +1,7 @@ { "id": "snapper", "name": "Snapper - Semantic Line Breaks", - "version": "0.11.8", + "version": "0.11.9", "minAppVersion": "1.0.0", "description": "Format prose with semantic line breaks for clean git diffs. Supports Org-mode, LaTeX, Markdown, and plaintext.", "author": "TurtleTech", diff --git a/editors/obsidian/package-lock.json b/editors/obsidian/package-lock.json index f75fce2c..2ca8e011 100644 --- a/editors/obsidian/package-lock.json +++ b/editors/obsidian/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-snapper", - "version": "0.11.8", + "version": "0.11.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-snapper", - "version": "0.11.8", + "version": "0.11.9", "license": "MIT", "devDependencies": { "@snapper/wasm": "file:../../packages/snapper-wasm", @@ -17,7 +17,7 @@ }, "../../packages/snapper-wasm": { "name": "@snapper/wasm", - "version": "0.11.8", + "version": "0.11.9", "dev": true, "license": "MIT", "devDependencies": { diff --git a/editors/obsidian/package.json b/editors/obsidian/package.json index d4aa39f5..c79be731 100644 --- a/editors/obsidian/package.json +++ b/editors/obsidian/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-snapper", - "version": "0.11.8", + "version": "0.11.9", "private": true, "description": "Obsidian plugin for snapper semantic line break formatter", "scripts": { diff --git a/editors/vscode/README.md b/editors/vscode/README.md index 139e1cd5..96122848 100644 --- a/editors/vscode/README.md +++ b/editors/vscode/README.md @@ -1,9 +1,9 @@ # snapper - Semantic Line Breaks -Requires the **snapper** / **snapper-fmt** CLI **0.11.8+** on your PATH (or set `snapper.path`). +Requires the **snapper** / **snapper-fmt** CLI **0.11.9+** on your PATH (or set `snapper.path`). The crate is `snapper-fmt`; installers ship both names. If [openSUSE snapper](https://github.com/openSUSE/snapper) already owns `/usr/bin/snapper`, call `snapper-fmt` or set `snapper.path`. -Install: `cargo install snapper-fmt` or the [release installer](https://github.com/TurtleTech-ehf/snapper/releases/tag/v0.11.8). +Install: `cargo install snapper-fmt` or the [release installer](https://github.com/TurtleTech-ehf/snapper/releases/tag/v0.11.9). Format prose so each sentence occupies its own line, producing clean git diffs for collaborative writing. diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 221a2fbe..8c53f765 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -2,7 +2,7 @@ "name": "snapper", "displayName": "snapper - Semantic Line Breaks", "description": "Format prose with semantic line breaks for clean git diffs", - "version": "0.11.8", + "version": "0.11.9", "publisher": "TurtleTech", "license": "MIT", "repository": { diff --git a/editors/word/README.md b/editors/word/README.md index 750ddd0b..621adac0 100644 --- a/editors/word/README.md +++ b/editors/word/README.md @@ -2,7 +2,7 @@ Office add-in that formats document prose with **semantic line breaks** (one sentence per line) using the **snapper WASM** build (`@snapper/wasm`). Useful when drafting in Word and exporting to Org, Markdown, or LaTeX for git-friendly diffs. -**Version:** 0.11.8 (tracks snapper-fmt) +**Version:** 0.11.9 (tracks snapper-fmt) Development preview; not published in AppSource. @@ -34,7 +34,7 @@ CI builds the Word add-in in `.github/workflows/wasm.yml` (`build-word` job). ## Relationship to the CLI -Word uses the **plaintext** format path in WASM. For Org/LaTeX fidelity, prefer the CLI or VS Code extension (LSP). Delimiter-span and abbreviation behavior matches the current WASM API (requires snapper **0.11.8+**). +Word uses the **plaintext** format path in WASM. For Org/LaTeX fidelity, prefer the CLI or VS Code extension (LSP). Delimiter-span and abbreviation behavior matches the current WASM API (requires snapper **0.11.9+**). ## License diff --git a/editors/word/manifest.xml b/editors/word/manifest.xml index 585e47d5..70797bf7 100644 --- a/editors/word/manifest.xml +++ b/editors/word/manifest.xml @@ -5,7 +5,7 @@ xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" xsi:type="TaskPaneApp"> a8f3c2e1-9b4d-4e7a-8c5f-1d2e3f4a5b6c - 0.11.8 + 0.11.9 TurtleTech ehf en-US diff --git a/editors/word/package-lock.json b/editors/word/package-lock.json index 91d281b8..befa2cd6 100644 --- a/editors/word/package-lock.json +++ b/editors/word/package-lock.json @@ -1,12 +1,12 @@ { "name": "word-snapper", - "version": "0.11.8", + "version": "0.11.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "word-snapper", - "version": "0.11.8", + "version": "0.11.9", "license": "MIT", "devDependencies": { "@snapper/wasm": "file:../../packages/snapper-wasm", @@ -21,7 +21,7 @@ }, "../../packages/snapper-wasm": { "name": "@snapper/wasm", - "version": "0.11.8", + "version": "0.11.9", "dev": true, "license": "MIT", "devDependencies": { diff --git a/editors/word/package.json b/editors/word/package.json index 54b8e046..8fb4af81 100644 --- a/editors/word/package.json +++ b/editors/word/package.json @@ -1,6 +1,6 @@ { "name": "word-snapper", - "version": "0.11.8", + "version": "0.11.9", "private": true, "description": "Microsoft Word add-in for snapper semantic line break formatter", "scripts": { diff --git a/editors/word/src/taskpane/taskpane.html b/editors/word/src/taskpane/taskpane.html index 4a740f69..9fcd3d9c 100644 --- a/editors/word/src/taskpane/taskpane.html +++ b/editors/word/src/taskpane/taskpane.html @@ -10,7 +10,7 @@

Snapper

-

Semantic line breaks for Word (v0.11.8)

+

Semantic line breaks for Word (v0.11.9)

Formats paragraphs as plaintext. Use the CLI or VS Code for Org/LaTeX structure awareness.

diff --git a/npm/package.json b/npm/package.json index cba347f3..2a733ae3 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "@turtletech/snapper-mcp", - "version": "0.11.8", + "version": "0.11.9", "private": true, "description": "MCP server for snapper semantic line break formatter", "main": "bin/run.js", diff --git a/packages/snapper-wasm/package-lock.json b/packages/snapper-wasm/package-lock.json index a1742945..81f37f91 100644 --- a/packages/snapper-wasm/package-lock.json +++ b/packages/snapper-wasm/package-lock.json @@ -1,12 +1,12 @@ { "name": "@snapper/wasm", - "version": "0.11.8", + "version": "0.11.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@snapper/wasm", - "version": "0.11.8", + "version": "0.11.9", "license": "MIT", "devDependencies": { "typescript": "^5.4" diff --git a/packages/snapper-wasm/package.json b/packages/snapper-wasm/package.json index aee68ac5..9ab07005 100644 --- a/packages/snapper-wasm/package.json +++ b/packages/snapper-wasm/package.json @@ -1,6 +1,6 @@ { "name": "@snapper/wasm", - "version": "0.11.8", + "version": "0.11.9", "description": "WebAssembly build of snapper semantic line break formatter", "type": "module", "main": "dist/index.js", diff --git a/readme_src.org b/readme_src.org index e8e7a301..51ac3603 100644 --- a/readme_src.org +++ b/readme_src.org @@ -157,7 +157,7 @@ Configuration guide (org source in-tree): =docs/orgmode/howto/mcp-integration.or :END: #+begin_src yaml - repo: https://github.com/TurtleTech-ehf/snapper - rev: v0.11.8 + rev: v0.11.9 hooks: - id: snapper #+end_src diff --git a/site-svelte/src/app.html b/site-svelte/src/app.html index 8543f88d..07bf72d7 100644 --- a/site-svelte/src/app.html +++ b/site-svelte/src/app.html @@ -31,7 +31,7 @@ "operatingSystem": "Linux, macOS, Windows", "url": "https://snapper.turtletech.us", "downloadUrl": "https://crates.io/crates/snapper-fmt", - "softwareVersion": "0.11.8", + "softwareVersion": "0.11.9", "license": "https://opensource.org/licenses/MIT", "author": { "@type": "Organization", "name": "TurtleTech ehf", "url": "https://turtletech.us" }, "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" } diff --git a/site-svelte/src/routes/+page.svelte b/site-svelte/src/routes/+page.svelte index 9f9328fc..feaacc0a 100644 --- a/site-svelte/src/routes/+page.svelte +++ b/site-svelte/src/routes/+page.svelte @@ -73,7 +73,7 @@ lines: [ { text: '# .pre-commit-config.yaml', type: 'comment' as const }, { text: '- repo: https://github.com/TurtleTech-ehf/snapper', type: 'flag' as const }, - { text: ' rev: v0.11.8', type: 'flag' as const }, + { text: ' rev: v0.11.9', type: 'flag' as const }, { text: ' hooks:', type: 'flag' as const }, { text: ' - id: snapper', type: 'flag' as const }, ], diff --git a/site/index.html b/site/index.html index b96cc6a2..e7fa0027 100644 --- a/site/index.html +++ b/site/index.html @@ -31,7 +31,7 @@ "operatingSystem": "Linux, macOS, Windows", "url": "https://snapper.turtletech.us", "downloadUrl": "https://crates.io/crates/snapper-fmt", - "softwareVersion": "0.11.8", + "softwareVersion": "0.11.9", "license": "https://opensource.org/licenses/MIT", "author": { "@type": "Organization", @@ -934,7 +934,7 @@

Install in seconds

Pre-commit
# .pre-commit-config.yaml
 - repo: https://github.com/TurtleTech-ehf/snapper
-  rev: v0.11.8
+  rev: v0.11.9
   hooks:
     - id: snapper