Skip to main content

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