Like in other "unsupported" cases that return a specific exit code
Details
Details
Diff Detail
Diff Detail
- Repository
- rHG Mercurial
- Branch
- default
- Lint
No Linters Available - Unit
No Unit Test Coverage
( )
Like in other "unsupported" cases that return a specific exit code
No Linters Available |
No Unit Test Coverage |
Path | Packages | |||
---|---|---|---|---|
M | rust/rhg/src/error.rs (8 lines) | |||
M | rust/rhg/src/main.rs (29 lines) | |||
M | rust/rhg/src/ui.rs (5 lines) | |||
M | tests/test-rhg.t (6 lines) |
Commit | Parents | Author | Summary | Date |
---|---|---|---|---|
210b228178e8 | c82d6363bc9e | Simon Sapin | Feb 12 2021, 10:54 AM |
Status | Author | Revision | |
---|---|---|---|
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | SimonSapin |
// TODO: bytes-based (instead of Unicode-based) formatting | // TODO: bytes-based (instead of Unicode-based) formatting | ||||
// of error messages to handle non-UTF-8 filenames etc: | // of error messages to handle non-UTF-8 filenames etc: | ||||
// https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output | // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output | ||||
message: utf8_to_local(message.as_ref()).into(), | message: utf8_to_local(message.as_ref()).into(), | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/// For now we don’t differenciate between invalid CLI args and valid for `hg` | |||||
/// but not supported yet by `rhg`. | |||||
impl From<clap::Error> for CommandError { | |||||
fn from(_: clap::Error) -> Self { | |||||
CommandError::Unimplemented | |||||
} | |||||
} | |||||
impl From<HgError> for CommandError { | impl From<HgError> for CommandError { | ||||
fn from(error: HgError) -> Self { | fn from(error: HgError) -> Self { | ||||
match error { | match error { | ||||
HgError::UnsupportedFeature(_) => CommandError::Unimplemented, | HgError::UnsupportedFeature(_) => CommandError::Unimplemented, | ||||
_ => CommandError::abort(error.to_string()), | _ => CommandError::abort(error.to_string()), | ||||
} | } | ||||
} | } | ||||
} | } |
.takes_value(true) | .takes_value(true) | ||||
// Ok: `--config section.key1=val --config section.key2=val2` | // Ok: `--config section.key1=val --config section.key2=val2` | ||||
.multiple(true) | .multiple(true) | ||||
// Not ok: `--config section.key1=val section.key2=val2` | // Not ok: `--config section.key1=val section.key2=val2` | ||||
.number_of_values(1), | .number_of_values(1), | ||||
) | ) | ||||
} | } | ||||
fn main() { | fn main_with_result(ui: &ui::Ui) -> Result<(), CommandError> { | ||||
env_logger::init(); | env_logger::init(); | ||||
let app = App::new("rhg") | let app = App::new("rhg") | ||||
.setting(AppSettings::AllowInvalidUtf8) | .setting(AppSettings::AllowInvalidUtf8) | ||||
.setting(AppSettings::SubcommandRequired) | .setting(AppSettings::SubcommandRequired) | ||||
.setting(AppSettings::VersionlessSubcommands) | .setting(AppSettings::VersionlessSubcommands) | ||||
.version("0.0.1"); | .version("0.0.1"); | ||||
let app = add_global_args(app); | let app = add_global_args(app); | ||||
let app = add_subcommand_args(app); | let app = add_subcommand_args(app); | ||||
let ui = ui::Ui::new(); | let matches = app.clone().get_matches_safe()?; | ||||
let matches = app.clone().get_matches_safe().unwrap_or_else(|err| { | |||||
let _ = ui.writeln_stderr_str(&err.message); | |||||
std::process::exit(exitcode::UNIMPLEMENTED) | |||||
}); | |||||
let (subcommand_name, subcommand_matches) = matches.subcommand(); | let (subcommand_name, subcommand_matches) = matches.subcommand(); | ||||
let run = subcommand_run_fn(subcommand_name) | let run = subcommand_run_fn(subcommand_name) | ||||
.expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired"); | .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired"); | ||||
let args = subcommand_matches | let args = subcommand_matches | ||||
.expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired"); | .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired"); | ||||
// Global arguments can be in either based on e.g. `hg -R ./foo log` v.s. | // Global arguments can be in either based on e.g. `hg -R ./foo log` v.s. | ||||
// `hg log -R ./foo` | // `hg log -R ./foo` | ||||
let value_of_global_arg = | let value_of_global_arg = | ||||
|name| args.value_of_os(name).or_else(|| matches.value_of_os(name)); | |name| args.value_of_os(name).or_else(|| matches.value_of_os(name)); | ||||
// For arguments where multiple occurences are allowed, return a | // For arguments where multiple occurences are allowed, return a | ||||
// possibly-iterator of all values. | // possibly-iterator of all values. | ||||
let values_of_global_arg = |name: &str| { | let values_of_global_arg = |name: &str| { | ||||
let a = matches.values_of_os(name).into_iter().flatten(); | let a = matches.values_of_os(name).into_iter().flatten(); | ||||
let b = args.values_of_os(name).into_iter().flatten(); | let b = args.values_of_os(name).into_iter().flatten(); | ||||
a.chain(b) | a.chain(b) | ||||
}; | }; | ||||
let repo_path = value_of_global_arg("repository").map(Path::new); | let repo_path = value_of_global_arg("repository").map(Path::new); | ||||
let result = (|| -> Result<(), CommandError> { | |||||
let config_args = values_of_global_arg("config") | let config_args = values_of_global_arg("config") | ||||
// `get_bytes_from_path` works for OsStr the same as for Path | // `get_bytes_from_path` works for OsStr the same as for Path | ||||
.map(hg::utils::files::get_bytes_from_path); | .map(hg::utils::files::get_bytes_from_path); | ||||
let config = hg::config::Config::load(config_args)?; | let config = hg::config::Config::load(config_args)?; | ||||
run(&ui, &config, repo_path, args) | run(&ui, &config, repo_path, args) | ||||
})(); | } | ||||
fn main() { | |||||
let ui = ui::Ui::new(); | |||||
let exit_code = match result { | let exit_code = match main_with_result(&ui) { | ||||
Ok(_) => exitcode::OK, | Ok(()) => exitcode::OK, | ||||
// Exit with a specific code and no error message to let a potential | // Exit with a specific code and no error message to let a potential | ||||
// wrapper script fallback to Python-based Mercurial. | // wrapper script fallback to Python-based Mercurial. | ||||
Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED, | Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED, | ||||
Err(CommandError::Abort { message }) => { | Err(CommandError::Abort { message }) => { | ||||
if !message.is_empty() { | if !message.is_empty() { | ||||
// Ignore errors when writing to stderr, we’re already exiting | // Ignore errors when writing to stderr, we’re already exiting |
/// Write bytes to stderr | /// Write bytes to stderr | ||||
pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> { | pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> { | ||||
let mut stderr = self.stderr.lock(); | let mut stderr = self.stderr.lock(); | ||||
stderr.write_all(bytes).or_else(handle_stderr_error)?; | stderr.write_all(bytes).or_else(handle_stderr_error)?; | ||||
stderr.flush().or_else(handle_stderr_error) | stderr.flush().or_else(handle_stderr_error) | ||||
} | } | ||||
/// Write string line to stderr | |||||
pub fn writeln_stderr_str(&self, s: &str) -> Result<(), UiError> { | |||||
self.write_stderr(&format!("{}\n", s).as_bytes()) | |||||
} | |||||
} | } | ||||
/// A buffered stdout writer for faster batch printing operations. | /// A buffered stdout writer for faster batch printing operations. | ||||
pub struct StdoutBuffer<W: Write> { | pub struct StdoutBuffer<W: Write> { | ||||
buf: io::BufWriter<W>, | buf: io::BufWriter<W>, | ||||
} | } | ||||
impl<W: Write> StdoutBuffer<W> { | impl<W: Write> StdoutBuffer<W> { |
#require rust | #require rust | ||||
Define an rhg function that will only run if rhg exists | Define an rhg function that will only run if rhg exists | ||||
$ rhg() { | $ rhg() { | ||||
> if [ -f "$RUNTESTDIR/../rust/target/release/rhg" ]; then | > if [ -f "$RUNTESTDIR/../rust/target/release/rhg" ]; then | ||||
> "$RUNTESTDIR/../rust/target/release/rhg" "$@" | > "$RUNTESTDIR/../rust/target/release/rhg" "$@" | ||||
> else | > else | ||||
> echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg." | > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg." | ||||
> exit 80 | > exit 80 | ||||
> fi | > fi | ||||
> } | > } | ||||
Unimplemented command | Unimplemented command | ||||
$ rhg unimplemented-command | $ rhg unimplemented-command | ||||
error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context | |||||
USAGE: | |||||
rhg [OPTIONS] <SUBCOMMAND> | |||||
For more information try --help | |||||
[252] | [252] | ||||
Finding root | Finding root | ||||
$ rhg root | $ rhg root | ||||
abort: no repository found in '$TESTTMP' (.hg not found)! | abort: no repository found in '$TESTTMP' (.hg not found)! | ||||
[255] | [255] | ||||
$ hg init repository | $ hg init repository |