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