1use std::io::{Write, stderr, stdout};
4
5use clap::{Arg, ArgAction, ArgMatches, Command, command};
6use serde_json::json;
7
8use crate::config::Config;
9use crate::subcommands;
10
11pub fn get_clap_app() -> Command {
13 command!()
14 .arg(
15 Arg::new("config")
16 .short('c')
17 .long("config")
18 .value_name("FILE")
19 .global(true)
20 .help("Read configuration from FILE instead of the default gitconfig."),
21 )
22 .arg(
23 Arg::new("verbose")
24 .short('v')
25 .long("verbose")
26 .action(ArgAction::SetTrue)
27 .global(true)
28 .help("Enable verbose mode."),
29 )
30 .arg(
31 Arg::new("json")
32 .short('j')
33 .long("json")
34 .action(ArgAction::SetTrue)
35 .global(true)
36 .help("Output subcommand results in JSON."),
37 )
38 .arg(
39 Arg::new("untracked")
40 .short('u')
41 .long("untracked")
42 .action(ArgAction::SetTrue)
43 .conflicts_with("nountracked")
44 .global(true)
45 .help("Show untracked files in output."),
46 )
47 .arg(
48 Arg::new("nountracked")
49 .short('t')
50 .long("nountracked")
51 .action(ArgAction::SetTrue)
52 .conflicts_with("untracked")
53 .global(true)
54 .help("Don't show untracked files in output."),
55 )
56 .subcommands(
57 subcommands::get_subcommands()
58 .iter()
59 .map(|(cmd, desc)| {
60 let mut subcmd = Command::new(*cmd).about(*desc);
61 if *cmd == "ignore" {
62 subcmd = subcmd.arg(
63 Arg::new("pattern")
64 .help("Pattern to add to global.ignore (matches paths containing this string)")
65 .required(true)
66 .index(1),
67 );
68 }
69 if *cmd == "scan" {
70 subcmd = subcmd.arg(
71 Arg::new("paths")
72 .help("Additional directories to scan for git repos")
73 .num_args(0..)
74 .value_name("PATH"),
75 );
76 }
77 subcmd
78 }),
79 )
80}
81
82fn merge_args_with_config(config: &mut Config, matches: &ArgMatches) {
84 if matches.get_flag("verbose") {
85 config.verbose = true;
86 }
87 if matches.get_flag("untracked") {
88 config.show_untracked = true;
89 }
90 if matches.get_flag("nountracked") {
91 config.show_untracked = false;
92 }
93}
94
95pub fn run_from_command_line() -> i32 {
100 let clap_app = get_clap_app();
101 let matches = clap_app.get_matches();
102 let mut config = match matches.get_one::<String>("config") {
103 Some(path) => Config::from_gitconfig(path),
104 None => Config::new(),
105 };
106 merge_args_with_config(&mut config, &matches);
107
108 let args: Vec<String> = matches
110 .subcommand()
111 .map(|(name, sub_matches)| match name {
112 "ignore" => sub_matches
113 .get_one::<String>("pattern")
114 .cloned()
115 .into_iter()
116 .collect(),
117 "scan" => sub_matches
118 .get_many::<String>("paths")
119 .map(|v| v.cloned().collect())
120 .unwrap_or_default(),
121 _ => vec![],
122 })
123 .unwrap_or_default();
124
125 let report = subcommands::run(matches.subcommand_name(), config, args);
126 let use_json = matches.get_flag("json");
127 match report {
128 Ok(rep) => {
129 if use_json {
130 rep.print_json(&mut stdout());
131 } else {
132 rep.print(&mut stdout());
133 }
134 0
135 }
136 Err(err) => {
137 if use_json {
138 let out = json!({
139 "error": true,
140 "message": format!("{}", err)
141 });
142 writeln!(&mut stderr(), "{:#}", out).unwrap();
143 } else {
144 writeln!(&mut stderr(), "{}", err).unwrap();
145 }
146 1
147 }
148 }
149}