Skip to main content

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 = call.assert_ast_call()?.positional_iter().nth(1).ok_or(
90            ShellError::MissingParameter {
91                param_name: "example".into(),
92                span: call.head,
93            },
94        )?;
95
96        attr_example_impl(
97            example_expr,
98            example_string,
99            working_set,
100            call,
101            description,
102            result,
103        )
104    }
105
106    fn is_const(&self) -> bool {
107        true
108    }
109
110    fn requires_ast_for_arguments(&self) -> bool {
111        true
112    }
113
114    fn examples(&self) -> Vec<Example<'_>> {
115        vec![Example {
116            description: "Add examples to custom command.",
117            example: r###"# Double numbers
118    @example "double an int" { 2 | double } --result 4
119    @example "double a float" { 0.25 | double } --result 0.5
120    def double []: [number -> number] { $in * 2 }"###,
121            result: None,
122        }]
123    }
124}
125
126fn attr_example_impl(
127    example_expr: &nu_protocol::ast::Expression,
128    example_string: Result<String, ShellError>,
129    working_set: &StateWorkingSet<'_>,
130    call: &Call<'_>,
131    description: Spanned<String>,
132    result: Option<Value>,
133) -> Result<PipelineData, ShellError> {
134    let example_content = match example_expr.as_block() {
135        Some(block_id) => {
136            let block = working_set.get_block(block_id);
137            let contents =
138                working_set.get_span_contents(block.span.expect("a block must have a span"));
139            let contents = contents
140                .strip_prefix(b"{")
141                .and_then(|x| x.strip_suffix(b"}"))
142                .unwrap_or(contents)
143                .trim_ascii();
144            String::from_utf8_lossy(contents).into_owned()
145        }
146        None => example_string?,
147    };
148
149    let mut rec = record! {
150        "description" => Value::string(description.item, description.span),
151        "example" => Value::string(example_content, example_expr.span),
152    };
153    if let Some(result) = result {
154        rec.push("result", result);
155    }
156
157    Ok(Value::record(rec, call.head).into_pipeline_data())
158}