diff --git a/src/main.rs b/src/main.rs index 45bfc31..1438f57 100644 --- a/src/main.rs +++ b/src/main.rs @@ -100,18 +100,6 @@ impl<'a> From for CliError<'a> { } } -impl<'a> From<&'a str> for CliError<'a> { - fn from(err: &'a str) -> CliError<'a> { - CliError::Other(err.into()) - } -} - -impl<'a> From for CliError<'a> { - fn from(err: String) -> CliError<'a> { - CliError::Other(err.into()) - } -} - impl<'a> fmt::Display for CliError<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -140,7 +128,6 @@ impl<'a> Error for CliError<'a> { } } -#[derive(Debug)] enum CliArgument<'a> { HelpShort, HelpLong, @@ -149,7 +136,6 @@ enum CliArgument<'a> { Config(ConfigArgument<'a>), } -#[derive(Debug)] enum ConfigArgument<'a> { Width(Option<&'a str>), } @@ -180,29 +166,30 @@ impl Config { let cfg = Config { width: width as usize, - comment_char: Config::comment_char_from_git(), + comment_char: parse_git_config_commentchar(git_config_commentchar()), }; Ok(cfg) } +} - fn comment_char_from_git() -> char { - use std::process::Command; +fn git_config_commentchar() -> Result, io::Error> { + std::process::Command::new("git") + .args(&["config", "core.commentChar"]) + .output() + .map(|o| o.stdout) +} - let output: Vec = Command::new("git") - .args(&["config", "core.commentChar"]) - .output() - .map(|o| o.stdout) - .unwrap_or_else(|_| "#".into()); - - // The setting is either unset, "auto", or precisely 1 ASCII character; - // Git won't commit with an invalid configuration value. "auto" support - // can be added on-demand, it requires at least 2 passes. - if output.is_empty() || output == b"auto" { - '#' - } else { - output[0].into() - } +fn parse_git_config_commentchar(git_output: Result, io::Error>) -> char { + let output: Vec = git_output.unwrap_or_else(|_| "#".into()); + + // The setting is either unset, "auto", or precisely 1 ASCII character; + // Git won't commit with an invalid configuration value. "auto" support + // can be added on-demand, it requires at least 2 passes. + if output.is_empty() || output == b"auto" { + '#' + } else { + output[0].into() } } @@ -450,6 +437,65 @@ mod tests { assert_eq!("å", str_from_latin1); } + #[test] + fn impl_clierror_display_debug() { + #[derive(Debug, PartialEq)] + struct Expect<'a> { + display: Cow<'a, str>, + debug: Cow<'a, str>, + } + impl<'a> Expect<'a> { + fn display_debug(display: Cow<'a, str>, debug: Cow<'a, str>) -> Self { + Self { display, debug } + } + } + let errs = vec![ + ( + CliError::ArgUnrecognized("foo".into()), + Expect::display_debug( + r#"Found argument 'foo'"#.into(), + r#"ArgUnrecognized("foo")"#.into(), + ), + ), + ( + CliError::from(i32::from_str_radix("nan", 10).unwrap_err()), + Expect::display_debug( + "--width: invalid digit found in string".into(), + "ArgWidthNaN(ParseIntError { kind: InvalidDigit })".into(), + ), + ), + ( + CliError::ArgWidthOutOfBounds(0), + Expect::display_debug( + "--width must be greater than 0, was: '0'".into(), + "ArgWidthOutOfBounds(0)".into(), + ), + ), + ( + CliError::EarlyExit("help".into()), + Expect::display_debug(r#"help"#.into(), r#"EarlyExit("help")"#.into()), + ), + ( + CliError::from(io::Error::from(io::ErrorKind::BrokenPipe)), + Expect::display_debug(r#"broken pipe"#.into(), r#"Io(Kind(BrokenPipe))"#.into()), + ), + ( + CliError::Other("other".into()), + Expect::display_debug(r#"other"#.into(), r#"Other("other")"#.into()), + ), + ]; + let (actual, expected): (Vec<_>, Vec<_>) = errs + .into_iter() + .map(|(input, ex)| { + let actual_display = format!("{}", &input); + let actual_debug = format!("{:?}", &input); + let actual = Expect::display_debug(actual_display.into(), actual_debug.into()); + (actual, ex) + }) + .unzip(); + assert_eq!(expected, actual); + } + #[test] fn smoke_decide_behavior_from_command_line() { use crate::CliError::*; @@ -508,6 +554,14 @@ mod tests { vec!["binary", "--nonexistent"], Err(ArgUnrecognized("--nonexistent".into())), )); + matrix.push(( + vec!["binary", "nonexistent"], + Err(ArgUnrecognized("nonexistent".into())), + )); + matrix.push(( + vec!["binary", "-uw42"], + Err(ArgUnrecognized("-uw42".into())), + )); matrix.push((vec!["binary", "-w0"], Err(ArgWidthOutOfBounds(0)))); matrix.push(( vec!["binary", "--width=nan"], @@ -565,6 +619,26 @@ mod tests { } } + #[test] + fn parses_git_config_commentchar() { + let matrix = vec![ + (Ok("".into()), '#'), + (Ok("auto".into()), '#'), + (Ok("#".into()), '#'), + (Ok("xy".into()), 'x'), + (Err(io::Error::from(io::ErrorKind::PermissionDenied)), '#'), + ]; + let (actual, expected): (Vec<_>, Vec<_>) = matrix + .into_iter() + .map(|(input, expected)| { + let x = format!("{:?}", &input); + let actual = parse_git_config_commentchar(input); + ((x.clone(), actual), (x, expected)) + }) + .unzip(); + assert_eq!(expected, actual); + } + #[test] fn arg_width_0_exits_with_code_1() { let output = run_debug_binary_no_input(target_binary_with_width("0"));