From 9aa5158d26a39f93ebe9df516a217f7aa0dcf412 Mon Sep 17 00:00:00 2001 From: claude Date: Thu, 12 Feb 2026 00:47:22 -0300 Subject: [PATCH 1/6] feat: add run-once fence option (closes #71) --- numd/commands.nu | 3 ++- tests/test_commands.nu | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/numd/commands.nu b/numd/commands.nu index 883793d..a01ed22 100644 --- a/numd/commands.nu +++ b/numd/commands.nu @@ -256,7 +256,7 @@ export def decorate-original-code-blocks [ | insert code {|i| $i.line | process-code-block-content ($i.row_type | extract-fence-options) - | generate-block-markers $i.block_index $i.row_type + | generate-block-markers $i.block_index ($i.row_type | str replace 'run-once' 'no-run') } } @@ -422,6 +422,7 @@ const fence_options = [ [t try "execute block inside `try {}` for error handling"] [n new-instance "execute block in new Nushell instance (useful with `try` block)"] [s separate-block "output results in a separate code block instead of inline `# =>`"] + [1 run-once "execute code block once, then set to no-run"] ] # List fence options for execution and output customization. diff --git a/tests/test_commands.nu b/tests/test_commands.nu index a232bef..8d369ca 100644 --- a/tests/test_commands.nu +++ b/tests/test_commands.nu @@ -82,6 +82,11 @@ def "classify-block-action returns print-as-it-is for no-run" [] { assert equal (classify-block-action "```nushell no-run") "print-as-it-is" } +@test +def "classify-block-action returns execute for run-once" [] { + assert equal (classify-block-action "```nushell run-once") "execute" +} + @test def "classify-block-action returns delete for output-numd" [] { assert equal (classify-block-action "```output-numd") "delete" @@ -126,6 +131,13 @@ def "extract-fence-options expands short options" [] { assert ("no-output" in $result) } +@test +def "extract-fence-options parses run-once" [] { + let result = "```nu run-once" | extract-fence-options + + assert equal $result ["run-once"] +} + @test def "extract-fence-options handles empty options" [] { let result = "```nushell" | extract-fence-options @@ -157,6 +169,11 @@ def "convert-short-options expands n" [] { assert equal (convert-short-options "n") "new-instance" } +@test +def "convert-short-options expands 1" [] { + assert equal (convert-short-options "1") "run-once" +} + @test def "convert-short-options keeps long options unchanged" [] { assert equal (convert-short-options "no-output") "no-output" From 2c5d54a80d9f7a1746ae6f2a4b615d22880ae3ee Mon Sep 17 00:00:00 2001 From: claude Date: Thu, 12 Feb 2026 00:47:28 -0300 Subject: [PATCH 2/6] docs: add run-once fence option to CLAUDE.md --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CLAUDE.md b/CLAUDE.md index 068af96..26527dd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,6 +82,7 @@ Blocks support fence options (e.g., ` ```nushell try, no-output `): - `try` / `t`: Wrap in try-catch - `new-instance` / `n`: Execute in separate Nushell instance - `separate-block` / `s`: Output results in separate code block instead of inline `# =>` +- `run-once` / `1`: Execute code block once, then set to `no-run` ### Output Format Conventions From b09f66434c1bcaf9e1045a3cbcfab4edf62da1c3 Mon Sep 17 00:00:00 2001 From: claude Date: Thu, 12 Feb 2026 00:54:46 -0300 Subject: [PATCH 3/6] fix: handle short-form 1 option in run-once fence replacement --- numd/commands.nu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numd/commands.nu b/numd/commands.nu index a01ed22..17fdc03 100644 --- a/numd/commands.nu +++ b/numd/commands.nu @@ -256,7 +256,7 @@ export def decorate-original-code-blocks [ | insert code {|i| $i.line | process-code-block-content ($i.row_type | extract-fence-options) - | generate-block-markers $i.block_index ($i.row_type | str replace 'run-once' 'no-run') + | generate-block-markers $i.block_index ($i.row_type | str replace 'run-once' 'no-run' | str replace -r '\b1\b' 'no-run') } } From 8ef03f33e95c3bcc7b4bda748cea9108885738fb Mon Sep 17 00:00:00 2001 From: claude Date: Thu, 12 Feb 2026 00:55:01 -0300 Subject: [PATCH 4/6] fix: correct @example list literal syntax in extract-fence-options --- numd/commands.nu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numd/commands.nu b/numd/commands.nu index 17fdc03..4bfa60e 100644 --- a/numd/commands.nu +++ b/numd/commands.nu @@ -649,7 +649,7 @@ export def generate-block-markers [ } # Parse options from a code fence and return them as a list. -@example "parse fence options with short forms" { '```nu no-run, t' | extract-fence-options } --result [no-run, try] +@example "parse fence options with short forms" { '```nu no-run, t' | extract-fence-options } --result [no-run try] export def extract-fence-options []: string -> list { str replace -r '```nu(shell)?\s*' '' | split row ',' From c3fd9a42ed9e0ce4f1010c28eb034e0a3f818ab3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 30 Mar 2026 00:28:45 +0000 Subject: [PATCH 5/6] remove: drop short-form `1` for run-once fence option The `1` short form required a fragile `\b1\b` regex on the raw fence string in decorate-original-code-blocks, separate from the existing extract-fence-options/convert-short-options pipeline. Since run-once is inherently a one-time annotation (it rewrites itself to no-run), a short form adds no real convenience. Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 2 +- numd/commands.nu | 3 +-- tests/test_commands.nu | 5 ----- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 26527dd..850bd16 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,7 +82,7 @@ Blocks support fence options (e.g., ` ```nushell try, no-output `): - `try` / `t`: Wrap in try-catch - `new-instance` / `n`: Execute in separate Nushell instance - `separate-block` / `s`: Output results in separate code block instead of inline `# =>` -- `run-once` / `1`: Execute code block once, then set to `no-run` +- `run-once`: Execute code block once, then set to `no-run` ### Output Format Conventions diff --git a/numd/commands.nu b/numd/commands.nu index 4bfa60e..b035146 100644 --- a/numd/commands.nu +++ b/numd/commands.nu @@ -256,7 +256,7 @@ export def decorate-original-code-blocks [ | insert code {|i| $i.line | process-code-block-content ($i.row_type | extract-fence-options) - | generate-block-markers $i.block_index ($i.row_type | str replace 'run-once' 'no-run' | str replace -r '\b1\b' 'no-run') + | generate-block-markers $i.block_index ($i.row_type | str replace 'run-once' 'no-run') } } @@ -422,7 +422,6 @@ const fence_options = [ [t try "execute block inside `try {}` for error handling"] [n new-instance "execute block in new Nushell instance (useful with `try` block)"] [s separate-block "output results in a separate code block instead of inline `# =>`"] - [1 run-once "execute code block once, then set to no-run"] ] # List fence options for execution and output customization. diff --git a/tests/test_commands.nu b/tests/test_commands.nu index 8d369ca..c17e4e0 100644 --- a/tests/test_commands.nu +++ b/tests/test_commands.nu @@ -169,11 +169,6 @@ def "convert-short-options expands n" [] { assert equal (convert-short-options "n") "new-instance" } -@test -def "convert-short-options expands 1" [] { - assert equal (convert-short-options "1") "run-once" -} - @test def "convert-short-options keeps long options unchanged" [] { assert equal (convert-short-options "no-output") "no-output" From 4007ca722ac3a6be3a3d06b43c647eff1e24a88d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 30 Mar 2026 00:35:19 +0000 Subject: [PATCH 6/6] test: add tests for run-once fence rewriting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Unit tests for decorate-original-code-blocks verifying run-once → no-run rewriting, including combined options (run-once, try) and plain blocks - Unit tests for classify-block-action and extract-fence-options with combined run-once options - Integration test file (z_examples/6_edge_cases/run_once.md) exercising run-once alone and combined with no-output The core rewriting logic (run-once → no-run in the output fence) had zero direct test coverage — these tests close that gap. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/test_commands.nu | 45 +++++++++++++++++++++++++++++ z_examples/6_edge_cases/run_once.md | 13 +++++++++ 2 files changed, 58 insertions(+) create mode 100644 z_examples/6_edge_cases/run_once.md diff --git a/tests/test_commands.nu b/tests/test_commands.nu index c17e4e0..19ab290 100644 --- a/tests/test_commands.nu +++ b/tests/test_commands.nu @@ -87,6 +87,11 @@ def "classify-block-action returns execute for run-once" [] { assert equal (classify-block-action "```nushell run-once") "execute" } +@test +def "classify-block-action returns execute for run-once combined with try" [] { + assert equal (classify-block-action "```nushell run-once, try") "execute" +} + @test def "classify-block-action returns delete for output-numd" [] { assert equal (classify-block-action "```output-numd") "delete" @@ -138,6 +143,15 @@ def "extract-fence-options parses run-once" [] { assert equal $result ["run-once"] } +@test +def "extract-fence-options parses run-once combined with try" [] { + let result = "```nu run-once, try" | extract-fence-options + + assert equal ($result | length) 2 + assert ("run-once" in $result) + assert ("try" in $result) +} + @test def "extract-fence-options handles empty options" [] { let result = "```nushell" | extract-fence-options @@ -387,6 +401,37 @@ def "generate-timestamp returns correct format" [] { assert ($result =~ '^\d{8}_\d{6}$') } +# ============================================================================= +# Tests for decorate-original-code-blocks (run-once rewriting) +# ============================================================================= + +@test +def "decorate-original-code-blocks rewrites run-once to no-run" [] { + let blocks = "```nu run-once\necho hello\n```" | parse-markdown-to-blocks + let result = decorate-original-code-blocks $blocks + + assert ($result.0.code =~ '```nu no-run') + assert ($result.0.code !~ 'run-once') +} + +@test +def "decorate-original-code-blocks rewrites run-once preserving other options" [] { + let blocks = "```nu run-once, try\necho hello\n```" | parse-markdown-to-blocks + let result = decorate-original-code-blocks $blocks + + assert ($result.0.code =~ '```nu no-run, try') + assert ($result.0.code !~ 'run-once') +} + +@test +def "decorate-original-code-blocks leaves plain blocks unchanged" [] { + let blocks = "```nu\necho hello\n```" | parse-markdown-to-blocks + let result = decorate-original-code-blocks $blocks + + assert ($result.0.code =~ '```nu') + assert ($result.0.code !~ 'no-run') +} + # ============================================================================= # Tests for strip-outputs # ============================================================================= diff --git a/z_examples/6_edge_cases/run_once.md b/z_examples/6_edge_cases/run_once.md new file mode 100644 index 0000000..f2241f9 --- /dev/null +++ b/z_examples/6_edge_cases/run_once.md @@ -0,0 +1,13 @@ +# run-once fence option + +After numd run, the run-once block below should become no-run with output preserved. + +```nu run-once +2 + 2 +``` + +This block uses run-once combined with no-output. + +```nu run-once, no-output +3 + 3 +```