diff --git a/dotnu/commands.nu b/dotnu/commands.nu index 22c1863..a88f39d 100644 --- a/dotnu/commands.nu +++ b/dotnu/commands.nu @@ -4,7 +4,11 @@ # Public API is controlled by mod.nu which selectively re-exports user-facing commands. # To make a command public, add it to the export list in mod.nu. -use std/iter scan +# Regex matching a capture point: a pipeline line ending in `| print $in`. +# Why: `find-capture-points` and `execute-and-parse-results` must agree on what a +# capture point is — they are zipped together in `embeds-update`, so any disagreement +# misaligns outputs against the wrong lines. +const capture_point = '\|\s*print\s+\$in\s*$' # Check .nu module files to determine which commands depend on other commands. @example 'Analyze command dependencies in a module' { @@ -222,9 +226,12 @@ export def 'embeds-update' [ let prevent_second_replacement = " # to-not-be-replaced-again" $replacements - | reduce --fold $script {|it| + # Why: prepend a newline so a capture point on the very first line is anchored + # by "\n" on both sides like any interior line; without it the match silently no-ops + | reduce --fold ("\n" + $script) {|it| str replace ("\n" + $it.0 + "\n") ("\n" + $it.0 + $prevent_second_replacement + "\n" + $it.1 + "\n") } + | str replace -r '^\n' '' # strip the single leading newline added above | str replace -a $prevent_second_replacement '' | str replace -ar '\n{3,}' "\n\n" | str replace -r "\n*$" "\n" @@ -802,8 +809,11 @@ export def 'module-commands-code-to-record' [ } | merge ( $in.command - | scan --noinit null {|i acc| - if $i == null { $acc } else { $i } + # Why: std `scan` with a null seed now errors — its internal `generate` treats + # null as "no initial value". Forward-fill the last non-null name manually instead. + | reduce --fold [] {|i acc| + let prev = $acc | last + $acc | append (if $i == null { $prev } else { $i }) } | wrap command ) @@ -958,7 +968,7 @@ export def execute-and-parse-results [ | each { if $in !~ '^\s*#' { # don't search for `print $in` inside of commented lines - str replace -r '\| *print +\$in *' '| embed-in-script' + str replace -r $capture_point '| embed-in-script' } else { } } | prepend $embed_in_script_src @@ -978,7 +988,7 @@ export def execute-and-parse-results [ # Finds lines where embed-in-script is used in the script export def find-capture-points [] { lines - | where $it !~ '^\s*#' and $it =~ '\|\s?print \$in *$' + | where $it !~ '^\s*#' and $it =~ $capture_point } # Removes annotation lines starting with "# => " from the script diff --git a/tests/assets/dotnu-capture-clean.nu b/tests/assets/dotnu-capture-clean.nu index c573cf4..8d8aca9 100644 --- a/tests/assets/dotnu-capture-clean.nu +++ b/tests/assets/dotnu-capture-clean.nu @@ -7,9 +7,9 @@ # And and this is the link on dotnu module: # https://github.com/nushell-prophet/dotnu -ls | sort-by modified -r | last 2 | print $in +40 + 2 | print $in -random int | print $in +[[name type]; [foo file] [bar dir]] | print $in 'Say hello to the core team of the Nushell' | str replace 'Nushell' 'Best shell' diff --git a/tests/assets/dotnu-capture-updated.nu b/tests/assets/dotnu-capture-updated.nu index fa10eec..3c568e1 100644 --- a/tests/assets/dotnu-capture-updated.nu +++ b/tests/assets/dotnu-capture-updated.nu @@ -6,16 +6,16 @@ # And and this is the link on dotnu module: # https://github.com/nushell-prophet/dotnu -ls | sort-by modified -r | last 2 | print $in -# => ╭───┬──────────────────────────────┬──────┬───────┬────────────╮ -# => │ # │ name │ type │ size │ modified │ -# => ├───┼──────────────────────────────┼──────┼───────┼────────────┤ -# => │ 0 │ set-x-demo.nu │ file │ 41 B │ a year ago │ -# => │ 1 │ parsing-pipe-in-docstring.nu │ file │ 923 B │ a year ago │ -# => ╰───┴──────────────────────────────┴──────┴───────┴────────────╯ +40 + 2 | print $in +# => 42 -random int | print $in -# => 2835756183325042638 +[[name type]; [foo file] [bar dir]] | print $in +# => ╭───┬──────┬──────╮ +# => │ # │ name │ type │ +# => ├───┼──────┼──────┤ +# => │ 0 │ foo │ file │ +# => │ 1 │ bar │ dir │ +# => ╰───┴──────┴──────╯ 'Say hello to the core team of the Nushell' | str replace 'Nushell' 'Best shell' diff --git a/tests/assets/dotnu-capture.nu b/tests/assets/dotnu-capture.nu index 3dc2917..29eb60d 100644 --- a/tests/assets/dotnu-capture.nu +++ b/tests/assets/dotnu-capture.nu @@ -5,16 +5,13 @@ # And and this is the link on dotnu module: # https://github.com/nushell-prophet/dotnu -ls | sort-by modified -r | last 2 | print $in -# => ╭─#─┬──────name──────┬─type─┬─size──┬───modified───╮ -# => │ 0 │ zzz_md_backups │ dir │ 160 B │ 2 months ago │ -# => │ 1 │ test.nu │ file │ 45 B │ 3 months ago │ -# => ╰─#─┴──────name──────┴─type─┴─size──┴───modified───╯ +40 + 2 | print $in +# => 0 -random int | print $in -# => 6970240173764648305 +[[name type]; [foo file] [bar dir]] | print $in +# => stale 'Say hello to the core team of the Nushell' | str replace 'Nushell' 'Best shell' | print $in -# => Say hello to the core team of the Best shell +# => WRONG diff --git a/tests/output-yaml/coverage-untested.yaml b/tests/output-yaml/coverage-untested.nuon similarity index 62% rename from tests/output-yaml/coverage-untested.yaml rename to tests/output-yaml/coverage-untested.nuon index 9e9463b..b5b5d2d 100644 --- a/tests/output-yaml/coverage-untested.yaml +++ b/tests/output-yaml/coverage-untested.nuon @@ -13,16 +13,21 @@ # | where caller in $public_api # | select caller # -# # Output as yaml +# # Why: serialize as nuon, not yaml — `to yaml`'s list indentation +# # changed between nushell versions and drifted this snapshot on +# # identical data. nuon compares the parsed structure stably. # { # public_api_count: ($public_api | length) # tested_count: (($public_api | length) - ($untested | length)) # untested: ($untested | get caller) # } -# | to yaml -public_api_count: 15 -tested_count: 12 -untested: -- embed-add -- embeds-capture-start -- embeds-capture-stop +# | to nuon --indent 2 +{ + public_api_count: 15, + tested_count: 12, + untested: [ + embed-add, + embeds-capture-start, + embeds-capture-stop + ] +} \ No newline at end of file diff --git a/tests/test_commands.nu b/tests/test_commands.nu index 02d0e18..5ce73d2 100644 --- a/tests/test_commands.nu +++ b/tests/test_commands.nu @@ -393,6 +393,15 @@ def "embeds-update updates embeds in piped script" [] { assert ($result =~ '# => HELLO') } +@test +def "embeds-update annotates a capture point on the first line" [] { + # No leading newline before the capture point + let script = "'hello' | str upcase | print $in\n" + let result = $script | embeds-update + + assert ($result =~ '# => HELLO') +} + @test def "embeds-update preserves script structure" [] { let script = "# comment\n\n1 + 1 | print $in\n\n# another" diff --git a/toolkit.nu b/toolkit.nu index a6bd9bf..9545a8d 100644 --- a/toolkit.nu +++ b/toolkit.nu @@ -103,7 +103,7 @@ export def 'main test-integration' [ } ) ( - run-snapshot-test 'coverage' ([tests output-yaml coverage-untested.yaml] | path join) { + run-snapshot-test 'coverage' ([tests output-yaml coverage-untested.nuon] | path join) { # Public API from mod.nu let public_api = open ([dotnu mod.nu] | path join) | lines @@ -119,13 +119,15 @@ export def 'main test-integration' [ | where caller in $public_api | select caller - # Output as yaml + # Why: serialize as nuon, not yaml — `to yaml`'s list indentation + # changed between nushell versions and drifted this snapshot on + # identical data. nuon compares the parsed structure stably. { public_api_count: ($public_api | length) tested_count: (($public_api | length) - ($untested | length)) untested: ($untested | get caller) } - | to yaml + | to nuon --indent 2 } ) ] @@ -177,9 +179,17 @@ def run-snapshot-test [name: string output_file: string command_src: closure] { $command_text + (char nl) + (do $command_src) | save -f $output_file - # Check git diff to determine status - let diff_result = do { ^git diff --quiet $output_file } | complete - let status = if $diff_result.exit_code == 0 { 'passed' } else { 'changed' } + # Check git diff to determine status. + # Why: `git diff` ignores untracked files, so a brand-new snapshot would + # report 'passed' without ever being compared to a baseline. Treat an + # untracked file as 'changed' so it surfaces and `--update` stages it. + let tracked = do { ^git ls-files --error-unmatch $output_file } | complete | get exit_code | $in == 0 + let status = if not $tracked { + 'changed' + } else { + let diff_result = do { ^git diff --quiet $output_file } | complete + if $diff_result.exit_code == 0 { 'passed' } else { 'changed' } + } {type: 'integration' name: $name status: $status file: $output_file} } catch {|err|