##// END OF EJS Templates
rhg: Use clap’s support for global CLI arguments...
Simon Sapin -
r47351:4e4c7040 default
parent child Browse files
Show More
@@ -1,191 +1,179 b''
1 extern crate log;
1 extern crate log;
2 use crate::ui::Ui;
2 use crate::ui::Ui;
3 use clap::App;
3 use clap::App;
4 use clap::AppSettings;
4 use clap::AppSettings;
5 use clap::Arg;
5 use clap::Arg;
6 use clap::ArgMatches;
6 use clap::ArgMatches;
7 use format_bytes::format_bytes;
7 use format_bytes::format_bytes;
8 use hg::config::Config;
8 use hg::config::Config;
9 use hg::repo::{Repo, RepoError};
9 use hg::repo::{Repo, RepoError};
10 use std::path::{Path, PathBuf};
10 use std::path::{Path, PathBuf};
11
11
12 mod blackbox;
12 mod blackbox;
13 mod error;
13 mod error;
14 mod exitcode;
14 mod exitcode;
15 mod ui;
15 mod ui;
16 use error::CommandError;
16 use error::CommandError;
17
17
18 fn add_global_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
19 app.arg(
20 Arg::with_name("repository")
21 .help("repository root directory")
22 .short("-R")
23 .long("--repository")
24 .value_name("REPO")
25 .takes_value(true),
26 )
27 .arg(
28 Arg::with_name("config")
29 .help("set/override config option (use 'section.name=value')")
30 .long("--config")
31 .value_name("CONFIG")
32 .takes_value(true)
33 // Ok: `--config section.key1=val --config section.key2=val2`
34 .multiple(true)
35 // Not ok: `--config section.key1=val section.key2=val2`
36 .number_of_values(1),
37 )
38 }
39
40 fn main_with_result(
18 fn main_with_result(
41 ui: &ui::Ui,
19 ui: &ui::Ui,
42 process_start_time: &blackbox::ProcessStartTime,
20 process_start_time: &blackbox::ProcessStartTime,
43 ) -> Result<(), CommandError> {
21 ) -> Result<(), CommandError> {
44 env_logger::init();
22 env_logger::init();
45 let app = App::new("rhg")
23 let app = App::new("rhg")
46 .setting(AppSettings::AllowInvalidUtf8)
24 .global_setting(AppSettings::AllowInvalidUtf8)
47 .setting(AppSettings::SubcommandRequired)
25 .setting(AppSettings::SubcommandRequired)
48 .setting(AppSettings::VersionlessSubcommands)
26 .setting(AppSettings::VersionlessSubcommands)
27 .arg(
28 Arg::with_name("repository")
29 .help("repository root directory")
30 .short("-R")
31 .long("--repository")
32 .value_name("REPO")
33 .takes_value(true)
34 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
35 .global(true),
36 )
37 .arg(
38 Arg::with_name("config")
39 .help("set/override config option (use 'section.name=value')")
40 .long("--config")
41 .value_name("CONFIG")
42 .takes_value(true)
43 .global(true)
44 // Ok: `--config section.key1=val --config section.key2=val2`
45 .multiple(true)
46 // Not ok: `--config section.key1=val section.key2=val2`
47 .number_of_values(1),
48 )
49 .version("0.0.1");
49 .version("0.0.1");
50 let app = add_global_args(app);
51 let app = add_subcommand_args(app);
50 let app = add_subcommand_args(app);
52
51
53 let matches = app.clone().get_matches_safe()?;
52 let matches = app.clone().get_matches_safe()?;
54
53
55 let (subcommand_name, subcommand_matches) = matches.subcommand();
54 let (subcommand_name, subcommand_matches) = matches.subcommand();
56 let run = subcommand_run_fn(subcommand_name)
55 let run = subcommand_run_fn(subcommand_name)
57 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
56 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
58 let subcommand_args = subcommand_matches
57 let subcommand_args = subcommand_matches
59 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
58 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
60
59
61 // Global arguments can be in either based on e.g. `hg -R ./foo log` v.s.
60 let config_args = matches
62 // `hg log -R ./foo`
61 .values_of_os("config")
63 let value_of_global_arg = |name| {
62 // Turn `Option::None` into an empty iterator:
64 subcommand_args
63 .into_iter()
65 .value_of_os(name)
64 .flatten()
66 .or_else(|| matches.value_of_os(name))
67 };
68 // For arguments where multiple occurences are allowed, return a
69 // possibly-iterator of all values.
70 let values_of_global_arg = |name: &str| {
71 let a = matches.values_of_os(name).into_iter().flatten();
72 let b = subcommand_args.values_of_os(name).into_iter().flatten();
73 a.chain(b)
74 };
75
76 let config_args = values_of_global_arg("config")
77 .map(hg::utils::files::get_bytes_from_os_str);
65 .map(hg::utils::files::get_bytes_from_os_str);
78 let non_repo_config = &hg::config::Config::load(config_args)?;
66 let non_repo_config = &hg::config::Config::load(config_args)?;
79
67
80 let repo_path = value_of_global_arg("repository").map(Path::new);
68 let repo_path = matches.value_of_os("repository").map(Path::new);
81 let repo = match Repo::find(non_repo_config, repo_path) {
69 let repo = match Repo::find(non_repo_config, repo_path) {
82 Ok(repo) => Ok(repo),
70 Ok(repo) => Ok(repo),
83 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
71 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
84 // Not finding a repo is not fatal yet, if `-R` was not given
72 // Not finding a repo is not fatal yet, if `-R` was not given
85 Err(NoRepoInCwdError { cwd: at })
73 Err(NoRepoInCwdError { cwd: at })
86 }
74 }
87 Err(error) => return Err(error.into()),
75 Err(error) => return Err(error.into()),
88 };
76 };
89
77
90 let invocation = CliInvocation {
78 let invocation = CliInvocation {
91 ui,
79 ui,
92 subcommand_args,
80 subcommand_args,
93 non_repo_config,
81 non_repo_config,
94 repo: repo.as_ref(),
82 repo: repo.as_ref(),
95 };
83 };
96 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
84 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
97 blackbox.log_command_start();
85 blackbox.log_command_start();
98 let result = run(&invocation);
86 let result = run(&invocation);
99 blackbox.log_command_end(exit_code(&result));
87 blackbox.log_command_end(exit_code(&result));
100 result
88 result
101 }
89 }
102
90
103 fn main() {
91 fn main() {
104 // Run this first, before we find out if the blackbox extension is even
92 // Run this first, before we find out if the blackbox extension is even
105 // enabled, in order to include everything in-between in the duration
93 // enabled, in order to include everything in-between in the duration
106 // measurements. Reading config files can be slow if they’re on NFS.
94 // measurements. Reading config files can be slow if they’re on NFS.
107 let process_start_time = blackbox::ProcessStartTime::now();
95 let process_start_time = blackbox::ProcessStartTime::now();
108
96
109 let ui = ui::Ui::new();
97 let ui = ui::Ui::new();
110
98
111 let result = main_with_result(&ui, &process_start_time);
99 let result = main_with_result(&ui, &process_start_time);
112 if let Err(CommandError::Abort { message }) = &result {
100 if let Err(CommandError::Abort { message }) = &result {
113 if !message.is_empty() {
101 if !message.is_empty() {
114 // Ignore errors when writing to stderr, we’re already exiting
102 // Ignore errors when writing to stderr, we’re already exiting
115 // with failure code so there’s not much more we can do.
103 // with failure code so there’s not much more we can do.
116 let _ = ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
104 let _ = ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
117 }
105 }
118 }
106 }
119 std::process::exit(exit_code(&result))
107 std::process::exit(exit_code(&result))
120 }
108 }
121
109
122 fn exit_code(result: &Result<(), CommandError>) -> i32 {
110 fn exit_code(result: &Result<(), CommandError>) -> i32 {
123 match result {
111 match result {
124 Ok(()) => exitcode::OK,
112 Ok(()) => exitcode::OK,
125 Err(CommandError::Abort { .. }) => exitcode::ABORT,
113 Err(CommandError::Abort { .. }) => exitcode::ABORT,
126
114
127 // Exit with a specific code and no error message to let a potential
115 // Exit with a specific code and no error message to let a potential
128 // wrapper script fallback to Python-based Mercurial.
116 // wrapper script fallback to Python-based Mercurial.
129 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
117 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
130 }
118 }
131 }
119 }
132
120
133 macro_rules! subcommands {
121 macro_rules! subcommands {
134 ($( $command: ident )+) => {
122 ($( $command: ident )+) => {
135 mod commands {
123 mod commands {
136 $(
124 $(
137 pub mod $command;
125 pub mod $command;
138 )+
126 )+
139 }
127 }
140
128
141 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
129 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
142 app
130 app
143 $(
131 $(
144 .subcommand(add_global_args(commands::$command::args()))
132 .subcommand(commands::$command::args())
145 )+
133 )+
146 }
134 }
147
135
148 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
136 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
149
137
150 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
138 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
151 match name {
139 match name {
152 $(
140 $(
153 stringify!($command) => Some(commands::$command::run),
141 stringify!($command) => Some(commands::$command::run),
154 )+
142 )+
155 _ => None,
143 _ => None,
156 }
144 }
157 }
145 }
158 };
146 };
159 }
147 }
160
148
161 subcommands! {
149 subcommands! {
162 cat
150 cat
163 debugdata
151 debugdata
164 debugrequirements
152 debugrequirements
165 files
153 files
166 root
154 root
167 config
155 config
168 }
156 }
169 pub struct CliInvocation<'a> {
157 pub struct CliInvocation<'a> {
170 ui: &'a Ui,
158 ui: &'a Ui,
171 subcommand_args: &'a ArgMatches<'a>,
159 subcommand_args: &'a ArgMatches<'a>,
172 non_repo_config: &'a Config,
160 non_repo_config: &'a Config,
173 /// References inside `Result` is a bit peculiar but allow
161 /// References inside `Result` is a bit peculiar but allow
174 /// `invocation.repo?` to work out with `&CliInvocation` since this
162 /// `invocation.repo?` to work out with `&CliInvocation` since this
175 /// `Result` type is `Copy`.
163 /// `Result` type is `Copy`.
176 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
164 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
177 }
165 }
178
166
179 struct NoRepoInCwdError {
167 struct NoRepoInCwdError {
180 cwd: PathBuf,
168 cwd: PathBuf,
181 }
169 }
182
170
183 impl CliInvocation<'_> {
171 impl CliInvocation<'_> {
184 fn config(&self) -> &Config {
172 fn config(&self) -> &Config {
185 if let Ok(repo) = self.repo {
173 if let Ok(repo) = self.repo {
186 repo.config()
174 repo.config()
187 } else {
175 } else {
188 self.non_repo_config
176 self.non_repo_config
189 }
177 }
190 }
178 }
191 }
179 }
General Comments 0
You need to be logged in to leave comments. Login now