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