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]
48 pub fn long_name(&self) -> Option<&str> {
49 (!self.long.is_empty()).then_some(self.long.as_str())
50 }
51
52 #[inline]
53 pub fn new(long: impl Into<String>) -> Self {
54 Flag {
55 long: long.into(),
56 short: None,
57 arg: None,
58 required: false,
59 desc: String::new(),
60 completion: None,
61 var_id: None,
62 default_value: None,
63 }
64 }
65
66 #[inline]
67 pub fn short(self, short: char) -> Self {
68 Self {
69 short: Some(short),
70 ..self
71 }
72 }
73
74 #[inline]
75 pub fn arg(self, arg: SyntaxShape) -> Self {
76 Self {
77 arg: Some(arg),
78 ..self
79 }
80 }
81
82 #[inline]
83 pub fn required(self) -> Self {
84 Self {
85 required: true,
86 ..self
87 }
88 }
89
90 #[inline]
91 pub fn desc(self, desc: impl Into<String>) -> Self {
92 Self {
93 desc: desc.into(),
94 ..self
95 }
96 }
97
98 #[inline]
99 pub fn completion(self, completion: Completion) -> Self {
100 Self {
101 completion: Some(completion),
102 ..self
103 }
104 }
105}
106
107#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub struct PositionalArg {
110 pub name: String,
111 pub desc: String,
112 pub shape: SyntaxShape,
113 pub completion: Option<Completion>,
114
115 pub var_id: Option<VarId>,
117 pub default_value: Option<Value>,
118}
119
120impl PositionalArg {
121 #[inline]
122 pub fn new(name: impl Into<String>, shape: SyntaxShape) -> Self {
123 Self {
124 name: name.into(),
125 desc: String::new(),
126 shape,
127 completion: None,
128 var_id: None,
129 default_value: None,
130 }
131 }
132
133 #[inline]
134 pub fn desc(self, desc: impl Into<String>) -> Self {
135 Self {
136 desc: desc.into(),
137 ..self
138 }
139 }
140
141 #[inline]
142 pub fn completion(self, completion: Completion) -> Self {
143 Self {
144 completion: Some(completion),
145 ..self
146 }
147 }
148
149 #[inline]
150 pub fn required(self) -> Parameter {
151 Parameter::Required(self)
152 }
153
154 #[inline]
155 pub fn optional(self) -> Parameter {
156 Parameter::Optional(self)
157 }
158
159 #[inline]
160 pub fn rest(self) -> Parameter {
161 Parameter::Rest(self)
162 }
163}
164
165#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
166pub enum CommandWideCompleter {
167 External,
168 Command(DeclId),
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
174pub enum BuiltinCompletion {
175 NuFile { std_virtual_path: bool },
178 ModuleExports,
180 EnvVar,
182 Command { internal_only: bool },
184}
185
186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
187pub enum Completion {
188 Command(DeclId),
189 List(NuCow<&'static [&'static str], Vec<String>>),
190 Builtin(BuiltinCompletion),
192}
193
194impl Completion {
195 pub const fn new_list(list: &'static [&'static str]) -> Self {
196 Self::List(NuCow::Borrowed(list))
197 }
198
199 pub fn to_value(&self, engine_state: &EngineState, span: Span) -> Value {
200 match self {
201 Completion::Command(id) => engine_state
202 .get_decl(*id)
203 .name()
204 .to_owned()
205 .into_value(span),
206 Completion::Builtin(kind) => Value::string(
208 match kind {
209 BuiltinCompletion::NuFile { .. } => "<nu-file>",
210 BuiltinCompletion::ModuleExports => "<module-exports>",
211 BuiltinCompletion::EnvVar => "<env-var>",
212 BuiltinCompletion::Command { .. } => "<command-name>",
213 },
214 span,
215 ),
216 Completion::List(list) => match list {
217 NuCow::Borrowed(list) => list
218 .iter()
219 .map(|&e| e.into_value(span))
220 .collect::<Vec<Value>>()
221 .into_value(span),
222 NuCow::Owned(list) => list
223 .iter()
224 .cloned()
225 .map(|e| e.into_value(span))
226 .collect::<Vec<Value>>()
227 .into_value(span),
228 },
229 }
230 }
231}
232
233#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
235pub enum Category {
236 Bits,
237 Bytes,
238 Chart,
239 Conversions,
240 Core,
241 Custom(String),
242 Database,
243 Date,
244 Debug,
245 Default,
246 Deprecated,
247 Removed,
248 Env,
249 Experimental,
250 FileSystem,
251 Filters,
252 Formats,
253 Generators,
254 Hash,
255 History,
256 Math,
257 Misc,
258 Network,
259 Path,
260 Platform,
261 Plugin,
262 Random,
263 Shells,
264 Strings,
265 System,
266 Viewers,
267}
268
269impl std::fmt::Display for Category {
270 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271 let msg = match self {
272 Category::Bits => "bits",
273 Category::Bytes => "bytes",
274 Category::Chart => "chart",
275 Category::Conversions => "conversions",
276 Category::Core => "core",
277 Category::Custom(name) => name,
278 Category::Database => "database",
279 Category::Date => "date",
280 Category::Debug => "debug",
281 Category::Default => "default",
282 Category::Deprecated => "deprecated",
283 Category::Removed => "removed",
284 Category::Env => "env",
285 Category::Experimental => "experimental",
286 Category::FileSystem => "filesystem",
287 Category::Filters => "filters",
288 Category::Formats => "formats",
289 Category::Generators => "generators",
290 Category::Hash => "hash",
291 Category::History => "history",
292 Category::Math => "math",
293 Category::Misc => "misc",
294 Category::Network => "network",
295 Category::Path => "path",
296 Category::Platform => "platform",
297 Category::Plugin => "plugin",
298 Category::Random => "random",
299 Category::Shells => "shells",
300 Category::Strings => "strings",
301 Category::System => "system",
302 Category::Viewers => "viewers",
303 };
304
305 write!(f, "{msg}")
306 }
307}
308
309pub fn category_from_string(category: &str) -> Category {
310 match category {
311 "bits" => Category::Bits,
312 "bytes" => Category::Bytes,
313 "chart" => Category::Chart,
314 "conversions" => Category::Conversions,
315 "core" => Category::Custom("custom_core".to_string()),
317 "database" => Category::Database,
318 "date" => Category::Date,
319 "debug" => Category::Debug,
320 "default" => Category::Default,
321 "deprecated" => Category::Deprecated,
322 "removed" => Category::Removed,
323 "env" => Category::Env,
324 "experimental" => Category::Experimental,
325 "filesystem" => Category::FileSystem,
326 "filter" => Category::Filters,
327 "formats" => Category::Formats,
328 "generators" => Category::Generators,
329 "hash" => Category::Hash,
330 "history" => Category::History,
331 "math" => Category::Math,
332 "misc" => Category::Misc,
333 "network" => Category::Network,
334 "path" => Category::Path,
335 "platform" => Category::Platform,
336 "plugin" => Category::Plugin,
337 "random" => Category::Random,
338 "shells" => Category::Shells,
339 "strings" => Category::Strings,
340 "system" => Category::System,
341 "viewers" => Category::Viewers,
342 _ => Category::Custom(category.to_string()),
343 }
344}
345
346#[derive(Clone, Debug, Serialize, Deserialize)]
348pub struct Signature {
349 pub name: String,
350 pub description: String,
351 pub extra_description: String,
352 pub search_terms: Vec<String>,
353 pub required_positional: Vec<PositionalArg>,
354 pub optional_positional: Vec<PositionalArg>,
355 pub rest_positional: Option<PositionalArg>,
356 pub named: Vec<Flag>,
357 pub input_output_types: Vec<(Type, Type)>,
358 pub allow_variants_without_examples: bool,
359 pub is_filter: bool,
360 pub creates_scope: bool,
361 pub allows_unknown_args: bool,
362 pub complete: Option<CommandWideCompleter>,
363 pub category: Category,
365}
366
367impl PartialEq for Signature {
368 fn eq(&self, other: &Self) -> bool {
369 self.name == other.name
370 && self.description == other.description
371 && self.required_positional == other.required_positional
372 && self.optional_positional == other.optional_positional
373 && self.rest_positional == other.rest_positional
374 && self.is_filter == other.is_filter
375 }
376}
377
378impl Eq for Signature {}
379
380impl Signature {
381 pub fn new(name: impl Into<String>) -> Signature {
383 Signature {
384 name: name.into(),
385 description: String::new(),
386 extra_description: String::new(),
387 search_terms: vec![],
388 required_positional: vec![],
389 optional_positional: vec![],
390 rest_positional: None,
391 input_output_types: vec![],
392 allow_variants_without_examples: false,
393 named: vec![],
394 is_filter: false,
395 creates_scope: false,
396 category: Category::Default,
397 allows_unknown_args: false,
398 complete: None,
399 }
400 }
401
402 pub fn get_input_type(&self) -> Type {
408 match self.input_output_types.as_slice() {
409 [] => Type::Any,
410 [(input, _output)] => input.clone(),
411 multiple => Type::one_of(multiple.iter().map(|(input, _)| input.clone())),
412 }
413 }
414
415 pub fn get_output_type(&self, input_type: Option<&Type>) -> Option<Type> {
425 if self.input_output_types.is_empty() {
426 return Some(Type::Any);
427 }
428 let input = input_type.unwrap_or(&Type::Any);
429 let mut it = self
430 .input_output_types
431 .iter()
432 .filter(|(in_ty, _out_ty)| input.is_assignable_to(in_ty))
433 .map(|(_, out)| out)
434 .peekable();
435
436 it.peek()?;
437 it.cloned().reduce(Type::union)
438 }
439
440 pub fn add_help(mut self) -> Signature {
442 let flag = Flag {
444 long: "help".into(),
445 short: Some('h'),
446 arg: None,
447 desc: "Display the help message for this command".into(),
448 required: false,
449 var_id: None,
450 default_value: None,
451 completion: None,
452 };
453 self.named.push(flag);
454 self
455 }
456
457 pub fn build(name: impl Into<String>) -> Signature {
461 Signature::new(name.into()).add_help()
462 }
463
464 pub fn description(mut self, msg: impl Into<String>) -> Signature {
469 self.description = msg.into();
470 self
471 }
472
473 pub fn extra_description(mut self, msg: impl Into<String>) -> Signature {
477 self.extra_description = msg.into();
478 self
479 }
480
481 pub fn search_terms(mut self, terms: Vec<String>) -> Signature {
483 self.search_terms = terms;
484 self
485 }
486
487 pub fn update_from_command(mut self, command: &dyn Command) -> Signature {
489 self.search_terms = command
490 .search_terms()
491 .into_iter()
492 .map(|term| term.to_string())
493 .collect();
494 self.extra_description = command.extra_description().to_string();
495 self.description = command.description().to_string();
496 self
497 }
498
499 pub fn allows_unknown_args(mut self) -> Signature {
501 self.allows_unknown_args = true;
502 self
503 }
504
505 pub fn param(mut self, param: impl Into<Parameter>) -> Self {
506 let param: Parameter = param.into();
507 match param {
508 Parameter::Flag(flag) => {
509 if let Some(s) = flag.short {
510 assert!(
511 !self.get_shorts().contains(&s),
512 "There may be duplicate short flags for '-{s}'"
513 );
514 }
515
516 let name = flag.long.as_str();
517 assert!(
518 !self.get_names().contains(&name),
519 "There may be duplicate name flags for '--{name}'"
520 );
521
522 self.named.push(flag);
523 }
524 Parameter::Required(positional_arg) => {
525 self.required_positional.push(positional_arg);
526 }
527 Parameter::Optional(positional_arg) => {
528 self.optional_positional.push(positional_arg);
529 }
530 Parameter::Rest(positional_arg) => {
531 assert!(
532 self.rest_positional.is_none(),
533 "Tried to set rest arguments more than once"
534 );
535 self.rest_positional = Some(positional_arg);
536 }
537 }
538 self
539 }
540
541 pub fn required(
543 mut self,
544 name: impl Into<String>,
545 shape: impl Into<SyntaxShape>,
546 desc: impl Into<String>,
547 ) -> Signature {
548 self.required_positional.push(PositionalArg {
549 name: name.into(),
550 desc: desc.into(),
551 shape: shape.into(),
552 var_id: None,
553 default_value: None,
554 completion: None,
555 });
556
557 self
558 }
559
560 pub fn optional(
562 mut self,
563 name: impl Into<String>,
564 shape: impl Into<SyntaxShape>,
565 desc: impl Into<String>,
566 ) -> Signature {
567 self.optional_positional.push(PositionalArg {
568 name: name.into(),
569 desc: desc.into(),
570 shape: shape.into(),
571 var_id: None,
572 default_value: None,
573 completion: None,
574 });
575
576 self
577 }
578
579 pub fn rest(
587 mut self,
588 name: &str,
589 shape: impl Into<SyntaxShape>,
590 desc: impl Into<String>,
591 ) -> Signature {
592 self.rest_positional = Some(PositionalArg {
593 name: name.into(),
594 desc: desc.into(),
595 shape: shape.into(),
596 var_id: None,
597 default_value: None,
598 completion: None,
599 });
600
601 self
602 }
603
604 pub fn operates_on_cell_paths(&self) -> bool {
606 self.required_positional
607 .iter()
608 .chain(self.rest_positional.iter())
609 .any(|pos| {
610 matches!(
611 pos,
612 PositionalArg {
613 shape: SyntaxShape::CellPath,
614 ..
615 }
616 )
617 })
618 }
619
620 pub fn named(
622 mut self,
623 name: impl Into<String>,
624 shape: impl Into<SyntaxShape>,
625 desc: impl Into<String>,
626 short: Option<char>,
627 ) -> Signature {
628 let (name, s) = self.check_names(name, short);
629
630 self.named.push(Flag {
631 long: name,
632 short: s,
633 arg: Some(shape.into()),
634 required: false,
635 desc: desc.into(),
636 var_id: None,
637 default_value: None,
638 completion: None,
639 });
640
641 self
642 }
643
644 pub fn required_named(
646 mut self,
647 name: impl Into<String>,
648 shape: impl Into<SyntaxShape>,
649 desc: impl Into<String>,
650 short: Option<char>,
651 ) -> Signature {
652 let (name, s) = self.check_names(name, short);
653
654 self.named.push(Flag {
655 long: name,
656 short: s,
657 arg: Some(shape.into()),
658 required: true,
659 desc: desc.into(),
660 var_id: None,
661 default_value: None,
662 completion: None,
663 });
664
665 self
666 }
667
668 pub fn switch(
670 mut self,
671 name: impl Into<String>,
672 desc: impl Into<String>,
673 short: Option<char>,
674 ) -> Signature {
675 let (name, s) = self.check_names(name, short);
676
677 self.named.push(Flag {
678 long: name,
679 short: s,
680 arg: None,
681 required: false,
682 desc: desc.into(),
683 var_id: None,
684 default_value: None,
685 completion: None,
686 });
687
688 self
689 }
690
691 pub fn input_output_type(mut self, input_type: Type, output_type: Type) -> Signature {
693 self.input_output_types.push((input_type, output_type));
694 self
695 }
696
697 pub fn input_output_types(mut self, input_output_types: Vec<(Type, Type)>) -> Signature {
699 self.input_output_types = input_output_types;
700 self
701 }
702
703 pub fn category(mut self, category: Category) -> Signature {
705 self.category = category;
706
707 self
708 }
709
710 pub fn creates_scope(mut self) -> Signature {
712 self.creates_scope = true;
713 self
714 }
715
716 pub fn allow_variants_without_examples(mut self, allow: bool) -> Signature {
718 self.allow_variants_without_examples = allow;
719 self
720 }
721
722 pub fn call_signature(&self) -> String {
727 let mut one_liner = String::new();
728 one_liner.push_str(&self.name);
729 one_liner.push(' ');
730
731 if self.named.len() > 1 {
736 one_liner.push_str("{flags} ");
737 }
738
739 for positional in &self.required_positional {
740 one_liner.push_str(&get_positional_short_name(positional, true));
741 }
742 for positional in &self.optional_positional {
743 one_liner.push_str(&get_positional_short_name(positional, false));
744 }
745
746 if let Some(rest) = &self.rest_positional {
747 let _ = write!(one_liner, "...{}", get_positional_short_name(rest, false));
748 }
749
750 one_liner
755 }
756
757 pub fn get_shorts(&self) -> Vec<char> {
759 self.named.iter().filter_map(|f| f.short).collect()
760 }
761
762 pub fn get_names(&self) -> Vec<&str> {
764 self.named.iter().map(|f| f.long.as_str()).collect()
765 }
766
767 fn check_names(&self, name: impl Into<String>, short: Option<char>) -> (String, Option<char>) {
774 let s = short.inspect(|c| {
775 assert!(
776 !self.get_shorts().contains(c),
777 "There may be duplicate short flags for '-{c}'"
778 );
779 });
780
781 let name = {
782 let name: String = name.into();
783 assert!(
784 !self.get_names().contains(&name.as_str()),
785 "There may be duplicate name flags for '--{name}'"
786 );
787 name
788 };
789
790 (name, s)
791 }
792
793 pub fn get_positional(&self, position: usize) -> Option<&PositionalArg> {
800 if position < self.required_positional.len() {
801 self.required_positional.get(position)
802 } else if position < (self.required_positional.len() + self.optional_positional.len()) {
803 self.optional_positional
804 .get(position - self.required_positional.len())
805 } else {
806 self.rest_positional.as_ref()
807 }
808 }
809
810 pub fn num_positionals(&self) -> usize {
814 let mut total = self.required_positional.len() + self.optional_positional.len();
815
816 for positional in &self.required_positional {
817 if let SyntaxShape::Keyword(..) = positional.shape {
818 total += 1;
820 }
821 }
822 for positional in &self.optional_positional {
823 if let SyntaxShape::Keyword(..) = positional.shape {
824 total += 1;
826 }
827 }
828 total
829 }
830
831 pub fn get_long_flag(&self, name: &str) -> Option<Flag> {
833 if name.is_empty() {
834 return None;
835 }
836 for flag in &self.named {
837 if flag.long == name {
838 return Some(flag.clone());
839 }
840 }
841 None
842 }
843
844 pub fn get_short_flag(&self, short: char) -> Option<Flag> {
846 for flag in &self.named {
847 if let Some(short_flag) = &flag.short
848 && *short_flag == short
849 {
850 return Some(flag.clone());
851 }
852 }
853 None
854 }
855
856 pub fn filter(mut self) -> Signature {
858 self.is_filter = true;
859 self
860 }
861
862 pub fn predeclare(self) -> Box<dyn Command> {
866 self.predeclare_with_command_type(CommandType::Builtin)
867 }
868
869 pub fn predeclare_with_command_type(self, command_type: CommandType) -> Box<dyn Command> {
872 Box::new(Predeclaration {
873 signature: self,
874 command_type,
875 })
876 }
877
878 pub fn into_block_command(
880 self,
881 block_id: BlockId,
882 attributes: Vec<(String, Value)>,
883 examples: Vec<CustomExample>,
884 ) -> Box<dyn Command> {
885 Box::new(BlockCommand {
886 signature: self,
887 block_id,
888 attributes,
889 examples,
890 })
891 }
892}
893
894#[derive(Clone)]
895struct Predeclaration {
896 signature: Signature,
897 command_type: CommandType,
898}
899
900impl Command for Predeclaration {
901 fn name(&self) -> &str {
902 &self.signature.name
903 }
904
905 fn signature(&self) -> Signature {
906 self.signature.clone()
907 }
908
909 fn description(&self) -> &str {
910 &self.signature.description
911 }
912
913 fn extra_description(&self) -> &str {
914 &self.signature.extra_description
915 }
916
917 fn run(
918 &self,
919 _engine_state: &EngineState,
920 _stack: &mut Stack,
921 _call: &Call,
922 _input: PipelineData,
923 ) -> Result<PipelineData, crate::ShellError> {
924 panic!("Internal error: can't run a predeclaration without a body")
925 }
926
927 fn command_type(&self) -> CommandType {
928 self.command_type
929 }
930}
931
932fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String {
933 match &arg.shape {
934 SyntaxShape::Keyword(name, ..) => {
935 if is_required {
936 format!("{} <{}> ", String::from_utf8_lossy(name), arg.name)
937 } else {
938 format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name)
939 }
940 }
941 _ => {
942 if is_required {
943 format!("<{}> ", arg.name)
944 } else {
945 format!("({}) ", arg.name)
946 }
947 }
948 }
949}
950
951#[derive(Clone, DeriveFromValue)]
952pub struct CustomExample {
953 pub example: String,
954 pub description: String,
955 pub result: Option<Value>,
956}
957
958impl CustomExample {
959 pub fn to_example(&self) -> Example<'_> {
960 Example {
961 example: self.example.as_str(),
962 description: self.description.as_str(),
963 result: self.result.clone(),
964 }
965 }
966}
967
968#[derive(Clone)]
969struct BlockCommand {
970 signature: Signature,
971 block_id: BlockId,
972 attributes: Vec<(String, Value)>,
973 examples: Vec<CustomExample>,
974}
975
976impl Command for BlockCommand {
977 fn name(&self) -> &str {
978 &self.signature.name
979 }
980
981 fn signature(&self) -> Signature {
982 self.signature.clone()
983 }
984
985 fn description(&self) -> &str {
986 &self.signature.description
987 }
988
989 fn extra_description(&self) -> &str {
990 &self.signature.extra_description
991 }
992
993 fn run(
994 &self,
995 _engine_state: &EngineState,
996 _stack: &mut Stack,
997 _call: &Call,
998 _input: PipelineData,
999 ) -> Result<crate::PipelineData, crate::ShellError> {
1000 Err(ShellError::Generic(GenericError::new_internal(
1001 "Internal error: can't run custom command with 'run', use block_id",
1002 "",
1003 )))
1004 }
1005
1006 fn command_type(&self) -> CommandType {
1007 CommandType::Custom
1008 }
1009
1010 fn block_id(&self) -> Option<BlockId> {
1011 Some(self.block_id)
1012 }
1013
1014 fn attributes(&self) -> Vec<(String, Value)> {
1015 self.attributes.clone()
1016 }
1017
1018 fn examples(&self) -> Vec<Example<'_>> {
1019 self.examples
1020 .iter()
1021 .map(CustomExample::to_example)
1022 .collect()
1023 }
1024
1025 fn search_terms(&self) -> Vec<&str> {
1026 self.signature
1027 .search_terms
1028 .iter()
1029 .map(String::as_str)
1030 .collect()
1031 }
1032
1033 fn deprecation_info(&self) -> Vec<DeprecationEntry> {
1034 self.attributes
1035 .iter()
1036 .filter_map(|(key, value)| {
1037 (key == "deprecated")
1038 .then_some(value.clone())
1039 .map(DeprecationEntry::from_value)
1040 .and_then(Result::ok)
1041 })
1042 .collect()
1043 }
1044}