nu_protocol/
signature.rs

1use crate::{
2    BlockId, DeclId, DeprecationEntry, Example, FromValue, IntoValue, PipelineData, ShellError,
3    Span, SyntaxShape, Type, Value, VarId,
4    engine::{Call, Command, CommandType, EngineState, Stack},
5};
6use nu_derive_value::FromValue as DeriveFromValue;
7use nu_utils::NuCow;
8use serde::{Deserialize, Serialize};
9use std::fmt::Write;
10
11// Make nu_protocol available in this namespace, consumers of this crate will
12// have this without such an export.
13// The `FromValue` derive macro fully qualifies paths to "nu_protocol".
14use crate as nu_protocol;
15
16pub enum Parameter {
17    Required(PositionalArg),
18    Optional(PositionalArg),
19    Rest(PositionalArg),
20    Flag(Flag),
21}
22
23impl From<Flag> for Parameter {
24    fn from(value: Flag) -> Self {
25        Self::Flag(value)
26    }
27}
28
29/// The signature definition of a named flag that either accepts a value or acts as a toggle flag
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct Flag {
32    pub long: String,
33    pub short: Option<char>,
34    pub arg: Option<SyntaxShape>,
35    pub required: bool,
36    pub desc: String,
37    pub completion: Option<Completion>,
38
39    // For custom commands
40    pub var_id: Option<VarId>,
41    pub default_value: Option<Value>,
42}
43
44impl Flag {
45    #[inline]
46    pub fn new(long: impl Into<String>) -> Self {
47        Flag {
48            long: long.into(),
49            short: None,
50            arg: None,
51            required: false,
52            desc: String::new(),
53            completion: None,
54            var_id: None,
55            default_value: None,
56        }
57    }
58
59    #[inline]
60    pub fn short(self, short: char) -> Self {
61        Self {
62            short: Some(short),
63            ..self
64        }
65    }
66
67    #[inline]
68    pub fn arg(self, arg: SyntaxShape) -> Self {
69        Self {
70            arg: Some(arg),
71            ..self
72        }
73    }
74
75    #[inline]
76    pub fn required(self) -> Self {
77        Self {
78            required: true,
79            ..self
80        }
81    }
82
83    #[inline]
84    pub fn desc(self, desc: impl Into<String>) -> Self {
85        Self {
86            desc: desc.into(),
87            ..self
88        }
89    }
90
91    #[inline]
92    pub fn completion(self, completion: Completion) -> Self {
93        Self {
94            completion: Some(completion),
95            ..self
96        }
97    }
98}
99
100/// The signature definition for a positional argument
101#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
102pub struct PositionalArg {
103    pub name: String,
104    pub desc: String,
105    pub shape: SyntaxShape,
106    pub completion: Option<Completion>,
107
108    // For custom commands
109    pub var_id: Option<VarId>,
110    pub default_value: Option<Value>,
111}
112
113impl PositionalArg {
114    #[inline]
115    pub fn new(name: impl Into<String>, shape: SyntaxShape) -> Self {
116        Self {
117            name: name.into(),
118            desc: String::new(),
119            shape,
120            completion: None,
121            var_id: None,
122            default_value: None,
123        }
124    }
125
126    #[inline]
127    pub fn desc(self, desc: impl Into<String>) -> Self {
128        Self {
129            desc: desc.into(),
130            ..self
131        }
132    }
133
134    #[inline]
135    pub fn completion(self, completion: Completion) -> Self {
136        Self {
137            completion: Some(completion),
138            ..self
139        }
140    }
141
142    #[inline]
143    pub fn required(self) -> Parameter {
144        Parameter::Required(self)
145    }
146
147    #[inline]
148    pub fn optional(self) -> Parameter {
149        Parameter::Optional(self)
150    }
151
152    #[inline]
153    pub fn rest(self) -> Parameter {
154        Parameter::Rest(self)
155    }
156}
157
158#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
159pub enum CommandWideCompleter {
160    External,
161    Command(DeclId),
162}
163
164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
165pub enum Completion {
166    Command(DeclId),
167    List(NuCow<&'static [&'static str], Vec<String>>),
168}
169
170impl Completion {
171    pub const fn new_list(list: &'static [&'static str]) -> Self {
172        Self::List(NuCow::Borrowed(list))
173    }
174
175    pub fn to_value(&self, engine_state: &EngineState, span: Span) -> Value {
176        match self {
177            Completion::Command(id) => engine_state
178                .get_decl(*id)
179                .name()
180                .to_owned()
181                .into_value(span),
182            Completion::List(list) => match list {
183                NuCow::Borrowed(list) => list
184                    .iter()
185                    .map(|&e| e.into_value(span))
186                    .collect::<Vec<Value>>()
187                    .into_value(span),
188                NuCow::Owned(list) => list
189                    .iter()
190                    .cloned()
191                    .map(|e| e.into_value(span))
192                    .collect::<Vec<Value>>()
193                    .into_value(span),
194            },
195        }
196    }
197}
198
199/// Command categories
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
201pub enum Category {
202    Bits,
203    Bytes,
204    Chart,
205    Conversions,
206    Core,
207    Custom(String),
208    Database,
209    Date,
210    Debug,
211    Default,
212    Deprecated,
213    Removed,
214    Env,
215    Experimental,
216    FileSystem,
217    Filters,
218    Formats,
219    Generators,
220    Hash,
221    History,
222    Math,
223    Misc,
224    Network,
225    Path,
226    Platform,
227    Plugin,
228    Random,
229    Shells,
230    Strings,
231    System,
232    Viewers,
233}
234
235impl std::fmt::Display for Category {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        let msg = match self {
238            Category::Bits => "bits",
239            Category::Bytes => "bytes",
240            Category::Chart => "chart",
241            Category::Conversions => "conversions",
242            Category::Core => "core",
243            Category::Custom(name) => name,
244            Category::Database => "database",
245            Category::Date => "date",
246            Category::Debug => "debug",
247            Category::Default => "default",
248            Category::Deprecated => "deprecated",
249            Category::Removed => "removed",
250            Category::Env => "env",
251            Category::Experimental => "experimental",
252            Category::FileSystem => "filesystem",
253            Category::Filters => "filters",
254            Category::Formats => "formats",
255            Category::Generators => "generators",
256            Category::Hash => "hash",
257            Category::History => "history",
258            Category::Math => "math",
259            Category::Misc => "misc",
260            Category::Network => "network",
261            Category::Path => "path",
262            Category::Platform => "platform",
263            Category::Plugin => "plugin",
264            Category::Random => "random",
265            Category::Shells => "shells",
266            Category::Strings => "strings",
267            Category::System => "system",
268            Category::Viewers => "viewers",
269        };
270
271        write!(f, "{msg}")
272    }
273}
274
275pub fn category_from_string(category: &str) -> Category {
276    match category {
277        "bits" => Category::Bits,
278        "bytes" => Category::Bytes,
279        "chart" => Category::Chart,
280        "conversions" => Category::Conversions,
281        // Let's protect our own "core" commands by preventing scripts from having this category.
282        "core" => Category::Custom("custom_core".to_string()),
283        "database" => Category::Database,
284        "date" => Category::Date,
285        "debug" => Category::Debug,
286        "default" => Category::Default,
287        "deprecated" => Category::Deprecated,
288        "removed" => Category::Removed,
289        "env" => Category::Env,
290        "experimental" => Category::Experimental,
291        "filesystem" => Category::FileSystem,
292        "filter" => Category::Filters,
293        "formats" => Category::Formats,
294        "generators" => Category::Generators,
295        "hash" => Category::Hash,
296        "history" => Category::History,
297        "math" => Category::Math,
298        "misc" => Category::Misc,
299        "network" => Category::Network,
300        "path" => Category::Path,
301        "platform" => Category::Platform,
302        "plugin" => Category::Plugin,
303        "random" => Category::Random,
304        "shells" => Category::Shells,
305        "strings" => Category::Strings,
306        "system" => Category::System,
307        "viewers" => Category::Viewers,
308        _ => Category::Custom(category.to_string()),
309    }
310}
311
312/// Signature information of a [`Command`]
313#[derive(Clone, Debug, Serialize, Deserialize)]
314pub struct Signature {
315    pub name: String,
316    pub description: String,
317    pub extra_description: String,
318    pub search_terms: Vec<String>,
319    pub required_positional: Vec<PositionalArg>,
320    pub optional_positional: Vec<PositionalArg>,
321    pub rest_positional: Option<PositionalArg>,
322    pub named: Vec<Flag>,
323    pub input_output_types: Vec<(Type, Type)>,
324    pub allow_variants_without_examples: bool,
325    pub is_filter: bool,
326    pub creates_scope: bool,
327    pub allows_unknown_args: bool,
328    pub complete: Option<CommandWideCompleter>,
329    // Signature category used to classify commands stored in the list of declarations
330    pub category: Category,
331}
332
333impl PartialEq for Signature {
334    fn eq(&self, other: &Self) -> bool {
335        self.name == other.name
336            && self.description == other.description
337            && self.required_positional == other.required_positional
338            && self.optional_positional == other.optional_positional
339            && self.rest_positional == other.rest_positional
340            && self.is_filter == other.is_filter
341    }
342}
343
344impl Eq for Signature {}
345
346impl Signature {
347    /// Creates a new signature for a command with `name`
348    pub fn new(name: impl Into<String>) -> Signature {
349        Signature {
350            name: name.into(),
351            description: String::new(),
352            extra_description: String::new(),
353            search_terms: vec![],
354            required_positional: vec![],
355            optional_positional: vec![],
356            rest_positional: None,
357            input_output_types: vec![],
358            allow_variants_without_examples: false,
359            named: vec![],
360            is_filter: false,
361            creates_scope: false,
362            category: Category::Default,
363            allows_unknown_args: false,
364            complete: None,
365        }
366    }
367
368    /// Gets the input type from the signature
369    ///
370    /// If the input was unspecified or the signature has several different
371    /// input types, [`Type::Any`] is returned.  Otherwise, if the signature has
372    /// one or same input types, this type is returned.
373    // XXX: remove?
374    pub fn get_input_type(&self) -> Type {
375        match self.input_output_types.len() {
376            0 => Type::Any,
377            1 => self.input_output_types[0].0.clone(),
378            _ => {
379                let first = &self.input_output_types[0].0;
380                if self
381                    .input_output_types
382                    .iter()
383                    .all(|(input, _)| input == first)
384                {
385                    first.clone()
386                } else {
387                    Type::Any
388                }
389            }
390        }
391    }
392
393    /// Gets the output type from the signature
394    ///
395    /// If the output was unspecified or the signature has several different
396    /// input types, [`Type::Any`] is returned.  Otherwise, if the signature has
397    /// one or same output types, this type is returned.
398    // XXX: remove?
399    pub fn get_output_type(&self) -> Type {
400        match self.input_output_types.len() {
401            0 => Type::Any,
402            1 => self.input_output_types[0].1.clone(),
403            _ => {
404                let first = &self.input_output_types[0].1;
405                if self
406                    .input_output_types
407                    .iter()
408                    .all(|(_, output)| output == first)
409                {
410                    first.clone()
411                } else {
412                    Type::Any
413                }
414            }
415        }
416    }
417
418    /// Add a default help option to a signature
419    pub fn add_help(mut self) -> Signature {
420        // default help flag
421        let flag = Flag {
422            long: "help".into(),
423            short: Some('h'),
424            arg: None,
425            desc: "Display the help message for this command".into(),
426            required: false,
427            var_id: None,
428            default_value: None,
429            completion: None,
430        };
431        self.named.push(flag);
432        self
433    }
434
435    /// Build an internal signature with default help option
436    ///
437    /// This is equivalent to `Signature::new(name).add_help()`.
438    pub fn build(name: impl Into<String>) -> Signature {
439        Signature::new(name.into()).add_help()
440    }
441
442    /// Add a description to the signature
443    ///
444    /// This should be a single sentence as it is the part shown for example in the completion
445    /// menu.
446    pub fn description(mut self, msg: impl Into<String>) -> Signature {
447        self.description = msg.into();
448        self
449    }
450
451    /// Add an extra description to the signature.
452    ///
453    /// Here additional documentation can be added
454    pub fn extra_description(mut self, msg: impl Into<String>) -> Signature {
455        self.extra_description = msg.into();
456        self
457    }
458
459    /// Add search terms to the signature
460    pub fn search_terms(mut self, terms: Vec<String>) -> Signature {
461        self.search_terms = terms;
462        self
463    }
464
465    /// Update signature's fields from a Command trait implementation
466    pub fn update_from_command(mut self, command: &dyn Command) -> Signature {
467        self.search_terms = command
468            .search_terms()
469            .into_iter()
470            .map(|term| term.to_string())
471            .collect();
472        self.extra_description = command.extra_description().to_string();
473        self.description = command.description().to_string();
474        self
475    }
476
477    /// Allow unknown signature parameters
478    pub fn allows_unknown_args(mut self) -> Signature {
479        self.allows_unknown_args = true;
480        self
481    }
482
483    pub fn param(mut self, param: impl Into<Parameter>) -> Self {
484        let param: Parameter = param.into();
485        match param {
486            Parameter::Flag(flag) => {
487                if let Some(s) = flag.short {
488                    assert!(
489                        !self.get_shorts().contains(&s),
490                        "There may be duplicate short flags for '-{s}'"
491                    );
492                }
493
494                let name = flag.long.as_str();
495                assert!(
496                    !self.get_names().contains(&name),
497                    "There may be duplicate name flags for '--{name}'"
498                );
499
500                self.named.push(flag);
501            }
502            Parameter::Required(positional_arg) => {
503                self.required_positional.push(positional_arg);
504            }
505            Parameter::Optional(positional_arg) => {
506                self.optional_positional.push(positional_arg);
507            }
508            Parameter::Rest(positional_arg) => {
509                assert!(
510                    self.rest_positional.is_none(),
511                    "Tried to set rest arguments more than once"
512                );
513                self.rest_positional = Some(positional_arg);
514            }
515        }
516        self
517    }
518
519    /// Add a required positional argument to the signature
520    pub fn required(
521        mut self,
522        name: impl Into<String>,
523        shape: impl Into<SyntaxShape>,
524        desc: impl Into<String>,
525    ) -> Signature {
526        self.required_positional.push(PositionalArg {
527            name: name.into(),
528            desc: desc.into(),
529            shape: shape.into(),
530            var_id: None,
531            default_value: None,
532            completion: None,
533        });
534
535        self
536    }
537
538    /// Add an optional positional argument to the signature
539    pub fn optional(
540        mut self,
541        name: impl Into<String>,
542        shape: impl Into<SyntaxShape>,
543        desc: impl Into<String>,
544    ) -> Signature {
545        self.optional_positional.push(PositionalArg {
546            name: name.into(),
547            desc: desc.into(),
548            shape: shape.into(),
549            var_id: None,
550            default_value: None,
551            completion: None,
552        });
553
554        self
555    }
556
557    /// Add a rest positional parameter
558    ///
559    /// Rest positionals (also called [rest parameters][rp]) are treated as
560    /// optional: passing 0 arguments is a valid call.  If the command requires
561    /// at least one argument, it must be checked by the implementation.
562    ///
563    /// [rp]: https://www.nushell.sh/book/custom_commands.html#rest-parameters
564    pub fn rest(
565        mut self,
566        name: &str,
567        shape: impl Into<SyntaxShape>,
568        desc: impl Into<String>,
569    ) -> Signature {
570        self.rest_positional = Some(PositionalArg {
571            name: name.into(),
572            desc: desc.into(),
573            shape: shape.into(),
574            var_id: None,
575            default_value: None,
576            completion: None,
577        });
578
579        self
580    }
581
582    /// Is this command capable of operating on its input via cell paths?
583    pub fn operates_on_cell_paths(&self) -> bool {
584        self.required_positional
585            .iter()
586            .chain(self.rest_positional.iter())
587            .any(|pos| {
588                matches!(
589                    pos,
590                    PositionalArg {
591                        shape: SyntaxShape::CellPath,
592                        ..
593                    }
594                )
595            })
596    }
597
598    /// Add an optional named flag argument to the signature
599    pub fn named(
600        mut self,
601        name: impl Into<String>,
602        shape: impl Into<SyntaxShape>,
603        desc: impl Into<String>,
604        short: Option<char>,
605    ) -> Signature {
606        let (name, s) = self.check_names(name, short);
607
608        self.named.push(Flag {
609            long: name,
610            short: s,
611            arg: Some(shape.into()),
612            required: false,
613            desc: desc.into(),
614            var_id: None,
615            default_value: None,
616            completion: None,
617        });
618
619        self
620    }
621
622    /// Add a required named flag argument to the signature
623    pub fn required_named(
624        mut self,
625        name: impl Into<String>,
626        shape: impl Into<SyntaxShape>,
627        desc: impl Into<String>,
628        short: Option<char>,
629    ) -> Signature {
630        let (name, s) = self.check_names(name, short);
631
632        self.named.push(Flag {
633            long: name,
634            short: s,
635            arg: Some(shape.into()),
636            required: true,
637            desc: desc.into(),
638            var_id: None,
639            default_value: None,
640            completion: None,
641        });
642
643        self
644    }
645
646    /// Add a switch to the signature
647    pub fn switch(
648        mut self,
649        name: impl Into<String>,
650        desc: impl Into<String>,
651        short: Option<char>,
652    ) -> Signature {
653        let (name, s) = self.check_names(name, short);
654
655        self.named.push(Flag {
656            long: name,
657            short: s,
658            arg: None,
659            required: false,
660            desc: desc.into(),
661            var_id: None,
662            default_value: None,
663            completion: None,
664        });
665
666        self
667    }
668
669    /// Changes the input type of the command signature
670    pub fn input_output_type(mut self, input_type: Type, output_type: Type) -> Signature {
671        self.input_output_types.push((input_type, output_type));
672        self
673    }
674
675    /// Set the input-output type signature variants of the command
676    pub fn input_output_types(mut self, input_output_types: Vec<(Type, Type)>) -> Signature {
677        self.input_output_types = input_output_types;
678        self
679    }
680
681    /// Changes the signature category
682    pub fn category(mut self, category: Category) -> Signature {
683        self.category = category;
684
685        self
686    }
687
688    /// Sets that signature will create a scope as it parses
689    pub fn creates_scope(mut self) -> Signature {
690        self.creates_scope = true;
691        self
692    }
693
694    // Is it allowed for the type signature to feature a variant that has no corresponding example?
695    pub fn allow_variants_without_examples(mut self, allow: bool) -> Signature {
696        self.allow_variants_without_examples = allow;
697        self
698    }
699
700    /// A string rendering of the command signature
701    ///
702    /// If the command has flags, all of them will be shown together as
703    /// `{flags}`.
704    pub fn call_signature(&self) -> String {
705        let mut one_liner = String::new();
706        one_liner.push_str(&self.name);
707        one_liner.push(' ');
708
709        // Note: the call signature needs flags first because on the nu commandline,
710        // flags will precede the script file name. Flags for internal commands can come
711        // either before or after (or around) positional parameters, so there isn't a strong
712        // preference, so we default to the more constrained example.
713        if self.named.len() > 1 {
714            one_liner.push_str("{flags} ");
715        }
716
717        for positional in &self.required_positional {
718            one_liner.push_str(&get_positional_short_name(positional, true));
719        }
720        for positional in &self.optional_positional {
721            one_liner.push_str(&get_positional_short_name(positional, false));
722        }
723
724        if let Some(rest) = &self.rest_positional {
725            let _ = write!(one_liner, "...{}", get_positional_short_name(rest, false));
726        }
727
728        // if !self.subcommands.is_empty() {
729        //     one_liner.push_str("<subcommand> ");
730        // }
731
732        one_liner
733    }
734
735    /// Get list of the short-hand flags
736    pub fn get_shorts(&self) -> Vec<char> {
737        self.named.iter().filter_map(|f| f.short).collect()
738    }
739
740    /// Get list of the long-hand flags
741    pub fn get_names(&self) -> Vec<&str> {
742        self.named.iter().map(|f| f.long.as_str()).collect()
743    }
744
745    /// Checks if short or long options are already present
746    ///
747    /// ## Panics
748    ///
749    /// Panics if one of them is found.
750    // XXX: return result instead of a panic
751    fn check_names(&self, name: impl Into<String>, short: Option<char>) -> (String, Option<char>) {
752        let s = short.inspect(|c| {
753            assert!(
754                !self.get_shorts().contains(c),
755                "There may be duplicate short flags for '-{c}'"
756            );
757        });
758
759        let name = {
760            let name: String = name.into();
761            assert!(
762                !self.get_names().contains(&name.as_str()),
763                "There may be duplicate name flags for '--{name}'"
764            );
765            name
766        };
767
768        (name, s)
769    }
770
771    /// Returns an argument with the index `position`
772    ///
773    /// It will index, in order, required arguments, then optional, then the
774    /// trailing `...rest` argument.
775    pub fn get_positional(&self, position: usize) -> Option<&PositionalArg> {
776        if position < self.required_positional.len() {
777            self.required_positional.get(position)
778        } else if position < (self.required_positional.len() + self.optional_positional.len()) {
779            self.optional_positional
780                .get(position - self.required_positional.len())
781        } else {
782            self.rest_positional.as_ref()
783        }
784    }
785
786    /// Returns the number of (optional) positional parameters in a signature
787    ///
788    /// This does _not_ include the `...rest` parameter, even if it's present.
789    pub fn num_positionals(&self) -> usize {
790        let mut total = self.required_positional.len() + self.optional_positional.len();
791
792        for positional in &self.required_positional {
793            if let SyntaxShape::Keyword(..) = positional.shape {
794                // Keywords have a required argument, so account for that
795                total += 1;
796            }
797        }
798        for positional in &self.optional_positional {
799            if let SyntaxShape::Keyword(..) = positional.shape {
800                // Keywords have a required argument, so account for that
801                total += 1;
802            }
803        }
804        total
805    }
806
807    /// Find the matching long flag
808    pub fn get_long_flag(&self, name: &str) -> Option<Flag> {
809        for flag in &self.named {
810            if flag.long == name {
811                return Some(flag.clone());
812            }
813        }
814        None
815    }
816
817    /// Find the matching long flag
818    pub fn get_short_flag(&self, short: char) -> Option<Flag> {
819        for flag in &self.named {
820            if let Some(short_flag) = &flag.short
821                && *short_flag == short
822            {
823                return Some(flag.clone());
824            }
825        }
826        None
827    }
828
829    /// Set the filter flag for the signature
830    pub fn filter(mut self) -> Signature {
831        self.is_filter = true;
832        self
833    }
834
835    /// Create a placeholder implementation of Command as a way to predeclare a definition's
836    /// signature so other definitions can see it. This placeholder is later replaced with the
837    /// full definition in a second pass of the parser.
838    pub fn predeclare(self) -> Box<dyn Command> {
839        Box::new(Predeclaration { signature: self })
840    }
841
842    /// Combines a signature and a block into a runnable block
843    pub fn into_block_command(
844        self,
845        block_id: BlockId,
846        attributes: Vec<(String, Value)>,
847        examples: Vec<CustomExample>,
848    ) -> Box<dyn Command> {
849        Box::new(BlockCommand {
850            signature: self,
851            block_id,
852            attributes,
853            examples,
854        })
855    }
856}
857
858#[derive(Clone)]
859struct Predeclaration {
860    signature: Signature,
861}
862
863impl Command for Predeclaration {
864    fn name(&self) -> &str {
865        &self.signature.name
866    }
867
868    fn signature(&self) -> Signature {
869        self.signature.clone()
870    }
871
872    fn description(&self) -> &str {
873        &self.signature.description
874    }
875
876    fn extra_description(&self) -> &str {
877        &self.signature.extra_description
878    }
879
880    fn run(
881        &self,
882        _engine_state: &EngineState,
883        _stack: &mut Stack,
884        _call: &Call,
885        _input: PipelineData,
886    ) -> Result<PipelineData, crate::ShellError> {
887        panic!("Internal error: can't run a predeclaration without a body")
888    }
889}
890
891fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String {
892    match &arg.shape {
893        SyntaxShape::Keyword(name, ..) => {
894            if is_required {
895                format!("{} <{}> ", String::from_utf8_lossy(name), arg.name)
896            } else {
897                format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name)
898            }
899        }
900        _ => {
901            if is_required {
902                format!("<{}> ", arg.name)
903            } else {
904                format!("({}) ", arg.name)
905            }
906        }
907    }
908}
909
910#[derive(Clone, DeriveFromValue)]
911pub struct CustomExample {
912    pub example: String,
913    pub description: String,
914    pub result: Option<Value>,
915}
916
917impl CustomExample {
918    pub fn to_example(&self) -> Example<'_> {
919        Example {
920            example: self.example.as_str(),
921            description: self.description.as_str(),
922            result: self.result.clone(),
923        }
924    }
925}
926
927#[derive(Clone)]
928struct BlockCommand {
929    signature: Signature,
930    block_id: BlockId,
931    attributes: Vec<(String, Value)>,
932    examples: Vec<CustomExample>,
933}
934
935impl Command for BlockCommand {
936    fn name(&self) -> &str {
937        &self.signature.name
938    }
939
940    fn signature(&self) -> Signature {
941        self.signature.clone()
942    }
943
944    fn description(&self) -> &str {
945        &self.signature.description
946    }
947
948    fn extra_description(&self) -> &str {
949        &self.signature.extra_description
950    }
951
952    fn run(
953        &self,
954        _engine_state: &EngineState,
955        _stack: &mut Stack,
956        _call: &Call,
957        _input: PipelineData,
958    ) -> Result<crate::PipelineData, crate::ShellError> {
959        Err(ShellError::GenericError {
960            error: "Internal error: can't run custom command with 'run', use block_id".into(),
961            msg: "".into(),
962            span: None,
963            help: None,
964            inner: vec![],
965        })
966    }
967
968    fn command_type(&self) -> CommandType {
969        CommandType::Custom
970    }
971
972    fn block_id(&self) -> Option<BlockId> {
973        Some(self.block_id)
974    }
975
976    fn attributes(&self) -> Vec<(String, Value)> {
977        self.attributes.clone()
978    }
979
980    fn examples(&self) -> Vec<Example<'_>> {
981        self.examples
982            .iter()
983            .map(CustomExample::to_example)
984            .collect()
985    }
986
987    fn search_terms(&self) -> Vec<&str> {
988        self.signature
989            .search_terms
990            .iter()
991            .map(String::as_str)
992            .collect()
993    }
994
995    fn deprecation_info(&self) -> Vec<DeprecationEntry> {
996        self.attributes
997            .iter()
998            .filter_map(|(key, value)| {
999                (key == "deprecated")
1000                    .then_some(value.clone())
1001                    .map(DeprecationEntry::from_value)
1002                    .and_then(Result::ok)
1003            })
1004            .collect()
1005    }
1006}