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. Note that the `...rest` argument must be
776    /// a [`Value::List`], therefore this method may not work as intended when
777    /// the closure uses a rest argument.
778    pub fn get_positional(&self, position: usize) -> Option<&PositionalArg> {
779        if position < self.required_positional.len() {
780            self.required_positional.get(position)
781        } else if position < (self.required_positional.len() + self.optional_positional.len()) {
782            self.optional_positional
783                .get(position - self.required_positional.len())
784        } else {
785            self.rest_positional.as_ref()
786        }
787    }
788
789    /// Returns the number of (optional) positional parameters in a signature
790    ///
791    /// This does _not_ include the `...rest` parameter, even if it's present.
792    pub fn num_positionals(&self) -> usize {
793        let mut total = self.required_positional.len() + self.optional_positional.len();
794
795        for positional in &self.required_positional {
796            if let SyntaxShape::Keyword(..) = positional.shape {
797                // Keywords have a required argument, so account for that
798                total += 1;
799            }
800        }
801        for positional in &self.optional_positional {
802            if let SyntaxShape::Keyword(..) = positional.shape {
803                // Keywords have a required argument, so account for that
804                total += 1;
805            }
806        }
807        total
808    }
809
810    /// Find the matching long flag
811    pub fn get_long_flag(&self, name: &str) -> Option<Flag> {
812        if name.is_empty() {
813            return None;
814        }
815        for flag in &self.named {
816            if flag.long == name {
817                return Some(flag.clone());
818            }
819        }
820        None
821    }
822
823    /// Find the matching long flag
824    pub fn get_short_flag(&self, short: char) -> Option<Flag> {
825        for flag in &self.named {
826            if let Some(short_flag) = &flag.short
827                && *short_flag == short
828            {
829                return Some(flag.clone());
830            }
831        }
832        None
833    }
834
835    /// Set the filter flag for the signature
836    pub fn filter(mut self) -> Signature {
837        self.is_filter = true;
838        self
839    }
840
841    /// Create a placeholder implementation of Command as a way to predeclare a definition's
842    /// signature so other definitions can see it. This placeholder is later replaced with the
843    /// full definition in a second pass of the parser.
844    pub fn predeclare(self) -> Box<dyn Command> {
845        self.predeclare_with_command_type(CommandType::Builtin)
846    }
847
848    /// Create a placeholder implementation of Command as a way to predeclare a definition's
849    /// signature with an explicit command type.
850    pub fn predeclare_with_command_type(self, command_type: CommandType) -> Box<dyn Command> {
851        Box::new(Predeclaration {
852            signature: self,
853            command_type,
854        })
855    }
856
857    /// Combines a signature and a block into a runnable block
858    pub fn into_block_command(
859        self,
860        block_id: BlockId,
861        attributes: Vec<(String, Value)>,
862        examples: Vec<CustomExample>,
863    ) -> Box<dyn Command> {
864        Box::new(BlockCommand {
865            signature: self,
866            block_id,
867            attributes,
868            examples,
869        })
870    }
871}
872
873#[derive(Clone)]
874struct Predeclaration {
875    signature: Signature,
876    command_type: CommandType,
877}
878
879impl Command for Predeclaration {
880    fn name(&self) -> &str {
881        &self.signature.name
882    }
883
884    fn signature(&self) -> Signature {
885        self.signature.clone()
886    }
887
888    fn description(&self) -> &str {
889        &self.signature.description
890    }
891
892    fn extra_description(&self) -> &str {
893        &self.signature.extra_description
894    }
895
896    fn run(
897        &self,
898        _engine_state: &EngineState,
899        _stack: &mut Stack,
900        _call: &Call,
901        _input: PipelineData,
902    ) -> Result<PipelineData, crate::ShellError> {
903        panic!("Internal error: can't run a predeclaration without a body")
904    }
905
906    fn command_type(&self) -> CommandType {
907        self.command_type
908    }
909}
910
911fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String {
912    match &arg.shape {
913        SyntaxShape::Keyword(name, ..) => {
914            if is_required {
915                format!("{} <{}> ", String::from_utf8_lossy(name), arg.name)
916            } else {
917                format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name)
918            }
919        }
920        _ => {
921            if is_required {
922                format!("<{}> ", arg.name)
923            } else {
924                format!("({}) ", arg.name)
925            }
926        }
927    }
928}
929
930#[derive(Clone, DeriveFromValue)]
931pub struct CustomExample {
932    pub example: String,
933    pub description: String,
934    pub result: Option<Value>,
935}
936
937impl CustomExample {
938    pub fn to_example(&self) -> Example<'_> {
939        Example {
940            example: self.example.as_str(),
941            description: self.description.as_str(),
942            result: self.result.clone(),
943        }
944    }
945}
946
947#[derive(Clone)]
948struct BlockCommand {
949    signature: Signature,
950    block_id: BlockId,
951    attributes: Vec<(String, Value)>,
952    examples: Vec<CustomExample>,
953}
954
955impl Command for BlockCommand {
956    fn name(&self) -> &str {
957        &self.signature.name
958    }
959
960    fn signature(&self) -> Signature {
961        self.signature.clone()
962    }
963
964    fn description(&self) -> &str {
965        &self.signature.description
966    }
967
968    fn extra_description(&self) -> &str {
969        &self.signature.extra_description
970    }
971
972    fn run(
973        &self,
974        _engine_state: &EngineState,
975        _stack: &mut Stack,
976        _call: &Call,
977        _input: PipelineData,
978    ) -> Result<crate::PipelineData, crate::ShellError> {
979        Err(ShellError::Generic(GenericError::new_internal(
980            "Internal error: can't run custom command with 'run', use block_id",
981            "",
982        )))
983    }
984
985    fn command_type(&self) -> CommandType {
986        CommandType::Custom
987    }
988
989    fn block_id(&self) -> Option<BlockId> {
990        Some(self.block_id)
991    }
992
993    fn attributes(&self) -> Vec<(String, Value)> {
994        self.attributes.clone()
995    }
996
997    fn examples(&self) -> Vec<Example<'_>> {
998        self.examples
999            .iter()
1000            .map(CustomExample::to_example)
1001            .collect()
1002    }
1003
1004    fn search_terms(&self) -> Vec<&str> {
1005        self.signature
1006            .search_terms
1007            .iter()
1008            .map(String::as_str)
1009            .collect()
1010    }
1011
1012    fn deprecation_info(&self) -> Vec<DeprecationEntry> {
1013        self.attributes
1014            .iter()
1015            .filter_map(|(key, value)| {
1016                (key == "deprecated")
1017                    .then_some(value.clone())
1018                    .map(DeprecationEntry::from_value)
1019                    .and_then(Result::ok)
1020            })
1021            .collect()
1022    }
1023}