1use crate::{
2 BlockId, DeclId, DeprecationEntry, Example, FromValue, IntoValue, PipelineData, ShellError,
3 Span, SyntaxShape, Type, Value, VarId,
4 engine::{Call, Command, CommandType, EngineState, Stack},
5 shell_error::generic::GenericError,
6};
7use nu_derive_value::FromValue as DeriveFromValue;
8use nu_utils::NuCow;
9use serde::{Deserialize, Serialize};
10use std::fmt::Write;
11
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 {
376 match self.input_output_types.len() {
377 0 => Type::Any,
378 1 => self.input_output_types[0].0.clone(),
379 _ => {
380 let first = &self.input_output_types[0].0;
381 if self
382 .input_output_types
383 .iter()
384 .all(|(input, _)| input == first)
385 {
386 first.clone()
387 } else {
388 Type::Any
389 }
390 }
391 }
392 }
393
394 pub fn get_output_type(&self) -> Type {
401 match self.input_output_types.len() {
402 0 => Type::Any,
403 1 => self.input_output_types[0].1.clone(),
404 _ => {
405 let first = &self.input_output_types[0].1;
406 if self
407 .input_output_types
408 .iter()
409 .all(|(_, output)| output == first)
410 {
411 first.clone()
412 } else {
413 Type::Any
414 }
415 }
416 }
417 }
418
419 pub fn add_help(mut self) -> Signature {
421 let flag = Flag {
423 long: "help".into(),
424 short: Some('h'),
425 arg: None,
426 desc: "Display the help message for this command".into(),
427 required: false,
428 var_id: None,
429 default_value: None,
430 completion: None,
431 };
432 self.named.push(flag);
433 self
434 }
435
436 pub fn build(name: impl Into<String>) -> Signature {
440 Signature::new(name.into()).add_help()
441 }
442
443 pub fn description(mut self, msg: impl Into<String>) -> Signature {
448 self.description = msg.into();
449 self
450 }
451
452 pub fn extra_description(mut self, msg: impl Into<String>) -> Signature {
456 self.extra_description = msg.into();
457 self
458 }
459
460 pub fn search_terms(mut self, terms: Vec<String>) -> Signature {
462 self.search_terms = terms;
463 self
464 }
465
466 pub fn update_from_command(mut self, command: &dyn Command) -> Signature {
468 self.search_terms = command
469 .search_terms()
470 .into_iter()
471 .map(|term| term.to_string())
472 .collect();
473 self.extra_description = command.extra_description().to_string();
474 self.description = command.description().to_string();
475 self
476 }
477
478 pub fn allows_unknown_args(mut self) -> Signature {
480 self.allows_unknown_args = true;
481 self
482 }
483
484 pub fn param(mut self, param: impl Into<Parameter>) -> Self {
485 let param: Parameter = param.into();
486 match param {
487 Parameter::Flag(flag) => {
488 if let Some(s) = flag.short {
489 assert!(
490 !self.get_shorts().contains(&s),
491 "There may be duplicate short flags for '-{s}'"
492 );
493 }
494
495 let name = flag.long.as_str();
496 assert!(
497 !self.get_names().contains(&name),
498 "There may be duplicate name flags for '--{name}'"
499 );
500
501 self.named.push(flag);
502 }
503 Parameter::Required(positional_arg) => {
504 self.required_positional.push(positional_arg);
505 }
506 Parameter::Optional(positional_arg) => {
507 self.optional_positional.push(positional_arg);
508 }
509 Parameter::Rest(positional_arg) => {
510 assert!(
511 self.rest_positional.is_none(),
512 "Tried to set rest arguments more than once"
513 );
514 self.rest_positional = Some(positional_arg);
515 }
516 }
517 self
518 }
519
520 pub fn required(
522 mut self,
523 name: impl Into<String>,
524 shape: impl Into<SyntaxShape>,
525 desc: impl Into<String>,
526 ) -> Signature {
527 self.required_positional.push(PositionalArg {
528 name: name.into(),
529 desc: desc.into(),
530 shape: shape.into(),
531 var_id: None,
532 default_value: None,
533 completion: None,
534 });
535
536 self
537 }
538
539 pub fn optional(
541 mut self,
542 name: impl Into<String>,
543 shape: impl Into<SyntaxShape>,
544 desc: impl Into<String>,
545 ) -> Signature {
546 self.optional_positional.push(PositionalArg {
547 name: name.into(),
548 desc: desc.into(),
549 shape: shape.into(),
550 var_id: None,
551 default_value: None,
552 completion: None,
553 });
554
555 self
556 }
557
558 pub fn rest(
566 mut self,
567 name: &str,
568 shape: impl Into<SyntaxShape>,
569 desc: impl Into<String>,
570 ) -> Signature {
571 self.rest_positional = Some(PositionalArg {
572 name: name.into(),
573 desc: desc.into(),
574 shape: shape.into(),
575 var_id: None,
576 default_value: None,
577 completion: None,
578 });
579
580 self
581 }
582
583 pub fn operates_on_cell_paths(&self) -> bool {
585 self.required_positional
586 .iter()
587 .chain(self.rest_positional.iter())
588 .any(|pos| {
589 matches!(
590 pos,
591 PositionalArg {
592 shape: SyntaxShape::CellPath,
593 ..
594 }
595 )
596 })
597 }
598
599 pub fn named(
601 mut self,
602 name: impl Into<String>,
603 shape: impl Into<SyntaxShape>,
604 desc: impl Into<String>,
605 short: Option<char>,
606 ) -> Signature {
607 let (name, s) = self.check_names(name, short);
608
609 self.named.push(Flag {
610 long: name,
611 short: s,
612 arg: Some(shape.into()),
613 required: false,
614 desc: desc.into(),
615 var_id: None,
616 default_value: None,
617 completion: None,
618 });
619
620 self
621 }
622
623 pub fn required_named(
625 mut self,
626 name: impl Into<String>,
627 shape: impl Into<SyntaxShape>,
628 desc: impl Into<String>,
629 short: Option<char>,
630 ) -> Signature {
631 let (name, s) = self.check_names(name, short);
632
633 self.named.push(Flag {
634 long: name,
635 short: s,
636 arg: Some(shape.into()),
637 required: true,
638 desc: desc.into(),
639 var_id: None,
640 default_value: None,
641 completion: None,
642 });
643
644 self
645 }
646
647 pub fn switch(
649 mut self,
650 name: impl Into<String>,
651 desc: impl Into<String>,
652 short: Option<char>,
653 ) -> Signature {
654 let (name, s) = self.check_names(name, short);
655
656 self.named.push(Flag {
657 long: name,
658 short: s,
659 arg: None,
660 required: false,
661 desc: desc.into(),
662 var_id: None,
663 default_value: None,
664 completion: None,
665 });
666
667 self
668 }
669
670 pub fn input_output_type(mut self, input_type: Type, output_type: Type) -> Signature {
672 self.input_output_types.push((input_type, output_type));
673 self
674 }
675
676 pub fn input_output_types(mut self, input_output_types: Vec<(Type, Type)>) -> Signature {
678 self.input_output_types = input_output_types;
679 self
680 }
681
682 pub fn category(mut self, category: Category) -> Signature {
684 self.category = category;
685
686 self
687 }
688
689 pub fn creates_scope(mut self) -> Signature {
691 self.creates_scope = true;
692 self
693 }
694
695 pub fn allow_variants_without_examples(mut self, allow: bool) -> Signature {
697 self.allow_variants_without_examples = allow;
698 self
699 }
700
701 pub fn call_signature(&self) -> String {
706 let mut one_liner = String::new();
707 one_liner.push_str(&self.name);
708 one_liner.push(' ');
709
710 if self.named.len() > 1 {
715 one_liner.push_str("{flags} ");
716 }
717
718 for positional in &self.required_positional {
719 one_liner.push_str(&get_positional_short_name(positional, true));
720 }
721 for positional in &self.optional_positional {
722 one_liner.push_str(&get_positional_short_name(positional, false));
723 }
724
725 if let Some(rest) = &self.rest_positional {
726 let _ = write!(one_liner, "...{}", get_positional_short_name(rest, false));
727 }
728
729 one_liner
734 }
735
736 pub fn get_shorts(&self) -> Vec<char> {
738 self.named.iter().filter_map(|f| f.short).collect()
739 }
740
741 pub fn get_names(&self) -> Vec<&str> {
743 self.named.iter().map(|f| f.long.as_str()).collect()
744 }
745
746 fn check_names(&self, name: impl Into<String>, short: Option<char>) -> (String, Option<char>) {
753 let s = short.inspect(|c| {
754 assert!(
755 !self.get_shorts().contains(c),
756 "There may be duplicate short flags for '-{c}'"
757 );
758 });
759
760 let name = {
761 let name: String = name.into();
762 assert!(
763 !self.get_names().contains(&name.as_str()),
764 "There may be duplicate name flags for '--{name}'"
765 );
766 name
767 };
768
769 (name, s)
770 }
771
772 pub fn get_positional(&self, position: usize) -> Option<&PositionalArg> {
779 if position < self.required_positional.len() {
780 self.required_positional.get(position)
781 } else if position < (self.required_positional.len() + self.optional_positional.len()) {
782 self.optional_positional
783 .get(position - self.required_positional.len())
784 } else {
785 self.rest_positional.as_ref()
786 }
787 }
788
789 pub fn num_positionals(&self) -> usize {
793 let mut total = self.required_positional.len() + self.optional_positional.len();
794
795 for positional in &self.required_positional {
796 if let SyntaxShape::Keyword(..) = positional.shape {
797 total += 1;
799 }
800 }
801 for positional in &self.optional_positional {
802 if let SyntaxShape::Keyword(..) = positional.shape {
803 total += 1;
805 }
806 }
807 total
808 }
809
810 pub fn get_long_flag(&self, name: &str) -> Option<Flag> {
812 if name.is_empty() {
813 return None;
814 }
815 for flag in &self.named {
816 if flag.long == name {
817 return Some(flag.clone());
818 }
819 }
820 None
821 }
822
823 pub fn get_short_flag(&self, short: char) -> Option<Flag> {
825 for flag in &self.named {
826 if let Some(short_flag) = &flag.short
827 && *short_flag == short
828 {
829 return Some(flag.clone());
830 }
831 }
832 None
833 }
834
835 pub fn filter(mut self) -> Signature {
837 self.is_filter = true;
838 self
839 }
840
841 pub fn predeclare(self) -> Box<dyn Command> {
845 self.predeclare_with_command_type(CommandType::Builtin)
846 }
847
848 pub fn predeclare_with_command_type(self, command_type: CommandType) -> Box<dyn Command> {
851 Box::new(Predeclaration {
852 signature: self,
853 command_type,
854 })
855 }
856
857 pub fn into_block_command(
859 self,
860 block_id: BlockId,
861 attributes: Vec<(String, Value)>,
862 examples: Vec<CustomExample>,
863 ) -> Box<dyn Command> {
864 Box::new(BlockCommand {
865 signature: self,
866 block_id,
867 attributes,
868 examples,
869 })
870 }
871}
872
873#[derive(Clone)]
874struct Predeclaration {
875 signature: Signature,
876 command_type: CommandType,
877}
878
879impl Command for Predeclaration {
880 fn name(&self) -> &str {
881 &self.signature.name
882 }
883
884 fn signature(&self) -> Signature {
885 self.signature.clone()
886 }
887
888 fn description(&self) -> &str {
889 &self.signature.description
890 }
891
892 fn extra_description(&self) -> &str {
893 &self.signature.extra_description
894 }
895
896 fn run(
897 &self,
898 _engine_state: &EngineState,
899 _stack: &mut Stack,
900 _call: &Call,
901 _input: PipelineData,
902 ) -> Result<PipelineData, crate::ShellError> {
903 panic!("Internal error: can't run a predeclaration without a body")
904 }
905
906 fn command_type(&self) -> CommandType {
907 self.command_type
908 }
909}
910
911fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String {
912 match &arg.shape {
913 SyntaxShape::Keyword(name, ..) => {
914 if is_required {
915 format!("{} <{}> ", String::from_utf8_lossy(name), arg.name)
916 } else {
917 format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name)
918 }
919 }
920 _ => {
921 if is_required {
922 format!("<{}> ", arg.name)
923 } else {
924 format!("({}) ", arg.name)
925 }
926 }
927 }
928}
929
930#[derive(Clone, DeriveFromValue)]
931pub struct CustomExample {
932 pub example: String,
933 pub description: String,
934 pub result: Option<Value>,
935}
936
937impl CustomExample {
938 pub fn to_example(&self) -> Example<'_> {
939 Example {
940 example: self.example.as_str(),
941 description: self.description.as_str(),
942 result: self.result.clone(),
943 }
944 }
945}
946
947#[derive(Clone)]
948struct BlockCommand {
949 signature: Signature,
950 block_id: BlockId,
951 attributes: Vec<(String, Value)>,
952 examples: Vec<CustomExample>,
953}
954
955impl Command for BlockCommand {
956 fn name(&self) -> &str {
957 &self.signature.name
958 }
959
960 fn signature(&self) -> Signature {
961 self.signature.clone()
962 }
963
964 fn description(&self) -> &str {
965 &self.signature.description
966 }
967
968 fn extra_description(&self) -> &str {
969 &self.signature.extra_description
970 }
971
972 fn run(
973 &self,
974 _engine_state: &EngineState,
975 _stack: &mut Stack,
976 _call: &Call,
977 _input: PipelineData,
978 ) -> Result<crate::PipelineData, crate::ShellError> {
979 Err(ShellError::Generic(GenericError::new_internal(
980 "Internal error: can't run custom command with 'run', use block_id",
981 "",
982 )))
983 }
984
985 fn command_type(&self) -> CommandType {
986 CommandType::Custom
987 }
988
989 fn block_id(&self) -> Option<BlockId> {
990 Some(self.block_id)
991 }
992
993 fn attributes(&self) -> Vec<(String, Value)> {
994 self.attributes.clone()
995 }
996
997 fn examples(&self) -> Vec<Example<'_>> {
998 self.examples
999 .iter()
1000 .map(CustomExample::to_example)
1001 .collect()
1002 }
1003
1004 fn search_terms(&self) -> Vec<&str> {
1005 self.signature
1006 .search_terms
1007 .iter()
1008 .map(String::as_str)
1009 .collect()
1010 }
1011
1012 fn deprecation_info(&self) -> Vec<DeprecationEntry> {
1013 self.attributes
1014 .iter()
1015 .filter_map(|(key, value)| {
1016 (key == "deprecated")
1017 .then_some(value.clone())
1018 .map(DeprecationEntry::from_value)
1019 .and_then(Result::ok)
1020 })
1021 .collect()
1022 }
1023}