hyper_scripter/args/
mod.rs

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