Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 182 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
![dotnu](https://github.com/user-attachments/assets/4fb74e46-f75b-4155-8e61-8ff75db66117)

<h1 align="center"><strong>dotnu - tools for Nushell module developers 🛠️</strong></h1>
<h1 align="center"><strong>dotnu - tools for Nushell module developers</strong></h1>

<p align="center"><em>(A good companion for <a href="https://github.com/nushell-prophet/numd">numd</a>)</em></p>

<p align="center">dotnu augments Nushell with helpers for literate programming, dependency analysis, and script profiling.</p>

## dotnu video demo
## Video demo

<a href="https://youtu.be/-C7_dfLXXrE">
<img src="https://github.com/user-attachments/assets/fdd07bfc-7d77-4dca-8a1c-3e27ac3063f9" alt="dotnu demo" width="100"/>
Expand All @@ -22,6 +22,7 @@ use dotnu
```

### [`nupm`](https://github.com/nushell/nupm)

```nushell no-run
nupm install https://github.com/nushell-prophet/dotnu --git
# if nupm modules are not in `NU_LIB_DIRS`:
Expand All @@ -30,15 +31,15 @@ $env.NU_LIB_DIRS ++= [ ($env.NUPM_HOME | path join "modules") ]
use dotnu
```

## Embeds — keeping examples in sync
## Embeds — Literate Programming

`dotnu` lets you write **literate Nushell**: ordinary Nushell scripts that include the real command output right after each pipeline ending in `| print $in`. See the [capture example](/dotnu-capture.nu) to grasp the idea quickly.

The `| print $in` suffix acts as a simple `print` in native Nushell and as a capture marker for dotnu, so scripts remain valid and functional even when run without loading the `dotnu` module.

The main command is `dotnu embeds-update`.
### `dotnu embeds-update`

`dotnu embeds-update` takes a script, rewrites every `print $in` line so its output is easy to parse, runs the modified script, captures what each marked line prints, and then replaces the old `# =>` blocks in the original file with the fresh output.
The main command. It takes a script, rewrites every `print $in` line so its output is easy to parse, runs the modified script, captures what each marked line prints, and then replaces the old `# =>` blocks in the original file with the fresh output.

You can run it on a file path (e.g., `dotnu embeds-update dotnu-capture.nu`) or pipe a script into it (e.g., `"ls | print $in" | dotnu embeds-update`).

Expand Down Expand Up @@ -69,15 +70,15 @@ dotnu embeds-update --help
# =>
```

### Embeds helper commands
### Helper commands

While it is easy to write scripts in editor, there are several convenience helper commands that facilitate populating script files from terminal.
While it is easy to write scripts in an editor, there are several convenience helper commands that facilitate populating script files from the terminal.

### `dotnu embeds-setup`
#### `dotnu embeds-setup`

define or change the capture file (add `--auto-commit` to autocommit snapshots).
Define or change the capture file (add `--auto-commit` to auto-commit snapshots).

```nu
```nushell
dotnu embeds-setup --help
# => Set environment variables to operate with embeds
# =>
Expand All @@ -100,11 +101,11 @@ dotnu embeds-setup --help
# =>
```

### `dotnu embeds-capture-start` and `dotnu embeds-capture-stop`
#### `dotnu embeds-capture-start` and `dotnu embeds-capture-stop`

record every result printed in the interactive session.
Record every result printed in the interactive session.

```nu
```nushell
dotnu embeds-capture-start --help
# => start capturing commands and their outputs into a file
# =>
Expand All @@ -126,11 +127,11 @@ dotnu embeds-capture-start --help
# =>
```

### `dotnu embed-add`
#### `dotnu embed-add`

capture only the pipeline you run it on; useful for finegrained examples.
Capture only the pipeline you run it on; useful for fine-grained examples.

```nu
```nushell
dotnu embed-add --help
# => Embed stdin together with its command into the file
# =>
Expand All @@ -152,11 +153,11 @@ dotnu embed-add --help
# =>
```

### `dotnu embeds-remove`
#### `dotnu embeds-remove`

strip all captured output, leaving clean code.
Strip all captured output, leaving clean code.

```nu
```nushell
dotnu embeds-remove --help
# => Removes annotation lines starting with "# => " from the script
# =>
Expand All @@ -175,9 +176,9 @@ dotnu embeds-remove --help
# =>
```

## Commands
## Dependency Analysis

### dotnu dependencies
### `dotnu dependencies`

```nushell
dotnu dependencies --help
Expand Down Expand Up @@ -218,7 +219,7 @@ dotnu dependencies --help
# =>
```

### dotnu filter-commands-with-no-tests
### `dotnu filter-commands-with-no-tests`

```nushell
dotnu filter-commands-with-no-tests --help
Expand Down Expand Up @@ -251,11 +252,46 @@ dotnu filter-commands-with-no-tests --help
# =>
```

### dotnu set-x
## Script Profiling

### `dotnu set-x`

Divide a script into blocks and generate a new script that prints each block before executing it, along with timing information.

`dotnu set-x` opens a regular .nu script. It divides it into blocks using the specified regex (by default, it is "\n\n") and generates a new script that will print the code of each block before executing it, along with the timings of each block's execution.
```nushell
dotnu set-x --help
# => Open a regular .nu script. Divide it into blocks by "\n\n". Generate a new script
# => that will print the code of each block before executing it, and print the timings of each block's execution.
# =>
# => Usage:
# => > set-x {flags} <file>
# =>
# => Flags:
# => -h, --help: Display the help message for this command
# => --regex <string>: regex to split on blocks (default: '\n+\n' - blank lines)
# => --echo: output script to terminal
# => --quiet: don't print any messages
# =>
# => Parameters:
# => file <path>: path to `.nu` file
# =>
# => Input/output types:
# => ╭───┬───────┬────────╮
# => │ # │ input │ output │
# => ├───┼───────┼────────┤
# => │ 0 │ any │ any │
# => ╰───┴───────┴────────╯
# =>
# => Examples:
# =>
# => > 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)
# => sleep 0.5sec
# =>
```

Let's check the code of the simple `set-x-demo.nu` script
Example with a simple script:

```nushell
let $filename = [tests assets set-x-demo.nu] | path join
Expand All @@ -269,8 +305,6 @@ open $filename | lines | table -i false
# => ╰──────────────╯
```

Let's see how `dotnu set-x` will modify this script

```nushell
dotnu set-x $filename --echo | lines | table -i false
# => ╭─────────────────────────────────────────────────────────────────────────────────╮
Expand All @@ -292,3 +326,124 @@ dotnu set-x $filename --echo | lines | table -i false
# => │ │
# => ╰─────────────────────────────────────────────────────────────────────────────────╯
```

## Utilities

### `dotnu generate-numd`

Pipe a `.nu` script into this command to convert it into `.numd` format (markdown with code blocks).

```nushell
dotnu generate-numd --help
# => Generate `.numd` from `.nu` divided into blocks by "\n\n"
# =>
# => Usage:
# => > generate-numd
# =>
# => Flags:
# => -h, --help: Display the help message for this command
# =>
# => Input/output types:
# => ╭───┬───────┬────────╮
# => │ # │ input │ output │
# => ├───┼───────┼────────┤
# => │ 0 │ any │ any │
# => ╰───┴───────┴────────╯
# =>
```

```nushell
"sleep 0.5sec\n\nsleep 0.7sec" | dotnu generate-numd
# => ```nu
# => sleep 0.5sec
# => ```
# =>
# => ```nu
# => sleep 0.7sec
# => ```
# =>
```

### `dotnu extract-command-code`

Extract a command from a module, resolve its parameter defaults, and create a standalone script you can source to get all variables in scope. Useful for debugging.

```nushell
dotnu extract-command-code --help
# => Extract command code from a module and save it as a `.nu` file that can be sourced.
# => By executing this `.nu` file, you'll have all the variables in your environment for debugging or development.
# =>
# => Usage:
# => > extract-command-code {flags} <$module_path> <$command>
# =>
# => Flags:
# => -h, --help: Display the help message for this command
# => --output <path>: a file path to save the extracted command script
# => --clear-vars: clear variables previously set in the extracted .nu file
# => --echo: output the command to the terminal
# => --set-vars <record>: set variables for a command (default: {})
# => --code-editor <string>: code is my editor of choice to open the result file (default: 'code')
# =>
# => Parameters:
# => $module_path <path>: path to a Nushell module file
# => $command <string>: the name of the command to extract
# =>
# => Input/output types:
# => ╭───┬───────┬────────╮
# => │ # │ input │ output │
# => ├───┼───────┼────────┤
# => │ 0 │ any │ any │
# => ╰───┴───────┴────────╯
# =>
```

### `dotnu list-exported-commands`

List commands defined in a module file. Use `--export` to show only exported commands.

```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 <path>
# =>
# => Input/output types:
# => ╭───┬───────┬────────╮
# => │ # │ input │ output │
# => ├───┼───────┼────────┤
# => │ 0 │ any │ any │
# => ╰───┴───────┴────────╯
# =>
```

### `dotnu module-commands-code-to-record`

Extract all commands from a module file and return them as a record where keys are command names and values are their source code.

```nushell
dotnu module-commands-code-to-record --help
# => Extract all commands from a module as a record of {command_name: source_code}
# =>
# => Usage:
# => > module-commands-code-to-record <module_path>
# =>
# => Flags:
# => -h, --help: Display the help message for this command
# =>
# => Parameters:
# => module_path <path>: path to a Nushell module file
# =>
# => Input/output types:
# => ╭───┬───────┬────────╮
# => │ # │ input │ output │
# => ├───┼───────┼────────┤
# => │ 0 │ any │ any │
# => ╰───┴───────┴────────╯
# =>
```
19 changes: 10 additions & 9 deletions dotnu/commands.nu
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ sleep 0.5sec
'
export def 'set-x' [
file: path # path to `.nu` file
--regex: string = "\n+\n" # regex to use to split .nu on blocks
--regex: string # regex to split on blocks (default: '\n+\n' - blank lines)
--echo # output script to terminal
--quiet # don't print any messages
] {
let regex = $regex | default "\n+\n"
let out_file = $file | str replace -r '(\.nu)?$' '_setx.nu'

open $file
Expand Down Expand Up @@ -91,14 +92,14 @@ export def 'set-x' [
}
}

# Generate `.numd` from `.nu` divided on blocks by "\n\n"
# Generate `.numd` from `.nu` divided into blocks by "\n\n"
export def 'generate-numd' [] {
split row -r "\n+\n"
| each { $"```nu\n($in)\n```\n" }
| to text
}

# Extract a command code from a module and save it as a `.nu` file that can be sourced.
# Extract command code from a module and save it as a `.nu` file that can be sourced.
# By executing this `.nu` file, you'll have all the variables in your environment for debugging or development.
export def 'extract-command-code' [
$module_path: path # path to a Nushell module file
Expand Down Expand Up @@ -155,7 +156,7 @@ export def 'extract-command-code' [
}
}

# todo: `list-exported-commands` should be a completion for nushell cli
# todo: `list-exported-commands` should be a completion for Nushell CLI

export def 'list-exported-commands' [
$path: path
Expand All @@ -179,7 +180,7 @@ export def 'list-exported-commands' [
} else { }
}

#todo: make configuration like --autocommit in file itself
# todo: make configuration like --autocommit in file itself

# Inserts captured output back into the script at capture points
export def 'embeds-update' [
Expand Down Expand Up @@ -337,7 +338,7 @@ export def --env 'embeds-capture-stop' []: nothing -> nothing {
}

#### helpers
# they used to be separately here from the main code, but I want to experiment with structure
# they used to be separate from the main code, but I want to experiment with structure
# so all the commands are in one file now, and all are exported, to be available in my scripts
# that can use this file commands with 'use ..', though main commands are exported in mod.nu

Expand Down Expand Up @@ -386,7 +387,7 @@ export def check-clean-working-tree [
}
}

# make a record from code with variable definitions
# Make a record from code with variable definitions
@example '' {
"let $quiet = false; let no_timestamp = false" | variable-definitions-to-record
} --result {quiet: false no_timestamp: false}
Expand Down Expand Up @@ -561,7 +562,7 @@ export def list-module-commands [
$calls | append $defs_without_calls
}

# Extract the specified command and all its dependencies, outputting them to stdout
# Extract all commands from a module as a record of {command_name: source_code}
export def 'module-commands-code-to-record' [
module_path: path # path to a Nushell module file
] {
Expand Down Expand Up @@ -709,7 +710,7 @@ export def 'comment-hash-colon' [
}
}

# Extracts captured output from a script file execution result
# Extract captured output from a script file execution results
export def execute-and-parse-results [
script: string
--script_path: path
Expand Down
Loading