1use crate::{
2    BlockId, DeclId, DeprecationEntry, Example, FromValue, PipelineData, ShellError, SyntaxShape,
3    Type, Value, VarId,
4    engine::{Call, Command, CommandType, EngineState, Stack},
5};
6use nu_derive_value::FromValue as DeriveFromValue;
7use serde::{Deserialize, Serialize};
8use std::fmt::Write;
9
10use crate as nu_protocol;
14
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct Flag {
18    pub long: String,
19    pub short: Option<char>,
20    pub arg: Option<SyntaxShape>,
21    pub required: bool,
22    pub desc: String,
23
24    pub var_id: Option<VarId>,
26    pub default_value: Option<Value>,
27    pub custom_completion: Option<DeclId>,
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct PositionalArg {
33    pub name: String,
34    pub desc: String,
35    pub shape: SyntaxShape,
36
37    pub var_id: Option<VarId>,
39    pub default_value: Option<Value>,
40    pub custom_completion: Option<DeclId>,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45pub enum Category {
46    Bits,
47    Bytes,
48    Chart,
49    Conversions,
50    Core,
51    Custom(String),
52    Database,
53    Date,
54    Debug,
55    Default,
56    Deprecated,
57    Removed,
58    Env,
59    Experimental,
60    FileSystem,
61    Filters,
62    Formats,
63    Generators,
64    Hash,
65    History,
66    Math,
67    Misc,
68    Network,
69    Path,
70    Platform,
71    Plugin,
72    Random,
73    Shells,
74    Strings,
75    System,
76    Viewers,
77}
78
79impl std::fmt::Display for Category {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        let msg = match self {
82            Category::Bits => "bits",
83            Category::Bytes => "bytes",
84            Category::Chart => "chart",
85            Category::Conversions => "conversions",
86            Category::Core => "core",
87            Category::Custom(name) => name,
88            Category::Database => "database",
89            Category::Date => "date",
90            Category::Debug => "debug",
91            Category::Default => "default",
92            Category::Deprecated => "deprecated",
93            Category::Removed => "removed",
94            Category::Env => "env",
95            Category::Experimental => "experimental",
96            Category::FileSystem => "filesystem",
97            Category::Filters => "filters",
98            Category::Formats => "formats",
99            Category::Generators => "generators",
100            Category::Hash => "hash",
101            Category::History => "history",
102            Category::Math => "math",
103            Category::Misc => "misc",
104            Category::Network => "network",
105            Category::Path => "path",
106            Category::Platform => "platform",
107            Category::Plugin => "plugin",
108            Category::Random => "random",
109            Category::Shells => "shells",
110            Category::Strings => "strings",
111            Category::System => "system",
112            Category::Viewers => "viewers",
113        };
114
115        write!(f, "{msg}")
116    }
117}
118
119pub fn category_from_string(category: &str) -> Category {
120    match category {
121        "bits" => Category::Bits,
122        "bytes" => Category::Bytes,
123        "chart" => Category::Chart,
124        "conversions" => Category::Conversions,
125        "core" => Category::Custom("custom_core".to_string()),
127        "database" => Category::Database,
128        "date" => Category::Date,
129        "debug" => Category::Debug,
130        "default" => Category::Default,
131        "deprecated" => Category::Deprecated,
132        "removed" => Category::Removed,
133        "env" => Category::Env,
134        "experimental" => Category::Experimental,
135        "filesystem" => Category::FileSystem,
136        "filter" => Category::Filters,
137        "formats" => Category::Formats,
138        "generators" => Category::Generators,
139        "hash" => Category::Hash,
140        "history" => Category::History,
141        "math" => Category::Math,
142        "misc" => Category::Misc,
143        "network" => Category::Network,
144        "path" => Category::Path,
145        "platform" => Category::Platform,
146        "plugin" => Category::Plugin,
147        "random" => Category::Random,
148        "shells" => Category::Shells,
149        "strings" => Category::Strings,
150        "system" => Category::System,
151        "viewers" => Category::Viewers,
152        _ => Category::Custom(category.to_string()),
153    }
154}
155
156#[derive(Clone, Debug, Serialize, Deserialize)]
158pub struct Signature {
159    pub name: String,
160    pub description: String,
161    pub extra_description: String,
162    pub search_terms: Vec<String>,
163    pub required_positional: Vec<PositionalArg>,
164    pub optional_positional: Vec<PositionalArg>,
165    pub rest_positional: Option<PositionalArg>,
166    pub named: Vec<Flag>,
167    pub input_output_types: Vec<(Type, Type)>,
168    pub allow_variants_without_examples: bool,
169    pub is_filter: bool,
170    pub creates_scope: bool,
171    pub allows_unknown_args: bool,
172    pub category: Category,
174}
175
176impl PartialEq for Signature {
177    fn eq(&self, other: &Self) -> bool {
178        self.name == other.name
179            && self.description == other.description
180            && self.required_positional == other.required_positional
181            && self.optional_positional == other.optional_positional
182            && self.rest_positional == other.rest_positional
183            && self.is_filter == other.is_filter
184    }
185}
186
187impl Eq for Signature {}
188
189impl Signature {
190    pub fn new(name: impl Into<String>) -> Signature {
192        Signature {
193            name: name.into(),
194            description: String::new(),
195            extra_description: String::new(),
196            search_terms: vec![],
197            required_positional: vec![],
198            optional_positional: vec![],
199            rest_positional: None,
200            input_output_types: vec![],
201            allow_variants_without_examples: false,
202            named: vec![],
203            is_filter: false,
204            creates_scope: false,
205            category: Category::Default,
206            allows_unknown_args: false,
207        }
208    }
209
210    pub fn get_input_type(&self) -> Type {
217        match self.input_output_types.len() {
218            0 => Type::Any,
219            1 => self.input_output_types[0].0.clone(),
220            _ => {
221                let first = &self.input_output_types[0].0;
222                if self
223                    .input_output_types
224                    .iter()
225                    .all(|(input, _)| input == first)
226                {
227                    first.clone()
228                } else {
229                    Type::Any
230                }
231            }
232        }
233    }
234
235    pub fn get_output_type(&self) -> Type {
242        match self.input_output_types.len() {
243            0 => Type::Any,
244            1 => self.input_output_types[0].1.clone(),
245            _ => {
246                let first = &self.input_output_types[0].1;
247                if self
248                    .input_output_types
249                    .iter()
250                    .all(|(_, output)| output == first)
251                {
252                    first.clone()
253                } else {
254                    Type::Any
255                }
256            }
257        }
258    }
259
260    pub fn add_help(mut self) -> Signature {
262        let flag = Flag {
264            long: "help".into(),
265            short: Some('h'),
266            arg: None,
267            desc: "Display the help message for this command".into(),
268            required: false,
269            var_id: None,
270            default_value: None,
271            custom_completion: None,
272        };
273        self.named.push(flag);
274        self
275    }
276
277    pub fn build(name: impl Into<String>) -> Signature {
281        Signature::new(name.into()).add_help()
282    }
283
284    pub fn description(mut self, msg: impl Into<String>) -> Signature {
289        self.description = msg.into();
290        self
291    }
292
293    pub fn extra_description(mut self, msg: impl Into<String>) -> Signature {
297        self.extra_description = msg.into();
298        self
299    }
300
301    pub fn search_terms(mut self, terms: Vec<String>) -> Signature {
303        self.search_terms = terms;
304        self
305    }
306
307    pub fn update_from_command(mut self, command: &dyn Command) -> Signature {
309        self.search_terms = command
310            .search_terms()
311            .into_iter()
312            .map(|term| term.to_string())
313            .collect();
314        self.extra_description = command.extra_description().to_string();
315        self.description = command.description().to_string();
316        self
317    }
318
319    pub fn allows_unknown_args(mut self) -> Signature {
321        self.allows_unknown_args = true;
322        self
323    }
324
325    pub fn required(
327        mut self,
328        name: impl Into<String>,
329        shape: impl Into<SyntaxShape>,
330        desc: impl Into<String>,
331    ) -> Signature {
332        self.required_positional.push(PositionalArg {
333            name: name.into(),
334            desc: desc.into(),
335            shape: shape.into(),
336            var_id: None,
337            default_value: None,
338            custom_completion: None,
339        });
340
341        self
342    }
343
344    pub fn optional(
346        mut self,
347        name: impl Into<String>,
348        shape: impl Into<SyntaxShape>,
349        desc: impl Into<String>,
350    ) -> Signature {
351        self.optional_positional.push(PositionalArg {
352            name: name.into(),
353            desc: desc.into(),
354            shape: shape.into(),
355            var_id: None,
356            default_value: None,
357            custom_completion: None,
358        });
359
360        self
361    }
362
363    pub fn rest(
371        mut self,
372        name: &str,
373        shape: impl Into<SyntaxShape>,
374        desc: impl Into<String>,
375    ) -> Signature {
376        self.rest_positional = Some(PositionalArg {
377            name: name.into(),
378            desc: desc.into(),
379            shape: shape.into(),
380            var_id: None,
381            default_value: None,
382            custom_completion: None,
383        });
384
385        self
386    }
387
388    pub fn operates_on_cell_paths(&self) -> bool {
390        self.required_positional
391            .iter()
392            .chain(self.rest_positional.iter())
393            .any(|pos| {
394                matches!(
395                    pos,
396                    PositionalArg {
397                        shape: SyntaxShape::CellPath,
398                        ..
399                    }
400                )
401            })
402    }
403
404    pub fn named(
406        mut self,
407        name: impl Into<String>,
408        shape: impl Into<SyntaxShape>,
409        desc: impl Into<String>,
410        short: Option<char>,
411    ) -> Signature {
412        let (name, s) = self.check_names(name, short);
413
414        self.named.push(Flag {
415            long: name,
416            short: s,
417            arg: Some(shape.into()),
418            required: false,
419            desc: desc.into(),
420            var_id: None,
421            default_value: None,
422            custom_completion: None,
423        });
424
425        self
426    }
427
428    pub fn required_named(
430        mut self,
431        name: impl Into<String>,
432        shape: impl Into<SyntaxShape>,
433        desc: impl Into<String>,
434        short: Option<char>,
435    ) -> Signature {
436        let (name, s) = self.check_names(name, short);
437
438        self.named.push(Flag {
439            long: name,
440            short: s,
441            arg: Some(shape.into()),
442            required: true,
443            desc: desc.into(),
444            var_id: None,
445            default_value: None,
446            custom_completion: None,
447        });
448
449        self
450    }
451
452    pub fn switch(
454        mut self,
455        name: impl Into<String>,
456        desc: impl Into<String>,
457        short: Option<char>,
458    ) -> Signature {
459        let (name, s) = self.check_names(name, short);
460
461        self.named.push(Flag {
462            long: name,
463            short: s,
464            arg: None,
465            required: false,
466            desc: desc.into(),
467            var_id: None,
468            default_value: None,
469            custom_completion: None,
470        });
471
472        self
473    }
474
475    pub fn input_output_type(mut self, input_type: Type, output_type: Type) -> Signature {
477        self.input_output_types.push((input_type, output_type));
478        self
479    }
480
481    pub fn input_output_types(mut self, input_output_types: Vec<(Type, Type)>) -> Signature {
483        self.input_output_types = input_output_types;
484        self
485    }
486
487    pub fn category(mut self, category: Category) -> Signature {
489        self.category = category;
490
491        self
492    }
493
494    pub fn creates_scope(mut self) -> Signature {
496        self.creates_scope = true;
497        self
498    }
499
500    pub fn allow_variants_without_examples(mut self, allow: bool) -> Signature {
502        self.allow_variants_without_examples = allow;
503        self
504    }
505
506    pub fn call_signature(&self) -> String {
511        let mut one_liner = String::new();
512        one_liner.push_str(&self.name);
513        one_liner.push(' ');
514
515        if self.named.len() > 1 {
520            one_liner.push_str("{flags} ");
521        }
522
523        for positional in &self.required_positional {
524            one_liner.push_str(&get_positional_short_name(positional, true));
525        }
526        for positional in &self.optional_positional {
527            one_liner.push_str(&get_positional_short_name(positional, false));
528        }
529
530        if let Some(rest) = &self.rest_positional {
531            let _ = write!(one_liner, "...{}", get_positional_short_name(rest, false));
532        }
533
534        one_liner
539    }
540
541    pub fn get_shorts(&self) -> Vec<char> {
543        self.named.iter().filter_map(|f| f.short).collect()
544    }
545
546    pub fn get_names(&self) -> Vec<&str> {
548        self.named.iter().map(|f| f.long.as_str()).collect()
549    }
550
551    fn check_names(&self, name: impl Into<String>, short: Option<char>) -> (String, Option<char>) {
558        let s = short.inspect(|c| {
559            assert!(
560                !self.get_shorts().contains(c),
561                "There may be duplicate short flags for '-{c}'"
562            );
563        });
564
565        let name = {
566            let name: String = name.into();
567            assert!(
568                !self.get_names().contains(&name.as_str()),
569                "There may be duplicate name flags for '--{name}'"
570            );
571            name
572        };
573
574        (name, s)
575    }
576
577    pub fn get_positional(&self, position: usize) -> Option<&PositionalArg> {
582        if position < self.required_positional.len() {
583            self.required_positional.get(position)
584        } else if position < (self.required_positional.len() + self.optional_positional.len()) {
585            self.optional_positional
586                .get(position - self.required_positional.len())
587        } else {
588            self.rest_positional.as_ref()
589        }
590    }
591
592    pub fn num_positionals(&self) -> usize {
596        let mut total = self.required_positional.len() + self.optional_positional.len();
597
598        for positional in &self.required_positional {
599            if let SyntaxShape::Keyword(..) = positional.shape {
600                total += 1;
602            }
603        }
604        for positional in &self.optional_positional {
605            if let SyntaxShape::Keyword(..) = positional.shape {
606                total += 1;
608            }
609        }
610        total
611    }
612
613    pub fn get_long_flag(&self, name: &str) -> Option<Flag> {
615        for flag in &self.named {
616            if flag.long == name {
617                return Some(flag.clone());
618            }
619        }
620        None
621    }
622
623    pub fn get_short_flag(&self, short: char) -> Option<Flag> {
625        for flag in &self.named {
626            if let Some(short_flag) = &flag.short {
627                if *short_flag == short {
628                    return Some(flag.clone());
629                }
630            }
631        }
632        None
633    }
634
635    pub fn filter(mut self) -> Signature {
637        self.is_filter = true;
638        self
639    }
640
641    pub fn predeclare(self) -> Box<dyn Command> {
645        Box::new(Predeclaration { signature: self })
646    }
647
648    pub fn into_block_command(
650        self,
651        block_id: BlockId,
652        attributes: Vec<(String, Value)>,
653        examples: Vec<CustomExample>,
654    ) -> Box<dyn Command> {
655        Box::new(BlockCommand {
656            signature: self,
657            block_id,
658            attributes,
659            examples,
660        })
661    }
662}
663
664#[derive(Clone)]
665struct Predeclaration {
666    signature: Signature,
667}
668
669impl Command for Predeclaration {
670    fn name(&self) -> &str {
671        &self.signature.name
672    }
673
674    fn signature(&self) -> Signature {
675        self.signature.clone()
676    }
677
678    fn description(&self) -> &str {
679        &self.signature.description
680    }
681
682    fn extra_description(&self) -> &str {
683        &self.signature.extra_description
684    }
685
686    fn run(
687        &self,
688        _engine_state: &EngineState,
689        _stack: &mut Stack,
690        _call: &Call,
691        _input: PipelineData,
692    ) -> Result<PipelineData, crate::ShellError> {
693        panic!("Internal error: can't run a predeclaration without a body")
694    }
695}
696
697fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String {
698    match &arg.shape {
699        SyntaxShape::Keyword(name, ..) => {
700            if is_required {
701                format!("{} <{}> ", String::from_utf8_lossy(name), arg.name)
702            } else {
703                format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name)
704            }
705        }
706        _ => {
707            if is_required {
708                format!("<{}> ", arg.name)
709            } else {
710                format!("({}) ", arg.name)
711            }
712        }
713    }
714}
715
716#[derive(Clone, DeriveFromValue)]
717pub struct CustomExample {
718    pub example: String,
719    pub description: String,
720    pub result: Option<Value>,
721}
722
723impl CustomExample {
724    pub fn to_example(&self) -> Example<'_> {
725        Example {
726            example: self.example.as_str(),
727            description: self.description.as_str(),
728            result: self.result.clone(),
729        }
730    }
731}
732
733#[derive(Clone)]
734struct BlockCommand {
735    signature: Signature,
736    block_id: BlockId,
737    attributes: Vec<(String, Value)>,
738    examples: Vec<CustomExample>,
739}
740
741impl Command for BlockCommand {
742    fn name(&self) -> &str {
743        &self.signature.name
744    }
745
746    fn signature(&self) -> Signature {
747        self.signature.clone()
748    }
749
750    fn description(&self) -> &str {
751        &self.signature.description
752    }
753
754    fn extra_description(&self) -> &str {
755        &self.signature.extra_description
756    }
757
758    fn run(
759        &self,
760        _engine_state: &EngineState,
761        _stack: &mut Stack,
762        _call: &Call,
763        _input: PipelineData,
764    ) -> Result<crate::PipelineData, crate::ShellError> {
765        Err(ShellError::GenericError {
766            error: "Internal error: can't run custom command with 'run', use block_id".into(),
767            msg: "".into(),
768            span: None,
769            help: None,
770            inner: vec![],
771        })
772    }
773
774    fn command_type(&self) -> CommandType {
775        CommandType::Custom
776    }
777
778    fn block_id(&self) -> Option<BlockId> {
779        Some(self.block_id)
780    }
781
782    fn attributes(&self) -> Vec<(String, Value)> {
783        self.attributes.clone()
784    }
785
786    fn examples(&self) -> Vec<Example> {
787        self.examples
788            .iter()
789            .map(CustomExample::to_example)
790            .collect()
791    }
792
793    fn search_terms(&self) -> Vec<&str> {
794        self.signature
795            .search_terms
796            .iter()
797            .map(String::as_str)
798            .collect()
799    }
800
801    fn deprecation_info(&self) -> Vec<DeprecationEntry> {
802        self.attributes
803            .iter()
804            .filter_map(|(key, value)| {
805                (key == "deprecated")
806                    .then_some(value.clone())
807                    .map(DeprecationEntry::from_value)
808                    .and_then(Result::ok)
809            })
810            .collect()
811    }
812}