diff --git a/CLAUDE.md b/CLAUDE.md index 3f19afb..cec4f7f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,6 +26,11 @@ nu toolkit.nu release --major # major bump **Important**: Always use `nu toolkit.nu test` (not `test-unit` or `test-integration` separately). The combined command provides proper test output and summary. +```bash +# Check test coverage - requires both source AND test files +nu -c 'use dotnu/; ["dotnu/*.nu" "tests/test_commands.nu" "toolkit.nu"] | each { glob $in } | flatten | dotnu dependencies ...$in | dotnu filter-commands-with-no-tests' +``` + ## Architecture ### Module Structure @@ -38,11 +43,16 @@ dotnu/ **Export convention**: All commands in `commands.nu` are exported by default (for internal use, testing, and development). The public API is managed through `mod.nu`, which selectively re-exports only the user-facing commands. To add a command to the public API, add it to the list in `mod.nu`. +**Imports**: +- `use dotnu/` - import public API commands +- `use dotnu/commands.nu *` - import all commands (including internal) + **mod.nu** exports these public commands: - `dependencies` - Analyze command call chains - `extract-command-code` - Extract command with its dependencies - `filter-commands-with-no-tests` - Find untested commands -- `list-exported-commands` - List module's exported commands +- `list-module-exports` - List all exported definitions (export def + export use) +- `list-module-interface` - List module's callable interface (main commands) - `embeds-*` / `embed-add` - Literate programming tools - `set-x` / `generate-numd` - Script profiling diff --git a/README.md b/README.md index 40e98c5..3046267 100644 --- a/README.md +++ b/README.md @@ -203,13 +203,13 @@ dotnu dependencies --help # => ╰───┴───────┴────────╯ # => # => Examples: -# => +# => Analyze command dependencies in a module # => > dotnu dependencies ...(glob tests/assets/module-say/say/*.nu) # => ╭───┬──────────┬────────────────────┬──────────┬──────╮ # => │ # │ caller │ filename_of_caller │ callee │ step │ # => ├───┼──────────┼────────────────────┼──────────┼──────┤ -# => │ 0 │ hello │ hello.nu │ │ 0 │ -# => │ 1 │ question │ ask.nu │ │ 0 │ +# => │ 0 │ question │ ask.nu │ │ 0 │ +# => │ 1 │ hello │ hello.nu │ │ 0 │ # => │ 2 │ say │ mod.nu │ hello │ 0 │ # => │ 3 │ say │ mod.nu │ hi │ 0 │ # => │ 4 │ say │ mod.nu │ question │ 0 │ @@ -240,13 +240,13 @@ dotnu filter-commands-with-no-tests --help # => ╰───┴───────┴────────╯ # => # => Examples: -# => +# => Find commands not covered by tests # => > dependencies ...(glob tests/assets/module-say/say/*.nu) | filter-commands-with-no-tests # => ╭───┬──────────┬────────────────────╮ # => │ # │ caller │ filename_of_caller │ # => ├───┼──────────┼────────────────────┤ -# => │ 0 │ hello │ hello.nu │ -# => │ 1 │ question │ ask.nu │ +# => │ 0 │ question │ ask.nu │ +# => │ 1 │ hello │ hello.nu │ # => │ 2 │ say │ mod.nu │ # => ╰───┴──────────┴────────────────────╯ # => @@ -283,7 +283,7 @@ dotnu set-x --help # => ╰───┴───────┴────────╯ # => # => Examples: -# => +# => Generate script with timing instrumentation # => > set-x tests/assets/set-x-demo.nu --echo | lines | first 3 | to text # => mut $prev_ts = ( date now ) # => print ("> sleep 0.5sec" | nu-highlight) @@ -397,29 +397,30 @@ dotnu extract-command-code --help # => ``` -### `dotnu list-exported-commands` +### `dotnu list-module-exports` -List commands defined in a module file. Use `--export` to show only exported commands. +List all exported definitions from a module file. Finds commands from `export def` and `export use [...commands]` patterns. ```nushell -dotnu list-exported-commands --help -# => Usage: -# => > list-exported-commands {flags} <$path> -# => -# => Flags: -# => -h, --help: Display the help message for this command -# => --export: use only commands that are exported -# => -# => Parameters: -# => $path -# => -# => Input/output types: -# => ╭───┬───────┬────────╮ -# => │ # │ input │ output │ -# => ├───┼───────┼────────┤ -# => │ 0 │ any │ any │ -# => ╰───┴───────┴────────╯ -# => +dotnu list-module-exports dotnu/mod.nu | first 5 +# => ╭───┬──────────────────────╮ +# => │ 0 │ dependencies │ +# => │ 1 │ embed-add │ +# => │ 2 │ embeds-capture-start │ +# => │ 3 │ embeds-capture-stop │ +# => │ 4 │ embeds-remove │ +# => ╰───┴──────────────────────╯ +``` + +### `dotnu list-module-interface` + +List module's callable interface - the `main` and `main subcommand` patterns that become available when you `use` the module. + +```nushell +dotnu list-module-interface tests/assets/b/example-mod1.nu +# => ╭───┬──────╮ +# => │ 0 │ main │ +# => ╰───┴──────╯ ``` ### `dotnu module-commands-code-to-record` diff --git a/dotnu/commands.nu b/dotnu/commands.nu index 4d13045..22c1863 100644 --- a/dotnu/commands.nu +++ b/dotnu/commands.nu @@ -162,24 +162,34 @@ export def 'extract-command-code' [ } } -# todo: `list-exported-commands` should be a completion for Nushell CLI +# List all exported definitions from a module file +# +# Finds commands from `export def` and `export use [...commands]` patterns. +export def 'list-module-exports' [ + $path: path +]: nothing -> list { + open $path -r + | extract-exported-commands + | replace-main-with-module-name $path + | if ($in | is-empty) { + print 'No command found' + return + } else { } +} -export def 'list-exported-commands' [ +# List module's callable interface (main commands) +# +# Finds `def main` and `def 'main subcommand'` patterns - the commands +# available when you `use` the module. +export def 'list-module-interface' [ $path: path - --export # use only commands that are exported -] { +]: nothing -> list { open $path -r | lines - | if $export { - where $it =~ '^export def ' - | extract-command-name - | replace-main-with-module-name $path - } else { - where $it =~ '^(export )?def ' - | extract-command-name - | where $it starts-with 'main' - | str replace 'main ' '' - } + | where $it =~ '^(export )?def ' + | extract-command-name + | where $it starts-with 'main' + | str replace 'main ' '' | if ($in | is-empty) { print 'No command found' return @@ -259,8 +269,10 @@ export def 'examples-update' [ # Using full original text ensures unique matches even with duplicate results let updated = $results | reduce --fold $content {|item acc| # Build new example by replacing just the result value in the original + # Escape $ as $$ to prevent regex backreference interpretation + let escaped_result = $item.new_result | str replace -a '$' '$$' let new_example = $item.original - | str replace -r '\} --result .+$' $"} --result ($item.new_result)" + | str replace -r '\} --result .+$' $"} --result ($escaped_result)" $acc | str replace $item.original $new_example } @@ -288,10 +300,12 @@ export def find-examples []: string -> table table table table list { + let tokens = ast --flatten $in | flatten span + + $tokens + | enumerate + | where item.content in ['export def' 'export use'] + | each {|match| + let idx = $match.index + if $match.item.content == 'export def' { + # Command name is next token + $tokens | get ($idx + 1) | get content | str trim -c "'" | str trim -c '"' + } else { + # export use: skip module path, get shape_string tokens from list + $tokens + | skip ($idx + 2) + | take while { $in.shape in ['shape_string' 'shape_list'] } + | where shape == 'shape_string' + | get content + | str trim -c '"' + | str trim -c "'" + } + } + | flatten +} + # Complete AST output by filling gaps with synthetic tokens # # `ast --flatten` omits certain syntax elements (semicolons, assignment operators, etc). @@ -991,7 +1059,7 @@ export def ast-complete []: string -> table { | where {|p| $p.0.end < $p.1.start } | each {|p| let content = $bytes | bytes at $p.0.end..<$p.1.start | decode utf8 - {content: $content, start: $p.0.end, end: $p.1.start, shape: (classify-gap $content)} + {content: $content start: $p.0.end end: $p.1.start shape: (classify-gap $content)} } $tokens | select content start end shape | append $gaps | sort-by start @@ -1045,22 +1113,18 @@ export def split-statements []: string -> table