Custom command attributes (#14906)

# Description
Add custom command attributes.

- Attributes are placed before a command definition and start with a `@`
character.
- Attribute invocations consist of const command call. The command's
name must start with "attr ", but this prefix is not used in the
invocation.
- A command named `attr example` is invoked as an attribute as
`@example`
-   Several built-in attribute commands are provided as part of this PR
    -   `attr example`: Attaches an example to the commands help text
        ```nushell
        # Double numbers
        @example "double an int"  { 5 | double }   --result 10
        @example "double a float" { 0.5 | double } --result 1.0
        def double []: [number -> number] {
            $in * 2
        }
        ```
    -   `attr search-terms`: Adds search terms to a command
    -   ~`attr env`: Equivalent to using `def --env`~
- ~`attr wrapped`: Equivalent to using `def --wrapped`~ shelved for
later discussion
    -   several testing related attributes in `std/testing`
- If an attribute has no internal/special purpose, it's stored as
command metadata that can be obtained with `scope commands`.
- This allows having attributes like `@test` which can be used by test
runners.
-   Used the `@example` attribute for `std` examples.
-   Updated the std tests and test runner to use `@test` attributes
-   Added completions for attributes

# User-Facing Changes
Users can add examples to their own command definitions, and add other
arbitrary attributes.

# Tests + Formatting

- 🟢 toolkit fmt
- 🟢 toolkit clippy
- 🟢 toolkit test
- 🟢 toolkit test stdlib

# After Submitting
- Add documentation about the attribute syntax and built-in attributes
- `help attributes`

---------

Co-authored-by: 132ikl <132@ikl.sh>
This commit is contained in:
Bahex
2025-02-11 15:34:51 +03:00
committed by GitHub
parent a58d9b0b3a
commit 442df9e39c
57 changed files with 2028 additions and 987 deletions

View File

@ -72,6 +72,14 @@ pub(crate) fn compile_expression(
};
match &expr.expr {
Expr::AttributeBlock(ab) => compile_expression(
working_set,
builder,
&ab.item,
redirect_modes,
in_reg,
out_reg,
),
Expr::Bool(b) => lit(builder, Literal::Bool(*b)),
Expr::Int(i) => lit(builder, Literal::Int(*i)),
Expr::Float(f) => lit(builder, Literal::Float(*f)),

View File

@ -106,12 +106,27 @@ impl<'e, 's> ScopeData<'e, 's> {
})
.collect();
let attributes = decl
.attributes()
.into_iter()
.map(|(name, value)| {
Value::record(
record! {
"name" => Value::string(name, span),
"value" => value,
},
span,
)
})
.collect();
let record = record! {
"name" => Value::string(String::from_utf8_lossy(command_name), span),
"category" => Value::string(signature.category.to_string(), span),
"signatures" => self.collect_signatures(&signature, span),
"description" => Value::string(decl.description(), span),
"examples" => Value::list(examples, span),
"attributes" => Value::list(attributes, span),
"type" => Value::string(decl.command_type().to_string(), span),
"is_sub" => Value::bool(decl.is_sub(), span),
"is_const" => Value::bool(decl.is_const(), span),