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