Skip to content

Commit ca4ed42

Browse files
authored
add docs about @structured (#2273)
1 parent c0a5d53 commit ca4ed42

5 files changed

Lines changed: 248 additions & 3 deletions

File tree

‎book/custom_commands.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,6 +1333,10 @@ help commands | where category == deprecated | select name category command_type
13331333
# => ╰───┴───────┴────────────┴──────────────╯
13341334
```
13351335

1336+
Attributes also work on [`extern`](externs.md) declarations. One of them, [`@structured`](/commands/docs/attr_structured.md),
1337+
applies only to `extern`. It lets an external command return data instead of text. See
1338+
[Structured Output](externs.md#structured-output) for examples.
1339+
13361340
To see other attributes available for use within nushell, check the [command reference](/commands/docs/attr.html)
13371341
or run `help attr` from the nushell REPL.
13381342

‎book/externs.md‎

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,169 @@ export extern "git add" [
5858
]
5959
```
6060

61+
## Structured Output
62+
63+
Many external commands print machine-readable output when you pass a certain flag, such as `git status --porcelain` or `pip list --format=json`. Getting data from them means remembering that flag and the command that parses its output, every time. The [`@structured`](/commands/docs/attr_structured.md) attribute puts both in the `extern` declaration, so you write them once:
64+
65+
```nu
66+
@structured [--porcelain] { lines | parse --regex '^(?<status>..) (?<file>.+)$' }
67+
extern "git status" []
68+
```
69+
70+
`@structured` takes two arguments:
71+
72+
- A list of arguments that make the command print machine-readable output. Each item in the list is one argument, so `[--porcelain]` adds one argument and `[-o json]` adds two, `-o` and `json`. Quote an item that contains spaces or `|`. Otherwise Nushell splits it into several items.
73+
- A block that turns that output into data. The block receives the command's output as `$in`.
74+
75+
With this declaration, `git status` still prints git's usual text when you run it by itself:
76+
77+
```nu
78+
git status
79+
# => On branch main
80+
# => Changes to be committed:
81+
# => (use "git restore --staged <file>..." to unstage)
82+
# => new file: INSTALL.md
83+
# =>
84+
# => Changes not staged for commit:
85+
# => (use "git add <file>..." to update what will be committed)
86+
# => (use "git restore <file>..." to discard changes in working directory)
87+
# => modified: README.md
88+
# =>
89+
# => Untracked files:
90+
# => (use "git add <file>..." to include in what will be committed)
91+
# => notes.txt
92+
```
93+
94+
Pipe it into a Nushell command, and Nushell runs `git status --porcelain` instead and passes the output through the block. You get a table:
95+
96+
```nu
97+
git status | where status =~ M
98+
# => ╭───┬────────┬───────────╮
99+
# => │ # │ status │ file │
100+
# => ├───┼────────┼───────────┤
101+
# => │ 0 │ M │ README.md │
102+
# => ╰───┴────────┴───────────╯
103+
```
104+
105+
You don't need a filter to get the table. When you want all of the data, pipe the command into [`collect`](/commands/docs/collect.md). It's an internal command, so the output gets decoded, and it passes its input through unchanged:
106+
107+
```nu
108+
git status | collect
109+
# => ╭───┬────────┬────────────╮
110+
# => │ # │ status │ file │
111+
# => ├───┼────────┼────────────┤
112+
# => │ 0 │ A │ INSTALL.md │
113+
# => │ 1 │ M │ README.md │
114+
# => │ 2 │ ?? │ notes.txt │
115+
# => ╰───┴────────┴────────────╯
116+
```
117+
118+
Saving the output in a variable decodes it too:
119+
120+
```nu
121+
let changes = git status
122+
$changes | get file
123+
# => ╭───┬────────────╮
124+
# => │ 0 │ INSTALL.md │
125+
# => │ 1 │ README.md │
126+
# => │ 2 │ notes.txt │
127+
# => ╰───┴────────────╯
128+
```
129+
130+
When a command can print JSON, the block only needs [`from json`](/commands/docs/from_json.md):
131+
132+
```nu
133+
@structured [--format=json] { from json }
134+
extern "pip list" []
135+
136+
pip list | first 3
137+
# => ╭───┬───────────┬─────────╮
138+
# => │ # │ name │ version │
139+
# => ├───┼───────────┼─────────┤
140+
# => │ 0 │ contourpy │ 1.4.0 │
141+
# => │ 1 │ cycler │ 0.12.1 │
142+
# => │ 2 │ fonttools │ 4.66.1 │
143+
# => ╰───┴───────────┴─────────╯
144+
```
145+
146+
### When Nushell Decodes the Output
147+
148+
Nushell decides by looking at where the output of the extern goes. It adds the `@structured` arguments and runs the block when Nushell itself uses the output:
149+
150+
- Piped into an internal command: `git status | where status =~ M`, or `git status | collect` to get all of the data
151+
- Saved in a variable: `let changes = git status`
152+
- Used in a subexpression: `(git status).file`
153+
154+
In every other case, the command runs exactly as you typed it. That includes output that goes:
155+
156+
- To the terminal: `git status`
157+
- Through `print`: `git status | print`
158+
- To a file: `git status o> status.txt` or `git status | save status.txt`
159+
- Into `complete`, `tee`, or `ignore`: `git status | complete`
160+
- Into another external command or `run-external`: `git status | ^less`
161+
- Into the same pipe as stderr: `git status o+e>| str trim`
162+
163+
The same rules apply when the extern is the last command of a custom command or a `do` block. After `def gs [] { git status }`, running `gs` prints git's text, and `gs | length` returns `3`, the number of changed files.
164+
165+
::: tip
166+
Every internal command counts, including commands that work on text. `git status | lines` sends the decoded table to `lines`, not git's text. To get the text, call the command with the caret sigil, which always runs it as written:
167+
168+
```nu
169+
^git status | lines | first 1
170+
# => ╭───┬────────────────╮
171+
# => │ 0 │ On branch main │
172+
# => ╰───┴────────────────╯
173+
```
174+
175+
:::
176+
177+
### Adding Your Own Arguments
178+
179+
Nushell puts the `@structured` arguments right after the command name, before any arguments you type. This extern gives `git log` an output format that is easy to split into columns:
180+
181+
```nu
182+
@structured ["--format=%h|%an|%s"] { lines | split column "|" hash author subject }
183+
extern "git log" []
184+
185+
git log | where author == "Ana Silva"
186+
# => ╭───┬─────────┬───────────┬──────────────────╮
187+
# => │ # │ hash │ author │ subject │
188+
# => ├───┼─────────┼───────────┼──────────────────┤
189+
# => │ 0 │ 73e9a8f │ Ana Silva │ Print a greeting │
190+
# => │ 1 │ 83c0a31 │ Ana Silva │ Initial commit │
191+
# => ╰───┴─────────┴───────────┴──────────────────╯
192+
```
193+
194+
`git log -n 2 | where author == "Ana Silva"` runs `git log --format=%h|%an|%s -n 2`.
195+
196+
Because your arguments come last, a flag that changes the output format replaces the one from `@structured`. The block then gets output it wasn't written for. Here `--oneline` wins, so each whole line lands in the `hash` column:
197+
198+
```nu
199+
git log --oneline -n 1 | first
200+
# => ╭──────┬──────────────────────────╮
201+
# => │ hash │ 73e9a8f Print a greeting │
202+
# => ╰──────┴──────────────────────────╯
203+
```
204+
205+
To use a different format, call the command with the caret sigil, such as `^git log --oneline`.
206+
207+
### More About `@structured`
208+
209+
- The block can read `$env`, constants, and `let` variables defined before the `extern`, just like the body of a `def`. It can't use `mut` variables.
210+
- `@structured` only works on `extern`. Putting it on a `def` is a parse error.
211+
- If an `extern` has more than one `@structured`, Nushell uses the last one.
212+
- When the command fails, its error message is printed. What happens next depends on the block. A block that reads all of the output, such as `from json`, stops the pipeline with a non-zero exit code error that points at the call. A block that streams the output, such as `lines`, passes on whatever the command printed and doesn't raise an error.
213+
214+
`@structured` also works with `export extern`, so a module can share these declarations:
215+
216+
```nu
217+
module git-data {
218+
@structured [--porcelain] { lines | parse --regex '^(?<status>..) (?<file>.+)$' }
219+
export extern "git status" []
220+
}
221+
use git-data "git status"
222+
```
223+
61224
## Limitations
62225

63226
There are a few limitations to the current `extern` syntax. In Nushell, flags and positional arguments are very flexible—flags can precede positional arguments, flags can be mixed into positional arguments, and flags can follow positional arguments. Many external commands are not this flexible. There is not yet a way to require a particular ordering of flags and positional arguments to the style required by the external.

‎book/running_externals.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ let program = "git"
7171
^$program status
7272
```
7373

74-
See [Strings as external commands](working_with_strings.md#strings-as-external-commands) for the supported string forms. An [`extern` declaration](externs.md) can add type checking and completions for a known external command; calling it follows the same argument rules described here.
74+
See [Strings as external commands](working_with_strings.md#strings-as-external-commands) for the supported string forms. An [`extern` declaration](externs.md) can add type checking and completions for a known external command, and its [`@structured`](externs.md#structured-output) attribute can turn the command's output into data. Calling it follows the same argument rules described here.
7575

7676
## Additional Windows Notes
7777

‎commands/docs/attr.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: attr
33
categories: |
44
core
5-
version: 0.116.0
5+
version: 0.116.2
66
core: |
77
Various attributes for custom commands.
88
usage: |
@@ -43,4 +43,5 @@ You must use one of the following subcommands. Using this command as-is will onl
4343
| [`attr deprecated`](/commands/docs/attr_deprecated.md) | Attribute for marking a command or flag as deprecated. | built-in |
4444
| [`attr example`](/commands/docs/attr_example.md) | Attribute for adding examples to custom commands. | built-in |
4545
| [`attr interactive`](/commands/docs/attr_interactive.md) | Attribute marking a completer as interactive. | built-in |
46-
| [`attr search-terms`](/commands/docs/attr_search-terms.md) | Attribute for adding search terms to custom commands. | built-in |
46+
| [`attr search-terms`](/commands/docs/attr_search-terms.md) | Attribute for adding search terms to custom commands. | built-in |
47+
| [`attr structured`](/commands/docs/attr_structured.md) | Attribute for getting structured output from an external command. | built-in |

‎commands/docs/attr_structured.md‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
title: attr structured
3+
categories: |
4+
core
5+
version: 0.116.2
6+
core: |
7+
Attribute for getting structured output from an external command.
8+
usage: |
9+
Attribute for getting structured output from an external command.
10+
editLink: false
11+
contributors: false
12+
---
13+
<!-- This file is automatically generated. Please edit the command in https://github.com/nushell/nushell instead. -->
14+
15+
# `attr structured` for [core](/commands/categories/core.md)
16+
17+
<div class='command-title'>Attribute for getting structured output from an external command.</div>
18+
19+
## Command Type
20+
21+
`built-in`
22+
23+
## Signature
24+
25+
```> attr structured {flags} (args) (decode)```
26+
27+
## Parameters
28+
29+
- `args`: Arguments that make the external command print structured output.
30+
- `decode`: Turns that output, given as `$in`, into data.
31+
32+
33+
## Input/output types:
34+
35+
| input | output |
36+
| ------- | ------------------------------------------- |
37+
| nothing | record&lt;args: list&lt;string&gt;, decode: closure&gt; |
38+
## Examples
39+
40+
Get the changed files from `git status --porcelain` as a table.
41+
```nu
42+
> @structured [--porcelain] { lines | parse --regex '^(?<status>..) (?<file>.+)$' }
43+
extern "git status" []
44+
git status | where status =~ M | get file
45+
46+
```
47+
48+
Get the installed Python packages from `pip list --format=json` as a table.
49+
```nu
50+
> @structured [--format=json] { from json }
51+
extern "pip list" []
52+
pip list | where name starts-with num
53+
54+
```
55+
56+
Get the outdated Homebrew formulae from `brew outdated --json=v2` as a table.
57+
```nu
58+
> @structured [--json=v2] { from json | get formulae }
59+
extern "brew outdated" []
60+
brew outdated | where pinned == false | get name
61+
62+
```
63+
64+
Get pods from kubectl as a table when the output is piped into `where`.
65+
```nu
66+
> @structured [-o json] { from json | get items }
67+
extern "kubectl get" [resource: string, --namespace(-n): string]: nothing -> table
68+
kubectl get pods | where status.phase != Running
69+
70+
```
71+
72+
## Notes
73+
When the output of the extern is used by nushell (piped into an internal command or collected into a variable), the arguments are added right after the command's name and the output is decoded with the block. When the output goes to the terminal, `print`, a file, `ignore`, `save`, `tee`, `complete` or another external command (`run-external` included), the command runs as written. The same holds when the extern is the last command of a `do` block or a custom command. Calling the command with a caret, like `^git status`, always runs it as written.
74+
75+
The arguments come before the user's own, so a format flag the user passes can override them and break the decode, as in `kubectl get pods -o wide`. Use a caret call for that. `tee` passes the command's own output on to the commands after it. When an extern has more than one `@structured`, the last one is used.
76+
77+
Like a `def` body, the block can read `$in`, `$env`, constants and the `let` variables declared before the extern, but not `mut` variables.

0 commit comments

Comments
 (0)