From 517be5c7bcb034bda56ba8572f2e5f103163bf58 Mon Sep 17 00:00:00 2001 From: Kyle Criddle Date: Tue, 17 Mar 2020 20:24:48 -0600 Subject: [PATCH 1/6] Implement --file-name option - can specify filename to be displayed when printing. - useful for when piping data from STDIN Closes #654 --- src/bin/bat/app.rs | 1 + src/bin/bat/clap_app.rs | 12 ++++++++++++ src/lib.rs | 3 +++ src/printer.rs | 7 +++++-- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index 75e66854..4a6d4961 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -222,6 +222,7 @@ impl App { .transpose()? .unwrap_or_else(|| vec![LineRange { lower: 0, upper: 0 }]), ), + filename: self.matches.value_of("file-name").or_else(|| None), }) } diff --git a/src/bin/bat/clap_app.rs b/src/bin/bat/clap_app.rs index f234085a..41827cd8 100644 --- a/src/bin/bat/clap_app.rs +++ b/src/bin/bat/clap_app.rs @@ -93,6 +93,18 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { '--highlight-line 40:' highlights lines 40 to the end of the file" ), ) + .arg( + Arg::with_name("file-name") + .long("file-name") + .takes_value(true) + .number_of_values(1) + .multiple(true) + .value_name("name") + .help("Specify the name to display for a file.") + .long_help("Specify the name to display for a file. Useful when piping \ + data to bat from STDIN when bat does not otherwise know \ + the filename."), + ) .arg( Arg::with_name("tabs") .long("tabs") diff --git a/src/lib.rs b/src/lib.rs index 2172892f..437288b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,4 +129,7 @@ pub struct Config<'a> { /// Lines to highlight pub highlight_lines: LineRanges, + + /// Name of file to display when printing + pub filename: Option<&'a str>, } diff --git a/src/printer.rs b/src/printer.rs index 71cbf028..203a125b 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -231,7 +231,7 @@ impl<'a> Printer for InteractivePrinter<'a> { InputFile::Ordinary(filename) => { format!("file '{}'", filename.to_string_lossy()) } - _ => "STDIN".into(), + _ => self.config.filename.unwrap_or("STDIN").to_owned(), }; writeln!( @@ -267,7 +267,10 @@ impl<'a> Printer for InteractivePrinter<'a> { let (prefix, name) = match file { InputFile::Ordinary(filename) => ("File: ", filename.to_string_lossy()), - _ => ("", Cow::from("STDIN")), + _ => ( + "File: ", + Cow::from(self.config.filename.unwrap_or("STDIN").to_owned()), + ), }; let mode = match self.content_type { From cfa2cb6ec77f8faaab134c296350b982ff1ffe40 Mon Sep 17 00:00:00 2001 From: Kyle Criddle Date: Thu, 19 Mar 2020 20:46:19 -0600 Subject: [PATCH 2/6] --file-name for normal files. integration tests. --- src/printer.rs | 17 ++++++++--- tests/examples/test.binary | Bin 0 -> 4 bytes tests/integration_tests.rs | 57 +++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 tests/examples/test.binary diff --git a/src/printer.rs b/src/printer.rs index 203a125b..0c5f7fdc 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -228,9 +228,10 @@ impl<'a> Printer for InteractivePrinter<'a> { if !self.config.output_components.header() { if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable { let input = match file { - InputFile::Ordinary(filename) => { - format!("file '{}'", filename.to_string_lossy()) - } + InputFile::Ordinary(filename) => format!( + "file '{}'", + self.config.filename.unwrap_or(&filename.to_string_lossy()) + ), _ => self.config.filename.unwrap_or("STDIN").to_owned(), }; @@ -266,7 +267,15 @@ impl<'a> Printer for InteractivePrinter<'a> { } let (prefix, name) = match file { - InputFile::Ordinary(filename) => ("File: ", filename.to_string_lossy()), + InputFile::Ordinary(filename) => ( + "File: ", + Cow::from( + self.config + .filename + .unwrap_or(&filename.to_string_lossy()) + .to_owned(), + ), + ), _ => ( "File: ", Cow::from(self.config.filename.unwrap_or("STDIN").to_owned()), diff --git a/tests/examples/test.binary b/tests/examples/test.binary new file mode 100644 index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4 GIT binary patch literal 4 LcmZQzU|;|M00aO5 literal 0 HcmV?d00001 diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 0d5f4a3c..f2722339 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -541,3 +541,60 @@ fn empty_file_leads_to_empty_output_with_grid_enabled() { .success() .stdout(""); } + +#[test] +fn filename_basic() { + bat() + .arg("test.txt") + .arg("--decorations=always") + .arg("--style=header") + .arg("-r=0:0") + .arg("--file-name=foo") + .assert() + .success() + .stdout("File: foo\n") + .stderr(""); +} + +#[test] +fn filename_binary() { + bat() + .arg("test.binary") + .arg("--decorations=always") + .arg("--style=header") + .arg("-r=0:0") + .arg("--file-name=foo") + .assert() + .success() + .stdout("File: foo \n") + .stderr(""); +} + +#[test] +fn filename_stdin() { + bat() + .arg("--decorations=always") + .arg("--style=header") + .arg("-r=0:0") + .arg("-") + .write_stdin("stdin\n") + .arg("--file-name=foo") + .assert() + .success() + .stdout("File: foo\n") + .stderr(""); +} + +#[test] +fn filename_stdin_binary() { + let vec = vec![0; 1]; + bat_with_config() + .arg("--decorations=always") + .arg("--style=header") + .write_stdin(vec) + .arg("--file-name=foo") + .assert() + .success() + .stdout("File: foo \n") + .stderr(""); +} From 8adce9fae8eb9ceb5dac44546a35c592378607fc Mon Sep 17 00:00:00 2001 From: Kyle Criddle Date: Tue, 17 Mar 2020 20:24:48 -0600 Subject: [PATCH 3/6] Implement --file-name option - can specify filename to be displayed when printing. - useful for when piping data from STDIN Closes #654 --- src/bin/bat/app.rs | 1 + src/bin/bat/clap_app.rs | 12 ++++++++++++ src/config.rs | 3 +++ src/printer.rs | 7 +++++-- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index ae889453..edcffe86 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -222,6 +222,7 @@ impl App { .map(LineRanges::from) .map(|lr| HighlightedLineRanges(lr)) .unwrap_or_default(), + filename: self.matches.value_of("file-name").or_else(|| None), }) } diff --git a/src/bin/bat/clap_app.rs b/src/bin/bat/clap_app.rs index 0016a345..a430b844 100644 --- a/src/bin/bat/clap_app.rs +++ b/src/bin/bat/clap_app.rs @@ -93,6 +93,18 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { '--highlight-line 40:' highlights lines 40 to the end of the file" ), ) + .arg( + Arg::with_name("file-name") + .long("file-name") + .takes_value(true) + .number_of_values(1) + .multiple(true) + .value_name("name") + .help("Specify the name to display for a file.") + .long_help("Specify the name to display for a file. Useful when piping \ + data to bat from STDIN when bat does not otherwise know \ + the filename."), + ) .arg( Arg::with_name("tabs") .long("tabs") diff --git a/src/config.rs b/src/config.rs index 19e91484..c9859a97 100644 --- a/src/config.rs +++ b/src/config.rs @@ -70,6 +70,9 @@ pub struct Config<'a> { /// Ranges of lines which should be highlighted with a special background color pub highlighted_lines: HighlightedLineRanges, + + /// Name of file to display when printing + pub filename: Option<&'a str>, } #[test] diff --git a/src/printer.rs b/src/printer.rs index 629b11da..9313d3bd 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -231,7 +231,7 @@ impl<'a> Printer for InteractivePrinter<'a> { InputFile::Ordinary(filename) => { format!("file '{}'", filename.to_string_lossy()) } - _ => "STDIN".into(), + _ => self.config.filename.unwrap_or("STDIN").to_owned(), }; writeln!( @@ -267,7 +267,10 @@ impl<'a> Printer for InteractivePrinter<'a> { let (prefix, name) = match file { InputFile::Ordinary(filename) => ("File: ", filename.to_string_lossy()), - _ => ("", Cow::from("STDIN")), + _ => ( + "File: ", + Cow::from(self.config.filename.unwrap_or("STDIN").to_owned()), + ), }; let mode = match self.content_type { From fb3c775c8b4cf25b648bbdc422b99f93608914c9 Mon Sep 17 00:00:00 2001 From: Kyle Criddle Date: Thu, 19 Mar 2020 20:46:19 -0600 Subject: [PATCH 4/6] --file-name for normal files. integration tests. --- src/printer.rs | 17 ++++++++--- tests/examples/test.binary | Bin 0 -> 4 bytes tests/integration_tests.rs | 57 +++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 tests/examples/test.binary diff --git a/src/printer.rs b/src/printer.rs index 9313d3bd..d032d11f 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -228,9 +228,10 @@ impl<'a> Printer for InteractivePrinter<'a> { if !self.config.style_components.header() { if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable { let input = match file { - InputFile::Ordinary(filename) => { - format!("file '{}'", filename.to_string_lossy()) - } + InputFile::Ordinary(filename) => format!( + "file '{}'", + self.config.filename.unwrap_or(&filename.to_string_lossy()) + ), _ => self.config.filename.unwrap_or("STDIN").to_owned(), }; @@ -266,7 +267,15 @@ impl<'a> Printer for InteractivePrinter<'a> { } let (prefix, name) = match file { - InputFile::Ordinary(filename) => ("File: ", filename.to_string_lossy()), + InputFile::Ordinary(filename) => ( + "File: ", + Cow::from( + self.config + .filename + .unwrap_or(&filename.to_string_lossy()) + .to_owned(), + ), + ), _ => ( "File: ", Cow::from(self.config.filename.unwrap_or("STDIN").to_owned()), diff --git a/tests/examples/test.binary b/tests/examples/test.binary new file mode 100644 index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4 GIT binary patch literal 4 LcmZQzU|;|M00aO5 literal 0 HcmV?d00001 diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 0d5f4a3c..f2722339 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -541,3 +541,60 @@ fn empty_file_leads_to_empty_output_with_grid_enabled() { .success() .stdout(""); } + +#[test] +fn filename_basic() { + bat() + .arg("test.txt") + .arg("--decorations=always") + .arg("--style=header") + .arg("-r=0:0") + .arg("--file-name=foo") + .assert() + .success() + .stdout("File: foo\n") + .stderr(""); +} + +#[test] +fn filename_binary() { + bat() + .arg("test.binary") + .arg("--decorations=always") + .arg("--style=header") + .arg("-r=0:0") + .arg("--file-name=foo") + .assert() + .success() + .stdout("File: foo \n") + .stderr(""); +} + +#[test] +fn filename_stdin() { + bat() + .arg("--decorations=always") + .arg("--style=header") + .arg("-r=0:0") + .arg("-") + .write_stdin("stdin\n") + .arg("--file-name=foo") + .assert() + .success() + .stdout("File: foo\n") + .stderr(""); +} + +#[test] +fn filename_stdin_binary() { + let vec = vec![0; 1]; + bat_with_config() + .arg("--decorations=always") + .arg("--style=header") + .write_stdin(vec) + .arg("--file-name=foo") + .assert() + .success() + .stdout("File: foo \n") + .stderr(""); +} From 59f2e2d58d3dc8ca31b74b9bad5899c4521baf0c Mon Sep 17 00:00:00 2001 From: Kyle Criddle Date: Tue, 24 Mar 2020 18:26:00 -0600 Subject: [PATCH 5/6] Implemented --file-name for multiple files + tests --- src/bin/bat/app.rs | 11 ++++++++++- src/controller.rs | 22 ++++++++++++++++++---- src/lib.rs | 2 +- src/printer.rs | 37 ++++++++++++++++++++++--------------- tests/integration_tests.rs | 29 +++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 21 deletions(-) diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index 4a6d4961..b7891343 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -137,6 +137,12 @@ impl App { } }); + if self.matches.value_of("file-name").is_some() + && self.matches.values_of("file-name").unwrap().len() != files.len() + { + return Err("When using --file-name, each input file must have a corresponding --file-name specified.".into()); + } + Ok(Config { true_color: is_truecolor_terminal(), language: self.matches.value_of("language").or_else(|| { @@ -222,7 +228,10 @@ impl App { .transpose()? .unwrap_or_else(|| vec![LineRange { lower: 0, upper: 0 }]), ), - filename: self.matches.value_of("file-name").or_else(|| None), + filenames: self + .matches + .values_of("file-name") + .map(|values| values.collect()), }) } diff --git a/src/controller.rs b/src/controller.rs index ca2b9cfb..96633133 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -41,7 +41,20 @@ impl<'b> Controller<'b> { let stdin = io::stdin(); - for input_file in &self.config.files { + let filenames = if self.config.filenames.is_none() { + vec![None; self.config.files.len()] + } else { + self.config + .filenames + .as_ref() + .unwrap() + .into_iter() + .map(|name| Some(*name)) + .collect() + }; + + for it in self.config.files.iter().zip(filenames) { + let (input_file, file_name) = it; match input_file.get_reader(&stdin) { Err(error) => { handle_error(&error); @@ -50,7 +63,7 @@ impl<'b> Controller<'b> { Ok(mut reader) => { let result = if self.config.loop_through { let mut printer = SimplePrinter::new(); - self.print_file(reader, &mut printer, writer, *input_file) + self.print_file(reader, &mut printer, writer, *input_file, file_name) } else { let mut printer = InteractivePrinter::new( &self.config, @@ -58,7 +71,7 @@ impl<'b> Controller<'b> { *input_file, &mut reader, ); - self.print_file(reader, &mut printer, writer, *input_file) + self.print_file(reader, &mut printer, writer, *input_file, file_name) }; if let Err(error) = result { @@ -78,9 +91,10 @@ impl<'b> Controller<'b> { printer: &mut P, writer: &mut dyn Write, input_file: InputFile<'a>, + file_name: Option<&str>, ) -> Result<()> { if !reader.first_line.is_empty() || self.config.output_components.header() { - printer.print_header(writer, input_file)?; + printer.print_header(writer, input_file, file_name)?; } if !reader.first_line.is_empty() { diff --git a/src/lib.rs b/src/lib.rs index 437288b4..5723145d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,5 +131,5 @@ pub struct Config<'a> { pub highlight_lines: LineRanges, /// Name of file to display when printing - pub filename: Option<&'a str>, + pub filenames: Option>, } diff --git a/src/printer.rs b/src/printer.rs index 0c5f7fdc..64d42d1d 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -34,7 +34,12 @@ use crate::terminal::{as_terminal_escaped, to_ansi_color}; use crate::Config; pub trait Printer { - fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()>; + fn print_header( + &mut self, + handle: &mut dyn Write, + file: InputFile, + file_name: Option<&str>, + ) -> Result<()>; fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()>; fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()>; @@ -57,7 +62,12 @@ impl SimplePrinter { } impl Printer for SimplePrinter { - fn print_header(&mut self, _handle: &mut dyn Write, _file: InputFile) -> Result<()> { + fn print_header( + &mut self, + _handle: &mut dyn Write, + _file: InputFile, + _file_name: Option<&str>, + ) -> Result<()> { Ok(()) } @@ -224,15 +234,20 @@ impl<'a> InteractivePrinter<'a> { } impl<'a> Printer for InteractivePrinter<'a> { - fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()> { + fn print_header( + &mut self, + handle: &mut dyn Write, + file: InputFile, + file_name: Option<&str>, + ) -> Result<()> { if !self.config.output_components.header() { if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable { let input = match file { InputFile::Ordinary(filename) => format!( "file '{}'", - self.config.filename.unwrap_or(&filename.to_string_lossy()) + file_name.unwrap_or(&filename.to_string_lossy()) ), - _ => self.config.filename.unwrap_or("STDIN").to_owned(), + _ => file_name.unwrap_or("STDIN").to_owned(), }; writeln!( @@ -269,17 +284,9 @@ impl<'a> Printer for InteractivePrinter<'a> { let (prefix, name) = match file { InputFile::Ordinary(filename) => ( "File: ", - Cow::from( - self.config - .filename - .unwrap_or(&filename.to_string_lossy()) - .to_owned(), - ), - ), - _ => ( - "File: ", - Cow::from(self.config.filename.unwrap_or("STDIN").to_owned()), + Cow::from(file_name.unwrap_or(&filename.to_string_lossy()).to_owned()), ), + _ => ("File: ", Cow::from(file_name.unwrap_or("STDIN").to_owned())), }; let mode = match self.content_type { diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index f2722339..7750f6cd 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -598,3 +598,32 @@ fn filename_stdin_binary() { .stdout("File: foo \n") .stderr(""); } + +#[test] +fn filename_multiple_ok() { + bat() + .arg("--decorations=always") + .arg("--style=header") + .arg("-r=0:0") + .arg("test.txt") + .arg("--file-name=foo") + .arg("single-line.txt") + .arg("--file-name=bar") + .assert() + .success() + .stdout("File: foo\nFile: bar\n") + .stderr(""); +} + +#[test] +fn filename_multiple_err() { + bat() + .arg("--decorations=always") + .arg("--style=header") + .arg("-r=0:0") + .arg("test.txt") + .arg("--file-name=foo") + .arg("single-line.txt") + .assert() + .failure(); +} From 83772bd2cfd2603fd4185ee26cdd883383ad46cb Mon Sep 17 00:00:00 2001 From: Kyle Criddle Date: Wed, 25 Mar 2020 18:58:05 -0600 Subject: [PATCH 6/6] Minor --file-name code hygeine --- src/bin/bat/app.rs | 9 +++++---- src/controller.rs | 16 ++++------------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index 65f55472..542e5d99 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -136,10 +136,11 @@ impl App { } }); - if self.matches.value_of("file-name").is_some() - && self.matches.values_of("file-name").unwrap().len() != files.len() - { - return Err("When using --file-name, each input file must have a corresponding --file-name specified.".into()); + match self.matches.values_of("file-name") { + Some(filenames) if filenames.len() != files.len() => { + return Err(format!("{} {}", filenames.len(), files.len()).into()); + } + _ => {} } Ok(Config { diff --git a/src/controller.rs b/src/controller.rs index 21c7f81c..90e465bd 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -45,20 +45,12 @@ impl<'b> Controller<'b> { let stdin = io::stdin(); - let filenames = if self.config.filenames.is_none() { - vec![None; self.config.files.len()] - } else { - self.config - .filenames - .as_ref() - .unwrap() - .into_iter() - .map(|name| Some(*name)) - .collect() + let filenames: Box> = match self.config.filenames { + Some(ref filenames) => Box::new(filenames.into_iter().map(|name| Some(*name))), + None => Box::new(std::iter::repeat(None)), }; - for it in self.config.files.iter().zip(filenames) { - let (input_file, file_name) = it; + for (input_file, file_name) in self.config.files.iter().zip(filenames) { match input_file.get_reader(&stdin) { Err(error) => { handle_error(&error);