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 366be375755..ff5b1617876 100644 --- a/book/custom_commands.md +++ b/book/custom_commands.md @@ -1,67 +1,286 @@ # 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)!" +} +``` + +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. + +To run this command, we can call it just as 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" | describe +# => string +``` + +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 +``` + +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 -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`. ::: -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. +::: 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. -To run the above, we can call it like we would call built-in commands: +You can use the `ignore` keyword in this case: ```nu -> greet "world" +def create-three-files [] { + [ file1 file2 file3 ] | each {|filename| + touch $filename + } | ignore +} ``` -As we do, we also get output just as we would with built-in commands: +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). +::: + +::: 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 ``` -───┬─────── - 0 │ hello - 1 │ world -───┴─────── + +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 │ +# => ╰───┴─────╯ +::: + +::: details Match expression +```nu +# Return a random file in the current directory +def "random file" [] { + let files = (ls) + let num_files = ($files | length) + + 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 + ::: + +## 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 ``` -::: tip -If you want to generate a single string, you can use the string interpolation syntax to embed $name in it: +Let's move [`ls`](/commands/docs/ls.md) into a command that we've written: ```nu -def greet [name] { - $"hello ($name)" +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: -greet nushell +```nu +1.. | each {||} | double ``` -returns `hello nushell` +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. ::: -## Command Names +We can also store the input for later use using the [`$in` variable](pipelines.html#pipeline-input-and-the-special-in-variable): -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 `😊`. +```nu +def nullify [...cols] { + let start = $in + $cols | reduce --fold $start { |col, table| + $table | upsert $col null + } +} -_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`. +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 `😊`. + +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. -## Subcommands +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. -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: +::: 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 variable or constant. For example, the following is _not allowed_: + +````nu +let name = "foo" +def $name [] { foo } +::: + +### Subcommands + +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 +292,125 @@ 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 + +### Multiple parameters -When defining custom commands, you can name and optionally set the type for each parameter. For example, you can write the above as: +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 -def greet [name: string] { - $"hello ($name)" +# 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)!" } ``` -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. +### Required positional parameters -For example, let's say you wanted to take in an `int` instead: +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_. Using our previous definition of `greet` with two required, positional parameters: ```nu -def greet [name: int] { - $"hello ($name)" +def greet [name1, name2] { + $"Hello, ($name)!" } -greet world +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. ``` -If we try to run the above, Nushell will tell us that the types don't match: +::: 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. +::: -``` -error: Type Error - ┌─ shell:6:7 - │ -5 │ greet world - │ ^^^^^ Expected int +### Optional Positional Parameters + +We can define a positional parameter as optional by putting a question mark (`?`) after its name. For example: + +```nu +def greet [name?: string] { + $"Hello, ($name | default 'You')" +} + +greet + +# => Hello, You ``` -This can help you guide users of your definitions to call them with only the supported types. +::: tip +Notice that the name used to access the variable does not include the `?`; only its definition in the command signature. +::: -The currently accepted types are (as of version 0.86.0): +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`. -- `any` -- `binary` -- `bool` -- `cell-path` -- `closure` -- `datetime` -- `directory` -- `duration` -- `error` -- `filesize` -- `float` -- `glob` -- `int` -- `list` -- `nothing` -- `number` -- `path` -- `range` -- `record` -- `string` -- `table` +You could also compare the value directly: + +```nu +def greet [name?: string] { + match $name { + null => "Hello! I don't know your name!" + _ => $"Hello, ($name)!" + } +} + +greet + +# => Hello! I don't know your name! +``` + +If required and optional positional parameters are used together, then the required parameters must appear in the definition first. -## Parameters with a Default Value +#### Parameters with a Default Value -To make a parameter optional and directly provide a default value for it you can provide a default value in the command definition. +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 +418,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 +496,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. - -You can call the above using: +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`. -```nu -> greet world --age 10 -``` - -Or: +The `--age` flag can go before or after the positional `name`. Examples: ```nu -> greet --age 10 world -``` +greet Lucia --age 23 +# => ╭──────┬───────╮ +# => │ name │ Lucia │ +# => │ age │ 23 │ +# => ╰──────┴───────╯ -Or even leave the flag off altogether: +greet --age 39 Ali +# => ╭──────┬─────╮ +# => │ name │ Ali │ +# => │ age │ 39 │ +# => ╰──────┴─────╯ -```nu -> greet world +greet World +# => ╭──────┬───────╮ +# => │ name │ World │ +# => │ age │ │ +# => ╰──────┴───────╯ ``` -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 +536,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,152 +646,279 @@ 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 +## Pipeline Input-Output Signature -In order to best help users of your custom commands, you can also document them with additional descriptions for the commands and parameters. +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. -Taking our previous example: +For example, the signature for [`str stats`](/commands/docs/str_stats.md) looks like this: ```nu -def greet [ - name: string - --age (-a): int -] { - [$name $age] -} +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 +] { } ``` -Once defined, we can run `help greet` to get the help information for the command: +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 == +``` + +:::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. + +Run `help vip-greet` to examine our most recent command defined above: + +```text Usage: - > greet {flags} + > vip-greet ...(names) + +Flags: + -h, --help - Display the help message for this command Parameters: - + vip + ...names -Flags: - -h, --help: Display this help message - -a, --age +Input/output types: + ╭───┬───────┬────────╮ + │ # │ input │ output │ + ├───┼───────┼────────┤ + │ 0 │ any │ any │ + ╰───┴───────┴────────╯ ``` -You can see the parameter and flag that we defined, as well as the `-h` help flag that all commands get. +::: tip Cool! +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`. +::: -To improve this help, we can add descriptions to our definition that will show up in the help: +We can extend the help further with some simple comments describing the command and its parameters: ```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 +# 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)!" } ``` -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. +Now run `help vip-greet` again to see the difference: -::: warning Note -A Nushell comment that continues on the same line for argument documentation purposes requires a space before the ` #` pound sign. -::: +```text +Greet guests along with a VIP -Now, if we run `help greet`, we're given a more helpful help text: +Use for birthdays, graduation parties, +retirements, and any other event which +celebrates an event # for a particular +person. -``` -A greeting command that can greet the caller +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. Usage: - > greet {flags} + > vip-greet -Parameters: - The name of the person to greet Flags: - -h, --help: Display this help message - -a, --age : The age of the person -``` -## Pipeline Output -Custom commands stream their output just like built-in commands. For example, let's say we wanted to refactor this pipeline: + -h, --help - Display the help message for this command -```nu -> ls | get name -``` +Signatures: -Let's move [`ls`](/commands/docs/ls.md) into a command that we've written: + | vip-greet[ ] -> -```nu -def my-ls [] { ls } +Parameters: + + vip: The special guest + ...rest: The other guests ``` -We can use the output from this command just as we would [`ls`](/commands/docs/ls.md). +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. + +The remaining comment lines become its `extra_description` in the help data. + +::: tip +Run: ```nu -> my-ls | get name -───┬─────────────────────── - 0 │ myscript.nu - 1 │ myscript2.nu - 2 │ welcome_to_nushell.md -───┴─────────────────────── +scope commands +| where name == 'vip-greet' +| wrap help ``` -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 will show the Help _record_ that Nushell creates. +::: -## Pipeline Input +The comments following the parameters become their description. Only a single-line comment is valid for parameters. + +::: tip Note +A Nushell comment that continues on the same line for argument documentation purposes requires a space before the ` #` pound sign. +::: -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. +## Changing the Environment in a Custom Command -Let's make our own command that doubles every value it receives as input: +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 double [] { - each { |elt| 2 * $elt } +def foo [] { + $env.FOO = 'After' } + +$env.FOO = "Before" +foo +$env.FOO +# => Before ``` -Now, if we call the above command later in a pipeline, we can see what it does with the input: +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: ```nu -> [1 2 3] | double -───┬───── - 0 │ 2 - 1 │ 4 - 2 │ 6 -───┴───── +def --env foo [] { + $env.FOO = 'After' +} + +$env.FOO = "Before" +foo +$env.FOO +# => After ``` -We can also store the input for later use using the `$in` variable: +### 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 nullify [...cols] { - let start = $in - $cols | reduce --fold $start { |col, df| - $df | upsert $col null - } +def --env go-home [] { + cd ~ } + +cd / +go-home +pwd +# => Your home directory ``` ## 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.