nu_plugin_bexpand/
lib.rs

1use std::borrow::Cow;
2
3use bexpand::Expression;
4use nu_plugin::{EngineInterface, EvaluatedCall, Plugin, PluginCommand, SimplePluginCommand};
5use nu_protocol::{Category, ErrorLabel, Example, LabeledError, Signature, Span, Type, Value};
6
7fn bexpand(call: &EvaluatedCall, input: &Value) -> Result<Value, LabeledError> {
8    let span = input.span();
9    let output: Result<Vec<Cow<str>>, _> = match input {
10        Value::String { val, .. } => {
11            let val = val.as_str();
12            let expression: Expression = match val.try_into() {
13                Ok(e) => e,
14                Err(s) => {
15                    return Err(LabeledError {
16                        labels: Box::new(vec![ErrorLabel {
17                            text: "Brace expression failed to parse".into(),
18                            span: span,
19                        }]),
20                        msg: s,
21                        code: None,
22                        url: None,
23                        help: None,
24                        inner: Box::new(vec![]),
25                    });
26                }
27            };
28
29            expression
30                .into_iter()
31                .map(|value| value.map_err(move |e| (e, span)))
32                .collect()
33        }
34        Value::List { vals, .. } => {
35            let exprs: Result<Vec<_>, _> = vals
36                .into_iter()
37                .map(|val| match val {
38                    Value::String {
39                        val, internal_span, ..
40                    } => {
41                        let val = val.as_str();
42                        let expression: Expression = match val.try_into() {
43                            Ok(e) => e,
44                            Err(s) => {
45                                return Err(LabeledError {
46                                    labels: Box::new(vec![ErrorLabel {
47                                        text: "Brace expression failed to parse".into(),
48                                        span: *internal_span,
49                                    }]),
50                                    msg: s,
51                                    code: None,
52                                    url: None,
53                                    help: None,
54                                    inner: Box::new(vec![]),
55                                });
56                            }
57                        };
58
59                        Ok(expression
60                            .into_iter()
61                            .map(move |value| value.map_err(move |e| (e, *internal_span))))
62                    }
63                    v => {
64                        return Err(LabeledError {
65                            labels: Box::new(vec![ErrorLabel {
66                                text: "Input must be string".into(),
67                                span: v.span(),
68                            }]),
69                            msg: format!("Input type was {}", input.get_type()),
70                            code: None,
71                            url: None,
72                            help: None,
73                            inner: Box::new(vec![]),
74                        });
75                    }
76                })
77                .collect();
78            exprs?.into_iter().flatten().collect()
79        }
80
81        v => {
82            return Err(LabeledError {
83                labels: Box::new(vec![ErrorLabel {
84                    text: "Input must be string".into(),
85                    span: v.span(),
86                }]),
87                msg: format!("Input type was {}", input.get_type()),
88                code: None,
89                url: None,
90                help: None,
91                inner: Box::new(vec![]),
92            });
93        }
94    };
95
96    let output = match output {
97        Ok(o) => o
98            .into_iter()
99            .map(|s| Value::string(s.into_owned(), call.head))
100            .collect(),
101        Err((e, span)) => {
102            return Err(LabeledError {
103                labels: Box::new(vec![ErrorLabel {
104                    text: "Expression failed to generate".into(),
105                    span,
106                }]),
107                msg: e.to_string(),
108                code: None,
109                url: None,
110                help: None,
111                inner: Box::new(vec![]),
112            });
113        }
114    };
115
116    Ok(Value::list(output, call.head))
117}
118
119pub struct BexpandPlugin;
120
121impl Plugin for BexpandPlugin {
122    fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
123        vec![Box::new(Bexpand)]
124    }
125
126    fn version(&self) -> String {
127        env!("CARGO_PKG_VERSION").into()
128    }
129}
130
131pub struct Bexpand;
132
133impl SimplePluginCommand for Bexpand {
134    type Plugin = BexpandPlugin;
135
136    fn signature(&self) -> Signature {
137        Signature::new("str bexpand")
138            .input_output_types(vec![
139                (Type::String, Type::List(Box::new(Type::String))),
140                (
141                    Type::List(Box::new(Type::String)),
142                    Type::List(Box::new(Type::String)),
143                ),
144            ])
145            .description("Bash-style brace expansion")
146            .category(Category::Strings)
147    }
148
149    fn run(
150        &self,
151        _plugin: &Self::Plugin,
152        _engine: &EngineInterface,
153        call: &EvaluatedCall,
154        input: &Value,
155    ) -> Result<Value, LabeledError> {
156        // You can use the name to identify what plugin signature was called
157        bexpand(call, input)
158    }
159
160    fn name(&self) -> &str {
161        "str bexpand"
162    }
163
164    fn description(&self) -> &str {
165        "Does bash-style brace expansion"
166    }
167    fn examples(&self) -> Vec<Example<'_>> {
168        vec![Example {
169            example: "'~/.config/nushell/{env,config,plugin}.nu' | str bexpand".into(),
170            description: "Get a list of standard nushell config items".into(),
171            result: Some(Value::list(
172                vec![
173                    Value::string("~/.config/nushell/env.nu", Span::new(0, 0)),
174                    Value::string("~/.config/nushell/config.nu", Span::new(0, 0)),
175                    Value::string("~/.config/nushell/plugin.nu", Span::new(0, 0)),
176                ],
177                Span::new(0, 0),
178            )),
179        }]
180    }
181}