nu_cmd_lang/core_commands/attr/
example.rs

1use nu_engine::command_prelude::*;
2
3#[derive(Clone)]
4pub struct AttrExample;
5
6impl Command for AttrExample {
7    fn name(&self) -> &str {
8        "attr example"
9    }
10
11    // TODO: When const closure are available, switch to using them for the `example` argument
12    // rather than a block. That should remove the need for `requires_ast_for_arguments` to be true
13    fn signature(&self) -> Signature {
14        Signature::build("attr example")
15            .input_output_types(vec![(
16                Type::Nothing,
17                Type::Record(
18                    [
19                        ("description".into(), Type::String),
20                        ("example".into(), Type::String),
21                    ]
22                    .into(),
23                ),
24            )])
25            .allow_variants_without_examples(true)
26            .required(
27                "description",
28                SyntaxShape::String,
29                "Description of the example.",
30            )
31            .required(
32                "example",
33                SyntaxShape::OneOf(vec![SyntaxShape::Block, SyntaxShape::String]),
34                "Example code snippet.",
35            )
36            .named(
37                "result",
38                SyntaxShape::Any,
39                "Expected output of example.",
40                None,
41            )
42            .category(Category::Core)
43    }
44
45    fn description(&self) -> &str {
46        "Attribute for adding examples to custom commands."
47    }
48
49    fn run(
50        &self,
51        engine_state: &EngineState,
52        stack: &mut Stack,
53        call: &Call,
54        _input: PipelineData,
55    ) -> Result<PipelineData, ShellError> {
56        let description: Spanned<String> = call.req(engine_state, stack, 0)?;
57        let result: Option<Value> = call.get_flag(engine_state, stack, "result")?;
58
59        let example_string: Result<String, _> = call.req(engine_state, stack, 1);
60        let example_expr = call
61            .positional_nth(stack, 1)
62            .ok_or(ShellError::MissingParameter {
63                param_name: "example".into(),
64                span: call.head,
65            })?;
66
67        let working_set = StateWorkingSet::new(engine_state);
68
69        attr_example_impl(
70            example_expr,
71            example_string,
72            &working_set,
73            call,
74            description,
75            result,
76        )
77    }
78
79    fn run_const(
80        &self,
81        working_set: &StateWorkingSet,
82        call: &Call,
83        _input: PipelineData,
84    ) -> Result<PipelineData, ShellError> {
85        let description: Spanned<String> = call.req_const(working_set, 0)?;
86        let result: Option<Value> = call.get_flag_const(working_set, "result")?;
87
88        let example_string: Result<String, _> = call.req_const(working_set, 1);
89        let example_expr =
90            call.assert_ast_call()?
91                .positional_nth(1)
92                .ok_or(ShellError::MissingParameter {
93                    param_name: "example".into(),
94                    span: call.head,
95                })?;
96
97        attr_example_impl(
98            example_expr,
99            example_string,
100            working_set,
101            call,
102            description,
103            result,
104        )
105    }
106
107    fn is_const(&self) -> bool {
108        true
109    }
110
111    fn requires_ast_for_arguments(&self) -> bool {
112        true
113    }
114
115    fn examples(&self) -> Vec<Example> {
116        vec![Example {
117            description: "Add examples to custom command",
118            example: r###"# Double numbers
119    @example "double an int" { 2 | double } --result 4
120    @example "double a float" { 0.25 | double } --result 0.5
121    def double []: [number -> number] { $in * 2 }"###,
122            result: None,
123        }]
124    }
125}
126
127fn attr_example_impl(
128    example_expr: &nu_protocol::ast::Expression,
129    example_string: Result<String, ShellError>,
130    working_set: &StateWorkingSet<'_>,
131    call: &Call<'_>,
132    description: Spanned<String>,
133    result: Option<Value>,
134) -> Result<PipelineData, ShellError> {
135    let example_content = match example_expr.as_block() {
136        Some(block_id) => {
137            let block = working_set.get_block(block_id);
138            let contents =
139                working_set.get_span_contents(block.span.expect("a block must have a span"));
140            let contents = contents
141                .strip_prefix(b"{")
142                .and_then(|x| x.strip_suffix(b"}"))
143                .unwrap_or(contents)
144                .trim_ascii();
145            String::from_utf8_lossy(contents).into_owned()
146        }
147        None => example_string?,
148    };
149
150    let mut rec = record! {
151        "description" => Value::string(description.item, description.span),
152        "example" => Value::string(example_content, example_expr.span),
153    };
154    if let Some(result) = result {
155        rec.push("result", result);
156    }
157
158    Ok(Value::record(rec, call.head).into_pipeline_data())
159}