hyper_scripter/args/
mod.rs

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>, // TODO: new type?
61    #[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)] // NOTE: 我們需要那個 `allow_hyphen_values` 來允許 hs --dummy 這樣的命令
87pub 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 // shell 別名,完全無視開頭的參數(例如 `hs -s tag -H path/to/home`)
140            } 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            // log::trace!("新的參數為 {:?}", new_args);
159            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>, // FIXME: this flag isn't working...
344        #[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    // TODO: 好想把它寫在 history rm 裡面...
354    #[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    // TODO: 滿滿的其它排序/篩選選項
405    #[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    // 從 clap 的 parse_help_subcommand 函式抄的,不曉得有沒有更好的做法
428    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)); // NOTE: 不要讓這個錯誤傳上去,而是讓它掉入 Root::try_parse_from 中再來報錯
481            unreachable!()
482        }
483    }
484}
485
486impl Root {
487    /// 若帶了 --no-alias 選項,或是補全模式,我們可以把設定腳本之家(以及載入設定檔)的時間再推遲
488    /// 在補全模式中意義重大,因為使用者可能會用 -H 指定別的腳本之家
489    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}