1use crate::config::{Alias, Config, PromptLevel, Recent};
2use crate::env_pair::EnvPair;
3use crate::error::{DisplayError, DisplayResult, Result};
4use crate::list::Grouping;
5use crate::path;
6use crate::query::{EditQuery, ListQuery, RangeQuery, ScriptOrDirQuery, ScriptQuery};
7use crate::script_type::{ScriptFullType, ScriptType};
8use crate::tag::TagSelector;
9use crate::to_display_args;
10use crate::Either;
11use crate::APP_NAME;
12use clap::{CommandFactory, Error as ClapError, Parser};
13use serde::Serialize;
14use std::num::NonZeroUsize;
15use std::path::PathBuf;
16use std::str::FromStr;
17
18mod completion;
19pub use completion::*;
20mod tags;
21pub use tags::*;
22mod help_str;
23mod types;
24use help_str::*;
25pub use types::*;
26
27#[derive(Parser, Debug, Serialize)]
28pub struct RootArgs {
29 #[clap(short = 'H', long, help = "Path to hyper script home")]
30 pub hs_home: Option<String>,
31 #[clap(long, hide = true)]
32 pub dump_args: bool,
33 #[clap(long, global = true, help = "Don't record history")]
34 pub no_trace: bool,
35 #[clap(
36 long,
37 global = true,
38 conflicts_with = "no-trace",
39 help = "Don't affect script time order (but still record history and affect time filter)"
40 )]
41 pub humble: bool,
42 #[clap(short = 'A', long, global = true, help = "Show scripts NOT within recent days", conflicts_with_all = &["all"])]
43 pub archaeology: bool,
44 #[clap(long)]
45 pub no_alias: bool,
46 #[clap(
47 short,
48 long,
49 global = true,
50 conflicts_with = "all",
51 number_of_values = 1,
52 help = "Select by tags, e.g. `all,^remove`"
53 )]
54 pub select: Vec<TagSelector>,
55 #[clap(
56 long,
57 conflicts_with = "all",
58 number_of_values = 1,
59 help = "Toggle named selector temporarily"
60 )]
61 pub toggle: Vec<String>, #[clap(
63 short,
64 long,
65 global = true,
66 conflicts_with = "recent",
67 help = "Shorthand for `-s=all,^remove --timeless`"
68 )]
69 all: bool,
70 #[clap(long, global = true, help = "Show scripts within recent days.")]
71 pub recent: Option<u32>,
72 #[clap(
73 long,
74 global = true,
75 help = "Show scripts of all time.",
76 conflicts_with = "recent"
77 )]
78 pub timeless: bool,
79 #[clap(long, possible_values(&["never", "always", "smart", "on-multi-fuzz"]), help = "Prompt level of fuzzy finder.")]
80 pub prompt_level: Option<PromptLevel>,
81}
82
83#[derive(Parser, Debug, Serialize)]
84#[clap(about, author, version)]
85#[clap(allow_hyphen_values = true, args_override_self = true)] pub struct Root {
87 #[clap(skip)]
88 #[serde(skip)]
89 is_from_alias: bool,
90 #[clap(flatten)]
91 pub root_args: RootArgs,
92 #[clap(subcommand)]
93 pub subcmd: Option<Subs>,
94}
95
96#[derive(Parser, Debug, Serialize)]
97pub enum AliasSubs {
98 #[clap(external_subcommand)]
99 Other(Vec<String>),
100}
101#[derive(Parser, Debug, Serialize)]
102#[clap(
103 args_override_self = true,
104 allow_hyphen_values = true,
105 disable_help_flag = true,
106 disable_help_subcommand = true
107)]
108pub struct AliasRoot {
109 #[clap(flatten)]
110 pub root_args: RootArgs,
111 #[clap(subcommand)]
112 pub subcmd: Option<AliasSubs>,
113}
114impl AliasRoot {
115 fn find_alias<'a>(&'a self, conf: &'a Config) -> Option<(&'a Alias, &'a [String])> {
116 match &self.subcmd {
117 None => None,
118 Some(AliasSubs::Other(v)) => {
119 let first = v.first().unwrap().as_str();
120 if let Some(alias) = conf.alias.get(first) {
121 log::info!("別名 {} => {:?}", first, alias);
122 Some((alias, v))
123 } else {
124 None
125 }
126 }
127 }
128 }
129 pub fn expand_alias<'a, T: 'a + AsRef<str>>(
130 &'a self,
131 args: &'a [T],
132 conf: &'a Config,
133 ) -> Option<Either<impl Iterator<Item = &'a str>, Vec<String>>> {
134 if let Some((alias, remaining_args)) = self.find_alias(conf) {
135 let (is_shell, after_args) = alias.args();
136 let remaining_args = remaining_args[1..].iter().map(String::as_str);
137
138 if is_shell {
139 let remaining_args = remaining_args.map(|s| to_display_args(s).to_string());
141 let ret: Vec<_> = after_args
142 .map(ToOwned::to_owned)
143 .chain(remaining_args)
144 .collect();
145 return Some(Either::Two(ret));
146 }
147
148 let base_len = args.len() - remaining_args.len() - 1;
149 let base_args = args.iter().take(base_len).map(AsRef::as_ref);
150 let new_args = base_args.chain(after_args).chain(remaining_args);
151
152 Some(Either::One(new_args))
154 } else {
155 None
156 }
157 }
158}
159
160#[derive(Parser, Debug, Serialize)]
161#[clap(disable_help_subcommand = true, args_override_self = true)]
162pub enum Subs {
163 #[clap(external_subcommand)]
164 Other(Vec<String>),
165 #[clap(
166 about = "Prints this message, the help of the given subcommand(s), or a script's help message."
167 )]
168 Help { args: Vec<String> },
169 #[clap(hide = true)]
170 LoadUtils,
171 #[clap(about = "Migrate the database")]
172 Migrate,
173 #[clap(about = "Edit hyper script", trailing_var_arg = true)]
174 Edit {
175 #[clap(long, short = 'T', help = TYPE_HELP)]
176 ty: Option<ScriptFullType>,
177 #[clap(long, short)]
178 no_template: bool,
179 #[clap(long, short, help = TAGS_HELP)]
180 tags: Option<TagSelector>,
181 #[clap(long, short, help = "Create script without invoking the editor")]
182 fast: bool,
183 #[clap(default_value = "?", help = EDIT_QUERY_HELP)]
184 edit_query: Vec<EditQuery<ListQuery>>,
185 #[clap(last = true)]
186 content: Vec<String>,
187 },
188 #[clap(
189 about = "Manage alias",
190 disable_help_flag = true,
191 allow_hyphen_values = true
192 )]
193 Alias {
194 #[clap(
195 long,
196 short,
197 requires = "before",
198 conflicts_with = "after",
199 help = "Unset an alias."
200 )]
201 unset: bool,
202 before: Option<String>,
203 #[clap(allow_hyphen_values = true)]
204 after: Vec<String>,
205 },
206
207 #[clap(about = "Print the path to script")]
208 Config,
209 #[clap(
210 about = "Run the script",
211 disable_help_flag = true,
212 allow_hyphen_values = true
213 )]
214 Run {
215 #[clap(long, help = "Run caution scripts without warning")]
216 no_caution: bool,
217 #[clap(long, help = "Add a dummy run history instead of actually running it")]
218 dummy: bool,
219 #[clap(long, short)]
220 repeat: Option<u64>,
221 #[clap(long, short, help = "Use arguments from last run")]
222 previous: bool,
223 #[clap(
224 long,
225 short = 'E',
226 requires = "previous",
227 help = "Raise an error if --previous is given but there is no previous run"
228 )]
229 error_no_previous: bool,
230 #[clap(long, short, requires = "previous", help = "")]
231 dir: Option<PathBuf>,
232 #[clap(default_value = "-", help = SCRIPT_QUERY_HELP)]
233 script_query: ScriptQuery,
234 #[clap(
235 help = "Command line args to pass to the script",
236 allow_hyphen_values = true
237 )]
238 args: Vec<String>,
239 },
240 #[clap(about = "Execute the script query and get the exact file")]
241 Which {
242 #[clap(default_value = "-", help = LIST_QUERY_HELP)]
243 queries: Vec<ListQuery>,
244 },
245 #[clap(about = "Print the script to standard output")]
246 Cat {
247 #[clap(default_value = "-", help = LIST_QUERY_HELP)]
248 queries: Vec<ListQuery>,
249 #[clap(long, help = "Read with other program, e.g. bat")]
250 with: Option<String>,
251 },
252 #[clap(about = "Remove the script")]
253 RM {
254 #[clap(required = true, min_values = 1, help = LIST_QUERY_HELP)]
255 queries: Vec<ListQuery>,
256 #[clap(
257 long,
258 help = "Actually remove scripts, rather than hiding them with tag."
259 )]
260 purge: bool,
261 },
262 #[clap(about = "Set recent filter")]
263 Recent { recent_filter: Option<Recent> },
264 #[clap(about = "List hyper scripts")]
265 LS(List),
266 #[clap(about = "Manage script types")]
267 Types(Types),
268 #[clap(about = "Copy the script to another one")]
269 CP {
270 #[clap(long, short, help = TAGS_HELP)]
271 tags: Option<TagSelector>,
272 #[clap(help = SCRIPT_QUERY_HELP)]
273 origin: ListQuery,
274 #[clap(help = EDIT_CONCRETE_QUERY_HELP)]
275 new: EditQuery<ScriptOrDirQuery>,
276 },
277 #[clap(about = "Move the script to another one")]
278 MV {
279 #[clap(long, short = 'T', help = TYPE_HELP)]
280 ty: Option<ScriptType>,
281 #[clap(long, short, help = TAGS_HELP)]
282 tags: Option<TagSelector>,
283 #[clap(help = LIST_QUERY_HELP)]
284 origin: ListQuery,
285 #[clap(help = EDIT_CONCRETE_QUERY_HELP)]
286 new: Option<EditQuery<ScriptOrDirQuery>>,
287 },
288 #[clap(about = "Manage script tags")]
289 Tags(Tags),
290 #[clap(about = "Manage script history")]
291 History {
292 #[clap(subcommand)]
293 subcmd: History,
294 },
295 #[clap(about = "Monitor hs process")]
296 Top {
297 #[clap(long, short, help = "Wait for all involved processes to halt")]
298 wait: bool,
299 #[clap(long, help = "Run event ID")]
300 id: Vec<u64>,
301 #[clap(help = LIST_QUERY_HELP)]
302 queries: Vec<ListQuery>,
303 },
304}
305
306#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize)]
307pub enum HistoryDisplay {
308 Env,
309 Args,
310 All,
311}
312impl HistoryDisplay {
313 pub fn show_args(&self) -> bool {
314 match self {
315 Self::Args | Self::All => true,
316 Self::Env => false,
317 }
318 }
319 pub fn show_env(&self) -> bool {
320 match self {
321 Self::Env | Self::All => true,
322 Self::Args => false,
323 }
324 }
325}
326impl FromStr for HistoryDisplay {
327 type Err = DisplayError;
328 fn from_str(s: &str) -> DisplayResult<Self> {
329 let g = match s {
330 "env" => HistoryDisplay::Env,
331 "args" => HistoryDisplay::Args,
332 "all" => HistoryDisplay::All,
333 _ => unreachable!(),
334 };
335 Ok(g)
336 }
337}
338
339#[derive(Parser, Debug, Serialize)]
340pub enum History {
341 RM {
342 #[clap(short, long)]
343 dir: Option<PathBuf>, #[clap(long, possible_values(&["all", "env", "args"]), default_value = "args",)]
345 display: HistoryDisplay,
346 #[clap(long)]
347 no_humble: bool,
348 #[clap(required = true, min_values = 1, help = LIST_QUERY_HELP)]
349 queries: Vec<ListQuery>,
350 #[clap(last = true)]
351 range: RangeQuery,
352 },
353 #[clap(
355 name = "rm-id",
356 about = "Remove an event by it's id.\nUseful if you want to keep those illegal arguments from polluting the history."
357 )]
358 RMID {
359 event_id: u64,
360 },
361 #[clap(about = "Humble an event by it's id")]
362 Humble {
363 event_id: u64,
364 },
365 Show {
366 #[clap(default_value = "-", help = LIST_QUERY_HELP)]
367 queries: Vec<ListQuery>,
368 #[clap(short, long, default_value = "10")]
369 limit: u32,
370 #[clap(long)]
371 with_name: bool,
372 #[clap(long)]
373 no_humble: bool,
374 #[clap(short, long, default_value = "0")]
375 offset: u32,
376 #[clap(short, long)]
377 dir: Option<PathBuf>,
378 #[clap(long, possible_values(&["all", "env", "args"]), default_value = "args",)]
379 display: HistoryDisplay,
380 },
381 Neglect {
382 #[clap(required = true, min_values = 1, help = LIST_QUERY_HELP)]
383 queries: Vec<ListQuery>,
384 },
385 #[clap(disable_help_flag = true, allow_hyphen_values = true)]
386 Amend {
387 event_id: u64,
388 #[clap(short, long)]
389 env: Vec<EnvPair>,
390 #[clap(long, conflicts_with = "env")]
391 no_env: bool,
392 #[clap(
393 help = "Command line args to pass to the script",
394 allow_hyphen_values = true
395 )]
396 args: Vec<String>,
397 },
398 Tidy,
399}
400
401#[derive(Parser, Debug, Serialize, Default)]
402#[clap(args_override_self = true)]
403pub struct List {
404 #[clap(short, long, help = "Show verbose information.")]
406 pub long: bool,
407 #[clap(long, possible_values(&["tag", "tree", "none"]), default_value = "tag", help = "Grouping style.")]
408 pub grouping: Grouping,
409 #[clap(long, help = "Limit the amount of scripts found.")]
410 pub limit: Option<NonZeroUsize>,
411 #[clap(long, help = "No color and other decoration.")]
412 pub plain: bool,
413 #[clap(
414 long,
415 help = "Define the formatting for each script.",
416 conflicts_with = "long",
417 default_value = "{{name}}({{ty}})"
418 )]
419 pub format: String,
420 #[clap(help = LIST_QUERY_HELP)]
421 pub queries: Vec<ListQuery>,
422}
423
424fn set_home(p: &Option<String>, create_on_missing: bool) -> Result {
425 path::set_home(p.as_ref(), create_on_missing)?;
426 Config::init()
427}
428
429fn print_help<S: AsRef<str>>(cmds: impl IntoIterator<Item = S>) {
430 let c = Root::command();
432 let mut clap = &c;
433 let mut had_found = false;
434 for cmd in cmds {
435 let cmd = cmd.as_ref();
436 clap.find_subcommand(cmd);
437 if let Some(c) = clap.find_subcommand(cmd) {
438 clap = c;
439 had_found = true;
440 } else if !had_found {
441 return;
442 }
443 }
444 let _ = clap.clone().print_help();
445 println!();
446 std::process::exit(0);
447}
448
449macro_rules! map_clap_res {
450 ($res:expr) => {{
451 match $res {
452 Err(err) => return Ok(ArgsResult::Err(err)),
453 Ok(t) => t,
454 }
455 }};
456}
457
458fn handle_alias_args(args: Vec<String>) -> Result<ArgsResult> {
459 match AliasRoot::try_parse_from(&args) {
460 Ok(alias_root) if alias_root.root_args.no_alias => {
461 log::debug!("不使用別名!");
462 let root = map_clap_res!(Root::try_parse_from(args));
463 return Ok(ArgsResult::Normal(root));
464 }
465 Ok(alias_root) => {
466 log::info!("別名命令行物件 {:?}", alias_root);
467 set_home(&alias_root.root_args.hs_home, true)?;
468 let mut root = match alias_root.expand_alias(&args, Config::get()) {
469 Some(Either::One(new_args)) => map_clap_res!(Root::try_parse_from(new_args)),
470 Some(Either::Two(new_args)) => {
471 return Ok(ArgsResult::Shell(new_args));
472 }
473 None => map_clap_res!(Root::try_parse_from(&args)),
474 };
475 root.is_from_alias = true;
476 Ok(ArgsResult::Normal(root))
477 }
478 Err(e) => {
479 log::warn!(
480 "解析別名參數出錯(應和 root_args 有關,如 --select 無值):{}",
481 e
482 );
483 map_clap_res!(Root::try_parse_from(args)); unreachable!()
485 }
486 }
487}
488
489impl Root {
490 pub fn set_home_unless_from_alias(&self, create_on_missing: bool) -> Result {
493 if !self.is_from_alias {
494 set_home(&self.root_args.hs_home, create_on_missing)?;
495 }
496 Ok(())
497 }
498 pub fn sanitize_flags(&mut self, bang: bool) {
499 if bang {
500 self.root_args.timeless = true;
501 self.root_args.select = vec!["all".parse().unwrap()];
502 } else if self.root_args.all {
503 self.root_args.timeless = true;
504 self.root_args.select = vec!["all,^remove".parse().unwrap()];
505 }
506 }
507 pub fn sanitize(&mut self) -> std::result::Result<(), ClapError> {
508 match &mut self.subcmd {
509 Some(Subs::Other(args)) => {
510 let args = [APP_NAME, "run"]
511 .into_iter()
512 .chain(args.iter().map(|s| s.as_str()));
513 self.subcmd = Some(Subs::try_parse_from(args)?);
514 log::info!("執行模式 {:?}", self.subcmd);
515 }
516 Some(Subs::Help { args }) => {
517 print_help(args.iter());
518 }
519 Some(Subs::Tags(tags)) => {
520 tags.sanitize()?;
521 }
522 Some(Subs::Types(types)) => {
523 types.sanitize()?;
524 }
525 None => {
526 log::info!("無參數模式");
527 self.subcmd = Some(Subs::Edit {
528 edit_query: vec![EditQuery::Query(ListQuery::Query(Default::default()))],
529 ty: None,
530 content: vec![],
531 tags: None,
532 fast: false,
533 no_template: false,
534 });
535 }
536 _ => (),
537 }
538 self.sanitize_flags(false);
539 Ok(())
540 }
541}
542
543pub enum ArgsResult {
544 Normal(Root),
545 Completion(Completion),
546 Shell(Vec<String>),
547 Err(ClapError),
548}
549
550pub fn handle_args(args: Vec<String>) -> Result<ArgsResult> {
551 if let Some(completion) = Completion::from_args(&args) {
552 return Ok(ArgsResult::Completion(completion));
553 }
554 let mut root = handle_alias_args(args)?;
555 if let ArgsResult::Normal(root) = &mut root {
556 log::debug!("命令行物件:{:?}", root);
557 map_clap_res!(root.sanitize());
558 }
559 Ok(root)
560}
561
562#[cfg(test)]
563mod test {
564 use super::*;
565 fn try_build_args(args: &str) -> std::result::Result<Root, ClapError> {
566 let v: Vec<_> = std::iter::once(APP_NAME)
567 .chain(args.split(' '))
568 .map(|s| s.to_owned())
569 .collect();
570 match handle_args(v).unwrap() {
571 ArgsResult::Normal(root) => Ok(root),
572 ArgsResult::Err(err) => Err(err),
573 _ => panic!(),
574 }
575 }
576 fn build_args(args: &str) -> Root {
577 try_build_args(args).unwrap()
578 }
579 fn is_args_eq(arg1: &Root, arg2: &Root) -> bool {
580 let json1 = serde_json::to_value(arg1).unwrap();
581 let json2 = serde_json::to_value(arg2).unwrap();
582 json1 == json2
583 }
584 #[test]
585 fn test_strange_set_alias() {
586 let args = build_args("alias trash -s remove");
587 assert_eq!(args.root_args.select, vec![]);
588 match &args.subcmd {
589 Some(Subs::Alias {
590 unset,
591 after,
592 before: Some(before),
593 }) => {
594 assert_eq!(*unset, false);
595 assert_eq!(before, "trash");
596 assert_eq!(after, &["-s", "remove"]);
597 }
598 _ => panic!("{:?} should be alias...", args),
599 }
600 }
601 #[test]
602 fn test_displaced_no_alias() {
603 let ll = build_args("ll");
604 assert!(!ll.root_args.no_alias);
605 assert!(is_args_eq(&ll, &build_args("ls -l")));
606
607 try_build_args("ll --no-alias").expect_err("ll 即 ls -l,不該有 --no-alias 作參數");
608
609 let run_ll = build_args("--no-alias ll");
610 assert!(run_ll.root_args.no_alias);
611 assert!(is_args_eq(&run_ll, &build_args("--no-alias run ll")));
612
613 let run_some_script = build_args("some-script --no-alias");
614 assert!(!run_some_script.root_args.no_alias);
615 let run_some_script_no_alias = build_args("--no-alias some-script");
616 assert!(run_some_script_no_alias.root_args.no_alias);
617 }
618 #[test]
619 fn test_strange_alias() {
620 let args = build_args("-s e e -t e something -T e");
621 assert_eq!(args.root_args.select, vec!["e".parse().unwrap()]);
622 assert_eq!(args.root_args.all, false);
623 match &args.subcmd {
624 Some(Subs::Edit {
625 edit_query,
626 tags,
627 ty,
628 content,
629 ..
630 }) => {
631 let query = match &edit_query[0] {
632 EditQuery::Query(ListQuery::Query(query)) => query,
633 _ => panic!(),
634 };
635 assert_eq!(query, &"something".parse().unwrap());
636 assert_eq!(tags, &"e".parse().ok());
637 assert_eq!(ty, &"e".parse().ok());
638 assert_eq!(content, &Vec::<String>::new());
639 }
640 _ => {
641 panic!("{:?} should be edit...", args);
642 }
643 }
644
645 let args = build_args("la -l");
646 assert_eq!(args.root_args.all, true);
647 assert_eq!(args.root_args.select, vec!["all,^remove".parse().unwrap()]);
648 match &args.subcmd {
649 Some(Subs::LS(opt)) => {
650 assert_eq!(opt.long, true);
651 assert_eq!(opt.queries.len(), 0);
652 }
653 _ => {
654 panic!("{:?} should be edit...", args);
655 }
656 }
657 }
658 #[test]
659 fn test_multi_edit() {
660 assert!(is_args_eq(
661 &build_args("edit -- a b c"),
662 &build_args("edit ? -- a b c")
663 ));
664
665 let args = build_args("edit a ? * -- x y z");
666 match args.subcmd {
667 Some(Subs::Edit {
668 edit_query,
669 content,
670 ..
671 }) => {
672 assert_eq!(3, edit_query.len());
673 assert!(matches!(
674 edit_query[0],
675 EditQuery::Query(ListQuery::Query(..))
676 ));
677 assert!(matches!(edit_query[1], EditQuery::NewAnonimous));
678 assert!(matches!(
679 edit_query[2],
680 EditQuery::Query(ListQuery::Pattern(..))
681 ));
682 assert_eq!(
683 content,
684 vec!["x".to_owned(), "y".to_owned(), "z".to_owned()]
685 );
686 }
687 _ => {
688 panic!("{:?} should be edit...", args);
689 }
690 }
691 }
692 #[test]
693 fn test_external_run_tags() {
694 let args = build_args("-s test --dummy -r 42 =script -a --");
695 assert!(is_args_eq(
696 &args,
697 &build_args("-s test run --dummy -r 42 =script -a --")
698 ));
699 assert_eq!(args.root_args.select, vec!["test".parse().unwrap()]);
700 assert_eq!(args.root_args.all, false);
701 match args.subcmd {
702 Some(Subs::Run {
703 dummy: true,
704 previous: false,
705 error_no_previous: false,
706 repeat: Some(42),
707 dir: None,
708 no_caution: false,
709 script_query,
710 args,
711 }) => {
712 assert_eq!(script_query, "=script".parse().unwrap());
713 assert_eq!(args, vec!["-a", "--"]);
714 }
715 _ => {
716 panic!("{:?} should be run...", args);
717 }
718 }
719
720 let args = build_args("-s test --dump-args tags --name myname +mytag");
721 assert!(is_args_eq(
722 &args,
723 &build_args("-s test --dump-args tags set --name myname +mytag")
724 ));
725 assert_eq!(args.root_args.select, vec!["test".parse().unwrap()]);
726 assert_eq!(args.root_args.all, false);
727 assert!(args.root_args.dump_args);
728 match args.subcmd {
729 Some(Subs::Tags(Tags {
730 subcmd: Some(TagsSubs::Set { name, content }),
731 })) => {
732 assert_eq!(name, Some("myname".to_owned()));
733 assert_eq!(content, "+mytag".parse().unwrap());
734 }
735 _ => {
736 panic!("{:?} should be tags...", args);
737 }
738 }
739
740 assert!(is_args_eq(
741 &build_args("--humble"),
742 &build_args("--humble edit -")
743 ));
744 assert!(is_args_eq(&build_args("tags"), &build_args("tags ls")));
745 }
746 #[test]
747 fn test_disable_help() {
748 let help_v = vec!["--help".to_owned()];
749 let args = build_args("run =script --help");
750 match args.subcmd {
751 Some(Subs::Run {
752 script_query, args, ..
753 }) => {
754 assert_eq!(script_query, "=script".parse().unwrap());
755 assert_eq!(args, help_v);
756 }
757 _ => {
758 panic!("{:?} should be run...", args);
759 }
760 }
761
762 let args = build_args("alias a --help");
763 match args.subcmd {
764 Some(Subs::Alias { before, after, .. }) => {
765 assert_eq!(before, Some("a".to_owned()));
766 assert_eq!(after, help_v);
767 }
768 _ => {
769 panic!("{:?} should be alias...", args);
770 }
771 }
772
773 let args = build_args("history amend 42 --env A=1 --env B=2 --help");
774 match args.subcmd {
775 Some(Subs::History {
776 subcmd:
777 History::Amend {
778 event_id,
779 args,
780 no_env,
781 env,
782 },
783 }) => {
784 assert_eq!(event_id, 42);
785 assert_eq!(args, help_v);
786 assert_eq!(no_env, false);
787 assert_eq!(env, vec!["A=1".parse().unwrap(), "B=2".parse().unwrap()]);
788 }
789 _ => {
790 panic!("{:?} should be history amend...", args);
791 }
792 }
793 }
794 #[test]
795 #[ignore = "clap bug"]
796 fn test_allow_hyphen() {
797 assert!(is_args_eq(
798 &build_args("alias a -u"),
799 &build_args("alias a -- -u")
800 ));
801 assert!(is_args_eq(
802 &build_args("run s --repeat 1"),
803 &build_args("run s -- --repeat 1")
804 ));
805 }
806}