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 '-{c}'"
525 );
526 });
527
528 let name = {
529 let name: String = name.into();
530 assert!(
531 !self.get_names().contains(&name.as_str()),
532 "There may be duplicate name flags for '--{name}'"
533 );
534 name
535 };
536
537 (name, s)
538 }
539
540 pub fn get_positional(&self, position: usize) -> Option<&PositionalArg> {
541 if position < self.required_positional.len() {
542 self.required_positional.get(position)
543 } else if position < (self.required_positional.len() + self.optional_positional.len()) {
544 self.optional_positional
545 .get(position - self.required_positional.len())
546 } else {
547 self.rest_positional.as_ref()
548 }
549 }
550
551 pub fn num_positionals(&self) -> usize {
552 let mut total = self.required_positional.len() + self.optional_positional.len();
553
554 for positional in &self.required_positional {
555 if let SyntaxShape::Keyword(..) = positional.shape {
556 total += 1;
558 }
559 }
560 for positional in &self.optional_positional {
561 if let SyntaxShape::Keyword(..) = positional.shape {
562 total += 1;
564 }
565 }
566 total
567 }
568
569 pub fn get_long_flag(&self, name: &str) -> Option<Flag> {
571 for flag in &self.named {
572 if flag.long == name {
573 return Some(flag.clone());
574 }
575 }
576 None
577 }
578
579 pub fn get_short_flag(&self, short: char) -> Option<Flag> {
581 for flag in &self.named {
582 if let Some(short_flag) = &flag.short {
583 if *short_flag == short {
584 return Some(flag.clone());
585 }
586 }
587 }
588 None
589 }
590
591 pub fn filter(mut self) -> Signature {
593 self.is_filter = true;
594 self
595 }
596
597 pub fn predeclare(self) -> Box<dyn Command> {
601 Box::new(Predeclaration { signature: self })
602 }
603
604 pub fn into_block_command(
606 self,
607 block_id: BlockId,
608 attributes: Vec<(String, Value)>,
609 examples: Vec<CustomExample>,
610 ) -> Box<dyn Command> {
611 Box::new(BlockCommand {
612 signature: self,
613 block_id,
614 attributes,
615 examples,
616 })
617 }
618
619 pub fn formatted_flags(self) -> String {
620 if self.named.len() < 11 {
621 let mut s = "Available flags:".to_string();
622 for flag in self.named {
623 if let Some(short) = flag.short {
624 let _ = write!(s, " --{}(-{}),", flag.long, short);
625 } else {
626 let _ = write!(s, " --{},", flag.long);
627 }
628 }
629 s.remove(s.len() - 1);
630 let _ = write!(s, ". Use `--help` for more information.");
631 s
632 } else {
633 let mut s = "Some available flags:".to_string();
634 for flag in self.named {
635 if let Some(short) = flag.short {
636 let _ = write!(s, " --{}(-{}),", flag.long, short);
637 } else {
638 let _ = write!(s, " --{},", flag.long);
639 }
640 }
641 s.remove(s.len() - 1);
642 let _ = write!(
643 s,
644 "... Use `--help` for a full list of flags and more information."
645 );
646 s
647 }
648 }
649}
650
651#[derive(Clone)]
652struct Predeclaration {
653 signature: Signature,
654}
655
656impl Command for Predeclaration {
657 fn name(&self) -> &str {
658 &self.signature.name
659 }
660
661 fn signature(&self) -> Signature {
662 self.signature.clone()
663 }
664
665 fn description(&self) -> &str {
666 &self.signature.description
667 }
668
669 fn extra_description(&self) -> &str {
670 &self.signature.extra_description
671 }
672
673 fn run(
674 &self,
675 _engine_state: &EngineState,
676 _stack: &mut Stack,
677 _call: &Call,
678 _input: PipelineData,
679 ) -> Result<PipelineData, crate::ShellError> {
680 panic!("Internal error: can't run a predeclaration without a body")
681 }
682}
683
684fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String {
685 match &arg.shape {
686 SyntaxShape::Keyword(name, ..) => {
687 if is_required {
688 format!("{} <{}> ", String::from_utf8_lossy(name), arg.name)
689 } else {
690 format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name)
691 }
692 }
693 _ => {
694 if is_required {
695 format!("<{}> ", arg.name)
696 } else {
697 format!("({}) ", arg.name)
698 }
699 }
700 }
701}
702
703#[derive(Clone, DeriveFromValue)]
704pub struct CustomExample {
705 pub example: String,
706 pub description: String,
707 pub result: Option<Value>,
708}
709
710impl CustomExample {
711 pub fn to_example(&self) -> Example<'_> {
712 Example {
713 example: self.example.as_str(),
714 description: self.description.as_str(),
715 result: self.result.clone(),
716 }
717 }
718}
719
720#[derive(Clone)]
721struct BlockCommand {
722 signature: Signature,
723 block_id: BlockId,
724 attributes: Vec<(String, Value)>,
725 examples: Vec<CustomExample>,
726}
727
728impl Command for BlockCommand {
729 fn name(&self) -> &str {
730 &self.signature.name
731 }
732
733 fn signature(&self) -> Signature {
734 self.signature.clone()
735 }
736
737 fn description(&self) -> &str {
738 &self.signature.description
739 }
740
741 fn extra_description(&self) -> &str {
742 &self.signature.extra_description
743 }
744
745 fn run(
746 &self,
747 _engine_state: &EngineState,
748 _stack: &mut Stack,
749 _call: &Call,
750 _input: PipelineData,
751 ) -> Result<crate::PipelineData, crate::ShellError> {
752 Err(ShellError::GenericError {
753 error: "Internal error: can't run custom command with 'run', use block_id".into(),
754 msg: "".into(),
755 span: None,
756 help: None,
757 inner: vec![],
758 })
759 }
760
761 fn command_type(&self) -> CommandType {
762 CommandType::Custom
763 }
764
765 fn block_id(&self) -> Option<BlockId> {
766 Some(self.block_id)
767 }
768
769 fn attributes(&self) -> Vec<(String, Value)> {
770 self.attributes.clone()
771 }
772
773 fn examples(&self) -> Vec<Example> {
774 self.examples
775 .iter()
776 .map(CustomExample::to_example)
777 .collect()
778 }
779
780 fn search_terms(&self) -> Vec<&str> {
781 self.signature
782 .search_terms
783 .iter()
784 .map(String::as_str)
785 .collect()
786 }
787
788 fn deprecation_info(&self) -> Vec<DeprecationEntry> {
789 self.attributes
790 .iter()
791 .filter_map(|(key, value)| {
792 (key == "deprecated")
793 .then_some(value.clone())
794 .map(DeprecationEntry::from_value)
795 .and_then(Result::ok)
796 })
797 .collect()
798 }
799}