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};
6use nu_derive_value::FromValue as DeriveFromValue;
7use nu_utils::NuCow;
8use serde::{Deserialize, Serialize};
9use std::fmt::Write;
10
11use crate as nu_protocol;
15
16pub enum Parameter {
17 Required(PositionalArg),
18 Optional(PositionalArg),
19 Rest(PositionalArg),
20 Flag(Flag),
21}
22
23impl From<Flag> for Parameter {
24 fn from(value: Flag) -> Self {
25 Self::Flag(value)
26 }
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct Flag {
32 pub long: String,
33 pub short: Option<char>,
34 pub arg: Option<SyntaxShape>,
35 pub required: bool,
36 pub desc: String,
37 pub completion: Option<Completion>,
38
39 pub var_id: Option<VarId>,
41 pub default_value: Option<Value>,
42}
43
44impl Flag {
45 #[inline]
46 pub fn new(long: impl Into<String>) -> Self {
47 Flag {
48 long: long.into(),
49 short: None,
50 arg: None,
51 required: false,
52 desc: String::new(),
53 completion: None,
54 var_id: None,
55 default_value: None,
56 }
57 }
58
59 #[inline]
60 pub fn short(self, short: char) -> Self {
61 Self {
62 short: Some(short),
63 ..self
64 }
65 }
66
67 #[inline]
68 pub fn arg(self, arg: SyntaxShape) -> Self {
69 Self {
70 arg: Some(arg),
71 ..self
72 }
73 }
74
75 #[inline]
76 pub fn required(self) -> Self {
77 Self {
78 required: true,
79 ..self
80 }
81 }
82
83 #[inline]
84 pub fn desc(self, desc: impl Into<String>) -> Self {
85 Self {
86 desc: desc.into(),
87 ..self
88 }
89 }
90
91 #[inline]
92 pub fn completion(self, completion: Completion) -> Self {
93 Self {
94 completion: Some(completion),
95 ..self
96 }
97 }
98}
99
100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
102pub struct PositionalArg {
103 pub name: String,
104 pub desc: String,
105 pub shape: SyntaxShape,
106 pub completion: Option<Completion>,
107
108 pub var_id: Option<VarId>,
110 pub default_value: Option<Value>,
111}
112
113impl PositionalArg {
114 #[inline]
115 pub fn new(name: impl Into<String>, shape: SyntaxShape) -> Self {
116 Self {
117 name: name.into(),
118 desc: String::new(),
119 shape,
120 completion: None,
121 var_id: None,
122 default_value: None,
123 }
124 }
125
126 #[inline]
127 pub fn desc(self, desc: impl Into<String>) -> Self {
128 Self {
129 desc: desc.into(),
130 ..self
131 }
132 }
133
134 #[inline]
135 pub fn completion(self, completion: Completion) -> Self {
136 Self {
137 completion: Some(completion),
138 ..self
139 }
140 }
141
142 #[inline]
143 pub fn required(self) -> Parameter {
144 Parameter::Required(self)
145 }
146
147 #[inline]
148 pub fn optional(self) -> Parameter {
149 Parameter::Optional(self)
150 }
151
152 #[inline]
153 pub fn rest(self) -> Parameter {
154 Parameter::Rest(self)
155 }
156}
157
158#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
159pub enum CommandWideCompleter {
160 External,
161 Command(DeclId),
162}
163
164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
165pub enum Completion {
166 Command(DeclId),
167 List(NuCow<&'static [&'static str], Vec<String>>),
168}
169
170impl Completion {
171 pub const fn new_list(list: &'static [&'static str]) -> Self {
172 Self::List(NuCow::Borrowed(list))
173 }
174
175 pub fn to_value(&self, engine_state: &EngineState, span: Span) -> Value {
176 match self {
177 Completion::Command(id) => engine_state
178 .get_decl(*id)
179 .name()
180 .to_owned()
181 .into_value(span),
182 Completion::List(list) => match list {
183 NuCow::Borrowed(list) => list
184 .iter()
185 .map(|&e| e.into_value(span))
186 .collect::<Vec<Value>>()
187 .into_value(span),
188 NuCow::Owned(list) => list
189 .iter()
190 .cloned()
191 .map(|e| e.into_value(span))
192 .collect::<Vec<Value>>()
193 .into_value(span),
194 },
195 }
196 }
197}
198
199#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
201pub enum Category {
202 Bits,
203 Bytes,
204 Chart,
205 Conversions,
206 Core,
207 Custom(String),
208 Database,
209 Date,
210 Debug,
211 Default,
212 Deprecated,
213 Removed,
214 Env,
215 Experimental,
216 FileSystem,
217 Filters,
218 Formats,
219 Generators,
220 Hash,
221 History,
222 Math,
223 Misc,
224 Network,
225 Path,
226 Platform,
227 Plugin,
228 Random,
229 Shells,
230 Strings,
231 System,
232 Viewers,
233}
234
235impl std::fmt::Display for Category {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 let msg = match self {
238 Category::Bits => "bits",
239 Category::Bytes => "bytes",
240 Category::Chart => "chart",
241 Category::Conversions => "conversions",
242 Category::Core => "core",
243 Category::Custom(name) => name,
244 Category::Database => "database",
245 Category::Date => "date",
246 Category::Debug => "debug",
247 Category::Default => "default",
248 Category::Deprecated => "deprecated",
249 Category::Removed => "removed",
250 Category::Env => "env",
251 Category::Experimental => "experimental",
252 Category::FileSystem => "filesystem",
253 Category::Filters => "filters",
254 Category::Formats => "formats",
255 Category::Generators => "generators",
256 Category::Hash => "hash",
257 Category::History => "history",
258 Category::Math => "math",
259 Category::Misc => "misc",
260 Category::Network => "network",
261 Category::Path => "path",
262 Category::Platform => "platform",
263 Category::Plugin => "plugin",
264 Category::Random => "random",
265 Category::Shells => "shells",
266 Category::Strings => "strings",
267 Category::System => "system",
268 Category::Viewers => "viewers",
269 };
270
271 write!(f, "{msg}")
272 }
273}
274
275pub fn category_from_string(category: &str) -> Category {
276 match category {
277 "bits" => Category::Bits,
278 "bytes" => Category::Bytes,
279 "chart" => Category::Chart,
280 "conversions" => Category::Conversions,
281 "core" => Category::Custom("custom_core".to_string()),
283 "database" => Category::Database,
284 "date" => Category::Date,
285 "debug" => Category::Debug,
286 "default" => Category::Default,
287 "deprecated" => Category::Deprecated,
288 "removed" => Category::Removed,
289 "env" => Category::Env,
290 "experimental" => Category::Experimental,
291 "filesystem" => Category::FileSystem,
292 "filter" => Category::Filters,
293 "formats" => Category::Formats,
294 "generators" => Category::Generators,
295 "hash" => Category::Hash,
296 "history" => Category::History,
297 "math" => Category::Math,
298 "misc" => Category::Misc,
299 "network" => Category::Network,
300 "path" => Category::Path,
301 "platform" => Category::Platform,
302 "plugin" => Category::Plugin,
303 "random" => Category::Random,
304 "shells" => Category::Shells,
305 "strings" => Category::Strings,
306 "system" => Category::System,
307 "viewers" => Category::Viewers,
308 _ => Category::Custom(category.to_string()),
309 }
310}
311
312#[derive(Clone, Debug, Serialize, Deserialize)]
314pub struct Signature {
315 pub name: String,
316 pub description: String,
317 pub extra_description: String,
318 pub search_terms: Vec<String>,
319 pub required_positional: Vec<PositionalArg>,
320 pub optional_positional: Vec<PositionalArg>,
321 pub rest_positional: Option<PositionalArg>,
322 pub named: Vec<Flag>,
323 pub input_output_types: Vec<(Type, Type)>,
324 pub allow_variants_without_examples: bool,
325 pub is_filter: bool,
326 pub creates_scope: bool,
327 pub allows_unknown_args: bool,
328 pub complete: Option<CommandWideCompleter>,
329 pub category: Category,
331}
332
333impl PartialEq for Signature {
334 fn eq(&self, other: &Self) -> bool {
335 self.name == other.name
336 && self.description == other.description
337 && self.required_positional == other.required_positional
338 && self.optional_positional == other.optional_positional
339 && self.rest_positional == other.rest_positional
340 && self.is_filter == other.is_filter
341 }
342}
343
344impl Eq for Signature {}
345
346impl Signature {
347 pub fn new(name: impl Into<String>) -> Signature {
349 Signature {
350 name: name.into(),
351 description: String::new(),
352 extra_description: String::new(),
353 search_terms: vec![],
354 required_positional: vec![],
355 optional_positional: vec![],
356 rest_positional: None,
357 input_output_types: vec![],
358 allow_variants_without_examples: false,
359 named: vec![],
360 is_filter: false,
361 creates_scope: false,
362 category: Category::Default,
363 allows_unknown_args: false,
364 complete: None,
365 }
366 }
367
368 pub fn get_input_type(&self) -> Type {
375 match self.input_output_types.len() {
376 0 => Type::Any,
377 1 => self.input_output_types[0].0.clone(),
378 _ => {
379 let first = &self.input_output_types[0].0;
380 if self
381 .input_output_types
382 .iter()
383 .all(|(input, _)| input == first)
384 {
385 first.clone()
386 } else {
387 Type::Any
388 }
389 }
390 }
391 }
392
393 pub fn get_output_type(&self) -> Type {
400 match self.input_output_types.len() {
401 0 => Type::Any,
402 1 => self.input_output_types[0].1.clone(),
403 _ => {
404 let first = &self.input_output_types[0].1;
405 if self
406 .input_output_types
407 .iter()
408 .all(|(_, output)| output == first)
409 {
410 first.clone()
411 } else {
412 Type::Any
413 }
414 }
415 }
416 }
417
418 pub fn add_help(mut self) -> Signature {
420 let flag = Flag {
422 long: "help".into(),
423 short: Some('h'),
424 arg: None,
425 desc: "Display the help message for this command".into(),
426 required: false,
427 var_id: None,
428 default_value: None,
429 completion: None,
430 };
431 self.named.push(flag);
432 self
433 }
434
435 pub fn build(name: impl Into<String>) -> Signature {
439 Signature::new(name.into()).add_help()
440 }
441
442 pub fn description(mut self, msg: impl Into<String>) -> Signature {
447 self.description = msg.into();
448 self
449 }
450
451 pub fn extra_description(mut self, msg: impl Into<String>) -> Signature {
455 self.extra_description = msg.into();
456 self
457 }
458
459 pub fn search_terms(mut self, terms: Vec<String>) -> Signature {
461 self.search_terms = terms;
462 self
463 }
464
465 pub fn update_from_command(mut self, command: &dyn Command) -> Signature {
467 self.search_terms = command
468 .search_terms()
469 .into_iter()
470 .map(|term| term.to_string())
471 .collect();
472 self.extra_description = command.extra_description().to_string();
473 self.description = command.description().to_string();
474 self
475 }
476
477 pub fn allows_unknown_args(mut self) -> Signature {
479 self.allows_unknown_args = true;
480 self
481 }
482
483 pub fn param(mut self, param: impl Into<Parameter>) -> Self {
484 let param: Parameter = param.into();
485 match param {
486 Parameter::Flag(flag) => {
487 if let Some(s) = flag.short {
488 assert!(
489 !self.get_shorts().contains(&s),
490 "There may be duplicate short flags for '-{s}'"
491 );
492 }
493
494 let name = flag.long.as_str();
495 assert!(
496 !self.get_names().contains(&name),
497 "There may be duplicate name flags for '--{name}'"
498 );
499
500 self.named.push(flag);
501 }
502 Parameter::Required(positional_arg) => {
503 self.required_positional.push(positional_arg);
504 }
505 Parameter::Optional(positional_arg) => {
506 self.optional_positional.push(positional_arg);
507 }
508 Parameter::Rest(positional_arg) => {
509 assert!(
510 self.rest_positional.is_none(),
511 "Tried to set rest arguments more than once"
512 );
513 self.rest_positional = Some(positional_arg);
514 }
515 }
516 self
517 }
518
519 pub fn required(
521 mut self,
522 name: impl Into<String>,
523 shape: impl Into<SyntaxShape>,
524 desc: impl Into<String>,
525 ) -> Signature {
526 self.required_positional.push(PositionalArg {
527 name: name.into(),
528 desc: desc.into(),
529 shape: shape.into(),
530 var_id: None,
531 default_value: None,
532 completion: None,
533 });
534
535 self
536 }
537
538 pub fn optional(
540 mut self,
541 name: impl Into<String>,
542 shape: impl Into<SyntaxShape>,
543 desc: impl Into<String>,
544 ) -> Signature {
545 self.optional_positional.push(PositionalArg {
546 name: name.into(),
547 desc: desc.into(),
548 shape: shape.into(),
549 var_id: None,
550 default_value: None,
551 completion: None,
552 });
553
554 self
555 }
556
557 pub fn rest(
565 mut self,
566 name: &str,
567 shape: impl Into<SyntaxShape>,
568 desc: impl Into<String>,
569 ) -> Signature {
570 self.rest_positional = Some(PositionalArg {
571 name: name.into(),
572 desc: desc.into(),
573 shape: shape.into(),
574 var_id: None,
575 default_value: None,
576 completion: None,
577 });
578
579 self
580 }
581
582 pub fn operates_on_cell_paths(&self) -> bool {
584 self.required_positional
585 .iter()
586 .chain(self.rest_positional.iter())
587 .any(|pos| {
588 matches!(
589 pos,
590 PositionalArg {
591 shape: SyntaxShape::CellPath,
592 ..
593 }
594 )
595 })
596 }
597
598 pub fn named(
600 mut self,
601 name: impl Into<String>,
602 shape: impl Into<SyntaxShape>,
603 desc: impl Into<String>,
604 short: Option<char>,
605 ) -> Signature {
606 let (name, s) = self.check_names(name, short);
607
608 self.named.push(Flag {
609 long: name,
610 short: s,
611 arg: Some(shape.into()),
612 required: false,
613 desc: desc.into(),
614 var_id: None,
615 default_value: None,
616 completion: None,
617 });
618
619 self
620 }
621
622 pub fn required_named(
624 mut self,
625 name: impl Into<String>,
626 shape: impl Into<SyntaxShape>,
627 desc: impl Into<String>,
628 short: Option<char>,
629 ) -> Signature {
630 let (name, s) = self.check_names(name, short);
631
632 self.named.push(Flag {
633 long: name,
634 short: s,
635 arg: Some(shape.into()),
636 required: true,
637 desc: desc.into(),
638 var_id: None,
639 default_value: None,
640 completion: None,
641 });
642
643 self
644 }
645
646 pub fn switch(
648 mut self,
649 name: impl Into<String>,
650 desc: impl Into<String>,
651 short: Option<char>,
652 ) -> Signature {
653 let (name, s) = self.check_names(name, short);
654
655 self.named.push(Flag {
656 long: name,
657 short: s,
658 arg: None,
659 required: false,
660 desc: desc.into(),
661 var_id: None,
662 default_value: None,
663 completion: None,
664 });
665
666 self
667 }
668
669 pub fn input_output_type(mut self, input_type: Type, output_type: Type) -> Signature {
671 self.input_output_types.push((input_type, output_type));
672 self
673 }
674
675 pub fn input_output_types(mut self, input_output_types: Vec<(Type, Type)>) -> Signature {
677 self.input_output_types = input_output_types;
678 self
679 }
680
681 pub fn category(mut self, category: Category) -> Signature {
683 self.category = category;
684
685 self
686 }
687
688 pub fn creates_scope(mut self) -> Signature {
690 self.creates_scope = true;
691 self
692 }
693
694 pub fn allow_variants_without_examples(mut self, allow: bool) -> Signature {
696 self.allow_variants_without_examples = allow;
697 self
698 }
699
700 pub fn call_signature(&self) -> String {
705 let mut one_liner = String::new();
706 one_liner.push_str(&self.name);
707 one_liner.push(' ');
708
709 if self.named.len() > 1 {
714 one_liner.push_str("{flags} ");
715 }
716
717 for positional in &self.required_positional {
718 one_liner.push_str(&get_positional_short_name(positional, true));
719 }
720 for positional in &self.optional_positional {
721 one_liner.push_str(&get_positional_short_name(positional, false));
722 }
723
724 if let Some(rest) = &self.rest_positional {
725 let _ = write!(one_liner, "...{}", get_positional_short_name(rest, false));
726 }
727
728 one_liner
733 }
734
735 pub fn get_shorts(&self) -> Vec<char> {
737 self.named.iter().filter_map(|f| f.short).collect()
738 }
739
740 pub fn get_names(&self) -> Vec<&str> {
742 self.named.iter().map(|f| f.long.as_str()).collect()
743 }
744
745 fn check_names(&self, name: impl Into<String>, short: Option<char>) -> (String, Option<char>) {
752 let s = short.inspect(|c| {
753 assert!(
754 !self.get_shorts().contains(c),
755 "There may be duplicate short flags for '-{c}'"
756 );
757 });
758
759 let name = {
760 let name: String = name.into();
761 assert!(
762 !self.get_names().contains(&name.as_str()),
763 "There may be duplicate name flags for '--{name}'"
764 );
765 name
766 };
767
768 (name, s)
769 }
770
771 pub fn get_positional(&self, position: usize) -> Option<&PositionalArg> {
776 if position < self.required_positional.len() {
777 self.required_positional.get(position)
778 } else if position < (self.required_positional.len() + self.optional_positional.len()) {
779 self.optional_positional
780 .get(position - self.required_positional.len())
781 } else {
782 self.rest_positional.as_ref()
783 }
784 }
785
786 pub fn num_positionals(&self) -> usize {
790 let mut total = self.required_positional.len() + self.optional_positional.len();
791
792 for positional in &self.required_positional {
793 if let SyntaxShape::Keyword(..) = positional.shape {
794 total += 1;
796 }
797 }
798 for positional in &self.optional_positional {
799 if let SyntaxShape::Keyword(..) = positional.shape {
800 total += 1;
802 }
803 }
804 total
805 }
806
807 pub fn get_long_flag(&self, name: &str) -> Option<Flag> {
809 for flag in &self.named {
810 if flag.long == name {
811 return Some(flag.clone());
812 }
813 }
814 None
815 }
816
817 pub fn get_short_flag(&self, short: char) -> Option<Flag> {
819 for flag in &self.named {
820 if let Some(short_flag) = &flag.short
821 && *short_flag == short
822 {
823 return Some(flag.clone());
824 }
825 }
826 None
827 }
828
829 pub fn filter(mut self) -> Signature {
831 self.is_filter = true;
832 self
833 }
834
835 pub fn predeclare(self) -> Box<dyn Command> {
839 Box::new(Predeclaration { signature: self })
840 }
841
842 pub fn into_block_command(
844 self,
845 block_id: BlockId,
846 attributes: Vec<(String, Value)>,
847 examples: Vec<CustomExample>,
848 ) -> Box<dyn Command> {
849 Box::new(BlockCommand {
850 signature: self,
851 block_id,
852 attributes,
853 examples,
854 })
855 }
856}
857
858#[derive(Clone)]
859struct Predeclaration {
860 signature: Signature,
861}
862
863impl Command for Predeclaration {
864 fn name(&self) -> &str {
865 &self.signature.name
866 }
867
868 fn signature(&self) -> Signature {
869 self.signature.clone()
870 }
871
872 fn description(&self) -> &str {
873 &self.signature.description
874 }
875
876 fn extra_description(&self) -> &str {
877 &self.signature.extra_description
878 }
879
880 fn run(
881 &self,
882 _engine_state: &EngineState,
883 _stack: &mut Stack,
884 _call: &Call,
885 _input: PipelineData,
886 ) -> Result<PipelineData, crate::ShellError> {
887 panic!("Internal error: can't run a predeclaration without a body")
888 }
889}
890
891fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String {
892 match &arg.shape {
893 SyntaxShape::Keyword(name, ..) => {
894 if is_required {
895 format!("{} <{}> ", String::from_utf8_lossy(name), arg.name)
896 } else {
897 format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name)
898 }
899 }
900 _ => {
901 if is_required {
902 format!("<{}> ", arg.name)
903 } else {
904 format!("({}) ", arg.name)
905 }
906 }
907 }
908}
909
910#[derive(Clone, DeriveFromValue)]
911pub struct CustomExample {
912 pub example: String,
913 pub description: String,
914 pub result: Option<Value>,
915}
916
917impl CustomExample {
918 pub fn to_example(&self) -> Example<'_> {
919 Example {
920 example: self.example.as_str(),
921 description: self.description.as_str(),
922 result: self.result.clone(),
923 }
924 }
925}
926
927#[derive(Clone)]
928struct BlockCommand {
929 signature: Signature,
930 block_id: BlockId,
931 attributes: Vec<(String, Value)>,
932 examples: Vec<CustomExample>,
933}
934
935impl Command for BlockCommand {
936 fn name(&self) -> &str {
937 &self.signature.name
938 }
939
940 fn signature(&self) -> Signature {
941 self.signature.clone()
942 }
943
944 fn description(&self) -> &str {
945 &self.signature.description
946 }
947
948 fn extra_description(&self) -> &str {
949 &self.signature.extra_description
950 }
951
952 fn run(
953 &self,
954 _engine_state: &EngineState,
955 _stack: &mut Stack,
956 _call: &Call,
957 _input: PipelineData,
958 ) -> Result<crate::PipelineData, crate::ShellError> {
959 Err(ShellError::GenericError {
960 error: "Internal error: can't run custom command with 'run', use block_id".into(),
961 msg: "".into(),
962 span: None,
963 help: None,
964 inner: vec![],
965 })
966 }
967
968 fn command_type(&self) -> CommandType {
969 CommandType::Custom
970 }
971
972 fn block_id(&self) -> Option<BlockId> {
973 Some(self.block_id)
974 }
975
976 fn attributes(&self) -> Vec<(String, Value)> {
977 self.attributes.clone()
978 }
979
980 fn examples(&self) -> Vec<Example<'_>> {
981 self.examples
982 .iter()
983 .map(CustomExample::to_example)
984 .collect()
985 }
986
987 fn search_terms(&self) -> Vec<&str> {
988 self.signature
989 .search_terms
990 .iter()
991 .map(String::as_str)
992 .collect()
993 }
994
995 fn deprecation_info(&self) -> Vec<DeprecationEntry> {
996 self.attributes
997 .iter()
998 .filter_map(|(key, value)| {
999 (key == "deprecated")
1000 .then_some(value.clone())
1001 .map(DeprecationEntry::from_value)
1002 .and_then(Result::ok)
1003 })
1004 .collect()
1005 }
1006}