on master help sign in
Merge pull request #871 from neuronull/fix_654_stdin_filename
37b3b87David Peter
src/bin/bat/app.rs+11
@@ -136,6 +136,13 @@ impl App {
136 }
136 }
137 });
137 });
138
138
139+ match self.matches.values_of("file-name") {
140+ Some(filenames) if filenames.len() != files.len() => {
141+ return Err(format!("{} {}", filenames.len(), files.len()).into());
142+ }
143+ _ => {}
144+ }
145+
139 Ok(Config {
146 Ok(Config {
140 true_color: is_truecolor_terminal(),
147 true_color: is_truecolor_terminal(),
141 language: self.matches.value_of("language").or_else(|| {
148 language: self.matches.value_of("language").or_else(|| {
@@ -222,6 +229,10 @@ impl App {
222 .map(LineRanges::from)
229 .map(LineRanges::from)
223 .map(|lr| HighlightedLineRanges(lr))
230 .map(|lr| HighlightedLineRanges(lr))
224 .unwrap_or_default(),
231 .unwrap_or_default(),
232+ filenames: self
233+ .matches
234+ .values_of("file-name")
235+ .map(|values| values.collect()),
225 })
236 })
226 }
237 }
227
238
src/bin/bat/clap_app.rs+12
@@ -93,6 +93,18 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
93 '--highlight-line 40:' highlights lines 40 to the end of the file"
93 '--highlight-line 40:' highlights lines 40 to the end of the file"
94 ),
94 ),
95 )
95 )
96+ .arg(
97+ Arg::with_name("file-name")
98+ .long("file-name")
99+ .takes_value(true)
100+ .number_of_values(1)
101+ .multiple(true)
102+ .value_name("name")
103+ .help("Specify the name to display for a file.")
104+ .long_help("Specify the name to display for a file. Useful when piping \
105+ data to bat from STDIN when bat does not otherwise know \
106+ the filename."),
107+ )
96 .arg(
108 .arg(
97 Arg::with_name("tabs")
109 Arg::with_name("tabs")
98 .long("tabs")
110 .long("tabs")
src/config.rs+3
@@ -70,6 +70,9 @@ pub struct Config<'a> {
70
70
71 /// Ranges of lines which should be highlighted with a special background color
71 /// Ranges of lines which should be highlighted with a special background color
72 pub highlighted_lines: HighlightedLineRanges,
72 pub highlighted_lines: HighlightedLineRanges,
73+
74+ /// Names of files to display when printing
75+ pub filenames: Option<Vec<&'a str>>,
73}
76}
74
77
75#[test]
78#[test]
src/controller.rs+10−4
@@ -45,7 +45,12 @@ impl<'b> Controller<'b> {
45
45
46 let stdin = io::stdin();
46 let stdin = io::stdin();
47
47
48+ let filenames: Box<dyn Iterator<Item = _>> = match self.config.filenames {
49+ Some(ref filenames) => Box::new(filenames.into_iter().map(|name| Some(*name))),
50+ None => Box::new(std::iter::repeat(None)),
51+ };
52+
48− for input_file in &self.config.files {
53+ for (input_file, file_name) in self.config.files.iter().zip(filenames) {
49 match input_file.get_reader(&stdin) {
54 match input_file.get_reader(&stdin) {
50 Err(error) => {
55 Err(error) => {
51 handle_error(&error);
56 handle_error(&error);
@@ -54,7 +59,7 @@ impl<'b> Controller<'b> {
54 Ok(mut reader) => {
59 Ok(mut reader) => {
55 let result = if self.config.loop_through {
60 let result = if self.config.loop_through {
56 let mut printer = SimplePrinter::new();
61 let mut printer = SimplePrinter::new();
57− self.print_file(reader, &mut printer, writer, *input_file)
62+ self.print_file(reader, &mut printer, writer, *input_file, file_name)
58 } else {
63 } else {
59 let mut printer = InteractivePrinter::new(
64 let mut printer = InteractivePrinter::new(
60 &self.config,
65 &self.config,
@@ -62,7 +67,7 @@ impl<'b> Controller<'b> {
62 *input_file,
67 *input_file,
63 &mut reader,
68 &mut reader,
64 );
69 );
65− self.print_file(reader, &mut printer, writer, *input_file)
70+ self.print_file(reader, &mut printer, writer, *input_file, file_name)
66 };
71 };
67
72
68 if let Err(error) = result {
73 if let Err(error) = result {
@@ -82,9 +87,10 @@ impl<'b> Controller<'b> {
82 printer: &mut P,
87 printer: &mut P,
83 writer: &mut dyn Write,
88 writer: &mut dyn Write,
84 input_file: InputFile<'a>,
89 input_file: InputFile<'a>,
90+ file_name: Option<&str>,
85 ) -> Result<()> {
91 ) -> Result<()> {
86 if !reader.first_line.is_empty() || self.config.style_components.header() {
92 if !reader.first_line.is_empty() || self.config.style_components.header() {
87− printer.print_header(writer, input_file)?;
93+ printer.print_header(writer, input_file, file_name)?;
88 }
94 }
89
95
90 if !reader.first_line.is_empty() {
96 if !reader.first_line.is_empty() {
src/printer.rs+28−9
@@ -34,7 +34,12 @@ use crate::terminal::{as_terminal_escaped, to_ansi_color};
34use crate::wrap::OutputWrap;
34use crate::wrap::OutputWrap;
35
35
36pub trait Printer {
36pub trait Printer {
37+ fn print_header(
38+ &mut self,
37− fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()>;
39+ handle: &mut dyn Write,
40+ file: InputFile,
41+ file_name: Option<&str>,
42+ ) -> Result<()>;
38 fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()>;
43 fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()>;
39
44
40 fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()>;
45 fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()>;
@@ -57,7 +62,12 @@ impl SimplePrinter {
57}
62}
58
63
59impl Printer for SimplePrinter {
64impl Printer for SimplePrinter {
65+ fn print_header(
66+ &mut self,
60− fn print_header(&mut self, _handle: &mut dyn Write, _file: InputFile) -> Result<()> {
67+ _handle: &mut dyn Write,
68+ _file: InputFile,
69+ _file_name: Option<&str>,
70+ ) -> Result<()> {
61 Ok(())
71 Ok(())
62 }
72 }
63
73
@@ -224,14 +234,20 @@ impl<'a> InteractivePrinter<'a> {
224}
234}
225
235
226impl<'a> Printer for InteractivePrinter<'a> {
236impl<'a> Printer for InteractivePrinter<'a> {
237+ fn print_header(
238+ &mut self,
227− fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()> {
239+ handle: &mut dyn Write,
240+ file: InputFile,
241+ file_name: Option<&str>,
242+ ) -> Result<()> {
228 if !self.config.style_components.header() {
243 if !self.config.style_components.header() {
229 if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable {
244 if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable {
230 let input = match file {
245 let input = match file {
231− InputFile::Ordinary(filename) => {
246+ InputFile::Ordinary(filename) => format!(
247+ "file '{}'",
232− format!("file '{}'", filename.to_string_lossy())
248+ file_name.unwrap_or(&filename.to_string_lossy())
233− }
249+ ),
234− _ => "STDIN".into(),
250+ _ => file_name.unwrap_or("STDIN").to_owned(),
235 };
251 };
236
252
237 writeln!(
253 writeln!(
@@ -266,8 +282,11 @@ impl<'a> Printer for InteractivePrinter<'a> {
266 }
282 }
267
283
268 let (prefix, name) = match file {
284 let (prefix, name) = match file {
269− InputFile::Ordinary(filename) => ("File: ", filename.to_string_lossy()),
285+ InputFile::Ordinary(filename) => (
286+ "File: ",
287+ Cow::from(file_name.unwrap_or(&filename.to_string_lossy()).to_owned()),
288+ ),
270− _ => ("", Cow::from("STDIN")),
289+ _ => ("File: ", Cow::from(file_name.unwrap_or("STDIN").to_owned())),
271 };
290 };
272
291
273 let mode = match self.content_type {
292 let mode = match self.content_type {
tests/examples/test.binarynew file

Binary file not shown.

tests/integration_tests.rs+86
@@ -541,3 +541,89 @@ fn empty_file_leads_to_empty_output_with_grid_enabled() {
541 .success()
541 .success()
542 .stdout("");
542 .stdout("");
543}
543}
544+
545+#[test]
546+fn filename_basic() {
547+ bat()
548+ .arg("test.txt")
549+ .arg("--decorations=always")
550+ .arg("--style=header")
551+ .arg("-r=0:0")
552+ .arg("--file-name=foo")
553+ .assert()
554+ .success()
555+ .stdout("File: foo\n")
556+ .stderr("");
557+}
558+
559+#[test]
560+fn filename_binary() {
561+ bat()
562+ .arg("test.binary")
563+ .arg("--decorations=always")
564+ .arg("--style=header")
565+ .arg("-r=0:0")
566+ .arg("--file-name=foo")
567+ .assert()
568+ .success()
569+ .stdout("File: foo <BINARY>\n")
570+ .stderr("");
571+}
572+
573+#[test]
574+fn filename_stdin() {
575+ bat()
576+ .arg("--decorations=always")
577+ .arg("--style=header")
578+ .arg("-r=0:0")
579+ .arg("-")
580+ .write_stdin("stdin\n")
581+ .arg("--file-name=foo")
582+ .assert()
583+ .success()
584+ .stdout("File: foo\n")
585+ .stderr("");
586+}
587+
588+#[test]
589+fn filename_stdin_binary() {
590+ let vec = vec![0; 1];
591+ bat_with_config()
592+ .arg("--decorations=always")
593+ .arg("--style=header")
594+ .write_stdin(vec)
595+ .arg("--file-name=foo")
596+ .assert()
597+ .success()
598+ .stdout("File: foo <BINARY>\n")
599+ .stderr("");
600+}
601+
602+#[test]
603+fn filename_multiple_ok() {
604+ bat()
605+ .arg("--decorations=always")
606+ .arg("--style=header")
607+ .arg("-r=0:0")
608+ .arg("test.txt")
609+ .arg("--file-name=foo")
610+ .arg("single-line.txt")
611+ .arg("--file-name=bar")
612+ .assert()
613+ .success()
614+ .stdout("File: foo\nFile: bar\n")
615+ .stderr("");
616+}
617+
618+#[test]
619+fn filename_multiple_err() {
620+ bat()
621+ .arg("--decorations=always")
622+ .arg("--style=header")
623+ .arg("-r=0:0")
624+ .arg("test.txt")
625+ .arg("--file-name=foo")
626+ .arg("single-line.txt")
627+ .assert()
628+ .failure();
629+}