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
12use 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#[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 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#[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 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#[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 "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#[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 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 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 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 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 pub fn add_help(mut self) -> Signature {
409 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 pub fn build(name: impl Into<String>) -> Signature {
428 Signature::new(name.into()).add_help()
429 }
430
431 pub fn description(mut self, msg: impl Into<String>) -> Signature {
436 self.description = msg.into();
437 self
438 }
439
440 pub fn extra_description(mut self, msg: impl Into<String>) -> Signature {
444 self.extra_description = msg.into();
445 self
446 }
447
448 pub fn search_terms(mut self, terms: Vec<String>) -> Signature {
450 self.search_terms = terms;
451 self
452 }
453
454 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 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 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 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 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 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 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 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 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 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 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 pub fn category(mut self, category: Category) -> Signature {
672 self.category = category;
673
674 self
675 }
676
677 pub fn creates_scope(mut self) -> Signature {
679 self.creates_scope = true;
680 self
681 }
682
683 pub fn allow_variants_without_examples(mut self, allow: bool) -> Signature {
685 self.allow_variants_without_examples = allow;
686 self
687 }
688
689 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 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 one_liner
722 }
723
724 pub fn get_shorts(&self) -> Vec<char> {
726 self.named.iter().filter_map(|f| f.short).collect()
727 }
728
729 pub fn get_names(&self) -> Vec<&str> {
731 self.named.iter().map(|f| f.long.as_str()).collect()
732 }
733
734 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 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 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 total += 1;
787 }
788 }
789 for positional in &self.optional_positional {
790 if let SyntaxShape::Keyword(..) = positional.shape {
791 total += 1;
793 }
794 }
795 total
796 }
797
798 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 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 pub fn filter(mut self) -> Signature {
825 self.is_filter = true;
826 self
827 }
828
829 pub fn predeclare(self) -> Box<dyn Command> {
833 self.predeclare_with_command_type(CommandType::Builtin)
834 }
835
836 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 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}