1use crate::{
2 BlockId, DeprecationEntry, Example, FromValue, PipelineData, ShellError, SyntaxShape, Type,
3 Value, VarId,
4 engine::{Call, Command, CommandType, EngineState, Stack},
5};
6use nu_derive_value::FromValue as DeriveFromValue;
7use serde::{Deserialize, Serialize};
8use std::fmt::Write;
9
10use crate as nu_protocol;
14
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct Flag {
18 pub long: String,
19 pub short: Option<char>,
20 pub arg: Option<SyntaxShape>,
21 pub required: bool,
22 pub desc: String,
23
24 pub var_id: Option<VarId>,
26 pub default_value: Option<Value>,
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct PositionalArg {
32 pub name: String,
33 pub desc: String,
34 pub shape: SyntaxShape,
35
36 pub var_id: Option<VarId>,
38 pub default_value: Option<Value>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43pub enum Category {
44 Bits,
45 Bytes,
46 Chart,
47 Conversions,
48 Core,
49 Custom(String),
50 Database,
51 Date,
52 Debug,
53 Default,
54 Deprecated,
55 Removed,
56 Env,
57 Experimental,
58 FileSystem,
59 Filters,
60 Formats,
61 Generators,
62 Hash,
63 History,
64 Math,
65 Misc,
66 Network,
67 Path,
68 Platform,
69 Plugin,
70 Random,
71 Shells,
72 Strings,
73 System,
74 Viewers,
75}
76
77impl std::fmt::Display for Category {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 let msg = match self {
80 Category::Bits => "bits",
81 Category::Bytes => "bytes",
82 Category::Chart => "chart",
83 Category::Conversions => "conversions",
84 Category::Core => "core",
85 Category::Custom(name) => name,
86 Category::Database => "database",
87 Category::Date => "date",
88 Category::Debug => "debug",
89 Category::Default => "default",
90 Category::Deprecated => "deprecated",
91 Category::Removed => "removed",
92 Category::Env => "env",
93 Category::Experimental => "experimental",
94 Category::FileSystem => "filesystem",
95 Category::Filters => "filters",
96 Category::Formats => "formats",
97 Category::Generators => "generators",
98 Category::Hash => "hash",
99 Category::History => "history",
100 Category::Math => "math",
101 Category::Misc => "misc",
102 Category::Network => "network",
103 Category::Path => "path",
104 Category::Platform => "platform",
105 Category::Plugin => "plugin",
106 Category::Random => "random",
107 Category::Shells => "shells",
108 Category::Strings => "strings",
109 Category::System => "system",
110 Category::Viewers => "viewers",
111 };
112
113 write!(f, "{msg}")
114 }
115}
116
117pub fn category_from_string(category: &str) -> Category {
118 match category {
119 "bits" => Category::Bits,
120 "bytes" => Category::Bytes,
121 "chart" => Category::Chart,
122 "conversions" => Category::Conversions,
123 "core" => Category::Custom("custom_core".to_string()),
125 "database" => Category::Database,
126 "date" => Category::Date,
127 "debug" => Category::Debug,
128 "default" => Category::Default,
129 "deprecated" => Category::Deprecated,
130 "removed" => Category::Removed,
131 "env" => Category::Env,
132 "experimental" => Category::Experimental,
133 "filesystem" => Category::FileSystem,
134 "filter" => Category::Filters,
135 "formats" => Category::Formats,
136 "generators" => Category::Generators,
137 "hash" => Category::Hash,
138 "history" => Category::History,
139 "math" => Category::Math,
140 "misc" => Category::Misc,
141 "network" => Category::Network,
142 "path" => Category::Path,
143 "platform" => Category::Platform,
144 "plugin" => Category::Plugin,
145 "random" => Category::Random,
146 "shells" => Category::Shells,
147 "strings" => Category::Strings,
148 "system" => Category::System,
149 "viewers" => Category::Viewers,
150 _ => Category::Custom(category.to_string()),
151 }
152}
153
154#[derive(Clone, Debug, Serialize, Deserialize)]
156pub struct Signature {
157 pub name: String,
158 pub description: String,
159 pub extra_description: String,
160 pub search_terms: Vec<String>,
161 pub required_positional: Vec<PositionalArg>,
162 pub optional_positional: Vec<PositionalArg>,
163 pub rest_positional: Option<PositionalArg>,
164 pub named: Vec<Flag>,
165 pub input_output_types: Vec<(Type, Type)>,
166 pub allow_variants_without_examples: bool,
167 pub is_filter: bool,
168 pub creates_scope: bool,
169 pub allows_unknown_args: bool,
170 pub category: Category,
172}
173
174impl PartialEq for Signature {
175 fn eq(&self, other: &Self) -> bool {
176 self.name == other.name
177 && self.description == other.description
178 && self.required_positional == other.required_positional
179 && self.optional_positional == other.optional_positional
180 && self.rest_positional == other.rest_positional
181 && self.is_filter == other.is_filter
182 }
183}
184
185impl Eq for Signature {}
186
187impl Signature {
188 pub fn new(name: impl Into<String>) -> Signature {
189 Signature {
190 name: name.into(),
191 description: String::new(),
192 extra_description: String::new(),
193 search_terms: vec![],
194 required_positional: vec![],
195 optional_positional: vec![],
196 rest_positional: None,
197 input_output_types: vec![],
198 allow_variants_without_examples: false,
199 named: vec![],
200 is_filter: false,
201 creates_scope: false,
202 category: Category::Default,
203 allows_unknown_args: false,
204 }
205 }
206
207 pub fn get_input_type(&self) -> Type {
209 match self.input_output_types.len() {
210 0 => Type::Any,
211 1 => self.input_output_types[0].0.clone(),
212 _ => {
213 let first = &self.input_output_types[0].0;
214 if self
215 .input_output_types
216 .iter()
217 .all(|(input, _)| input == first)
218 {
219 first.clone()
220 } else {
221 Type::Any
222 }
223 }
224 }
225 }
226
227 pub fn get_output_type(&self) -> Type {
229 match self.input_output_types.len() {
230 0 => Type::Any,
231 1 => self.input_output_types[0].1.clone(),
232 _ => {
233 let first = &self.input_output_types[0].1;
234 if self
235 .input_output_types
236 .iter()
237 .all(|(_, output)| output == first)
238 {
239 first.clone()
240 } else {
241 Type::Any
242 }
243 }
244 }
245 }
246
247 pub fn add_help(mut self) -> Signature {
249 let flag = Flag {
251 long: "help".into(),
252 short: Some('h'),
253 arg: None,
254 desc: "Display the help message for this command".into(),
255 required: false,
256 var_id: None,
257 default_value: None,
258 };
259 self.named.push(flag);
260 self
261 }
262
263 pub fn build(name: impl Into<String>) -> Signature {
265 Signature::new(name.into()).add_help()
266 }
267
268 pub fn description(mut self, msg: impl Into<String>) -> Signature {
273 self.description = msg.into();
274 self
275 }
276
277 pub fn extra_description(mut self, msg: impl Into<String>) -> Signature {
281 self.extra_description = msg.into();
282 self
283 }
284
285 pub fn search_terms(mut self, terms: Vec<String>) -> Signature {
287 self.search_terms = terms;
288 self
289 }
290
291 pub fn update_from_command(mut self, command: &dyn Command) -> Signature {
293 self.search_terms = command
294 .search_terms()
295 .into_iter()
296 .map(|term| term.to_string())
297 .collect();
298 self.extra_description = command.extra_description().to_string();
299 self.description = command.description().to_string();
300 self
301 }
302
303 pub fn allows_unknown_args(mut self) -> Signature {
305 self.allows_unknown_args = true;
306 self
307 }
308
309 pub fn required(
311 mut self,
312 name: impl Into<String>,
313 shape: impl Into<SyntaxShape>,
314 desc: impl Into<String>,
315 ) -> Signature {
316 self.required_positional.push(PositionalArg {
317 name: name.into(),
318 desc: desc.into(),
319 shape: shape.into(),
320 var_id: None,
321 default_value: None,
322 });
323
324 self
325 }
326
327 pub fn optional(
329 mut self,
330 name: impl Into<String>,
331 shape: impl Into<SyntaxShape>,
332 desc: impl Into<String>,
333 ) -> Signature {
334 self.optional_positional.push(PositionalArg {
335 name: name.into(),
336 desc: desc.into(),
337 shape: shape.into(),
338 var_id: None,
339 default_value: None,
340 });
341
342 self
343 }
344
345 pub fn rest(
346 mut self,
347 name: &str,
348 shape: impl Into<SyntaxShape>,
349 desc: impl Into<String>,
350 ) -> Signature {
351 self.rest_positional = Some(PositionalArg {
352 name: name.into(),
353 desc: desc.into(),
354 shape: shape.into(),
355 var_id: None,
356 default_value: None,
357 });
358
359 self
360 }
361
362 pub fn operates_on_cell_paths(&self) -> bool {
364 self.required_positional
365 .iter()
366 .chain(self.rest_positional.iter())
367 .any(|pos| {
368 matches!(
369 pos,
370 PositionalArg {
371 shape: SyntaxShape::CellPath,
372 ..
373 }
374 )
375 })
376 }
377
378 pub fn named(
380 mut self,
381 name: impl Into<String>,
382 shape: impl Into<SyntaxShape>,
383 desc: impl Into<String>,
384 short: Option<char>,
385 ) -> Signature {
386 let (name, s) = self.check_names(name, short);
387
388 self.named.push(Flag {
389 long: name,
390 short: s,
391 arg: Some(shape.into()),
392 required: false,
393 desc: desc.into(),
394 var_id: None,
395 default_value: None,
396 });
397
398 self
399 }
400
401 pub fn required_named(
403 mut self,
404 name: impl Into<String>,
405 shape: impl Into<SyntaxShape>,
406 desc: impl Into<String>,
407 short: Option<char>,
408 ) -> Signature {
409 let (name, s) = self.check_names(name, short);
410
411 self.named.push(Flag {
412 long: name,
413 short: s,
414 arg: Some(shape.into()),
415 required: true,
416 desc: desc.into(),
417 var_id: None,
418 default_value: None,
419 });
420
421 self
422 }
423
424 pub fn switch(
426 mut self,
427 name: impl Into<String>,
428 desc: impl Into<String>,
429 short: Option<char>,
430 ) -> Signature {
431 let (name, s) = self.check_names(name, short);
432
433 self.named.push(Flag {
434 long: name,
435 short: s,
436 arg: None,
437 required: false,
438 desc: desc.into(),
439 var_id: None,
440 default_value: None,
441 });
442
443 self
444 }
445
446 pub fn input_output_type(mut self, input_type: Type, output_type: Type) -> Signature {
448 self.input_output_types.push((input_type, output_type));
449 self
450 }
451
452 pub fn input_output_types(mut self, input_output_types: Vec<(Type, Type)>) -> Signature {
454 self.input_output_types = input_output_types;
455 self
456 }
457
458 pub fn category(mut self, category: Category) -> Signature {
460 self.category = category;
461
462 self
463 }
464
465 pub fn creates_scope(mut self) -> Signature {
467 self.creates_scope = true;
468 self
469 }
470
471 pub fn allow_variants_without_examples(mut self, allow: bool) -> Signature {
473 self.allow_variants_without_examples = allow;
474 self
475 }
476
477 pub fn call_signature(&self) -> String {
478 let mut one_liner = String::new();
479 one_liner.push_str(&self.name);
480 one_liner.push(' ');
481
482 if self.named.len() > 1 {
487 one_liner.push_str("{flags} ");
488 }
489
490 for positional in &self.required_positional {
491 one_liner.push_str(&get_positional_short_name(positional, true));
492 }
493 for positional in &self.optional_positional {
494 one_liner.push_str(&get_positional_short_name(positional, false));
495 }
496
497 if let Some(rest) = &self.rest_positional {
498 let _ = write!(one_liner, "...{}", get_positional_short_name(rest, false));
499 }
500
501 one_liner
506 }
507
508 pub fn get_shorts(&self) -> Vec<char> {
510 self.named.iter().filter_map(|f| f.short).collect()
511 }
512
513 pub fn get_names(&self) -> Vec<&str> {
515 self.named.iter().map(|f| f.long.as_str()).collect()
516 }
517
518 fn check_names(&self, name: impl Into<String>, short: Option<char>) -> (String, Option<char>) {
521 let s = short.inspect(|c| {
522 assert!(
523 !self.get_shorts().contains(c),
524 "There may be duplicate short flags for '-{}'",
525 c
526 );
527 });
528
529 let name = {
530 let name: String = name.into();
531 assert!(
532 !self.get_names().contains(&name.as_str()),
533 "There may be duplicate name flags for '--{}'",
534 name
535 );
536 name
537 };
538
539 (name, s)
540 }
541
542 pub fn get_positional(&self, position: usize) -> Option<&PositionalArg> {
543 if position < self.required_positional.len() {
544 self.required_positional.get(position)
545 } else if position < (self.required_positional.len() + self.optional_positional.len()) {
546 self.optional_positional
547 .get(position - self.required_positional.len())
548 } else {
549 self.rest_positional.as_ref()
550 }
551 }
552
553 pub fn num_positionals(&self) -> usize {
554 let mut total = self.required_positional.len() + self.optional_positional.len();
555
556 for positional in &self.required_positional {
557 if let SyntaxShape::Keyword(..) = positional.shape {
558 total += 1;
560 }
561 }
562 for positional in &self.optional_positional {
563 if let SyntaxShape::Keyword(..) = positional.shape {
564 total += 1;
566 }
567 }
568 total
569 }
570
571 pub fn get_long_flag(&self, name: &str) -> Option<Flag> {
573 for flag in &self.named {
574 if flag.long == name {
575 return Some(flag.clone());
576 }
577 }
578 None
579 }
580
581 pub fn get_short_flag(&self, short: char) -> Option<Flag> {
583 for flag in &self.named {
584 if let Some(short_flag) = &flag.short {
585 if *short_flag == short {
586 return Some(flag.clone());
587 }
588 }
589 }
590 None
591 }
592
593 pub fn filter(mut self) -> Signature {
595 self.is_filter = true;
596 self
597 }
598
599 pub fn predeclare(self) -> Box<dyn Command> {
603 Box::new(Predeclaration { signature: self })
604 }
605
606 pub fn into_block_command(
608 self,
609 block_id: BlockId,
610 attributes: Vec<(String, Value)>,
611 examples: Vec<CustomExample>,
612 ) -> Box<dyn Command> {
613 Box::new(BlockCommand {
614 signature: self,
615 block_id,
616 attributes,
617 examples,
618 })
619 }
620
621 pub fn formatted_flags(self) -> String {
622 if self.named.len() < 11 {
623 let mut s = "Available flags:".to_string();
624 for flag in self.named {
625 if let Some(short) = flag.short {
626 let _ = write!(s, " --{}(-{}),", flag.long, short);
627 } else {
628 let _ = write!(s, " --{},", flag.long);
629 }
630 }
631 s.remove(s.len() - 1);
632 let _ = write!(s, ". Use `--help` for more information.");
633 s
634 } else {
635 let mut s = "Some available flags:".to_string();
636 for flag in self.named {
637 if let Some(short) = flag.short {
638 let _ = write!(s, " --{}(-{}),", flag.long, short);
639 } else {
640 let _ = write!(s, " --{},", flag.long);
641 }
642 }
643 s.remove(s.len() - 1);
644 let _ = write!(
645 s,
646 "... Use `--help` for a full list of flags and more information."
647 );
648 s
649 }
650 }
651}
652
653#[derive(Clone)]
654struct Predeclaration {
655 signature: Signature,
656}
657
658impl Command for Predeclaration {
659 fn name(&self) -> &str {
660 &self.signature.name
661 }
662
663 fn signature(&self) -> Signature {
664 self.signature.clone()
665 }
666
667 fn description(&self) -> &str {
668 &self.signature.description
669 }
670
671 fn extra_description(&self) -> &str {
672 &self.signature.extra_description
673 }
674
675 fn run(
676 &self,
677 _engine_state: &EngineState,
678 _stack: &mut Stack,
679 _call: &Call,
680 _input: PipelineData,
681 ) -> Result<PipelineData, crate::ShellError> {
682 panic!("Internal error: can't run a predeclaration without a body")
683 }
684}
685
686fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String {
687 match &arg.shape {
688 SyntaxShape::Keyword(name, ..) => {
689 if is_required {
690 format!("{} <{}> ", String::from_utf8_lossy(name), arg.name)
691 } else {
692 format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name)
693 }
694 }
695 _ => {
696 if is_required {
697 format!("<{}> ", arg.name)
698 } else {
699 format!("({}) ", arg.name)
700 }
701 }
702 }
703}
704
705#[derive(Clone, DeriveFromValue)]
706pub struct CustomExample {
707 pub example: String,
708 pub description: String,
709 pub result: Option<Value>,
710}
711
712impl CustomExample {
713 pub fn to_example(&self) -> Example<'_> {
714 Example {
715 example: self.example.as_str(),
716 description: self.description.as_str(),
717 result: self.result.clone(),
718 }
719 }
720}
721
722#[derive(Clone)]
723struct BlockCommand {
724 signature: Signature,
725 block_id: BlockId,
726 attributes: Vec<(String, Value)>,
727 examples: Vec<CustomExample>,
728}
729
730impl Command for BlockCommand {
731 fn name(&self) -> &str {
732 &self.signature.name
733 }
734
735 fn signature(&self) -> Signature {
736 self.signature.clone()
737 }
738
739 fn description(&self) -> &str {
740 &self.signature.description
741 }
742
743 fn extra_description(&self) -> &str {
744 &self.signature.extra_description
745 }
746
747 fn run(
748 &self,
749 _engine_state: &EngineState,
750 _stack: &mut Stack,
751 _call: &Call,
752 _input: PipelineData,
753 ) -> Result<crate::PipelineData, crate::ShellError> {
754 Err(ShellError::GenericError {
755 error: "Internal error: can't run custom command with 'run', use block_id".into(),
756 msg: "".into(),
757 span: None,
758 help: None,
759 inner: vec![],
760 })
761 }
762
763 fn command_type(&self) -> CommandType {
764 CommandType::Custom
765 }
766
767 fn block_id(&self) -> Option<BlockId> {
768 Some(self.block_id)
769 }
770
771 fn attributes(&self) -> Vec<(String, Value)> {
772 self.attributes.clone()
773 }
774
775 fn examples(&self) -> Vec<Example> {
776 self.examples
777 .iter()
778 .map(CustomExample::to_example)
779 .collect()
780 }
781
782 fn search_terms(&self) -> Vec<&str> {
783 self.signature
784 .search_terms
785 .iter()
786 .map(String::as_str)
787 .collect()
788 }
789
790 fn deprecation_info(&self) -> Vec<DeprecationEntry> {
791 self.attributes
792 .iter()
793 .filter_map(|(key, value)| {
794 (key == "deprecated")
795 .then_some(value.clone())
796 .map(DeprecationEntry::from_value)
797 .and_then(Result::ok)
798 })
799 .collect()
800 }
801}