Add backslash escape parsing (#26)

This commit is contained in:
Ryan Geary 2020-06-17 19:06:28 -04:00
parent 0e422090d3
commit 448b12bb84
5 changed files with 149 additions and 5 deletions

View file

@ -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 {

137
src/escape.rs Normal file
View file

@ -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'));
}
}
}

View file

@ -9,6 +9,7 @@ extern crate lazy_static;
mod choice;
mod config;
mod errors;
mod escape;
mod opt;
mod parse;
mod parse_error;

View file

@ -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<String>,
/// Fields to print. Either a, a:b, a..b, or a..=b, where a and b are integers. The beginning

View file

@ -60,10 +60,6 @@ pub fn choice(src: &str) -> Result<Choice, ParseError> {
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;