diff --git a/src/choice.rs b/src/choice.rs index 5e6e679..d7447fe 100644 --- a/src/choice.rs +++ b/src/choice.rs @@ -1115,6 +1115,15 @@ mod tests { fn print_neg_4_to_2_one_indexed() { test_fn(vec!["choose", "-4:2", "--one-indexed"], "a b c d", "a b"); } + + #[test] + fn print_2_to_4_newline_ofs() { + test_fn( + vec!["choose", "2:4", "-o", r#"\n"#], + "a b c d e f", + "c\nd\ne", + ); + } } mod is_reverse_range_tests { diff --git a/src/escape.rs b/src/escape.rs new file mode 100644 index 0000000..9098746 --- /dev/null +++ b/src/escape.rs @@ -0,0 +1,137 @@ +pub fn process_escapes(input: &str) -> String { + if input.len() < 1 { + return String::from(input); + } + + let mut v = Vec::from(input); + for i in 0..(v.len() - 1) { + if v[i] == '\\' as u8 && is_escapable(v[i + 1] as char) { + v.remove(i); + v[i] = char_to_escape_sequence(v[i] as char) as u8; + } + } + String::from_utf8(v).unwrap() +} + +fn char_to_escape_sequence(chr: char) -> char { + match chr { + 'n' => '\n', + 't' => '\t', + 'r' => '\r', + '\\' => '\\', + '0' => '\0', + _ => chr, + } +} + +fn is_escapable(chr: char) -> bool { + match chr { + 'n' | 't' | 'r' | '\\' | '0' => true, + _ => false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + mod test_process_escapes { + use super::*; + + #[test] + fn test_newline() { + assert_eq!( + String::from("hello\nworld"), + process_escapes(r#"hello\nworld"#) + ); + } + + #[test] + fn test_carriage_return() { + assert_eq!( + String::from("hello\rworld"), + process_escapes(r#"hello\rworld"#) + ); + } + + #[test] + fn test_tab() { + assert_eq!( + String::from("hello\tworld"), + process_escapes(r#"hello\tworld"#) + ); + } + + #[test] + fn test_backslash() { + assert_eq!( + String::from("hello\\world"), + process_escapes(r#"hello\\world"#) + ); + } + + #[test] + fn test_null() { + assert_eq!( + String::from("hello\0world"), + process_escapes(r#"hello\0world"#) + ); + } + } + + mod test_char_to_escape_sequence { + use super::*; + #[test] + fn test_escape_n() { + assert_eq!('\n', char_to_escape_sequence('n')); + } + + #[test] + fn test_escape_t() { + assert_eq!('\t', char_to_escape_sequence('t')); + } + + #[test] + fn test_escape_r() { + assert_eq!('\r', char_to_escape_sequence('r')); + } + + #[test] + fn test_escape_backslash() { + assert_eq!('\\', char_to_escape_sequence('\\')); + } + + #[test] + fn test_escape_0() { + assert_eq!('\0', char_to_escape_sequence('0')); + } + } + + mod is_escapable_tests { + use super::*; + + #[test] + fn test_escape_n() { + assert!(is_escapable('n')); + } + + #[test] + fn test_escape_t() { + assert!(is_escapable('t')); + } + + #[test] + fn test_escape_r() { + assert!(is_escapable('r')); + } + + #[test] + fn test_escape_backslash() { + assert!(is_escapable('\\')); + } + + #[test] + fn test_escape_0() { + assert!(is_escapable('0')); + } + } +} diff --git a/src/main.rs b/src/main.rs index 86bd108..4a8f1f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ extern crate lazy_static; mod choice; mod config; mod errors; +mod escape; mod opt; mod parse; mod parse_error; diff --git a/src/opt.rs b/src/opt.rs index 94f1236..1eb08ee 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use structopt::StructOpt; use crate::choice::Choice; +use crate::escape; use crate::parse; #[derive(Debug, StructOpt)] @@ -37,7 +38,7 @@ pub struct Opt { pub one_indexed: bool, /// Specify output field separator - #[structopt(short, long, parse(from_str = parse::output_field_separator))] + #[structopt(short, long, parse(from_str = escape::process_escapes))] pub output_field_separator: Option, /// Fields to print. Either a, a:b, a..b, or a..=b, where a and b are integers. The beginning diff --git a/src/parse.rs b/src/parse.rs index 7ff93cd..17b5bfa 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -60,10 +60,6 @@ pub fn choice(src: &str) -> Result { return Ok(Choice::new(start, end, kind)); } -pub fn output_field_separator(src: &str) -> String { - String::from(src) -} - #[cfg(test)] mod tests { use crate::parse;