Skip to main content

nu_protocol/
signature.rs

1use crate::{
2    BlockId, CompareTypes, DeclId, DeprecationEntry, Example, FromValue, IntoValue, PipelineData,
3    ShellError, Span, SyntaxShape, Type, TypeSet, 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  [`Type::Any`] is returned.
372    /// - If the signature has a single input type, it is returned.
373    /// - If there are multiple input types, a [union](Type::union) of them is returned.
374    pub fn get_input_type(&self) -> Type {
375        match self.input_output_types.as_slice() {
376            [] => Type::Any,
377            [(input, _output)] => input.clone(),
378            multiple => Type::one_of(multiple.iter().map(|(input, _)| input.clone())),
379        }
380    }
381
382    /// Gets the output type from the signature based on `input`
383    ///
384    /// - If the signature's output was unspecified [`Type::Any`] is returned.
385    /// - If `input` is [`None`], it's treated as [`Type::Any`]. i.e. all IO pairs are considered.
386    /// - IO pairs where the given `input` is [assignable to](crate::CompareTypes::is_assignable_to)
387    ///   the input type are considered valid.
388    /// - [Union](TypeSet::union) of all valid outputs are returned.
389    /// - If there are no valid IO pairs for the given `input`, [`None`] is returned.
390    // XXX: remove?
391    pub fn get_output_type(&self, input_type: Option<&Type>) -> Option<Type> {
392        if self.input_output_types.is_empty() {
393            return Some(Type::Any);
394        }
395        let input = input_type.unwrap_or(&Type::Any);
396        let mut it = self
397            .input_output_types
398            .iter()
399            .filter(|(in_ty, _out_ty)| input.is_assignable_to(in_ty))
400            .map(|(_, out)| out)
401            .peekable();
402
403        it.peek()?;
404        it.cloned().reduce(Type::union)
405    }
406
407    /// Add a default help option to a signature
408    pub fn add_help(mut self) -> Signature {
409        // default help flag
410        let flag = Flag {
411            long: "help".into(),
412            short: Some('h'),
413            arg: None,
414            desc: "Display the help message for this command".into(),
415            required: false,
416            var_id: None,
417            default_value: None,
418            completion: None,
419        };
420        self.named.push(flag);
421        self
422    }
423
424    /// Build an internal signature with default help option
425    ///
426    /// This is equivalent to `Signature::new(name).add_help()`.
427    pub fn build(name: impl Into<String>) -> Signature {
428        Signature::new(name.into()).add_help()
429    }
430
431    /// Add a description to the signature
432    ///
433    /// This should be a single sentence as it is the part shown for example in the completion
434    /// menu.
435    pub fn description(mut self, msg: impl Into<String>) -> Signature {
436        self.description = msg.into();
437        self
438    }
439
440    /// Add an extra description to the signature.
441    ///
442    /// Here additional documentation can be added
443    pub fn extra_description(mut self, msg: impl Into<String>) -> Signature {
444        self.extra_description = msg.into();
445        self
446    }
447
448    /// Add search terms to the signature
449    pub fn search_terms(mut self, terms: Vec<String>) -> Signature {
450        self.search_terms = terms;
451        self
452    }
453
454    /// Update signature's fields from a Command trait implementation
455    pub fn update_from_command(mut self, command: &dyn Command) -> Signature {
456        self.search_terms = command
457            .search_terms()
458            .into_iter()
459            .map(|term| term.to_string())
460            .collect();
461        self.extra_description = command.extra_description().to_string();
462        self.description = command.description().to_string();
463        self
464    }
465
466    /// Allow unknown signature parameters
467    pub fn allows_unknown_args(mut self) -> Signature {
468        self.allows_unknown_args = true;
469        self
470    }
471
472    pub fn param(mut self, param: impl Into<Parameter>) -> Self {
473        let param: Parameter = param.into();
474        match param {
475            Parameter::Flag(flag) => {
476                if let Some(s) = flag.short {
477                    assert!(
478                        !self.get_shorts().contains(&s),
479                        "There may be duplicate short flags for '-{s}'"
480                    );
481                }
482
483                let name = flag.long.as_str();
484                assert!(
485                    !self.get_names().contains(&name),
486                    "There may be duplicate name flags for '--{name}'"
487                );
488
489                self.named.push(flag);
490            }
491            Parameter::Required(positional_arg) => {
492                self.required_positional.push(positional_arg);
493            }
494            Parameter::Optional(positional_arg) => {
495                self.optional_positional.push(positional_arg);
496            }
497            Parameter::Rest(positional_arg) => {
498                assert!(
499                    self.rest_positional.is_none(),
500                    "Tried to set rest arguments more than once"
501                );
502                self.rest_positional = Some(positional_arg);
503            }
504        }
505        self
506    }
507
508    /// Add a required positional argument to the signature
509    pub fn required(
510        mut self,
511        name: impl Into<String>,
512        shape: impl Into<SyntaxShape>,
513        desc: impl Into<String>,
514    ) -> Signature {
515        self.required_positional.push(PositionalArg {
516            name: name.into(),
517            desc: desc.into(),
518            shape: shape.into(),
519            var_id: None,
520            default_value: None,
521            completion: None,
522        });
523
524        self
525    }
526
527    /// Add an optional positional argument to the signature
528    pub fn optional(
529        mut self,
530        name: impl Into<String>,
531        shape: impl Into<SyntaxShape>,
532        desc: impl Into<String>,
533    ) -> Signature {
534        self.optional_positional.push(PositionalArg {
535            name: name.into(),
536            desc: desc.into(),
537            shape: shape.into(),
538            var_id: None,
539            default_value: None,
540            completion: None,
541        });
542
543        self
544    }
545
546    /// Add a rest positional parameter
547    ///
548    /// Rest positionals (also called [rest parameters][rp]) are treated as
549    /// optional: passing 0 arguments is a valid call.  If the command requires
550    /// at least one argument, it must be checked by the implementation.
551    ///
552    /// [rp]: https://www.nushell.sh/book/custom_commands.html#rest-parameters
553    pub fn rest(
554        mut self,
555        name: &str,
556        shape: impl Into<SyntaxShape>,
557        desc: impl Into<String>,
558    ) -> Signature {
559        self.rest_positional = Some(PositionalArg {
560            name: name.into(),
561            desc: desc.into(),
562            shape: shape.into(),
563            var_id: None,
564            default_value: None,
565            completion: None,
566        });
567
568        self
569    }
570
571    /// Is this command capable of operating on its input via cell paths?
572    pub fn operates_on_cell_paths(&self) -> bool {
573        self.required_positional
574            .iter()
575            .chain(self.rest_positional.iter())
576            .any(|pos| {
577                matches!(
578                    pos,
579                    PositionalArg {
580                        shape: SyntaxShape::CellPath,
581                        ..
582                    }
583                )
584            })
585    }
586
587    /// Add an optional named flag argument to the signature
588    pub fn named(
589        mut self,
590        name: impl Into<String>,
591        shape: impl Into<SyntaxShape>,
592        desc: impl Into<String>,
593        short: Option<char>,
594    ) -> Signature {
595        let (name, s) = self.check_names(name, short);
596
597        self.named.push(Flag {
598            long: name,
599            short: s,
600            arg: Some(shape.into()),
601            required: false,
602            desc: desc.into(),
603            var_id: None,
604            default_value: None,
605            completion: None,
606        });
607
608        self
609    }
610
611    /// Add a required named flag argument to the signature
612    pub fn required_named(
613        mut self,
614        name: impl Into<String>,
615        shape: impl Into<SyntaxShape>,
616        desc: impl Into<String>,
617        short: Option<char>,
618    ) -> Signature {
619        let (name, s) = self.check_names(name, short);
620
621        self.named.push(Flag {
622            long: name,
623            short: s,
624            arg: Some(shape.into()),
625            required: true,
626            desc: desc.into(),
627            var_id: None,
628            default_value: None,
629            completion: None,
630        });
631
632        self
633    }
634
635    /// Add a switch to the signature
636    pub fn switch(
637        mut self,
638        name: impl Into<String>,
639        desc: impl Into<String>,
640        short: Option<char>,
641    ) -> Signature {
642        let (name, s) = self.check_names(name, short);
643
644        self.named.push(Flag {
645            long: name,
646            short: s,
647            arg: None,
648            required: false,
649            desc: desc.into(),
650            var_id: None,
651            default_value: None,
652            completion: None,
653        });
654
655        self
656    }
657
658    /// Changes the input type of the command signature
659    pub fn input_output_type(mut self, input_type: Type, output_type: Type) -> Signature {
660        self.input_output_types.push((input_type, output_type));
661        self
662    }
663
664    /// Set the input-output type signature variants of the command
665    pub fn input_output_types(mut self, input_output_types: Vec<(Type, Type)>) -> Signature {
666        self.input_output_types = input_output_types;
667        self
668    }
669
670    /// Changes the signature category
671    pub fn category(mut self, category: Category) -> Signature {
672        self.category = category;
673
674        self
675    }
676
677    /// Sets that signature will create a scope as it parses
678    pub fn creates_scope(mut self) -> Signature {
679        self.creates_scope = true;
680        self
681    }
682
683    // Is it allowed for the type signature to feature a variant that has no corresponding example?
684    pub fn allow_variants_without_examples(mut self, allow: bool) -> Signature {
685        self.allow_variants_without_examples = allow;
686        self
687    }
688
689    /// A string rendering of the command signature
690    ///
691    /// If the command has flags, all of them will be shown together as
692    /// `{flags}`.
693    pub fn call_signature(&self) -> String {
694        let mut one_liner = String::new();
695        one_liner.push_str(&self.name);
696        one_liner.push(' ');
697
698        // Note: the call signature needs flags first because on the nu commandline,
699        // flags will precede the script file name. Flags for internal commands can come
700        // either before or after (or around) positional parameters, so there isn't a strong
701        // preference, so we default to the more constrained example.
702        if self.named.len() > 1 {
703            one_liner.push_str("{flags} ");
704        }
705
706        for positional in &self.required_positional {
707            one_liner.push_str(&get_positional_short_name(positional, true));
708        }
709        for positional in &self.optional_positional {
710            one_liner.push_str(&get_positional_short_name(positional, false));
711        }
712
713        if let Some(rest) = &self.rest_positional {
714            let _ = write!(one_liner, "...{}", get_positional_short_name(rest, false));
715        }
716
717        // if !self.subcommands.is_empty() {
718        //     one_liner.push_str("<subcommand> ");
719        // }
720
721        one_liner
722    }
723
724    /// Get list of the short-hand flags
725    pub fn get_shorts(&self) -> Vec<char> {
726        self.named.iter().filter_map(|f| f.short).collect()
727    }
728
729    /// Get list of the long-hand flags
730    pub fn get_names(&self) -> Vec<&str> {
731        self.named.iter().map(|f| f.long.as_str()).collect()
732    }
733
734    /// Checks if short or long options are already present
735    ///
736    /// ## Panics
737    ///
738    /// Panics if one of them is found.
739    // XXX: return result instead of a panic
740    fn check_names(&self, name: impl Into<String>, short: Option<char>) -> (String, Option<char>) {
741        let s = short.inspect(|c| {
742            assert!(
743                !self.get_shorts().contains(c),
744                "There may be duplicate short flags for '-{c}'"
745            );
746        });
747
748        let name = {
749            let name: String = name.into();
750            assert!(
751                !self.get_names().contains(&name.as_str()),
752                "There may be duplicate name flags for '--{name}'"
753            );
754            name
755        };
756
757        (name, s)
758    }
759
760    /// Returns an argument with the index `position`
761    ///
762    /// It will index, in order, required arguments, then optional, then the
763    /// trailing `...rest` argument. Note that the `...rest` argument must be
764    /// a [`Value::List`], therefore this method may not work as intended when
765    /// the closure uses a rest argument.
766    pub fn get_positional(&self, position: usize) -> Option<&PositionalArg> {
767        if position < self.required_positional.len() {
768            self.required_positional.get(position)
769        } else if position < (self.required_positional.len() + self.optional_positional.len()) {
770            self.optional_positional
771                .get(position - self.required_positional.len())
772        } else {
773            self.rest_positional.as_ref()
774        }
775    }
776
777    /// Returns the number of (optional) positional parameters in a signature
778    ///
779    /// This does _not_ include the `...rest` parameter, even if it's present.
780    pub fn num_positionals(&self) -> usize {
781        let mut total = self.required_positional.len() + self.optional_positional.len();
782
783        for positional in &self.required_positional {
784            if let SyntaxShape::Keyword(..) = positional.shape {
785                // Keywords have a required argument, so account for that
786                total += 1;
787            }
788        }
789        for positional in &self.optional_positional {
790            if let SyntaxShape::Keyword(..) = positional.shape {
791                // Keywords have a required argument, so account for that
792                total += 1;
793            }
794        }
795        total
796    }
797
798    /// Find the matching long flag
799    pub fn get_long_flag(&self, name: &str) -> Option<Flag> {
800        if name.is_empty() {
801            return None;
802        }
803        for flag in &self.named {
804            if flag.long == name {
805                return Some(flag.clone());
806            }
807        }
808        None
809    }
810
811    /// Find the matching long flag
812    pub fn get_short_flag(&self, short: char) -> Option<Flag> {
813        for flag in &self.named {
814            if let Some(short_flag) = &flag.short
815                && *short_flag == short
816            {
817                return Some(flag.clone());
818            }
819        }
820        None
821    }
822
823    /// Set the filter flag for the signature
824    pub fn filter(mut self) -> Signature {
825        self.is_filter = true;
826        self
827    }
828
829    /// Create a placeholder implementation of Command as a way to predeclare a definition's
830    /// signature so other definitions can see it. This placeholder is later replaced with the
831    /// full definition in a second pass of the parser.
832    pub fn predeclare(self) -> Box<dyn Command> {
833        self.predeclare_with_command_type(CommandType::Builtin)
834    }
835
836    /// Create a placeholder implementation of Command as a way to predeclare a definition's
837    /// signature with an explicit command type.
838    pub fn predeclare_with_command_type(self, command_type: CommandType) -> Box<dyn Command> {
839        Box::new(Predeclaration {
840            signature: self,
841            command_type,
842        })
843    }
844
845    /// Combines a signature and a block into a runnable block
846    pub fn into_block_command(
847        self,
848        block_id: BlockId,
849        attributes: Vec<(String, Value)>,
850        examples: Vec<CustomExample>,
851    ) -> Box<dyn Command> {
852        Box::new(BlockCommand {
853            signature: self,
854            block_id,
855            attributes,
856            examples,
857        })
858    }
859}
860
861#[derive(Clone)]
862struct Predeclaration {
863    signature: Signature,
864    command_type: CommandType,
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    fn command_type(&self) -> CommandType {
895        self.command_type
896    }
897}
898
899fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String {
900    match &arg.shape {
901        SyntaxShape::Keyword(name, ..) => {
902            if is_required {
903                format!("{} <{}> ", String::from_utf8_lossy(name), arg.name)
904            } else {
905                format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name)
906            }
907        }
908        _ => {
909            if is_required {
910                format!("<{}> ", arg.name)
911            } else {
912                format!("({}) ", arg.name)
913            }
914        }
915    }
916}
917
918#[derive(Clone, DeriveFromValue)]
919pub struct CustomExample {
920    pub example: String,
921    pub description: String,
922    pub result: Option<Value>,
923}
924
925impl CustomExample {
926    pub fn to_example(&self) -> Example<'_> {
927        Example {
928            example: self.example.as_str(),
929            description: self.description.as_str(),
930            result: self.result.clone(),
931        }
932    }
933}
934
935#[derive(Clone)]
936struct BlockCommand {
937    signature: Signature,
938    block_id: BlockId,
939    attributes: Vec<(String, Value)>,
940    examples: Vec<CustomExample>,
941}
942
943impl Command for BlockCommand {
944    fn name(&self) -> &str {
945        &self.signature.name
946    }
947
948    fn signature(&self) -> Signature {
949        self.signature.clone()
950    }
951
952    fn description(&self) -> &str {
953        &self.signature.description
954    }
955
956    fn extra_description(&self) -> &str {
957        &self.signature.extra_description
958    }
959
960    fn run(
961        &self,
962        _engine_state: &EngineState,
963        _stack: &mut Stack,
964        _call: &Call,
965        _input: PipelineData,
966    ) -> Result<crate::PipelineData, crate::ShellError> {
967        Err(ShellError::Generic(GenericError::new_internal(
968            "Internal error: can't run custom command with 'run', use block_id",
969            "",
970        )))
971    }
972
973    fn command_type(&self) -> CommandType {
974        CommandType::Custom
975    }
976
977    fn block_id(&self) -> Option<BlockId> {
978        Some(self.block_id)
979    }
980
981    fn attributes(&self) -> Vec<(String, Value)> {
982        self.attributes.clone()
983    }
984
985    fn examples(&self) -> Vec<Example<'_>> {
986        self.examples
987            .iter()
988            .map(CustomExample::to_example)
989            .collect()
990    }
991
992    fn search_terms(&self) -> Vec<&str> {
993        self.signature
994            .search_terms
995            .iter()
996            .map(String::as_str)
997            .collect()
998    }
999
1000    fn deprecation_info(&self) -> Vec<DeprecationEntry> {
1001        self.attributes
1002            .iter()
1003            .filter_map(|(key, value)| {
1004                (key == "deprecated")
1005                    .then_some(value.clone())
1006                    .map(DeprecationEntry::from_value)
1007                    .and_then(Result::ok)
1008            })
1009            .collect()
1010    }
1011}