From a76e102ee55c577a7248260f8ee893df9cc77a84 Mon Sep 17 00:00:00 2001 From: NotTheDr01ds <32344964+NotTheDr01ds@users.noreply.github.com> Date: Sun, 29 Sep 2024 10:43:37 -0400 Subject: [PATCH 1/6] Updates to Custom Commands chapter --- book/custom_commands.md | 743 ++++++++++++++++++++++++++++------------ book/environment.md | 29 +- 2 files changed, 525 insertions(+), 247 deletions(-) diff --git a/book/custom_commands.md b/book/custom_commands.md index 366be375755..6237f3e8373 100644 --- a/book/custom_commands.md +++ b/book/custom_commands.md @@ -1,67 +1,194 @@ # Custom Commands -Nu's ability to compose long pipelines allows you a lot of control over your data and system, but it comes at the price of a lot of typing. Ideally, you'd be able to save your well-crafted pipelines to use again and again. +As with any programming language, you'll quickly want to save longer pipelines and expressions so that you can call them again easily when needed. This is where custom commands come in. -An example definition of a custom command: +::: tip Note +Custom commands are similar to functions in many languages, but in Nushell, custom commands _act as first-class commands themselves_. As you'll see below, they are included in the Help system along with built-in commands, can be a part of a pipeline, are parsed in real-time for type errors, and much more. +::: + +[[toc]] + +## Creating and Running a Custom Command + +Let's start with a simple `greet` custom command: ```nu def greet [name] { - ['hello' $name] + $"Hello, ($name)!" } ``` -::: tip -The value produced by the last line of a command becomes the command's returned value. In this case, a list containing the string `'hello'` and `$name` is returned. To prevent this, you can place `null` (or the [`ignore`](/commands/docs/ignore.md) command) at the end of the pipeline, like so: `['hello' $name] | null`. Also note that most file system commands, such as [`save`](/commands/docs/save.md) or [`cd`](/commands/docs/cd.md), always output `null`. -::: +Here, we define the `greet` command, which takes a single parameter `name`. Following this parameter is the block that represents what will happen when the custom command runs. When called, the custom command will set the value passed for `name` as the `$name` variable, which will be available to the block. -In this definition, we define the `greet` command, which takes a single parameter `name`. Following this parameter is the block that represents what will happen when the custom command runs. When called, the custom command will set the value passed for `name` as the `$name` variable, which will be available to the block. +To run this command, we can call it just as we would call built-in commands: -To run the above, we can call it like we would call built-in commands: +```nu +greet "World" +# => Hello, World! +``` + +## Returning Values from Commands + +You might notice that there isn't a `return` or `echo` statement in the example above. + +Like some other languages, such as PowerShell and JavaScript (with arrow functions), Nushell features an _implicit return_, where the value of the final expression in the command becomes its return value. + +In the above example, there is only one expression—The string. This string becomes the return value of the command. ```nu -> greet "world" +greet "World" | describe +# => string ``` -As we do, we also get output just as we would with built-in commands: +A typical command, of course, will be made up of multiple expressions. For demonstration purposes, here's a non-sensical command that has 3 expressions: + +```nu +def eight [] { + 1 + 1 + 2 + 2 + 4 + 4 +} +eight +# => 8 ``` -───┬─────── - 0 │ hello - 1 │ world -───┴─────── + +The return value, again, is simply the result of the _final_ expression in the command, which is `4 + 4` (8). + +Additional examples: + +::: details Early return +Commands that need to exit early due to some condition can still return a value using the [`return` statement](/commands/docs/return.md). + +```nu +def process-list [] { + let input_length = length + if $input_length > 10_000 { + print "Input list is too long" + return null + } + + $in | each {|i| + # Process the list + $i * 4.25 + } +} ``` -::: tip -If you want to generate a single string, you can use the string interpolation syntax to embed $name in it: +::: + +::: details Suppressing the return value +You'll often want to create a custom command that acts as a _statement_ rather than an expression, and doesn't return a a value. + +You can use the `ignore` keyword in this case: ```nu -def greet [name] { - $"hello ($name)" +def create-three-files [] { + [ file1 file2 file3 ] | each {|filename| + touch $filename + } | ignore } +``` + +Without the `ignore` at the end of the pipeline, the command will return an empty list from the `each` statement. + +You could also return a `null` as the final expression. Or, in this contrived example, use a `for` statement, which doesn't return a value (see next example). +::: -greet nushell +::: details Statements which don't return a value +Some keywords in Nushell are _statements_ which don't return a value. If you use one of these statements as the final expression of a custom command, the _return value_ will be `null`. This may be unexpected in some cases. For example: + +```nu +def exponents-of-three [] { + for x in [ 0 1 2 3 4 5 ] { + 3 ** $x + } +} +exponents-of-three ``` -returns `hello nushell` +The above command will not display anything, and the return value is empty, or `null` because `for` is a _statement_ which doesn't return a value. + +To return a value from an input list, use a filter such as the `each` command: + +````nu +def exponents-of-three [] { + [ 0 1 2 3 4 5 ] | each {|x| + 3 ** $x + } +} + +exponents-of-three + +# => ╭───┬─────╮ +# => │ 0 │ 1 │ +# => │ 1 │ 3 │ +# => │ 2 │ 9 │ +# => │ 3 │ 27 │ +# => │ 4 │ 81 │ +# => │ 5 │ 243 │ +# => ╰───┴─────╯ ::: -## Command Names +::: details Match expression +```nu +# Return a random file in the current directory +def "random file" [] { + let files = (ls) + let num_files = ($files | length) -In Nushell, a command name is a string of characters. Here are some examples of valid command names: `greet`, `get-size`, `mycommand123`, `my command`, and `😊`. + match $num_files { + 0 => null # Return null for empty directory + _ => { + let random_file = (random int 0..($num_files - 1)) + ($files | get $random_file) + } + } +} +```` + +In this case, the final expression is the `match` statement which can return: + +- `null` if the directory is empty +- Otherwise, a `record` representing the randomly chosen file + ::: + +## Naming Commands + +In Nushell, a command name can be a string of characters. Here are some examples of valid command names: `greet`, `get-size`, `mycommand123`, `my command`, `命令` (English translation: "command"), and even `😊`. + +Strings which might be confused with other parser patterns should be avoided. For instance, the following command names might not be callable: + +- `1`, `"1"`, or `"1.5"`: Nushell will not allow numbers to be used as command names +- `4MiB` or `"4MiB"`: Nushell will not allow filesizes to be use₫ as command names +- `"number#four"` or `"number^four"`: Carets and hash symbols are not allowed in command names +- `-a`, `"{foo}"`, `"(bar)"`: Will not be callable, as Nushell will interpret them as flags, closures, or expressions. + +While names like `"+foo"` might work, they are best avoided as the parser rules might change over time. When in doubt, keep command names as simple as possible. + +::: tip +It's common practice in Nushell to separate the words of the command with `-` for better readability.\_ For example `get-size` instead of `getsize` or `get_size`. +::: + +::: tip +Because `def` is a parser keyword, the command name must be known at parse time. This means that command names may not be a variale or constant. For example, the following is _not allowed_: -_Note: It's common practice in Nushell to separate the words of the command with `-` for better readability._ For example `get-size` instead of `getsize` or `get_size`. +````nu +let name = "foo" +def $name [] { foo } +::: -## Subcommands +### Subcommands -You can also define subcommands to commands using a space. For example, if we wanted to add a new subcommand to [`str`](/commands/docs/str.md), we can create it by naming our subcommand to start with "str ". For example: +You can also define subcommands of commands using a space. For example, if we wanted to add a new subcommand to [`str`](/commands/docs/str.md), we can create it by naming our subcommand starting with "str ". For example: ```nu def "str mycommand" [] { "hello" } -``` +```` Now we can call our custom command as if it were a built-in subcommand of [`str`](/commands/docs/str.md): @@ -73,88 +200,91 @@ Of course, commands with spaces in their names are defined in the same way: ```nu def "custom command" [] { - "this is a custom command with a space in the name!" + "This is a custom command with a space in the name!" } ``` -## Parameter Types +## Parameters + +### Required positional parameters + +The basic argument definitions used above are _positional_. The first parameter passed into the `greet` command above is assigned to the first `name` parameter. -When defining custom commands, you can name and optionally set the type for each parameter. For example, you can write the above as: +By default, positional parameters are required. If a positional parameter is not provided, we will encounter an error. Using our basic `greet` command: ```nu -def greet [name: string] { - $"hello ($name)" +def greet [name] { + $"Hello, ($name)!" } + +greet +# => × Missing required positional argument. +# => ╭─[entry #23:1:1] +# => 1 │ greet +# => · ▲ +# => · ╰── missing name +# => ╰──── +# => help: Usage: greet ``` -The types of parameters are optional. Nushell supports leaving them off and treating the parameter as `any` if so. If you annotated a type on a parameter, Nushell will check this type when you call the function. +### Optional Positional Parameters -For example, let's say you wanted to take in an `int` instead: +We can define a positional parameter as optional by putting a question mark (`?`) after its name. For example: ```nu -def greet [name: int] { - $"hello ($name)" +def greet [name?: string] { + $"Hello, ($name | default 'You')" } -greet world +greet + +# => Hello, You ``` -If we try to run the above, Nushell will tell us that the types don't match: +::: tip +Notice that the name used to access the variable does not include the `?`; only its definition in the command signature. +::: -``` -error: Type Error - ┌─ shell:6:7 - │ -5 │ greet world - │ ^^^^^ Expected int -``` +When an optional parameter is not passed, its value in the command body is equal to `null`. The above example uses the `default` command to provide a default of "You" when `name` is `null`. -This can help you guide users of your definitions to call them with only the supported types. +You could also compare the value directly: -The currently accepted types are (as of version 0.86.0): +```nu +def greet [name?: string] { + match $name { + null => "Hello! I don't know your name!" + _ => $"Hello, ($name)!" + } +} -- `any` -- `binary` -- `bool` -- `cell-path` -- `closure` -- `datetime` -- `directory` -- `duration` -- `error` -- `filesize` -- `float` -- `glob` -- `int` -- `list` -- `nothing` -- `number` -- `path` -- `range` -- `record` -- `string` -- `table` +greet -## Parameters with a Default Value +# => Hello! I don't know your name! +``` -To make a parameter optional and directly provide a default value for it you can provide a default value in the command definition. +If required and optional positional parameters are used together, then the required parameters must appear in the definition first. + +#### Parameters with a Default Value + +You can also set a default value for the parameter when it is missing. Parameters with a default value are also optional when calling the command. ```nu -def greet [name = "nushell"] { - $"hello ($name)" +def greet [name = "Nushell"] { + $"Hello, ($name)!" } ``` You can call this command either without the parameter or with a value to override the default value: ```nu -> greet -hello nushell -> greet world -hello world +greet +# => Hello, Nushell! + +greet world +# => Hello, World! ``` -You can also combine a default value with a [type requirement](#parameter-types): +You can also combine a default value with a [type annotation](#parameter-types): ```nu def congratulate [age: int = 18] { @@ -162,55 +292,76 @@ def congratulate [age: int = 18] { } ``` -If you want to check if an optional parameter is present or not and not just rely on a default value use [optional positional parameters](#optional-positional-parameters) instead. - -## Optional Positional Parameters +### Parameter Types -By default, positional parameters are required. If a positional parameter is not passed, we will encounter an error: +For each parameter, you can optionally define its type. For example, you can write the basic `greet` command as: ```nu - × Missing required positional argument. - ╭─[entry #23:1:1] - 1 │ greet - · ▲ - · ╰── missing name - ╰──── - help: Usage: greet +def greet [name: string] { + $"Hello, ($name)" +} ``` -We can instead mark a positional parameter as optional by putting a question mark (`?`) after its name. For example: +If a parameter is not type-annotated, Nushell will treat it as an [`any` type](./types_of_data.html#any). If you annotate a type on a parameter, Nushell will check its type when you call the function. + +For example, let's say you wanted to only accept an `int` instead of a `string`: ```nu -def greet [name?: string] { +def greet [name: int] { $"hello ($name)" } -greet +greet World ``` -Making a positional parameter optional does not change its name when accessed in the body. As the example above shows, it is still accessed with `$name`, despite the `?` suffix in the parameter list. - -When an optional parameter is not passed, its value in the command body is equal to `null`. We can use this to act on the case where a parameter was not passed: +If we try to run the above, Nushell will tell us that the types don't match: ```nu -def greet [name?: string] { - if ($name == null) { - "hello, I don't know your name!" - } else { - $"hello ($name)" - } -} - -greet +error: Type Error + ┌─ shell:6:7 + │ +5 │ greet world + │ ^^^^^ Expected int ``` -If you just want to set a default value when the parameter is missing it is simpler to use a [default value](#parameters-with-a-default-value) instead. +::: tip Cool! +Type checks are a parser feature. When entering a custom command at the command-line, the Nushell parser can even detect invalid argument types in real-time and highlight them before executing the command. -If required and optional positional parameters are used together, then the required parameters must appear in the definition first. +The highlight style can be changed using a [theme](https://github.com/nushell/nu_scripts/tree/main/themes) or manually using `$env.config.color_config.shape_garbage`. +::: -## Flags +::: details List of Type Annotations +Most types can be used as type-annotations. In addition, there are a few "shapes" which can be used. For instance: -In addition to passing positional parameters, you can also pass named parameters by defining flags for your custom commands. +- `number`: Accepts either an `int` or a `float` +- `path`: A string where the `~` and `.` characters have special meaning and will automatically be expanded to the full-path equivalent. See [Path](/lang-guide/chapters/types/other_types/path.html) in the Language Reference Guide for example usage. +- `directory`: A subset of `path` (above). Only directories will be offered when using tab-completion for the parameter. Expansions take place just as with `path`. +- `error`: Available, but currently no known valid usage. See [Error](/lang-guide/chapters/types/other_types/error.html) in the Language Reference Guide for more information. + +The following [types](./types_of_data.html) can be used for parameter annotations: + +- `any` +- `binary` +- `bool` +- `cell-path` +- `closure` +- `datetime` +- `duration` +- `filesize` +- `float` +- `glob` +- `int` +- `list` +- `nothing` +- `range` +- `record` +- `string` +- `table` + ::: + +### Flags + +In addition to positional parameters, you can also define named flags. For example: @@ -219,31 +370,38 @@ def greet [ name: string --age: int ] { - [$name $age] -} + { + name: $name + age: $age + } + } ``` -In the `greet` definition above, we define the `name` positional parameter as well as an `age` flag. This allows the caller of `greet` to optionally pass the `age` parameter as well. +In this version of `greet`, we define the `name` positional parameter as well as an `age` flag. The positional parameter (since it doesn't have a `?`) is required. The named flag is optional. Calling the command without the `--age` flag will set `$age` to `null`. -You can call the above using: +The `--age` flag can go before or after the positional `name`. Examples: ```nu -> greet world --age 10 -``` +greet Lucia --age 23 +# => ╭──────┬───────╮ +# => │ name │ Lucia │ +# => │ age │ 23 │ +# => ╰──────┴───────╯ -Or: +greet --age 39 Ali +# => ╭──────┬─────╮ +# => │ name │ Ali │ +# => │ age │ 39 │ +# => ╰──────┴─────╯ -```nu -> greet --age 10 world +greet World +# => ╭──────┬───────╮ +# => │ name │ World │ +# => │ age │ │ +# => ╰──────┴───────╯ ``` -Or even leave the flag off altogether: - -```nu -> greet world -``` - -Flags can also be defined to have a shorthand version. This allows you to pass a simpler flag as well as a longhand, easier-to-read flag. +Flags can also be defined with a shorthand version. This allows you to pass a simpler flag as well as a longhand, easier-to-read flag. Let's extend the previous example to use a shorthand flag for the `age` value: @@ -252,94 +410,109 @@ def greet [ name: string --age (-a): int ] { - [$name $age] -} + { + name: $name + age: $age + } + } ``` -_Note:_ Flags are named by their longhand name, so the above example would need to use `$age` and not `$a`. +::: tip +The resulting variable is always based on the long flag name. In the above example, the variable continues to be `$age`. `$a` would not be valid. +::: Now, we can call this updated definition using the shorthand flag: ```nu -> greet -a 10 hello +greet Akosua -a 35 +# => ╭──────┬────────╮ +# => │ name │ Akosua │ +# => │ age │ 35 │ +# => ╰──────┴────────╯ ``` -Flags can also be used as basic switches. This means that their presence or absence is taken as an argument for the definition. Extending the previous example: +Flags can also be used as basic switches. When present, the variable based on the switch is `true`. When absent, it is `false`. ```nu def greet [ name: string - --age (-a): int - --twice + --caps ] { - if $twice { - [$name $name $age $age] - } else { - [$name $age] - } + let greeting = $"Hello, ($name)!" + if $caps { + $greeting | str upcase + } else { + $greeting + } } -``` -And the definition can be either called as: +greet Miguel --caps +# => HELLO, MIGUEL! -```nu -> greet -a 10 --twice hello +greet Chukwuemeka +# => Hello, Chukwuemeka! ``` -Or just without the switch flag: +You can also assign it to `true`/`false` to enable/disable the flag: ```nu -> greet -a 10 hello +greet Giulia --caps=false +# => Hello, Giulia! + +greet Hiroshi --caps=true +# => HELLO, HIROSHI! ``` -You can also assign it to true/false to enable/disable the flag too: +::: tip +Be careful of the following mistake: ```nu -> greet -a 10 --switch=false -> greet -a 10 --switch=true +greet Gabriel --caps true ``` -But note that this is not the behavior you want: `> greet -a 10 --switch false`, here the value `false` will pass as a positional argument. +Typing a space instead of an equals sign will pass `true` as a positional argument, which is likely not the desired result! -To avoid confusion, it's not allowed to annotate a boolean type on a flag: +To avoid confusion, annotating a boolean type on a flag is not allowed: ```nu def greet [ - --twice: bool # Not allowed + --caps: bool # Not allowed ] { ... } ``` -instead, you should define it as a basic switch: `def greet [--twice] { ... }` +::: -Flags can contain dashes. They can be accessed by replacing the dash with an underscore: +Flags can contain dashes. They can be accessed by replacing the dash with an underscore in the resulting variable name: ```nu def greet [ name: string - --age (-a): int - --two-times + --all-caps ] { - if $two_times { - [$name $name $age $age] - } else { - [$name $age] - } + let greeting = $"Hello, ($name)!" + if $all_caps { + $greeting | str upcase + } else { + $greeting + } } ``` -## Rest parameters +### Rest parameters -There may be cases when you want to define a command which takes any number of positional arguments. We can do this with a rest parameter, using the following `...` syntax: +There may be cases when you want to define a command which takes any number of positional arguments. We can do this with a "rest" parameter, using the following `...` syntax: ```nu -def greet [...name: string] { - print "hello all:" - for $n in $name { - print $n +def multi-greet [...names: string] { + for $name in $names { + print $"Hello, ($name)!" } } -greet earth mars jupiter venus +multi-greet Elin Lars Erik +# => Hello, Elin! +# => Hello, Lars! +# => Hello, Erik! ``` We could call the above definition of the `greet` command with any number of arguments, including none at all. All of the arguments are collected into `$name` as a list. @@ -347,92 +520,197 @@ We could call the above definition of the `greet` command with any number of arg Rest parameters can be used together with positional parameters: ```nu -def greet [vip: string, ...name: string] { - print $"hello to our VIP ($vip)" - print "and hello to everybody else:" - for $n in $name { - print $n +def vip-greet [vip: string, ...names: string] { + for $name in $names { + print $"Hello, ($name)!" } + + print $"And a special welcome to our VIP today, ($vip)!" } -# $vip $name -# ---- ------------------------ -greet moon earth mars jupiter venus +# $vip $name +# ----- ------------------------- +vip-greet Rahul Priya Arjun Anjali Vikram +# => Hello, Priya! +# => Hello, Arjun! +# => Hello, Anjali! +# => Hello, Vikram! +# => And a special welcome to our VIP today, Rahul! ``` -To pass a list to a rest parameter, you can use the [spread operator](/book/operators#spread-operator) (`...`): +To pass a list to a rest parameter, you can use the [spread operator](/book/operators#spread-operator) (`...`). Using the `vip-greet` command definition above: ```nu -> let planets = [earth mars jupiter venus] # This is equivalent to the previous example -> greet moon ...$planets +let vip = "Tanisha" +let guests = [ Dwayne, Shanice, Jerome ] +vip-greet $vip ...$guests +# => Hello, Dwayne! +# => Hello, Shanice! +# => Hello, Jerome! +# => And a special welcome to our VIP today, Tanisha! ``` ## Documenting Your Command -In order to best help users of your custom commands, you can also document them with additional descriptions for the commands and parameters. +In order to best help users understand how to use your custom commands, you can also document them with additional descriptions for the commands and parameters. + +Run `help vip-greet` to examine our most recent command defined above: + +```text +Usage: + > vip-greet ...(names) + +Flags: + -h, --help - Display the help message for this command -Taking our previous example: +Parameters: + vip + ...names + +Input/output types: + ╭───┬───────┬────────╮ + │ # │ input │ output │ + ├───┼───────┼────────┤ + │ 0 │ any │ any │ + ╰───┴───────┴────────╯ +``` + +::: tip Cool! +You can see that Nushell automatically created some basic help for the commmand simply based on our definition so far. Nushell also automatically adds a `--help`/`-h` flag to the command, so users can also access the help using `vip-greet --help`. +::: + +We can extend the help further with some simple comments describing the command and its parameters: ```nu -def greet [ - name: string - --age (-a): int +# Greet guests along with a VIP +# +# Use for birthdays, graduation parties, +# retirements, and any other event which +# celebrates an event # for a particular +# person. +def vip-greet [ + vip: string # The special guest + ...names: string # The other guests ] { - [$name $age] + for $name in $names { + print $"Hello, ($name)!" + } + + print $"And a special welcome to our VIP today, ($vip)!" } ``` -Once defined, we can run `help greet` to get the help information for the command: +Now run `help vip-greet` again to see the difference: + +```text +Greet guests along with a VIP + +Use for birthdays, graduation parties, +retirements, and any other event which +celebrates an event # for a particular +person. + +Category: default + +This command: +- does not create a scope. +- is not a built-in command. +- is not a subcommand. +- is not part of a plugin. +- is a custom command. +- is not a keyword. -```nu Usage: - > greet {flags} + > vip-greet -Parameters: - Flags: - -h, --help: Display this help message - -a, --age + + + -h, --help - Display the help message for this command + +Signatures: + + | vip-greet[ ] -> + +Parameters: + + vip: The special guest + ...rest: The other guests ``` -You can see the parameter and flag that we defined, as well as the `-h` help flag that all commands get. +Notice that the comments on the lines immediately before the `def` statement become a description of the command in the help system. Multiple lines of comments can be used. The first line (before the blank-comment line) becomes the Help `description`. This information is also shown when tab-completing commands. -To improve this help, we can add descriptions to our definition that will show up in the help: +The remaining comment lines become its `extra_description` in the help data. + +::: tip +Run: ```nu -# A greeting command that can greet the caller -def greet [ - name: string # The name of the person to greet - --age (-a): int # The age of the person -] { - [$name $age] -} +scope commands +| where name == 'vip-greet' +| wrap help ``` -The comments that we put on the definition and its parameters then appear as descriptions inside the [`help`](/commands/docs/help.md) of the command. +This will show the Help _record_ that Nushell creates. +::: + +The comments following the parameters become their description. Only a single-line comment is valid for parameters. -::: warning Note +::: tip Note A Nushell comment that continues on the same line for argument documentation purposes requires a space before the ` #` pound sign. ::: -Now, if we run `help greet`, we're given a more helpful help text: +## Changing the Environment in a Custom Command + +Normally, environment variable definitions and changes are [_scoped_ within a block](./environment.html#scoping). This means that changes to those variables are lost when they go out of scope at the end of the block, including the block of a custom command. +```nu +def foo [] { + $env.FOO = 'After' +} + +$env.FOO = "Before" +foo +$env.FOO +# => Before ``` -A greeting command that can greet the caller -Usage: - > greet {flags} +However, a command defined using [`def --env`](/commands/docs/def.md) or [`export def --env`](/commands/docs/export_def.md) (for a [Module](modules.md)) will preserve the environment on the caller's side: -Parameters: - The name of the person to greet +```nu +def --env foo [] { + $env.FOO = 'After' +} -Flags: - -h, --help: Display this help message - -a, --age : The age of the person +$env.FOO = "Before" +foo +$env.FOO +# => After +``` + +### Changing Directories in a Custom Command + +Likewise, changing the directory using the `cd` command results in a change of the `$env.PWD` environment variable. This means that directory changes (the `$env.PWD` variable) will also be reset when a custom command ends. The solution, as above, is to use `def --env` or `export def --env`. + +```nu +def --env go-home [] { + cd ~ +} + +cd / +go-home +pwd +# => Your home directory ``` -## Pipeline Output +## Custom Commands and Pipelines + +::: tip Important! +See also: [Pipelines](./pipelines.html) +::: + +### Pipeline Output Custom commands stream their output just like built-in commands. For example, let's say we wanted to refactor this pipeline: @@ -450,16 +728,16 @@ We can use the output from this command just as we would [`ls`](/commands/docs/l ```nu > my-ls | get name -───┬─────────────────────── - 0 │ myscript.nu - 1 │ myscript2.nu - 2 │ welcome_to_nushell.md -───┴─────────────────────── +# => ╭───┬───────────────────────╮ +# => │ 0 │ myscript.nu │ +# => │ 1 │ myscript2.nu │ +# => │ 2 │ welcome_to_nushell.md │ +# => ╰───┴───────────────────────╯ ``` -This lets us easily build custom commands and process their output. Note, that we don't use return statements like other languages. Instead, we build pipelines that output streams of data that can be connected to other pipelines. +This lets us easily build custom commands and process their output. Remember that that we don't use return statements like other languages. Instead, the [implicit return](#returning-values-from-a-command) allows us to build pipelines that output streams of data that can be connected to other pipelines. -## Pipeline Input +### Pipeline Input Custom commands can also take input from the pipeline, just like other commands. This input is automatically passed to the block that the custom command uses. @@ -467,32 +745,47 @@ Let's make our own command that doubles every value it receives as input: ```nu def double [] { - each { |elt| 2 * $elt } + each { |num| 2 * $num } } ``` Now, if we call the above command later in a pipeline, we can see what it does with the input: ```nu -> [1 2 3] | double -───┬───── - 0 │ 2 - 1 │ 4 - 2 │ 6 -───┴───── +[1 2 3] | double +# => ╭───┬───╮ +# => │ 0 │ 2 │ +# => │ 1 │ 4 │ +# => │ 2 │ 6 │ +# => ╰───┴───╯ ``` -We can also store the input for later use using the `$in` variable: +We can also store the input for later use using the [`$in` variable](pipelines.html#pipeline-input-and-the-special-in-variable): ```nu def nullify [...cols] { let start = $in - $cols | reduce --fold $start { |col, df| - $df | upsert $col null + $cols | reduce --fold $start { |col, table| + $table | upsert $col null } } + +ls | nullify name size +# => ╭───┬──────┬──────┬──────┬───────────────╮ +# => │ # │ name │ type │ size │ modified │ +# => ├───┼──────┼──────┼──────┼───────────────┤ +# => │ 0 │ │ file │ │ 8 minutes ago │ +# => │ 1 │ │ file │ │ 8 minutes ago │ +# => │ 2 │ │ file │ │ 8 minutes ago │ +# => ╰───┴──────┴──────┴──────┴───────────────╯ ``` ## Persisting -For information about how to persist custom commands so that they're visible when you start up Nushell, see the [configuration chapter](configuration.md) and add your startup script. +To make custom commands available in future Nushell sessions, you'll want to add them to your startup configuration. You can add command definitions: + +- Directly in your `config.nu` +- To a file sourced by your `config.nu` +- To a [module](./modules.html) imported by your `config.nu` + +See the [configuration chapter](configuration.md) for more details. diff --git a/book/environment.md b/book/environment.md index b9e115e7427..1aa249c2d16 100644 --- a/book/environment.md +++ b/book/environment.md @@ -81,6 +81,7 @@ BAR ``` Sometimes, you may want to access an environmental variable which might be unset. Consider using the [question mark operator](types_of_data.md#optional-cell-paths) to avoid an error: + ```nu > $env.FOO | describe Error: nu::shell::column_not_found @@ -135,11 +136,11 @@ true true ``` -## Changing Directory +See also: [Changing the Environment in a Custom Command](./custom_commands.html#changing-the-environment-in-a-custom-command). + +## Changing the Directory -Common task in a shell is to change directory with the [`cd`](/commands/docs/cd.md) command. -In Nushell, calling [`cd`](/commands/docs/cd.md) is equivalent to setting the `PWD` environment variable. -Therefore, it follows the same rules as other environment variables (for example, scoping). +A common task in a shell is to change the directory using the [`cd`](/commands/docs/cd.md) command. In Nushell, calling [`cd`](/commands/docs/cd.md) is equivalent to setting the `PWD` environment variable. Therefore, it follows the same rules as other environment variables (for example, scoping). ## Single-use Environment Variables @@ -161,8 +162,8 @@ The [`with-env`](/commands/docs/with-env.md) command will temporarily set the en ## Permanent Environment Variables -You can also set environment variables at startup so they are available for the duration of Nushell running. -To do this, set an environment variable inside [the Nu configuration file](configuration.md). +You can also set environment variables at startup so they are available for the duration of Nushell running. To do this, set an environment variable inside [the Nu configuration file](configuration.md). + For example: ```nu @@ -170,22 +171,6 @@ For example: $env.FOO = 'BAR' ``` -## Defining Environment from Custom Commands - -Due to the scoping rules, any environment variables defined inside a custom command will only exist inside the command's scope. -However, a command defined as [`def --env`](/commands/docs/def.md) instead of [`def`](/commands/docs/def.md) (it applies also to [`export def`](/commands/docs/export_def.md), see [Modules](modules.md)) will preserve the environment on the caller's side: - -```nu -> def --env foo [] { - $env.FOO = 'BAR' -} - -> foo - -> $env.FOO -BAR -``` - ## Environment Variable Conversions You can set the `ENV_CONVERSIONS` environment variable to convert other environment variables between a string and a value. From 98eb77cfb84557ed12fe03210dd555e5ffa555dd Mon Sep 17 00:00:00 2001 From: NotTheDr01ds <32344964+NotTheDr01ds@users.noreply.github.com> Date: Sun, 29 Sep 2024 12:11:11 -0400 Subject: [PATCH 2/6] Move straggler Command-Signature info into main chapter --- .vuepress/configs/sidebar/en.ts | 1 - book/command_signature.md | 22 ----------------- book/custom_commands.md | 43 +++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 23 deletions(-) delete mode 100644 book/command_signature.md diff --git a/.vuepress/configs/sidebar/en.ts b/.vuepress/configs/sidebar/en.ts index 71cc1c28e49..1b0b6a9ea14 100644 --- a/.vuepress/configs/sidebar/en.ts +++ b/.vuepress/configs/sidebar/en.ts @@ -53,7 +53,6 @@ export const sidebarEn: SidebarConfig = { '/book/scripts.md', '/book/modules.md', '/book/overlays.md', - '/book/command_signature.md', '/book/testing.md', '/book/style_guide.md', ], diff --git a/book/command_signature.md b/book/command_signature.md deleted file mode 100644 index 5f378574837..00000000000 --- a/book/command_signature.md +++ /dev/null @@ -1,22 +0,0 @@ -# Command Signature - -nu commands can be given explicit signatures; take [`str stats`](/commands/docs/str_stats.md) as an example, the signature is like this: - -```nu -def "str stats" []: string -> record { } -``` - -The type names between the `:` and the opening curly brace `{` describe the command's input/output pipeline types. The input type for a given pipeline, in this case `string`, is given before the `->`; and the output type `record` is given after `->`. There can be multiple input/output types. If there are multiple input/output types, they can be placed within brackets and separated with commas, as in [`str join`](/commands/docs/str_join.md): - -```nu -def "str join" [separator?: string]: [list -> string, string -> string] { } -``` - -It says that the [`str join`](/commands/docs/str_join.md) command takes an optional `string` type argument, and an input pipeline of either a `list` (implying `list`) with output type of `string`, or a single `string`, again with output type of `string`. - -Some commands don't accept or require data through the input pipeline, thus the input type will be ``. -The same is true for the output type if the command returns `null` (e.g. [`rm`](/commands/docs/rm.md) or [`hide`](/commands/docs/hide.md)): - -```nu -def hide [module: string, members?]: nothing -> nothing { } -``` diff --git a/book/custom_commands.md b/book/custom_commands.md index 6237f3e8373..c9756dbbd58 100644 --- a/book/custom_commands.md +++ b/book/custom_commands.md @@ -550,6 +550,49 @@ vip-greet $vip ...$guests # => And a special welcome to our VIP today, Tanisha! ``` +## Input-Output Signature + +Custom commands can be given explicit signatures. + +For example, the signature for [`str stats`](/commands/docs/str_stats.md) looks like this: + +```nu +def "str stats" []: string -> record { } +``` + +Here, `string -> record` defines the allowed types of the _pipeline input and output_ of the command: + +- It accepts a `string` as pipeline input +- It outputs a `record` + +If there are multiple input/output types, they can be placed within brackets and separated with commas or newlines, as in [`str join`](/commands/docs/str_join.md): + +```nu +def "str join" [separator?: string]: [ + list -> string + string -> string +] { } +``` + +This indicates that `str join` can accept either a `list` or a `string` as pipeline input. In either case, it will output a `string`. + +Some commands don't accept or require data as pipeline input. In this case, the input type will be ``. The same is true for the output type if the command returns `null` (e.g., [`rm`](/commands/docs/rm.md) or [`hide`](/commands/docs/hide.md)): + +```nu +def xhide [module: string, members?]: nothing -> nothing { } +``` + +::: tip Note +The example above is renamed `xhide` so that copying it to the REPL will not shadow the built-in `hide` command. +::: + +Input-output signatures are shown in the `help` for a command (both built-in and custom) and can also be introspected through: + +```nu +help commands | where name == +scope commands | where name == +``` + ## Documenting Your Command In order to best help users understand how to use your custom commands, you can also document them with additional descriptions for the commands and parameters. From 1634a3165fd9e17ff56b34827c73128c7ecd311f Mon Sep 17 00:00:00 2001 From: NotTheDr01ds <32344964+NotTheDr01ds@users.noreply.github.com> Date: Sun, 29 Sep 2024 14:02:13 -0400 Subject: [PATCH 3/6] Fix typos --- book/custom_commands.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/custom_commands.md b/book/custom_commands.md index c9756dbbd58..f43e689ff1a 100644 --- a/book/custom_commands.md +++ b/book/custom_commands.md @@ -173,7 +173,7 @@ It's common practice in Nushell to separate the words of the command with `-` fo ::: ::: tip -Because `def` is a parser keyword, the command name must be known at parse time. This means that command names may not be a variale or constant. For example, the following is _not allowed_: +Because `def` is a parser keyword, the command name must be known at parse time. This means that command names may not be a variable or constant. For example, the following is _not allowed_: ````nu let name = "foo" @@ -619,7 +619,7 @@ Input/output types: ``` ::: tip Cool! -You can see that Nushell automatically created some basic help for the commmand simply based on our definition so far. Nushell also automatically adds a `--help`/`-h` flag to the command, so users can also access the help using `vip-greet --help`. +You can see that Nushell automatically created some basic help for the command simply based on our definition so far. Nushell also automatically adds a `--help`/`-h` flag to the command, so users can also access the help using `vip-greet --help`. ::: We can extend the help further with some simple comments describing the command and its parameters: From e184047d6da5e375e301934a78996c1cd373f71b Mon Sep 17 00:00:00 2001 From: NotTheDr01ds <32344964+NotTheDr01ds@users.noreply.github.com> Date: Mon, 30 Sep 2024 07:19:23 -0400 Subject: [PATCH 4/6] Add multi-parameter info --- book/custom_commands.md | 56 +++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/book/custom_commands.md b/book/custom_commands.md index f43e689ff1a..29539c2706e 100644 --- a/book/custom_commands.md +++ b/book/custom_commands.md @@ -206,27 +206,61 @@ def "custom command" [] { ## Parameters +### Multiple parameters + +In the `def` command, the parameters are defined in a [`list`](./types_of_data.md#lists). This means that multiple parameters can be separated with spaces, commas, or line-breaks. + +For example, here's a version of `greet` that accepts two names. Any of these three definitions will work: + +```nu +# Spaces +def greet [name1 name2] { + $"Hello, ($name1) and ($name2)!" +} + +# Commas +def greet [name1, name2] { + $"Hello, ($name1) and ($name2)!" +} + +# Linebreaks +def greet [ + name1 + name2 +] { + $"Hello, ($name1) and ($name2)!" +} +``` + ### Required positional parameters -The basic argument definitions used above are _positional_. The first parameter passed into the `greet` command above is assigned to the first `name` parameter. +The basic argument definitions used above are _positional_. The first argument passed into the `greet` command above is assigned to the `name1` parameter (and, as mentioned above, the `$name1` variable). The second argument becomes the `name2` parameter and the `$name2` variable. -By default, positional parameters are required. If a positional parameter is not provided, we will encounter an error. Using our basic `greet` command: +By default, positional parameters are _required_. Using our previous definition of `greet` with two required, positional parameters: ```nu -def greet [name] { +def greet [name1, name2] { $"Hello, ($name)!" } -greet -# => × Missing required positional argument. -# => ╭─[entry #23:1:1] -# => 1 │ greet -# => · ▲ -# => · ╰── missing name -# => ╰──── -# => help: Usage: greet +greet Wei Mei +# => Hello, Wei and Mei! + +greet Wei +# => Error: nu::parser::missing_positional +# => +# => +# => × Missing required positional argument. +# => ╭─[entry #10:1:10] +# => 1 │ greet Mei +# => ╰──── +# => help: Usage: greet . Use `--help` for more information. ``` +::: tip +Try typing a third name after this version of `greet`. Notice that the parser automatically detects the error and highlights the third argument as an error even before execution. +::: + ### Optional Positional Parameters We can define a positional parameter as optional by putting a question mark (`?`) after its name. For example: From cc6bd32ac7f6d1f47934b38378b4008cc326ed61 Mon Sep 17 00:00:00 2001 From: NotTheDr01ds <32344964+NotTheDr01ds@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:03:19 -0400 Subject: [PATCH 5/6] Fixed issues from review --- book/custom_commands.md | 216 +++++++++++++++++++++++++--------------- 1 file changed, 136 insertions(+), 80 deletions(-) diff --git a/book/custom_commands.md b/book/custom_commands.md index 29539c2706e..19b296783b0 100644 --- a/book/custom_commands.md +++ b/book/custom_commands.md @@ -155,6 +155,98 @@ In this case, the final expression is the `match` statement which can return: - Otherwise, a `record` representing the randomly chosen file ::: +## Custom Commands and Pipelines + +Just as with built-in commands, the return value of a custom command can be passed into the next command in a pipeline. Custom commands can also accept pipeline input. In addition, whenever possible, pipeline input and output is streamed as it becomes available. + +::: tip Important! +See also: [Pipelines](./pipelines.html) +::: + +### Pipeline Output + +```nu +> ls | get name +``` + +Let's move [`ls`](/commands/docs/ls.md) into a command that we've written: + +```nu +def my-ls [] { ls } +``` + +We can use the output from this command just as we would [`ls`](/commands/docs/ls.md). + +```nu +> my-ls | get name +# => ╭───┬───────────────────────╮ +# => │ 0 │ myscript.nu │ +# => │ 1 │ myscript2.nu │ +# => │ 2 │ welcome_to_nushell.md │ +# => ╰───┴───────────────────────╯ +``` + +This lets us easily build custom commands and process their output. Remember that that we don't use return statements like other languages. Instead, the [implicit return](#returning-values-from-a-command) allows us to build pipelines that output streams of data that can be connected to other pipelines. + +::: tip Note +The `ls` content is still streamed in this case, even though it is in a separate command. Running this command against a long-directory on a slow (e.g., networked) filesystem would return rows as they became available. +::: + +### Pipeline Input + +Custom commands can also take input from the pipeline, just like other commands. This input is automatically passed to the custom command's block. + +Let's make our own command that doubles every value it receives as input: + +```nu +def double [] { + each { |num| 2 * $num } +} +``` + +Now, if we call the above command later in a pipeline, we can see what it does with the input: + +```nu +[1 2 3] | double +# => ╭───┬───╮ +# => │ 0 │ 2 │ +# => │ 1 │ 4 │ +# => │ 2 │ 6 │ +# => ╰───┴───╯ +``` + +::: tip Cool! +This command demonstrates both input and output _streaming_. Try running it with an infinite input: + +```nu +1.. | each {||} | double +``` + +Even though the input command hasn't ended, the `double` command can still receive and output values as they become available. + +Press Ctrl+C to stop the command. +::: + +We can also store the input for later use using the [`$in` variable](pipelines.html#pipeline-input-and-the-special-in-variable): + +```nu +def nullify [...cols] { + let start = $in + $cols | reduce --fold $start { |col, table| + $table | upsert $col null + } +} + +ls | nullify name size +# => ╭───┬──────┬──────┬──────┬───────────────╮ +# => │ # │ name │ type │ size │ modified │ +# => ├───┼──────┼──────┼──────┼───────────────┤ +# => │ 0 │ │ file │ │ 8 minutes ago │ +# => │ 1 │ │ file │ │ 8 minutes ago │ +# => │ 2 │ │ file │ │ 8 minutes ago │ +# => ╰───┴──────┴──────┴──────┴───────────────╯ +``` + ## Naming Commands In Nushell, a command name can be a string of characters. Here are some examples of valid command names: `greet`, `get-size`, `mycommand123`, `my command`, `命令` (English translation: "command"), and even `😊`. @@ -169,7 +261,7 @@ Strings which might be confused with other parser patterns should be avoided. Fo While names like `"+foo"` might work, they are best avoided as the parser rules might change over time. When in doubt, keep command names as simple as possible. ::: tip -It's common practice in Nushell to separate the words of the command with `-` for better readability.\_ For example `get-size` instead of `getsize` or `get_size`. +It's common practice in Nushell to separate the words of the command with `-` for better readability. For example `get-size` instead of `getsize` or `get_size`. ::: ::: tip @@ -408,7 +500,7 @@ def greet [ name: $name age: $age } - } +} ``` In this version of `greet`, we define the `name` positional parameter as well as an `age` flag. The positional parameter (since it doesn't have a `?`) is required. The named flag is optional. Calling the command without the `--age` flag will set `$age` to `null`. @@ -584,9 +676,9 @@ vip-greet $vip ...$guests # => And a special welcome to our VIP today, Tanisha! ``` -## Input-Output Signature +## Pipeline Input-Output Signature -Custom commands can be given explicit signatures. +By default, custom commands accept [`` type](./types_of_data.md#any) as pipline input and likewise can output `` type. But custom commands can also be given explicit signatures to narrow the types allowed. For example, the signature for [`str stats`](/commands/docs/str_stats.md) looks like this: @@ -627,6 +719,46 @@ help commands | where name == scope commands | where name == ``` +:::tip Cool! +Input-Output signatures allow Nushell to catch two additional categories of errors at parse-time: + +- Attempting to return the wrong type from a command. For example: + + ```nu + def inc []: int -> int { + $in + 1 + print "Did it!" + } + + # => Error: nu::parser::output_type_mismatch + # => + # => × Command output doesn't match int. + # => ╭─[entry #12:1:24] + # => 1 │ ╭─▶ def inc []: int -> int { + # => 2 │ │ $in + 1 + # => 3 │ │ print "Did it!" + # => 4 │ ├─▶ } + # => · ╰──── expected int, but command outputs nothing + # => ╰──── + ``` + +- And attempting to pass an invalid type into a command: + + ```nu + def inc []: int -> int { $in + 1 } + "Hi" | inc + # => Error: nu::parser::input_type_mismatch + # => + # => × Command does not support string input. + # => ╭─[entry #16:1:8] + # => 1 │ "Hi" | inc + # => · ─┬─ + # => · ╰── command doesn't support string input + # => ╰──── + ``` + + ::: + ## Documenting Your Command In order to best help users understand how to use your custom commands, you can also document them with additional descriptions for the commands and parameters. @@ -781,82 +913,6 @@ pwd # => Your home directory ``` -## Custom Commands and Pipelines - -::: tip Important! -See also: [Pipelines](./pipelines.html) -::: - -### Pipeline Output - -Custom commands stream their output just like built-in commands. For example, let's say we wanted to refactor this pipeline: - -```nu -> ls | get name -``` - -Let's move [`ls`](/commands/docs/ls.md) into a command that we've written: - -```nu -def my-ls [] { ls } -``` - -We can use the output from this command just as we would [`ls`](/commands/docs/ls.md). - -```nu -> my-ls | get name -# => ╭───┬───────────────────────╮ -# => │ 0 │ myscript.nu │ -# => │ 1 │ myscript2.nu │ -# => │ 2 │ welcome_to_nushell.md │ -# => ╰───┴───────────────────────╯ -``` - -This lets us easily build custom commands and process their output. Remember that that we don't use return statements like other languages. Instead, the [implicit return](#returning-values-from-a-command) allows us to build pipelines that output streams of data that can be connected to other pipelines. - -### Pipeline Input - -Custom commands can also take input from the pipeline, just like other commands. This input is automatically passed to the block that the custom command uses. - -Let's make our own command that doubles every value it receives as input: - -```nu -def double [] { - each { |num| 2 * $num } -} -``` - -Now, if we call the above command later in a pipeline, we can see what it does with the input: - -```nu -[1 2 3] | double -# => ╭───┬───╮ -# => │ 0 │ 2 │ -# => │ 1 │ 4 │ -# => │ 2 │ 6 │ -# => ╰───┴───╯ -``` - -We can also store the input for later use using the [`$in` variable](pipelines.html#pipeline-input-and-the-special-in-variable): - -```nu -def nullify [...cols] { - let start = $in - $cols | reduce --fold $start { |col, table| - $table | upsert $col null - } -} - -ls | nullify name size -# => ╭───┬──────┬──────┬──────┬───────────────╮ -# => │ # │ name │ type │ size │ modified │ -# => ├───┼──────┼──────┼──────┼───────────────┤ -# => │ 0 │ │ file │ │ 8 minutes ago │ -# => │ 1 │ │ file │ │ 8 minutes ago │ -# => │ 2 │ │ file │ │ 8 minutes ago │ -# => ╰───┴──────┴──────┴──────┴───────────────╯ -``` - ## Persisting To make custom commands available in future Nushell sessions, you'll want to add them to your startup configuration. You can add command definitions: From 3cb0576d242e3724078823f210c84cc680074da9 Mon Sep 17 00:00:00 2001 From: NotTheDr01ds <32344964+NotTheDr01ds@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:05:05 -0400 Subject: [PATCH 6/6] Fixed typo --- book/custom_commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/custom_commands.md b/book/custom_commands.md index 19b296783b0..ff5b1617876 100644 --- a/book/custom_commands.md +++ b/book/custom_commands.md @@ -678,7 +678,7 @@ vip-greet $vip ...$guests ## Pipeline Input-Output Signature -By default, custom commands accept [`` type](./types_of_data.md#any) as pipline input and likewise can output `` type. But custom commands can also be given explicit signatures to narrow the types allowed. +By default, custom commands accept [`` type](./types_of_data.md#any) as pipeline input and likewise can output `` type. But custom commands can also be given explicit signatures to narrow the types allowed. For example, the signature for [`str stats`](/commands/docs/str_stats.md) looks like this: