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