mirror of
https://github.com/theryangeary/choose
synced 2024-11-23 03:13:04 +00:00
[FEATURE] specify output field delimiter (#8)
Add output_field_separator option Add output_field_separator tests Change structopt req to 0.3 Separate negative choices into a function Prevent tail printing output_field_separator Change OFS to Option<String> with a default value of " " Reorder arguments to write_bytes to parallel print_choice Print output_separator in main loop if applicable Add `cargo test` to Makefile Add write_separator function
This commit is contained in:
parent
d40c9d5234
commit
7243843d2f
10 changed files with 188 additions and 46 deletions
|
@ -7,6 +7,6 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
structopt = "0.3.0"
|
structopt = "0.3"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
|
|
1
Makefile
1
Makefile
|
@ -8,6 +8,7 @@ flamegraph_commit: release-debug
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
|
cargo test
|
||||||
test/e2e_test.sh
|
test/e2e_test.sh
|
||||||
|
|
||||||
bench: release
|
bench: release
|
||||||
|
|
154
src/choice.rs
154
src/choice.rs
|
@ -23,11 +23,11 @@ impl Choice {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_choice<WriterType: Write>(
|
pub fn print_choice<W: Write>(
|
||||||
&self,
|
&self,
|
||||||
line: &String,
|
line: &String,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
handle: &mut BufWriter<WriterType>,
|
handle: &mut BufWriter<W>,
|
||||||
) {
|
) {
|
||||||
let mut line_iter = config
|
let mut line_iter = config
|
||||||
.separator
|
.separator
|
||||||
|
@ -51,15 +51,44 @@ impl Choice {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut iter = stack.iter().rev().peekable();
|
||||||
loop {
|
loop {
|
||||||
match stack.pop() {
|
match iter.next() {
|
||||||
Some(s) => Choice::write_bytes(handle, s.as_bytes()),
|
Some(s) => {
|
||||||
|
Choice::write_bytes(s.as_bytes(), config, handle, iter.peek().is_some())
|
||||||
|
}
|
||||||
None => break,
|
None => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if self.has_negative_index() {
|
} else if self.has_negative_index() {
|
||||||
let vec = line_iter.collect::<Vec<&str>>();
|
let vec = line_iter.collect::<Vec<&str>>();
|
||||||
|
self.print_negative_choice(vec, config, handle);
|
||||||
|
} else {
|
||||||
|
if self.start > 0 {
|
||||||
|
line_iter.nth((self.start - 1).try_into().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut peek_line_iter = line_iter.peekable();
|
||||||
|
for i in self.start..=self.end {
|
||||||
|
match peek_line_iter.next() {
|
||||||
|
Some(s) => Choice::write_bytes(
|
||||||
|
s.as_bytes(),
|
||||||
|
config,
|
||||||
|
handle,
|
||||||
|
peek_line_iter.peek().is_some() && i != self.end,
|
||||||
|
),
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_negative_choice<W: Write>(
|
||||||
|
&self,
|
||||||
|
vec: Vec<&str>,
|
||||||
|
config: &Config,
|
||||||
|
handle: &mut BufWriter<W>,
|
||||||
|
) {
|
||||||
let start = if self.start >= 0 {
|
let start = if self.start >= 0 {
|
||||||
self.start.try_into().unwrap()
|
self.start.try_into().unwrap()
|
||||||
} else {
|
} else {
|
||||||
|
@ -77,33 +106,32 @@ impl Choice {
|
||||||
};
|
};
|
||||||
|
|
||||||
if end > start {
|
if end > start {
|
||||||
for word in vec[start..=std::cmp::min(end, vec.len() - 1)].iter() {
|
for word in vec[start..std::cmp::min(end, vec.len() - 1)].iter() {
|
||||||
Choice::write_bytes(handle, word.as_bytes());
|
Choice::write_bytes(word.as_bytes(), config, handle, true);
|
||||||
}
|
}
|
||||||
|
Choice::write_bytes(
|
||||||
|
vec[std::cmp::min(end, vec.len() - 1)].as_bytes(),
|
||||||
|
config,
|
||||||
|
handle,
|
||||||
|
false,
|
||||||
|
);
|
||||||
} else if self.start < 0 {
|
} else if self.start < 0 {
|
||||||
for word in vec[end..=std::cmp::min(start, vec.len() - 1)].iter().rev() {
|
for word in vec[end + 1..=std::cmp::min(start, vec.len() - 1)]
|
||||||
Choice::write_bytes(handle, word.as_bytes());
|
.iter()
|
||||||
}
|
.rev()
|
||||||
}
|
{
|
||||||
} else {
|
Choice::write_bytes(word.as_bytes(), config, handle, true);
|
||||||
if self.start > 0 {
|
|
||||||
line_iter.nth((self.start - 1).try_into().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..=(self.end - self.start) {
|
|
||||||
match line_iter.next() {
|
|
||||||
Some(s) => Choice::write_bytes(handle, s.as_bytes()),
|
|
||||||
None => break,
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.end <= self.start + i {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Choice::write_bytes(vec[end].as_bytes(), config, handle, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_bytes<WriterType: Write>(handle: &mut BufWriter<WriterType>, b: &[u8]) {
|
fn write_bytes<WriterType: Write>(
|
||||||
|
b: &[u8],
|
||||||
|
config: &Config,
|
||||||
|
handle: &mut BufWriter<WriterType>,
|
||||||
|
print_separator: bool,
|
||||||
|
) {
|
||||||
let num_bytes_written = match handle.write(b) {
|
let num_bytes_written = match handle.write(b) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -111,13 +139,17 @@ impl Choice {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if num_bytes_written > 0 {
|
if num_bytes_written > 0 && print_separator {
|
||||||
match handle.write(b" ") {
|
Choice::write_separator(config, handle);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_separator<W: Write>(config: &Config, handle: &mut BufWriter<W>) {
|
||||||
|
match handle.write(&config.output_separator) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => eprintln!("Failed to write to output: {}", e),
|
Err(e) => eprintln!("Failed to write to output: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_reverse_range(&self) -> bool {
|
pub fn is_reverse_range(&self) -> bool {
|
||||||
self.reversed
|
self.reversed
|
||||||
|
@ -564,6 +596,72 @@ mod tests {
|
||||||
config.opt.choice[0].print_choice(&String::from("a:b::c:::d"), &config, &mut handle);
|
config.opt.choice[0].print_choice(&String::from("a:b::c:::d"), &config, &mut handle);
|
||||||
assert_eq!(String::from("d"), MockStdout::str_from_buf_writer(handle));
|
assert_eq!(String::from("d"), MockStdout::str_from_buf_writer(handle));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn print_1_to_3_with_output_field_separator() {
|
||||||
|
let config = Config::from_iter(vec!["choose", "1:3", "-o", "#"]);
|
||||||
|
let mut handle = BufWriter::new(MockStdout::new());
|
||||||
|
config.opt.choice[0].print_choice(&String::from("a b c d"), &config, &mut handle);
|
||||||
|
assert_eq!(
|
||||||
|
String::from("b#c#d"),
|
||||||
|
MockStdout::str_from_buf_writer(handle)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn print_1_and_3_with_output_field_separator() {
|
||||||
|
let config = Config::from_iter(vec!["choose", "1", "3", "-o", "#"]);
|
||||||
|
let mut handle = BufWriter::new(MockStdout::new());
|
||||||
|
config.opt.choice[0].print_choice(&String::from("a b c d"), &config, &mut handle);
|
||||||
|
handle.write(&config.output_separator).unwrap();
|
||||||
|
config.opt.choice[1].print_choice(&String::from("a b c d"), &config, &mut handle);
|
||||||
|
assert_eq!(String::from("b#d"), MockStdout::str_from_buf_writer(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn print_2_to_4_with_output_field_separator() {
|
||||||
|
let config = Config::from_iter(vec!["choose", "2:4", "-o", "%"]);
|
||||||
|
let mut handle = BufWriter::new(MockStdout::new());
|
||||||
|
config.opt.choice[0].print_choice(
|
||||||
|
&String::from("Lorem ipsum dolor sit amet, consectetur"),
|
||||||
|
&config,
|
||||||
|
&mut handle,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
String::from("dolor%sit%amet,"),
|
||||||
|
MockStdout::str_from_buf_writer(handle)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn print_3_to_1_with_output_field_separator() {
|
||||||
|
let config = Config::from_iter(vec!["choose", "3:1", "-o", "#"]);
|
||||||
|
let mut handle = BufWriter::new(MockStdout::new());
|
||||||
|
config.opt.choice[0].print_choice(&String::from("a b c d"), &config, &mut handle);
|
||||||
|
assert_eq!(
|
||||||
|
String::from("d#c#b"),
|
||||||
|
MockStdout::str_from_buf_writer(handle)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn print_0_to_neg_2_with_output_field_separator() {
|
||||||
|
let config = Config::from_iter(vec!["choose", "0:-2", "-o", "#"]);
|
||||||
|
let mut handle = BufWriter::new(MockStdout::new());
|
||||||
|
config.opt.choice[0].print_choice(&String::from("a b c d"), &config, &mut handle);
|
||||||
|
assert_eq!(
|
||||||
|
String::from("a#b#c"),
|
||||||
|
MockStdout::str_from_buf_writer(handle)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn print_0_to_2_with_empty_output_field_separator() {
|
||||||
|
let config = Config::from_iter(vec!["choose", "0:2", "-o", ""]);
|
||||||
|
let mut handle = BufWriter::new(MockStdout::new());
|
||||||
|
config.opt.choice[0].print_choice(&String::from("a b c d"), &config, &mut handle);
|
||||||
|
assert_eq!(String::from("abc"), MockStdout::str_from_buf_writer(handle));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod is_reverse_range_tests {
|
mod is_reverse_range_tests {
|
||||||
|
|
|
@ -12,6 +12,7 @@ lazy_static! {
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub opt: Opt,
|
pub opt: Opt,
|
||||||
pub separator: Regex,
|
pub separator: Regex,
|
||||||
|
pub output_separator: Box<[u8]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
@ -50,7 +51,16 @@ impl Config {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Config { opt, separator }
|
let output_separator = match opt.output_field_separator.clone() {
|
||||||
|
Some(s) => s.into_boxed_str().into_boxed_bytes(),
|
||||||
|
None => Box::new([0x20; 1]),
|
||||||
|
};
|
||||||
|
|
||||||
|
Config {
|
||||||
|
opt,
|
||||||
|
separator,
|
||||||
|
output_separator,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_choice(src: &str) -> Result<Choice, ParseIntError> {
|
pub fn parse_choice(src: &str) -> Result<Choice, ParseIntError> {
|
||||||
|
@ -91,6 +101,10 @@ impl Config {
|
||||||
|
|
||||||
return Ok(Choice::new(start, end));
|
return Ok(Choice::new(start, end));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_output_field_separator(src: &str) -> String {
|
||||||
|
String::from(src)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -39,8 +39,12 @@ fn main() {
|
||||||
while let Some(line) = reader.read_line(&mut buffer) {
|
while let Some(line) = reader.read_line(&mut buffer) {
|
||||||
match line {
|
match line {
|
||||||
Ok(l) => {
|
Ok(l) => {
|
||||||
for choice in &config.opt.choice {
|
let choice_iter = &mut config.opt.choice.iter().peekable();
|
||||||
|
while let Some(choice) = choice_iter.next() {
|
||||||
choice.print_choice(&l, &config, &mut handle);
|
choice.print_choice(&l, &config, &mut handle);
|
||||||
|
if choice_iter.peek().is_some() {
|
||||||
|
choice::Choice::write_separator(&config, &mut handle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
match handle.write(b"\n") {
|
match handle.write(b"\n") {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
|
|
|
@ -12,6 +12,10 @@ pub struct Opt {
|
||||||
#[structopt(short, long)]
|
#[structopt(short, long)]
|
||||||
pub field_separator: Option<String>,
|
pub field_separator: Option<String>,
|
||||||
|
|
||||||
|
/// Specify output field separator
|
||||||
|
#[structopt(short, long, parse(from_str = Config::parse_output_field_separator))]
|
||||||
|
pub output_field_separator: Option<String>,
|
||||||
|
|
||||||
/// Use non-greedy field separators
|
/// Use non-greedy field separators
|
||||||
#[structopt(short, long)]
|
#[structopt(short, long)]
|
||||||
pub non_greedy: bool,
|
pub non_greedy: bool,
|
||||||
|
|
6
test/choose_1:3of%.txt
Normal file
6
test/choose_1:3of%.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
ipsum%dolor%sit
|
||||||
|
ut%labore%et
|
||||||
|
exercitation%ullamco%laboris
|
||||||
|
aute%irure%dolor
|
||||||
|
nulla%pariatur.%Excepteur
|
||||||
|
qui%officia%deserunt
|
6
test/choose_1_3of%.txt
Normal file
6
test/choose_1_3of%.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
ipsum%sit
|
||||||
|
ut%et
|
||||||
|
exercitation%laboris
|
||||||
|
aute%dolor
|
||||||
|
nulla%Excepteur
|
||||||
|
qui%deserunt
|
6
test/choose_1_3of.txt
Normal file
6
test/choose_1_3of.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
ipsumsit
|
||||||
|
utet
|
||||||
|
exercitationlaboris
|
||||||
|
autedolor
|
||||||
|
nullaExcepteur
|
||||||
|
quideserunt
|
|
@ -14,6 +14,9 @@ diff -w <(cargo run -- 9 -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir
|
||||||
diff -w <(cargo run -- 12 -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir}/choose_12.txt")
|
diff -w <(cargo run -- 12 -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir}/choose_12.txt")
|
||||||
diff -w <(cargo run -- 4:2 -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir}/choose_4:2.txt")
|
diff -w <(cargo run -- 4:2 -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir}/choose_4:2.txt")
|
||||||
diff -w <(cargo run -- -4:-2 -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir}/choose_-4:-2.txt")
|
diff -w <(cargo run -- -4:-2 -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir}/choose_-4:-2.txt")
|
||||||
|
diff -w <(cargo run -- 1:3 -o % -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir}/choose_1:3of%.txt")
|
||||||
|
diff -w <(cargo run -- 1 3 -o % -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir}/choose_1_3of%.txt")
|
||||||
|
diff -w <(cargo run -- 1 3 -o '' -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir}/choose_1_3of.txt")
|
||||||
# add tests for different delimiters
|
# add tests for different delimiters
|
||||||
# add tests using piping
|
# add tests using piping
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue