From df6b27107aff6afe288ba24d843a01cd257a442d Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 3 Oct 2026 21:23:57 +0200 Subject: [PATCH 1/4] tst: restore the 42 CLI tests the consolidated binary overwrote The single integration binary took the name tests/integration.rs, which already held the CLI tests; #518 replaced them. They return as tests/cli_integration.rs, a module of the one binary. --- tests/cli_integration.rs | 622 +++++++++++++++++++++++++++++++++++++++ tests/integration.rs | 2 + 2 files changed, 624 insertions(+) create mode 100644 tests/cli_integration.rs diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs new file mode 100644 index 00000000..6e2faa91 --- /dev/null +++ b/tests/cli_integration.rs @@ -0,0 +1,622 @@ +use std::fs; +use std::path::Path; +use std::process::Command; + +fn snapper_binary() -> Command { + Command::new(env!("CARGO_BIN_EXE_snapper")) +} + +fn run_format(format: &str, input_path: &str) -> String { + let output = snapper_binary() + .args(["--native", "--format", format, input_path]) + .output() + .expect("failed to run snapper"); + assert!( + output.status.success(), + "snapper failed: {:?}", + output.stderr + ); + String::from_utf8(output.stdout).expect("invalid utf8") +} + +fn pipe_stdin(input: &str, args: &[&str]) -> std::process::Output { + let mut cmd = snapper_binary(); + cmd.arg("--native"); + cmd.args(args) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + let mut child = cmd.spawn().expect("failed to spawn snapper"); + { + use std::io::Write; + child + .stdin + .take() + .unwrap() + .write_all(input.as_bytes()) + .unwrap(); + } + child.wait_with_output().expect("failed to wait") +} + +fn pipe_stdin_in_dir(input: &str, args: &[&str], dir: &Path) -> std::process::Output { + let mut cmd = snapper_binary(); + cmd.arg("--native"); + cmd.args(args) + .current_dir(dir) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + let mut child = cmd.spawn().expect("failed to spawn snapper"); + { + use std::io::Write; + child + .stdin + .take() + .unwrap() + .write_all(input.as_bytes()) + .unwrap(); + } + child.wait_with_output().expect("failed to wait") +} + +fn fixture_path(name: &str) -> String { + let base = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures"); + base.join(name).to_string_lossy().to_string() +} + +// ---- EXISTING TESTS ---- + +#[test] +fn org_format() { + let actual = run_format("org", &fixture_path("sample.org")); + let expected = fs::read_to_string(fixture_path("expected.org")).unwrap(); + pretty_assertions::assert_eq!(actual, expected); +} + +#[test] +fn plaintext_format() { + let actual = run_format("plaintext", &fixture_path("sample.txt")); + let expected = fs::read_to_string(fixture_path("expected.txt")).unwrap(); + pretty_assertions::assert_eq!(actual, expected); +} + +#[test] +fn latex_format() { + let actual = run_format("latex", &fixture_path("sample.tex")); + let expected = fs::read_to_string(fixture_path("expected.tex")).unwrap(); + pretty_assertions::assert_eq!(actual, expected); +} + +#[test] +fn latex_filecontents_fixture_stays_verbatim() { + let actual = run_format("latex", &fixture_path("filecontents.tex")); + let expected = fs::read_to_string(fixture_path("filecontents.tex")).unwrap(); + pretty_assertions::assert_eq!(actual, expected); +} + +#[test] +fn latex_pycode_fixture_stays_verbatim() { + let actual = run_format("latex", &fixture_path("pycode.tex")); + let expected = fs::read_to_string(fixture_path("pycode.tex")).unwrap(); + pretty_assertions::assert_eq!(actual, expected); +} + +#[test] +fn latex_verbatim_fixture_stays_verbatim() { + let actual = run_format("latex", &fixture_path("verbatim.tex")); + let expected = fs::read_to_string(fixture_path("verbatim.tex")).unwrap(); + pretty_assertions::assert_eq!(actual, expected); +} + +#[test] +fn latex_tikzcd_fixture_stays_structure() { + let actual = run_format("latex", &fixture_path("tikzcd.tex")); + let expected = fs::read_to_string(fixture_path("tikzcd.tex")).unwrap(); + pretty_assertions::assert_eq!(actual, expected); +} + +#[test] +fn latex_comment_fixture_stays_verbatim() { + let actual = run_format("latex", &fixture_path("comment.tex")); + let expected = fs::read_to_string(fixture_path("comment.tex")).unwrap(); + pretty_assertions::assert_eq!(actual, expected); +} + +#[test] +fn latex_iffalse_fixture_stays_verbatim() { + let actual = run_format("latex", &fixture_path("iffalse.tex")); + let expected = fs::read_to_string(fixture_path("iffalse.tex")).unwrap(); + pretty_assertions::assert_eq!(actual, expected); +} + +#[test] +fn markdown_format() { + let actual = run_format("markdown", &fixture_path("sample.md")); + let expected = fs::read_to_string(fixture_path("expected.md")).unwrap(); + pretty_assertions::assert_eq!(actual, expected); +} + +#[test] +fn idempotent_org() { + let first = run_format("org", &fixture_path("sample.org")); + let tmp = tempfile::NamedTempFile::new().unwrap(); + fs::write(tmp.path(), &first).unwrap(); + let second = run_format("org", &tmp.path().to_string_lossy()); + pretty_assertions::assert_eq!(first, second, "org reflow must be idempotent"); +} + +#[test] +fn mixed_list_org_hangs_and_is_idempotent() { + let first = run_format("org", &fixture_path("mixed_list.org")); + assert!( + first.contains("- One.\n Two.\n"), + "org dash continuation must hang: {first}" + ); + assert!( + first.contains( + "1. Numbered one.\n Numbered two.\n - Nested dash.\n Nested second.\n" + ), + "org nested list must hang and stay two items: {first}" + ); + assert!( + first.contains(" 1. Nested numbered.\n Nested numbered two.\n"), + "org nested ordered continuation must hang: {first}" + ); + let tmp = tempfile::NamedTempFile::new().unwrap(); + fs::write(tmp.path(), &first).unwrap(); + let second = run_format("org", &tmp.path().to_string_lossy()); + pretty_assertions::assert_eq!(first, second, "mixed_list.org must be idempotent"); +} + +#[test] +fn mixed_list_markdown_hangs_and_is_idempotent() { + let first = run_format("markdown", &fixture_path("mixed_list.md")); + assert!( + first.contains("- One.\n Two.\n"), + "markdown dash continuation must hang: {first}" + ); + assert!( + first.contains( + "1. Numbered one.\n Numbered two.\n - Nested dash.\n Nested second.\n" + ), + "markdown nested list must hang and stay two items: {first}" + ); + assert!( + first.contains("> Quoted one.\n> Quoted two.\n"), + "markdown quote continuation must repeat the prefix: {first}" + ); + assert!( + first.contains("> > Nested one.\n> > Nested two.\n"), + "nested markdown quote must keep the full prefix: {first}" + ); + let tmp = tempfile::NamedTempFile::new().unwrap(); + fs::write(tmp.path(), &first).unwrap(); + let second = run_format("markdown", &tmp.path().to_string_lossy()); + pretty_assertions::assert_eq!(first, second, "mixed_list.md must be idempotent"); +} + +#[test] +fn idempotent_plaintext() { + let first = run_format("plaintext", &fixture_path("sample.txt")); + let tmp = tempfile::NamedTempFile::new().unwrap(); + fs::write(tmp.path(), &first).unwrap(); + let second = run_format("plaintext", &tmp.path().to_string_lossy()); + pretty_assertions::assert_eq!(first, second, "plaintext reflow must be idempotent"); +} + +#[test] +fn idempotent_latex() { + let first = run_format("latex", &fixture_path("sample.tex")); + let tmp = tempfile::NamedTempFile::new().unwrap(); + fs::write(tmp.path(), &first).unwrap(); + let second = run_format("latex", &tmp.path().to_string_lossy()); + pretty_assertions::assert_eq!(first, second, "latex reflow must be idempotent"); +} + +#[test] +fn idempotent_markdown() { + let first = run_format("markdown", &fixture_path("sample.md")); + let tmp = tempfile::NamedTempFile::new().unwrap(); + fs::write(tmp.path(), &first).unwrap(); + let second = run_format("markdown", &tmp.path().to_string_lossy()); + pretty_assertions::assert_eq!(first, second, "markdown reflow must be idempotent"); +} + +#[test] +fn check_mode_passes_on_formatted() { + let output = snapper_binary() + .args([ + "--native", + "--check", + "--format", + "org", + &fixture_path("expected.org"), + ]) + .output() + .expect("failed to run snapper"); + assert!( + output.status.success(), + "--check should pass on already-formatted file" + ); +} + +#[test] +fn check_mode_fails_on_unformatted() { + let output = snapper_binary() + .args([ + "--native", + "--check", + "--format", + "org", + &fixture_path("sample.org"), + ]) + .output() + .expect("failed to run snapper"); + assert!( + !output.status.success(), + "--check should fail on unformatted file" + ); +} + +#[test] +fn stdin_stdout() { + let input = "Hello world. This is a test. Another sentence."; + let out = pipe_stdin(input, &[]); + let result = String::from_utf8(out.stdout).unwrap(); + assert_eq!(result, "Hello world.\nThis is a test.\nAnother sentence."); +} + +// ---- FEATURE 1: --stdin-filepath ---- + +#[test] +fn stdin_filepath_auto_detects_org() { + let input = "#+TITLE: Test\n\nFirst sentence. Second sentence.\n"; + let out = pipe_stdin(input, &["--stdin-filepath", "paper.org"]); + assert!(out.status.success()); + let result = String::from_utf8(out.stdout).unwrap(); + // Should detect org format and preserve #+TITLE as structure + assert!(result.starts_with("#+TITLE: Test\n")); + assert!(result.contains("First sentence.\n")); + assert!(result.contains("Second sentence.\n")); +} + +#[test] +fn stdin_filepath_detects_latex() { + // .tex extension should trigger LaTeX parser (preamble preserved) + let input = + "\\documentclass{article}\n\\begin{document}\nHello world. Second.\n\\end{document}\n"; + let out = pipe_stdin(input, &["--stdin-filepath", "paper.tex"]); + assert!(out.status.success()); + let result = String::from_utf8(out.stdout).unwrap(); + assert!(result.contains("\\documentclass{article}")); + assert!(result.contains("Hello world.\n")); +} + +#[test] +fn format_flag_overrides_stdin_filepath() { + // --format should take priority over --stdin-filepath + let input = "Hello world. Second sentence.\n"; + let out = pipe_stdin( + input, + &["--format", "plaintext", "--stdin-filepath", "paper.org"], + ); + assert!(out.status.success()); + let result = String::from_utf8(out.stdout).unwrap(); + assert_eq!(result, "Hello world.\nSecond sentence.\n"); +} + +// ---- FEATURE 2: snapper:off / snapper:on pragmas ---- + +#[test] +fn pragma_off_on_org() { + let input = + "First. Second.\n# snapper:off\nDo not. Touch this.\n# snapper:on\nThird. Fourth.\n"; + let out = pipe_stdin(input, &["--format", "org"]); + let result = String::from_utf8(out.stdout).unwrap(); + assert!(result.contains("First.\nSecond.\n")); + assert!(result.contains("Do not. Touch this.\n")); // untouched + assert!(result.contains("Third.\nFourth.\n")); +} + +#[test] +fn pragma_off_on_latex() { + let input = "\\begin{document}\nHello world. Goodbye world.\n% snapper:off\nKeep this. Exactly here.\n% snapper:on\nFinal thing. Last sentence.\n\\end{document}\n"; + let out = pipe_stdin(input, &["--format", "latex"]); + let result = String::from_utf8(out.stdout).unwrap(); + assert!(result.contains("Hello world.\nGoodbye world.\n")); + assert!(result.contains("Keep this. Exactly here.\n")); // untouched + assert!(result.contains("Final thing.\nLast sentence.\n")); +} + +#[test] +fn pragma_off_on_markdown() { + let input = "Hello world. Goodbye world.\n\nKeep this. Exactly here.\n\nFinal thing. Last sentence.\n"; + let out = pipe_stdin(input, &["--format", "markdown"]); + let result = String::from_utf8(out.stdout).unwrap(); + assert!(result.contains("Hello world.\nGoodbye world.\n")); + assert!(result.contains("Keep this. Exactly here.\n")); + assert!(result.contains("Final thing.\nLast sentence.")); +} + +#[test] +fn pragma_off_on_plaintext() { + let input = "Hello world. Goodbye world.\nsnapper:off\nKeep this. Exactly here.\nsnapper:on\nFinal thing. Last sentence.\n"; + let out = pipe_stdin(input, &["--format", "plaintext"]); + let result = String::from_utf8(out.stdout).unwrap(); + assert!(result.contains("Hello world.\nGoodbye world.\n")); + assert!(result.contains("Keep this. Exactly here.\n")); + assert!(result.contains("Final thing.\nLast sentence.\n")); +} + +// ---- FEATURE 5: --range ---- + +#[test] +fn range_formats_only_specified_lines() { + let input = "Line one. Stay same.\nLine two. Should split. Into two.\nLine three. Stay same.\n"; + let out = pipe_stdin(input, &["--range", "2:2"]); + let result = String::from_utf8(out.stdout).unwrap(); + // Line 1 unchanged + assert!(result.starts_with("Line one. Stay same.\n")); + // Line 2 split + assert!(result.contains("Line two.\nShould split.\nInto two.\n")); + // Line 3 unchanged + assert!(result.ends_with("Line three. Stay same.\n")); +} + +#[test] +fn range_preserves_outside_lines_exactly() { + let tmp = tempfile::NamedTempFile::new().unwrap(); + fs::write( + tmp.path(), + "Keep. This line.\nReformat. This line. Please.\nKeep. This too.\n", + ) + .unwrap(); + let output = snapper_binary() + .args([ + "--native", + "--format", + "plaintext", + "--range", + "2:2", + &tmp.path().to_string_lossy(), + ]) + .output() + .expect("failed to run"); + let result = String::from_utf8(output.stdout).unwrap(); + assert!(result.starts_with("Keep. This line.\n")); + assert!(result.contains("Reformat.\nThis line.\nPlease.\n")); + assert!(result.ends_with("Keep. This too.\n")); +} + +// ---- FEATURE 6: --output-format json/sarif ---- + +#[test] +fn check_json_output() { + let output = snapper_binary() + .args([ + "--native", + "--check", + "--output-format", + "json", + "--format", + "org", + &fixture_path("sample.org"), + ]) + .output() + .expect("failed to run"); + let stdout = String::from_utf8(output.stdout).unwrap(); + let parsed: serde_json::Value = serde_json::from_str(&stdout).expect("invalid JSON"); + assert!(parsed.is_array()); + let arr = parsed.as_array().unwrap(); + assert_eq!(arr.len(), 1); + assert!(arr[0]["file"].as_str().unwrap().contains("sample.org")); +} + +#[test] +fn check_sarif_output() { + let output = snapper_binary() + .args([ + "--native", + "--check", + "--output-format", + "sarif", + "--format", + "org", + &fixture_path("sample.org"), + ]) + .output() + .expect("failed to run"); + let stdout = String::from_utf8(output.stdout).unwrap(); + let parsed: serde_json::Value = serde_json::from_str(&stdout).expect("invalid SARIF"); + assert_eq!(parsed["version"], "2.1.0"); + let results = &parsed["runs"][0]["results"]; + assert!(results.is_array()); + assert!(!results.as_array().unwrap().is_empty()); +} + +// ---- Golden master: edge cases ---- + +#[test] +fn edge_cases_org() { + let actual = run_format("org", &fixture_path("edge_cases.org")); + let expected = fs::read_to_string(fixture_path("expected_edge_cases.org")).unwrap(); + pretty_assertions::assert_eq!(actual, expected); +} + +#[test] +fn edge_cases_plaintext() { + let actual = run_format("plaintext", &fixture_path("edge_cases.txt")); + let expected = fs::read_to_string(fixture_path("expected_edge_cases.txt")).unwrap(); + pretty_assertions::assert_eq!(actual, expected); +} + +#[test] +fn idempotent_edge_cases_org() { + let first = run_format("org", &fixture_path("edge_cases.org")); + let tmp = tempfile::NamedTempFile::new().unwrap(); + fs::write(tmp.path(), &first).unwrap(); + let second = run_format("org", &tmp.path().to_string_lossy()); + pretty_assertions::assert_eq!(first, second, "edge cases org reflow must be idempotent"); +} + +#[test] +fn idempotent_edge_cases_plaintext() { + let first = run_format("plaintext", &fixture_path("edge_cases.txt")); + let tmp = tempfile::NamedTempFile::new().unwrap(); + fs::write(tmp.path(), &first).unwrap(); + let second = run_format("plaintext", &tmp.path().to_string_lossy()); + pretty_assertions::assert_eq!( + first, + second, + "edge cases plaintext reflow must be idempotent" + ); +} + +// ---- FEATURE 7: snapper init ---- + +#[test] +fn init_dry_run_shows_config() { + let output = snapper_binary() + .args(["init", "--dry-run"]) + .output() + .expect("failed to run"); + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(stderr.contains(".snapperrc.toml")); + assert!(stderr.contains("format =")); + assert!(stderr.contains("pre-commit")); + assert!(stderr.contains("apheleia")); +} + +#[test] +fn config_lang_applies_to_stdin() { + let dir = tempfile::tempdir().unwrap(); + fs::write(dir.path().join(".snapperrc.toml"), "lang = \"de\"\n").unwrap(); + let out = pipe_stdin_in_dir("z.B. Das ist ein Satz. Noch einer.\n", &[], dir.path()); + assert!(out.status.success()); + let result = String::from_utf8(out.stdout).unwrap(); + assert_eq!(result, "z.B. Das ist ein Satz.\nNoch einer.\n"); +} + +#[test] +fn config_default_format_applies_to_stdin() { + let dir = tempfile::tempdir().unwrap(); + fs::write(dir.path().join(".snapperrc.toml"), "format = \"org\"\n").unwrap(); + let out = pipe_stdin_in_dir("* Heading\nSentence one. Sentence two.\n", &[], dir.path()); + assert!(out.status.success()); + let result = String::from_utf8(out.stdout).unwrap(); + assert!(result.starts_with("* Heading\n")); + assert!(result.contains("Sentence one.\n")); + assert!(result.contains("Sentence two.\n")); +} + +#[test] +fn config_ignore_skips_check_mode() { + let dir = tempfile::tempdir().unwrap(); + fs::write(dir.path().join(".snapperrc.toml"), "ignore = [\"*.md\"]\n").unwrap(); + fs::write(dir.path().join("draft.md"), "Sentence one. Sentence two.\n").unwrap(); + let output = snapper_binary() + .current_dir(dir.path()) + .args(["--native", "--check", "draft.md"]) + .output() + .expect("failed to run snapper"); + assert!(output.status.success()); + assert!(String::from_utf8(output.stderr).unwrap().is_empty()); +} + +#[test] +fn config_per_format_width_applies() { + let dir = tempfile::tempdir().unwrap(); + fs::write( + dir.path().join(".snapperrc.toml"), + "max_width = 80\n[plaintext]\nmax_width = 12\n", + ) + .unwrap(); + fs::write( + dir.path().join("draft.txt"), + "Alpha beta gamma delta epsilon zeta.\n", + ) + .unwrap(); + let output = snapper_binary() + .current_dir(dir.path()) + .args(["--native", "draft.txt"]) + .output() + .expect("failed to run snapper"); + assert!(output.status.success()); + let result = String::from_utf8(output.stdout).unwrap(); + assert_eq!(result, "Alpha beta\ngamma delta\nepsilon\nzeta.\n"); +} + +#[test] +fn config_latex_envs_and_commands_apply() { + let dir = tempfile::tempdir().unwrap(); + fs::write( + dir.path().join(".snapperrc.toml"), + "[latex]\nverbatim_envs = [\"Verbatim\"]\nstructure_envs = [\"algorithm\", \"comment\"]\nverbatim_commands = [\"Verb\"]\n", + ) + .unwrap(); + let input = "\\begin{document}\n\\begin{algorithm}\nFirst step. Second step.\n\\end{algorithm}\nUse \\Verb|a.b! c| here. Next sentence.\n\\end{document}\n"; + let out = pipe_stdin_in_dir(input, &["--format", "latex"], dir.path()); + assert!( + out.status.success(), + "stderr={}", + String::from_utf8_lossy(&out.stderr) + ); + let result = String::from_utf8(out.stdout).unwrap(); + assert!( + result.contains("First step. Second step."), + "algorithm body must not reflow, got:\n{result}" + ); + assert!( + !result.contains("First step.\nSecond step."), + "algorithm must stay one source line, got:\n{result}" + ); + assert!( + result.contains("Use \\Verb|a.b! c| here.\nNext sentence."), + "configured Verb must tokenize like verb, got:\n{result}" + ); +} + +#[test] +fn markdown_numbered_atx_heading_not_split() { + // snapper-25kc / rtrash README regression + let input = + "### 1. `cargo binstall` (preferred binary install)\n\nBody one. Body two more words.\n"; + let out = pipe_stdin(input, &["--format", "markdown"]); + assert!( + out.status.success(), + "stderr={}", + String::from_utf8_lossy(&out.stderr) + ); + let result = String::from_utf8(out.stdout).unwrap(); + assert!( + result.starts_with("### 1. `cargo binstall` (preferred binary install)\n"), + "heading must stay one line, got:\n{result}" + ); + assert!( + !result.contains("### 1.\n`"), + "must not orphan ### 1. before title:\n{result}" + ); +} + +#[test] +fn markdown_setext_heading_not_collapsed() { + let input = "Setext Title With Period. Still Title\n=====================================\n\nBody after. Second body.\n"; + let out = pipe_stdin(input, &["--format", "markdown"]); + assert!( + out.status.success(), + "stderr={}", + String::from_utf8_lossy(&out.stderr) + ); + let result = String::from_utf8(out.stdout).unwrap(); + assert!( + result.starts_with( + "Setext Title With Period. Still Title\n=====================================\n" + ), + "setext title+underline must stay intact, got:\n{result}" + ); + assert!( + !result.contains("Still Title ====="), + "must not glue underline onto title:\n{result}" + ); +} diff --git a/tests/integration.rs b/tests/integration.rs index 6b10bd09..91337ac4 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -6,6 +6,8 @@ #[path = "check_line_diagnostics.rs"] mod check_line_diagnostics; +#[path = "cli_integration.rs"] +mod cli_integration; #[path = "code_block_format.rs"] mod code_block_format; #[path = "code_block_reflow.rs"] From ac3b44faad9ec8ff998151419e030a12ce5b6fe4 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 3 Oct 2026 21:24:18 +0200 Subject: [PATCH 2/4] bld(release): the bump moves every pre-commit rev pin, which now live under docs/orgmode --- cog.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cog.toml b/cog.toml index 72983d55..4e758df3 100644 --- a/cog.toml +++ b/cog.toml @@ -11,7 +11,8 @@ pre_bump_hooks = [ # dependency and ship upgrades no test ran against. "cargo update --workspace", "sed -i 's/^release = \".*\"/release = \"{{version}}\"/' docs/source/conf.py", - "sed -i 's/rev: v[0-9][0-9.\\-]*/rev: v{{version}}/' README.md docs/source/howto/vale-integration.rst", + # Only the rev under snapper's own repo line; other hooks keep their pins. + "sed -i '/repo: https:\\/\\/github.com\\/TurtleTech-ehf\\/snapper/{n;s/rev: v[0-9][0-9.\\-]*/rev: v{{version}}/}' README.md readme_src.org docs/orgmode/tutorials/quickstart.org docs/orgmode/howto/vale-integration.org docs/orgmode/howto/ci-enforcement.org", "sed -i 's/\"version\": \".*\"/\"version\": \"{{version}}\"/' editors/vscode/package.json editors/obsidian/package.json editors/obsidian/manifest.json editors/word/package.json packages/snapper-wasm/package.json npm/package.json", ] From 94b59890552bbafbb1508c6312d179a7315eaa99 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 3 Oct 2026 21:24:25 +0200 Subject: [PATCH 3/4] chore(version): v0.11.8 --- CHANGELOG.md | 53 +++++++++++++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 2 +- docs/orgmode/howto/ci-enforcement.org | 2 +- 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.json | 2 +- editors/vscode/package.json | 2 +- editors/word/package.json | 2 +- npm/package.json | 2 +- packages/snapper-wasm/package.json | 2 +- readme_src.org | 2 +- 15 files changed, 67 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc91b7b0..00b5d8ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,59 @@ All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines. - - - +## v0.11.8 - 2026-10-03 +#### Tests +- (**markdown**) accept the line-block terminator on the structure span - (6f3cd8c) - *HaoZeke* +- (**markdown**) keep the paragraph after an unclosed bracket - (037c3de) - *HaoZeke* +- (**markdown**) unclosed bracket math must not swallow the next line - (9223256) - *HaoZeke* +- restore the 42 CLI tests the consolidated binary overwrote - (df6b271) - *HaoZeke* +- the fancyvrb VerbatimInput test is named for what it checks - (9d043bb) - *HaoZeke* +- a nested test runs at the top level, and the clippy lints in the consolidated tests are cleared - (eeb13b4) - *HaoZeke* +#### Buildsystem +- (**release**) the bump moves every pre-commit rev pin, which now live under docs/orgmode - (ed8847a) - *HaoZeke* +- (**release**) a bump moves only the workspace crates in the lockfile - (439519a) - *HaoZeke* +- (**tests**) one integration test binary links the library once - (f0c1e75) - *HaoZeke* +#### Documentation +- (**org**) hang the vale item description under its tag - (9bda344) - *HaoZeke* +- cli reference reflowed where a sentence opens with a number - (918162e) - *HaoZeke* +- keep the 0.11.7 changelog on the released notes - (1029ef1) - *HaoZeke* +#### Bug Fixes +- (**build**) use thin LTO for the release profile - (bf92050) - *HaoZeke* +- (**latex**) keep letter-delimited verb and url spans whole - (dee2151) - *HaoZeke* +- (**latex**) keep pgfplots group and special axes whole - (c8fb922) - *HaoZeke* +- (**latex**) keep breqn dgroup and darray whole - (4ca94ae) - *HaoZeke* +- (**latex**) keep more math and verbatim environments whole - (1e41fb9) - *HaoZeke* +- (**latex**) keep leftover math and filecontents bodies whole - (2af7677) - *HaoZeke* +- (**markdown**) keep mid-line bracket display math whole - (3633660) - *HaoZeke* +- (**markdown**) hang the second sentence of a pandoc example item - (d1a833f) - *HaoZeke* +- (**markdown**) match shortcut labels without lookahead - (45405fd) - *HaoZeke* +- (**markdown**) keep shortcut labels, citations, and dollar math whole - (25d078f) - *HaoZeke* +- (**markdown**) keep pandoc line blocks and example lists whole - (37f3086) - *HaoZeke* +- (**markdown**) keep LaTeX non-prose environments whole - (c22f021) - *HaoZeke* +- (**markdown**) end a bracket math search at a blank line - (3b1e7bd) - *HaoZeke* +- (**markdown**) do not treat a div fence as a definition marker - (c124ffa) - *HaoZeke* +- (**markdown**) keep bracket math and div fences out of prose - (2a93456) - *HaoZeke* +- (**org**) take the item tag through the last separator - (ad67258) - *HaoZeke* +- (**org**) keep list item tags on the item line - (7a13061) - *HaoZeke* +- (**org,rst**) keep continued tags and titles whole - (711f7a8) - *HaoZeke* +- (**pandoc**) shield alltt, BVerbatim, and luacode - (3e207aa) - *HaoZeke* +- (**pandoc**) shield starred verbatim, lstlisting, and minted - (ceb11e5) - *HaoZeke* +- (**plaintext**) do not split a balanced quote on a period - (7a87430) - *HaoZeke* +- (**rst**) hang the indented line after a same-line title - (66651ec) - *HaoZeke* +- (**rst**) keep csv-table title continuations as structure - (2547a67) - *HaoZeke* +- (**rst**) keep directive title arguments on the directive line - (389e864) - *HaoZeke* +- (**sentence**) lowercase academic abbreviations rejoin a number after them - (67b0547) - *HaoZeke* +- (**sentence**) a number opens a sentence, and a lowercase start can be asked for - (e0a7df2) - *HaoZeke* +#### Style +- (**latex**) sort imports with the edition 2024 formatter - (b5de4ca) - *HaoZeke* +- (**org,rst**) wrap the lines rustfmt rejected - (3356281) - *HaoZeke* +- (**pandoc**) wrap the starred-verbatim test input - (1aefe7d) - *HaoZeke* +- (**rst**) keep the title-body fixture on one line - (c325926) - *HaoZeke* +- unwrap_or_else with panic! over expect(&format!) in tests, and two needless borrows - (db7b015) - *HaoZeke* +- clear the clippy lints Rust 1.98 raises in tests and the LaTeX parser - (ee1f49e) - *HaoZeke* + +- - - + ## v0.11.7 - 2026-09-23 #### Bug Fixes - generated pre-commit hook runs under bash so the format loop runs on Ubuntu diff --git a/Cargo.lock b/Cargo.lock index 6a7ca243..0b4ab786 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2221,7 +2221,7 @@ checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100" [[package]] name = "snapper-fmt" -version = "0.11.7" +version = "0.11.8" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 36669c15..2beaddbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "snapper-fmt" -version = "0.11.7" +version = "0.11.8" edition = "2024" autotests = false rust-version = "1.85" diff --git a/README.md b/README.md index 802a4f9f..ea03d62e 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.7 + rev: v0.11.8 hooks: - id: snapper diff --git a/docs/orgmode/howto/ci-enforcement.org b/docs/orgmode/howto/ci-enforcement.org index 209a97d6..a079c2f2 100644 --- a/docs/orgmode/howto/ci-enforcement.org +++ b/docs/orgmode/howto/ci-enforcement.org @@ -35,7 +35,7 @@ Add to =.pre-commit-config.yaml=: #+begin_src yaml - repo: https://github.com/TurtleTech-ehf/snapper - rev: v0.11.7 + rev: v0.11.8 hooks: - id: snapper #+end_src diff --git a/docs/orgmode/howto/vale-integration.org b/docs/orgmode/howto/vale-integration.org index d6315b39..a684d022 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.7 + rev: v0.11.8 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 78595c4b..4b0c6781 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.7 + rev: v0.11.8 hooks: - id: snapper #+end_src diff --git a/docs/source/conf.py b/docs/source/conf.py index 140e42b7..d9a2e77b 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.7" +release = "0.11.8" html_logo = "../../branding/logo/snapper_logo.png" extensions = [ diff --git a/editors/obsidian/manifest.json b/editors/obsidian/manifest.json index d944b987..e7fabfbd 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.7", + "version": "0.11.8", "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.json b/editors/obsidian/package.json index 3cf5cb02..d4aa39f5 100644 --- a/editors/obsidian/package.json +++ b/editors/obsidian/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-snapper", - "version": "0.11.7", + "version": "0.11.8", "private": true, "description": "Obsidian plugin for snapper semantic line break formatter", "scripts": { diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 11f52ff5..221a2fbe 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.7", + "version": "0.11.8", "publisher": "TurtleTech", "license": "MIT", "repository": { diff --git a/editors/word/package.json b/editors/word/package.json index f0292532..54b8e046 100644 --- a/editors/word/package.json +++ b/editors/word/package.json @@ -1,6 +1,6 @@ { "name": "word-snapper", - "version": "0.11.7", + "version": "0.11.8", "private": true, "description": "Microsoft Word add-in for snapper semantic line break formatter", "scripts": { diff --git a/npm/package.json b/npm/package.json index d5a5adb5..cba347f3 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "@turtletech/snapper-mcp", - "version": "0.11.7", + "version": "0.11.8", "private": true, "description": "MCP server for snapper semantic line break formatter", "main": "bin/run.js", diff --git a/packages/snapper-wasm/package.json b/packages/snapper-wasm/package.json index c0f5143c..aee68ac5 100644 --- a/packages/snapper-wasm/package.json +++ b/packages/snapper-wasm/package.json @@ -1,6 +1,6 @@ { "name": "@snapper/wasm", - "version": "0.11.7", + "version": "0.11.8", "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 76a02a30..e8e7a301 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.7 + rev: v0.11.8 hooks: - id: snapper #+end_src From 811d2d26c3c2d61d5405818d2445ea2fa1cce984 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 3 Oct 2026 14:54:06 -0500 Subject: [PATCH 4/4] chore(release): pin every channel version to v0.11.8 The version bump left the action pins, editor lockfiles, editor readmes, and the conda recipe version on v0.11.7. The recipe sha256 stays until the tag tarball exists. --- conda/recipe.yaml | 2 +- docs/orgmode/howto/ci-enforcement.org | 4 ++-- editors/obsidian/package-lock.json | 6 +++--- editors/vscode/README.md | 4 ++-- editors/word/README.md | 4 ++-- editors/word/manifest.xml | 2 +- editors/word/package-lock.json | 6 +++--- editors/word/src/taskpane/taskpane.html | 2 +- packages/snapper-wasm/package-lock.json | 4 ++-- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/conda/recipe.yaml b/conda/recipe.yaml index 51270949..4f9e66da 100644 --- a/conda/recipe.yaml +++ b/conda/recipe.yaml @@ -3,7 +3,7 @@ schema_version: 1 context: - version: "0.11.7" + version: "0.11.8" package: name: snapper-fmt diff --git a/docs/orgmode/howto/ci-enforcement.org b/docs/orgmode/howto/ci-enforcement.org index a079c2f2..b8388eaf 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.7 +- uses: TurtleTech-ehf/snapper@v0.11.8 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.7 +- uses: TurtleTech-ehf/snapper@v0.11.8 with: files: '**/*.org **/*.tex **/*.md' sarif: 'true' diff --git a/editors/obsidian/package-lock.json b/editors/obsidian/package-lock.json index ec95ae59..f75fce2c 100644 --- a/editors/obsidian/package-lock.json +++ b/editors/obsidian/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-snapper", - "version": "0.11.7", + "version": "0.11.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-snapper", - "version": "0.11.7", + "version": "0.11.8", "license": "MIT", "devDependencies": { "@snapper/wasm": "file:../../packages/snapper-wasm", @@ -17,7 +17,7 @@ }, "../../packages/snapper-wasm": { "name": "@snapper/wasm", - "version": "0.11.7", + "version": "0.11.8", "dev": true, "license": "MIT", "devDependencies": { diff --git a/editors/vscode/README.md b/editors/vscode/README.md index df1ea77a..139e1cd5 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.7+** on your PATH (or set `snapper.path`). +Requires the **snapper** / **snapper-fmt** CLI **0.11.8+** 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.7). +Install: `cargo install snapper-fmt` or the [release installer](https://github.com/TurtleTech-ehf/snapper/releases/tag/v0.11.8). Format prose so each sentence occupies its own line, producing clean git diffs for collaborative writing. diff --git a/editors/word/README.md b/editors/word/README.md index 6c23fe6d..750ddd0b 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.7 (tracks snapper-fmt) +**Version:** 0.11.8 (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.7+**). +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+**). ## License diff --git a/editors/word/manifest.xml b/editors/word/manifest.xml index d4a342bf..585e47d5 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.7 + 0.11.8 TurtleTech ehf en-US diff --git a/editors/word/package-lock.json b/editors/word/package-lock.json index 8f706653..91d281b8 100644 --- a/editors/word/package-lock.json +++ b/editors/word/package-lock.json @@ -1,12 +1,12 @@ { "name": "word-snapper", - "version": "0.11.7", + "version": "0.11.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "word-snapper", - "version": "0.11.7", + "version": "0.11.8", "license": "MIT", "devDependencies": { "@snapper/wasm": "file:../../packages/snapper-wasm", @@ -21,7 +21,7 @@ }, "../../packages/snapper-wasm": { "name": "@snapper/wasm", - "version": "0.11.7", + "version": "0.11.8", "dev": true, "license": "MIT", "devDependencies": { diff --git a/editors/word/src/taskpane/taskpane.html b/editors/word/src/taskpane/taskpane.html index 6919521f..4a740f69 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.7)

+

Semantic line breaks for Word (v0.11.8)

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

diff --git a/packages/snapper-wasm/package-lock.json b/packages/snapper-wasm/package-lock.json index 7687340a..a1742945 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.7", + "version": "0.11.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@snapper/wasm", - "version": "0.11.7", + "version": "0.11.8", "license": "MIT", "devDependencies": { "typescript": "^5.4"