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> {
777 if position < self.required_positional.len() {
778 self.required_positional.get(position)
779 } else if position < (self.required_positional.len() + self.optional_positional.len()) {
780 self.optional_positional
781 .get(position - self.required_positional.len())
782 } else {
783 self.rest_positional.as_ref()
784 }
785 }
786
787 pub fn num_positionals(&self) -> usize {
791 let mut total = self.required_positional.len() + self.optional_positional.len();
792
793 for positional in &self.required_positional {
794 if let SyntaxShape::Keyword(..) = positional.shape {
795 total += 1;
797 }
798 }
799 for positional in &self.optional_positional {
800 if let SyntaxShape::Keyword(..) = positional.shape {
801 total += 1;
803 }
804 }
805 total
806 }
807
808 pub fn get_long_flag(&self, name: &str) -> Option<Flag> {
810 if name.is_empty() {
811 return None;
812 }
813 for flag in &self.named {
814 if flag.long == name {
815 return Some(flag.clone());
816 }
817 }
818 None
819 }
820
821 pub fn get_short_flag(&self, short: char) -> Option<Flag> {
823 for flag in &self.named {
824 if let Some(short_flag) = &flag.short
825 && *short_flag == short
826 {
827 return Some(flag.clone());
828 }
829 }
830 None
831 }
832
833 pub fn filter(mut self) -> Signature {
835 self.is_filter = true;
836 self
837 }
838
839 pub fn predeclare(self) -> Box<dyn Command> {
843 Box::new(Predeclaration { signature: self })
844 }
845
846 pub fn into_block_command(
848 self,
849 block_id: BlockId,
850 attributes: Vec<(String, Value)>,
851 examples: Vec<CustomExample>,
852 ) -> Box<dyn Command> {
853 Box::new(BlockCommand {
854 signature: self,
855 block_id,
856 attributes,
857 examples,
858 })
859 }
860}
861
862#[derive(Clone)]
863struct Predeclaration {
864 signature: Signature,
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
895fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String {
896 match &arg.shape {
897 SyntaxShape::Keyword(name, ..) => {
898 if is_required {
899 format!("{} <{}> ", String::from_utf8_lossy(name), arg.name)
900 } else {
901 format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name)
902 }
903 }
904 _ => {
905 if is_required {
906 format!("<{}> ", arg.name)
907 } else {
908 format!("({}) ", arg.name)
909 }
910 }
911 }
912}
913
914#[derive(Clone, DeriveFromValue)]
915pub struct CustomExample {
916 pub example: String,
917 pub description: String,
918 pub result: Option<Value>,
919}
920
921impl CustomExample {
922 pub fn to_example(&self) -> Example<'_> {
923 Example {
924 example: self.example.as_str(),
925 description: self.description.as_str(),
926 result: self.result.clone(),
927 }
928 }
929}
930
931#[derive(Clone)]
932struct BlockCommand {
933 signature: Signature,
934 block_id: BlockId,
935 attributes: Vec<(String, Value)>,
936 examples: Vec<CustomExample>,
937}
938
939impl Command for BlockCommand {
940 fn name(&self) -> &str {
941 &self.signature.name
942 }
943
944 fn signature(&self) -> Signature {
945 self.signature.clone()
946 }
947
948 fn description(&self) -> &str {
949 &self.signature.description
950 }
951
952 fn extra_description(&self) -> &str {
953 &self.signature.extra_description
954 }
955
956 fn run(
957 &self,
958 _engine_state: &EngineState,
959 _stack: &mut Stack,
960 _call: &Call,
961 _input: PipelineData,
962 ) -> Result<crate::PipelineData, crate::ShellError> {
963 Err(ShellError::Generic(GenericError::new_internal(
964 "Internal error: can't run custom command with 'run', use block_id",
965 "",
966 )))
967 }
968
969 fn command_type(&self) -> CommandType {
970 CommandType::Custom
971 }
972
973 fn block_id(&self) -> Option<BlockId> {
974 Some(self.block_id)
975 }
976
977 fn attributes(&self) -> Vec<(String, Value)> {
978 self.attributes.clone()
979 }
980
981 fn examples(&self) -> Vec<Example<'_>> {
982 self.examples
983 .iter()
984 .map(CustomExample::to_example)
985 .collect()
986 }
987
988 fn search_terms(&self) -> Vec<&str> {
989 self.signature
990 .search_terms
991 .iter()
992 .map(String::as_str)
993 .collect()
994 }
995
996 fn deprecation_info(&self) -> Vec<DeprecationEntry> {
997 self.attributes
998 .iter()
999 .filter_map(|(key, value)| {
1000 (key == "deprecated")
1001 .then_some(value.clone())
1002 .map(DeprecationEntry::from_value)
1003 .and_then(Result::ok)
1004 })
1005 .collect()
1006 }
1007}