From e220b9d216a17a6d1d1720add6a96c332a8ff00f Mon Sep 17 00:00:00 2001 From: 132ikl <132@ikl.sh> Date: Mon, 30 Sep 2024 11:30:48 -0400 Subject: [PATCH 1/3] Highlight the differences between scripts and modules --- book/modules.md | 20 ++++++++++++-------- book/scripts.md | 43 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/book/modules.md b/book/modules.md index ae4e1ca48c1..8a526d5cf02 100644 --- a/book/modules.md +++ b/book/modules.md @@ -186,6 +186,8 @@ The `main` is exported only when Importing definitions selectively (`use greetings.nu hello` or `use greetings.nu [hello hi]`) does not define the `greetings` command from `main`. You can, however, selectively import `main` using `use greetings main` (or `[main]`) which defines _only_ the `greetings` command without pulling in `hello` or `hi`. +Additionally, `main` has special behavior if used in a script file, regardless of whether it is exported or not. See the [section on scripts](scripts.html#parameterizing-scripts) for more details. + Apart from commands (`def`, `def --env`), known externals (`extern`) can also be named `main`. ## Submodules and Subcommands @@ -197,8 +199,7 @@ Submodules are modules inside modules. They are automatically created when you c The difference is that `export module some-module` _only_ adds the module as a submodule, while `export use some-module` _also_ re-exports the submodule's definitions. Since definitions of submodules are available when importing from a module, `export use some-module` is typically redundant, unless you want to re-export its definitions without the namespace prefix. -> **Note** -> `module` without `export` defines only a local module, it does not export a submodule. +> **Note** > `module` without `export` defines only a local module, it does not export a submodule. Let's illustrate this with an example. Assume three files: @@ -425,10 +426,11 @@ A common pattern in traditional shells is dumping and auto-sourcing files from a Here we'll create a simple completion module with a submodule dedicated to some Git completions: 1. Create the completion directory -`mkdir ($nu.default-config-dir | path join completions)` + `mkdir ($nu.default-config-dir | path join completions)` 2. Create an empty `mod.nu` for it -`touch ($nu.default-config-dir | path join completions mod.nu)` + `touch ($nu.default-config-dir | path join completions mod.nu)` 3. Put the following snippet in `git.nu` under the `completions` directory + ```nu export extern main [ --version(-v) @@ -450,17 +452,20 @@ def complete-git-branch [] { # ... code to list git branches } ``` + 4. Add `export module git.nu` to `mod.nu` 5. Add the parent of the `completions` directory to your NU_LIB_DIRS inside `env.nu` + ```nu $env.NU_LIB_DIRS = [ ... $nu.default-config-dir ] ``` + 6. import the completions to Nushell in your `config.nu` -`use completions *` -Now you've set up a directory where you can put your completion files and you should have some Git completions the next time you start Nushell + `use completions *` + Now you've set up a directory where you can put your completion files and you should have some Git completions the next time you start Nushell > **Note** > This will use the file name (in our example `git` from `git.nu`) as the module name. This means some completions might not work if the definition has the base command in its name. @@ -515,5 +520,4 @@ It can be one of the following: - Hides all the module's exports, without the prefix -> **Note** -> `hide` is not a supported keyword at the root of a module (unlike `def` etc.) +> **Note** > `hide` is not a supported keyword at the root of a module (unlike `def` etc.) diff --git a/book/scripts.md b/book/scripts.md index e73b3fa7a01..ff947f608d6 100644 --- a/book/scripts.md +++ b/book/scripts.md @@ -25,7 +25,7 @@ greet "world" A script file defines the definitions for custom commands as well as the main script itself, which will run after the custom commands are defined. -In the above, first `greet` is defined by the Nushell interpreter. This allows us to later call this definition. We could have written the above as: +In the above example, first `greet` is defined by the Nushell interpreter. This allows us to later call this definition. We could have written the above as: ```nu greet "world" @@ -75,9 +75,9 @@ def main [x: int] { ## Argument Type Interpretation -By default, arguments provided to a script are interpreted with the type `Type::Any`, implying that they are not constrained to a specific data type and can be dynamically interpreted as fitting any of the available data types during script execution. +By default, arguments provided to a script are interpreted with the type `Type::Any`, implying that they are not constrained to a specific data type and can be dynamically interpreted as fitting any of the available data types during script execution. -In the previous example, `main [x: int]` denotes that the argument x should possess an integer data type. However, if arguments are not explicitly typed, they will be parsed according to their apparent data type. +In the previous example, `main [x: int]` denotes that the argument x should possess an integer data type. However, if arguments are not explicitly typed, they will be parsed according to their apparent data type. For example: @@ -103,7 +103,7 @@ Hello string +1 ## Subcommands -A script can have multiple sub-commands like `run`, `build`, etc. which allows to execute a specific main sub-function. The important part is to expose them correctly with `def main [] {}`. See more details in the [Custom Command](custom_commands.html#sub-commands) section. +A script's `main` command can have multiple [subcommands](custom_commands.html#subcommands) like `run`, `build`, etc. which allows executing a specific sub-function from the commandline. For example: @@ -117,17 +117,46 @@ def "main build" [] { print "building" } -# important for the command to be exposed to the outside -def main [] {} +def main [] { + print "hello from myscript!" +} ``` ```nu +> nu myscript.nu +hello from myscript! > nu myscript.nu build building > nu myscript.nu run running ``` +[Unlike modules](modules.html#main), `main` does _not_ need to exported in order to be visible. In the above example, our `main` command is not `export def`, however it was still executed when running `nu myscript.nu`. If we had used myscript as a module by running `use myscript.nu`, rather than running `myscript.nu` as a script, trying to execute the `myscript` command would not work since `myscript` is not exported. + +It is important to note that you must define a `main` command in order for subcommands of `main` to be correctly exposed. For example, if we had just defined the `run` and `build` subcommands, they wouldn't be accessible when running the script: + +```nu +# myscript.nu +def "main run" [] { + print "running" +} + +def "main build" [] { + print "building" +} +``` + +```nu +> nu myscript.nu build +> nu myscript.nu run +``` + +This is a limitation of the way scripts are currently processed. If your script only has subcommands, you can add an empty `main` to expose the subcommands, like so: + +```nu +def main [] {} +``` + ## Shebangs (`#!`) On Linux and macOS you can optionally use a [shebang]() to tell the OS that a file should be interpreted by Nu. For example, with the following in a file named `myscript`: @@ -141,7 +170,9 @@ On Linux and macOS you can optionally use a [shebang]( ./myscript Hello World! ``` + For script to have access to standard input, `nu` should be invoked with `--stdin` flag: + ```nu #!/usr/bin/env -S nu --stdin echo $"stdin: ($in)" From 0ea4770104881004c5360e0fb7d1cbb314f5a962 Mon Sep 17 00:00:00 2001 From: 132ikl <132@ikl.sh> Date: Mon, 30 Sep 2024 11:55:50 -0400 Subject: [PATCH 2/3] Change bespoke note to a tip --- book/modules.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/book/modules.md b/book/modules.md index 8a526d5cf02..1271f6b0dfe 100644 --- a/book/modules.md +++ b/book/modules.md @@ -199,7 +199,9 @@ Submodules are modules inside modules. They are automatically created when you c The difference is that `export module some-module` _only_ adds the module as a submodule, while `export use some-module` _also_ re-exports the submodule's definitions. Since definitions of submodules are available when importing from a module, `export use some-module` is typically redundant, unless you want to re-export its definitions without the namespace prefix. -> **Note** > `module` without `export` defines only a local module, it does not export a submodule. +::: tip Note +`module` without `export` defines only a local module, it does not export a submodule. +::: Let's illustrate this with an example. Assume three files: From 6b7ac93b794c6e6cbe5f4845c0006f69ab4e6c4b Mon Sep 17 00:00:00 2001 From: 132ikl <132@ikl.sh> Date: Mon, 30 Sep 2024 15:50:05 -0400 Subject: [PATCH 3/3] Address review notes Co-authored-by: Douglas <32344964+NotTheDr01ds@users.noreply.github.com> --- book/modules.md | 99 +++++++++++++++++++++++++++---------------------- book/scripts.md | 6 +-- 2 files changed, 57 insertions(+), 48 deletions(-) diff --git a/book/modules.md b/book/modules.md index 1271f6b0dfe..3c28ea29e5b 100644 --- a/book/modules.md +++ b/book/modules.md @@ -78,11 +78,12 @@ then The result should be similar as in the previous section. -> **Note** -> that the `use greetings.nu hello` call here first implicitly creates the `greetings` module, -> then takes `hello` from it. You could also write it as `module greetings.nu`, `use greetings hello`. -> Using `module` can be useful if you're not interested in any definitions from the module but want to, -> e.g., re-export the module (`export module greetings.nu`). +::: tip Note +Note that the `use greetings.nu hello` call here first implicitly creates the `greetings` module, +then takes `hello` from it. You could also write it as `module greetings.nu`, `use greetings hello`. +Using `module` can be useful if you're not interested in any definitions from the module but want to, +e.g., re-export the module (`export module greetings.nu`). +::: ## Modules from Directories @@ -112,7 +113,7 @@ then The name of the module follows the same rule as module created from a file: Stem of the directory name, i.e., the directory name, is used as the module name. Again, you could do this as a two-step action using `module` and `use` separately, as explained in the previous section. -::: tip +::: tip Note You can define `main` command inside `mod.nu` to create a command named after the module directory. ::: @@ -428,62 +429,68 @@ A common pattern in traditional shells is dumping and auto-sourcing files from a Here we'll create a simple completion module with a submodule dedicated to some Git completions: 1. Create the completion directory + `mkdir ($nu.default-config-dir | path join completions)` + 2. Create an empty `mod.nu` for it + `touch ($nu.default-config-dir | path join completions mod.nu)` + 3. Put the following snippet in `git.nu` under the `completions` directory -```nu -export extern main [ - --version(-v) - -C: string - # ... etc. -] - -export extern add [ - --verbose(-v) - --dry-run(-n) - # ... etc. -] - -export extern checkout [ - branch: string@complete-git-branch -] - -def complete-git-branch [] { - # ... code to list git branches -} -``` + ```nu + export extern main [ + --version(-v) + -C: string + # ... etc. + ] + + export extern add [ + --verbose(-v) + --dry-run(-n) + # ... etc. + ] + + export extern checkout [ + branch: string@complete-git-branch + ] + + def complete-git-branch [] { + # ... code to list git branches + } + ``` 4. Add `export module git.nu` to `mod.nu` 5. Add the parent of the `completions` directory to your NU_LIB_DIRS inside `env.nu` -```nu -$env.NU_LIB_DIRS = [ - ... - $nu.default-config-dir -] -``` + ```nu + $env.NU_LIB_DIRS = [ + ... + $nu.default-config-dir + ] + ``` + +6. Import the completions to Nushell in your `config.nu`: -6. import the completions to Nushell in your `config.nu` `use completions *` - Now you've set up a directory where you can put your completion files and you should have some Git completions the next time you start Nushell -> **Note** -> This will use the file name (in our example `git` from `git.nu`) as the module name. This means some completions might not work if the definition has the base command in its name. -> For example, if you defined our known externals in our `git.nu` as `export extern 'git push' []`, etc. and followed the rest of the steps, you would get subcommands like `git git push`, etc. -> You would need to call `use completions git *` to get the desired subcommands. For this reason, using `main` as outlined in the step above is the preferred way to define subcommands. +Now you've set up a directory where you can put your completion files, and you should have some Git completions the next time you start Nushell. + +::: tip Note +This will use the file name (in our example `git` from `git.nu`) as the module name. This means some completions might not work if the definition has the base command in its name. +For example, if you defined our known externals in our `git.nu` as `export extern 'git push' []`, etc. and followed the rest of the steps, you would get subcommands like `git git push`, etc. +You would need to call `use completions git *` to get the desired subcommands. For this reason, using `main` as outlined in the step above is the preferred way to define subcommands. +::: ### Setting environment + aliases (conda style) `def --env` commands, `export-env` block and aliases can be used to dynamically manipulate "virtual environments" (a concept well known from Python). -We use it in our official virtualenv integration https://github.com/pypa/virtualenv/blob/main/src/virtualenv/activation/nushell/activate.nu - -Another example could be our unofficial Conda module: https://github.com/nushell/nu_scripts/blob/f86a060c10f132407694e9ba0f536bfe3ee51efc/modules/virtual_environments/conda.nu +We use it in our [official virtualenv integration](https://github.com/pypa/virtualenv/blob/main/src/virtualenv/activation/nushell/activate.nu). Another example is our [unofficial Conda module](https://github.com/nushell/nu_scripts/blob/main/modules/virtual_environments/conda.nu). -> **Warning** -> Work In Progress +::: warning +Work in progress +::: ## Hiding @@ -522,4 +529,6 @@ It can be one of the following: - Hides all the module's exports, without the prefix -> **Note** > `hide` is not a supported keyword at the root of a module (unlike `def` etc.) +:::tip Note +`hide` is not a supported keyword at the root of a module (unlike `def` etc.) +::: diff --git a/book/scripts.md b/book/scripts.md index ff947f608d6..89dc74a4763 100644 --- a/book/scripts.md +++ b/book/scripts.md @@ -103,9 +103,7 @@ Hello string +1 ## Subcommands -A script's `main` command can have multiple [subcommands](custom_commands.html#subcommands) like `run`, `build`, etc. which allows executing a specific sub-function from the commandline. - -For example: +A script can have multiple [subcommands](custom_commands.html#subcommands), like `run` or `build` for example: ```nu # myscript.nu @@ -122,6 +120,8 @@ def main [] { } ``` +You can then execute the script's subcommands when calling it: + ```nu > nu myscript.nu hello from myscript!