on master help sign in
Large refactoring towards a better builder structure
057e4ecsharkdp
examples/cat.rs+8−27
@@ -1,36 +1,17 @@
1/// A very simple colorized `cat` clone, using `bat` as a library.
1/// A very simple colorized `cat` clone, using `bat` as a library.
2/// See `src/bin/bat` for the full `bat` application.
2/// See `src/bin/bat` for the full `bat` application.
3−use bat::{
4− config::{Config, InputFile, OrdinaryFile, StyleComponent, StyleComponents},
3+use bat::{PrettyPrinter, StyleComponent, StyleComponents};
5− Controller, HighlightingAssets,
6−};
7use console::Term;
4use console::Term;
8−use std::process;
9
5
10fn main() {
6fn main() {
11− let files = std::env::args_os().skip(1).collect::<Vec<_>>();
7+ PrettyPrinter::new()
12−
13− if files.is_empty() {
14− eprintln!("No input files specified");
15− process::exit(1);
16− }
17−
18− let config = Config {
19− term_width: Term::stdout().size().1 as usize,
8+ .term_width(Term::stdout().size().1 as usize)
20− colored_output: true,
21− true_color: true,
22− style_components: StyleComponents::new(&[
9+ .style_components(StyleComponents::new(&[
23 StyleComponent::Header,
10 StyleComponent::Header,
24 StyleComponent::Grid,
11 StyleComponent::Grid,
25 StyleComponent::Numbers,
12 StyleComponent::Numbers,
26− ]),
13+ ]))
27− files: files
14+ .files(std::env::args_os().skip(1))
28− .iter()
15+ .run()
29− .map(|file| InputFile::Ordinary(OrdinaryFile::from_path(file)))
30− .collect(),
31− ..Default::default()
32− };
33− let assets = HighlightingAssets::from_binary();
34−
35− Controller::new(&config, &assets).run().expect("no errors");
16+ .expect("no errors");
36}
17}
src/assets.rs+10−10
@@ -188,7 +188,7 @@ impl HighlightingAssets {
188 pub(crate) fn get_syntax(
188 pub(crate) fn get_syntax(
189 &self,
189 &self,
190 language: Option<&str>,
190 language: Option<&str>,
191− file: InputFile,
191+ file: &InputFile,
192 reader: &mut InputFileReader,
192 reader: &mut InputFileReader,
193 mapping: &SyntaxMapping,
193 mapping: &SyntaxMapping,
194 ) -> &SyntaxReference {
194 ) -> &SyntaxReference {
@@ -216,7 +216,7 @@ impl HighlightingAssets {
216 .ok()
216 .ok()
217 .and_then(|l| self.syntax_set.find_syntax_by_first_line(&l)),
217 .and_then(|l| self.syntax_set.find_syntax_by_first_line(&l)),
218 (None, InputFile::StdIn(Some(file_name))) => self
218 (None, InputFile::StdIn(Some(file_name))) => self
219− .get_extension_syntax(file_name)
219+ .get_extension_syntax(&file_name)
220 .or(self.get_first_line_syntax(reader)),
220 .or(self.get_first_line_syntax(reader)),
221 (_, InputFile::ThemePreviewFile) => self.syntax_set.find_syntax_by_name("Rust"),
221 (_, InputFile::ThemePreviewFile) => self.syntax_set.find_syntax_by_name("Rust"),
222 };
222 };
@@ -246,17 +246,17 @@ impl HighlightingAssets {
246
246
247#[cfg(test)]
247#[cfg(test)]
248mod tests {
248mod tests {
249+ use super::*;
250+
251+ use crate::inputfile::OrdinaryFile;
252+
249− use std::ffi::OsStr;
253+ use std::ffi::{OsStr, OsString};
250 use std::fs::File;
254 use std::fs::File;
251 use std::io;
255 use std::io;
252 use std::io::Write;
256 use std::io::Write;
253
257
254 use tempdir::TempDir;
258 use tempdir::TempDir;
255
259
256− use crate::assets::HighlightingAssets;
257− use crate::inputfile::{InputFile, OrdinaryFile};
258− use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
259−
260 struct SyntaxDetectionTest<'a> {
260 struct SyntaxDetectionTest<'a> {
261 assets: HighlightingAssets,
261 assets: HighlightingAssets,
262 pub syntax_mapping: SyntaxMapping<'a>,
262 pub syntax_mapping: SyntaxMapping<'a>,
@@ -283,7 +283,7 @@ mod tests {
283 let input_file = InputFile::Ordinary(OrdinaryFile::from_path(file_path.as_os_str()));
283 let input_file = InputFile::Ordinary(OrdinaryFile::from_path(file_path.as_os_str()));
284 let syntax = self.assets.get_syntax(
284 let syntax = self.assets.get_syntax(
285 None,
285 None,
286− input_file,
286+ &input_file,
287 &mut input_file.get_reader(io::stdin().lock()).unwrap(),
287 &mut input_file.get_reader(io::stdin().lock()).unwrap(),
288 &self.syntax_mapping,
288 &self.syntax_mapping,
289 );
289 );
@@ -304,10 +304,10 @@ mod tests {
304 }
304 }
305
305
306 fn syntax_for_stdin_with_content(&self, file_name: &str, content: &[u8]) -> String {
306 fn syntax_for_stdin_with_content(&self, file_name: &str, content: &[u8]) -> String {
307− let input_file = InputFile::StdIn(Some(OsStr::new(file_name)));
307+ let input_file = InputFile::StdIn(Some(OsString::from(file_name)));
308 let syntax = self.assets.get_syntax(
308 let syntax = self.assets.get_syntax(
309 None,
309 None,
310− input_file,
310+ &input_file,
311 &mut input_file.get_reader(content).unwrap(),
311 &mut input_file.get_reader(content).unwrap(),
312 &self.syntax_mapping,
312 &self.syntax_mapping,
313 );
313 );
src/bin/bat/app.rs+11−9
@@ -16,7 +16,7 @@ use console::Term;
16use bat::{
16use bat::{
17 config::{
17 config::{
18 Config, HighlightedLineRanges, InputFile, LineRange, LineRanges, MappingTarget,
18 Config, HighlightedLineRanges, InputFile, LineRange, LineRanges, MappingTarget,
19− OrdinaryFile, OutputWrap, PagingMode, StyleComponent, StyleComponents, SyntaxMapping,
19+ OrdinaryFile, PagingMode, StyleComponent, StyleComponents, SyntaxMapping, WrappingMode,
20 },
20 },
21 errors::*,
21 errors::*,
22 HighlightingAssets,
22 HighlightingAssets,
@@ -143,22 +143,22 @@ impl App {
143 }
143 }
144 }),
144 }),
145 show_nonprintable: self.matches.is_present("show-all"),
145 show_nonprintable: self.matches.is_present("show-all"),
146− output_wrap: if self.interactive_output || maybe_term_width.is_some() {
146+ wrapping_mode: if self.interactive_output || maybe_term_width.is_some() {
147 match self.matches.value_of("wrap") {
147 match self.matches.value_of("wrap") {
148− Some("character") => OutputWrap::Character,
148+ Some("character") => WrappingMode::Character,
149− Some("never") => OutputWrap::None,
149+ Some("never") => WrappingMode::NoWrapping,
150 Some("auto") | _ => {
150 Some("auto") | _ => {
151 if style_components.plain() {
151 if style_components.plain() {
152− OutputWrap::None
152+ WrappingMode::NoWrapping
153 } else {
153 } else {
154− OutputWrap::Character
154+ WrappingMode::Character
155 }
155 }
156 }
156 }
157 }
157 }
158 } else {
158 } else {
159 // We don't have the tty width when piping to another program.
159 // We don't have the tty width when piping to another program.
160 // There's no point in wrapping when this is the case.
160 // There's no point in wrapping when this is the case.
161− OutputWrap::None
161+ WrappingMode::NoWrapping
162 },
162 },
163 colored_output: match self.matches.value_of("color") {
163 colored_output: match self.matches.value_of("color") {
164 Some("always") => true,
164 Some("always") => true,
@@ -247,7 +247,9 @@ impl App {
247 let files: Option<Vec<&OsStr>> = self.matches.values_of_os("FILE").map(|vs| vs.collect());
247 let files: Option<Vec<&OsStr>> = self.matches.values_of_os("FILE").map(|vs| vs.collect());
248
248
249 if files.is_none() {
249 if files.is_none() {
250− return Ok(vec![InputFile::StdIn(filenames_or_none.nth(0).unwrap())]);
250+ return Ok(vec![InputFile::StdIn(
251+ filenames_or_none.nth(0).unwrap().map(|f| f.to_owned()),
252+ )]);
251 }
253 }
252 let files_or_none: Box<dyn Iterator<Item = _>> = match files {
254 let files_or_none: Box<dyn Iterator<Item = _>> = match files {
253 Some(ref files) => Box::new(files.into_iter().map(|name| Some(*name))),
255 Some(ref files) => Box::new(files.into_iter().map(|name| Some(*name))),
@@ -258,7 +260,7 @@ impl App {
258 for (input, name) in files_or_none.zip(filenames_or_none) {
260 for (input, name) in files_or_none.zip(filenames_or_none) {
259 if let Some(input) = input {
261 if let Some(input) = input {
260 if input.to_str().unwrap_or_default() == "-" {
262 if input.to_str().unwrap_or_default() == "-" {
261− file_input.push(InputFile::StdIn(name));
263+ file_input.push(InputFile::StdIn(name.map(|n| n.to_owned())));
262 } else {
264 } else {
263 let mut ofile = OrdinaryFile::from_path(input);
265 let mut ofile = OrdinaryFile::from_path(input);
264 if let Some(path) = name {
266 if let Some(path) = name {
src/config.rs+4−4
@@ -3,7 +3,7 @@ pub use crate::inputfile::OrdinaryFile;
3pub use crate::line_range::{HighlightedLineRanges, LineRange, LineRanges};
3pub use crate::line_range::{HighlightedLineRanges, LineRange, LineRanges};
4pub use crate::style::{StyleComponent, StyleComponents};
4pub use crate::style::{StyleComponent, StyleComponents};
5pub use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
5pub use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
6−pub use crate::wrap::OutputWrap;
6+pub use crate::wrap::WrappingMode;
7
7
8#[derive(Debug, Clone, Copy, PartialEq)]
8#[derive(Debug, Clone, Copy, PartialEq)]
9#[cfg(feature = "paging")]
9#[cfg(feature = "paging")]
@@ -23,7 +23,7 @@ impl Default for PagingMode {
23#[derive(Debug, Clone, Default)]
23#[derive(Debug, Clone, Default)]
24pub struct Config<'a> {
24pub struct Config<'a> {
25 /// List of files to print
25 /// List of files to print
26− pub files: Vec<InputFile<'a>>,
26+ pub files: Vec<InputFile>,
27
27
28 /// The explicitly configured language, if any
28 /// The explicitly configured language, if any
29 pub language: Option<&'a str>,
29 pub language: Option<&'a str>,
@@ -50,8 +50,8 @@ pub struct Config<'a> {
50 /// Style elements (grid, line numbers, ...)
50 /// Style elements (grid, line numbers, ...)
51 pub style_components: StyleComponents,
51 pub style_components: StyleComponents,
52
52
53− /// Text wrapping mode
53+ /// If and how text should be wrapped
54− pub output_wrap: OutputWrap,
54+ pub wrapping_mode: WrappingMode,
55
55
56 /// Pager or STDOUT
56 /// Pager or STDOUT
57 #[cfg(feature = "paging")]
57 #[cfg(feature = "paging")]
src/controller.rs+4−4
@@ -65,15 +65,15 @@ impl<'b> Controller<'b> {
65 Ok(mut reader) => {
65 Ok(mut reader) => {
66 let result = if self.config.loop_through {
66 let result = if self.config.loop_through {
67 let mut printer = SimplePrinter::new();
67 let mut printer = SimplePrinter::new();
68− self.print_file(reader, &mut printer, writer, *input_file)
68+ self.print_file(reader, &mut printer, writer, input_file)
69 } else {
69 } else {
70 let mut printer = InteractivePrinter::new(
70 let mut printer = InteractivePrinter::new(
71 &self.config,
71 &self.config,
72 &self.assets,
72 &self.assets,
73− *input_file,
73+ input_file,
74 &mut reader,
74 &mut reader,
75 );
75 );
76− self.print_file(reader, &mut printer, writer, *input_file)
76+ self.print_file(reader, &mut printer, writer, input_file)
77 };
77 };
78
78
79 if let Err(error) = result {
79 if let Err(error) = result {
@@ -92,7 +92,7 @@ impl<'b> Controller<'b> {
92 reader: InputFileReader,
92 reader: InputFileReader,
93 printer: &mut P,
93 printer: &mut P,
94 writer: &mut dyn Write,
94 writer: &mut dyn Write,
95− input_file: InputFile<'a>,
95+ input_file: &InputFile,
96 ) -> Result<()> {
96 ) -> Result<()> {
97 if !reader.first_line.is_empty() || self.config.style_components.header() {
97 if !reader.first_line.is_empty() || self.config.style_components.header() {
98 printer.print_header(writer, input_file)?;
98 printer.print_header(writer, input_file)?;
src/inputfile.rs+21−19
@@ -1,4 +1,4 @@
1−use std::ffi::OsStr;
1+use std::ffi::{OsStr, OsString};
2use std::fs::File;
2use std::fs::File;
3use std::io::{self, BufRead, BufReader};
3use std::io::{self, BufRead, BufReader};
4
4
@@ -52,42 +52,44 @@ impl<'a> InputFileReader<'a> {
52 }
52 }
53}
53}
54
54
55−#[derive(Debug, Clone, Copy, PartialEq)]
55+#[derive(Debug, Clone, PartialEq)]
56−pub struct OrdinaryFile<'a> {
56+pub struct OrdinaryFile {
57− path: &'a OsStr,
57+ path: OsString,
58− user_provided_path: Option<&'a OsStr>,
58+ user_provided_path: Option<OsString>,
59}
59}
60
60
61−impl<'a> OrdinaryFile<'a> {
61+impl OrdinaryFile {
62− pub fn from_path(path: &'a OsStr) -> OrdinaryFile<'a> {
62+ pub fn from_path(path: &OsStr) -> OrdinaryFile {
63 OrdinaryFile {
63 OrdinaryFile {
64− path,
64+ path: path.to_os_string(),
65 user_provided_path: None,
65 user_provided_path: None,
66 }
66 }
67 }
67 }
68
68
69− pub fn set_provided_path(&mut self, user_provided_path: &'a OsStr) {
69+ pub fn set_provided_path(&mut self, user_provided_path: &OsStr) {
70− self.user_provided_path = Some(user_provided_path);
70+ self.user_provided_path = Some(user_provided_path.to_os_string());
71 }
71 }
72
72
73− pub(crate) fn provided_path(&self) -> &'a OsStr {
73+ pub(crate) fn provided_path<'a>(&'a self) -> &'a OsStr {
74− self.user_provided_path.unwrap_or_else(|| self.path)
74+ self.user_provided_path
75+ .as_ref()
76+ .unwrap_or_else(|| &self.path)
75 }
77 }
76}
78}
77
79
78−#[derive(Debug, Clone, Copy, PartialEq)]
80+#[derive(Debug, Clone, PartialEq)]
79−pub enum InputFile<'a> {
81+pub enum InputFile {
80− StdIn(Option<&'a OsStr>),
82+ StdIn(Option<OsString>),
81− Ordinary(OrdinaryFile<'a>),
83+ Ordinary(OrdinaryFile),
82 ThemePreviewFile,
84 ThemePreviewFile,
83}
85}
84
86
85−impl<'a> InputFile<'a> {
87+impl InputFile {
86− pub(crate) fn get_reader<R: BufRead + 'a>(&self, stdin: R) -> Result<InputFileReader> {
88+ pub(crate) fn get_reader<'a, R: BufRead + 'a>(&self, stdin: R) -> Result<InputFileReader<'a>> {
87 match self {
89 match self {
88 InputFile::StdIn(_) => Ok(InputFileReader::new(stdin)),
90 InputFile::StdIn(_) => Ok(InputFileReader::new(stdin)),
89 InputFile::Ordinary(ofile) => {
91 InputFile::Ordinary(ofile) => {
90− let file = File::open(ofile.path)
92+ let file = File::open(&ofile.path)
91 .map_err(|e| format!("'{}': {}", ofile.path.to_string_lossy(), e))?;
93 .map_err(|e| format!("'{}': {}", ofile.path.to_string_lossy(), e))?;
92
94
93 if file.metadata()?.is_dir() {
95 if file.metadata()?.is_dir() {
src/lib.rs+1
@@ -25,3 +25,4 @@ pub use assets_metadata::AssetsMetadata;
25pub use controller::Controller;
25pub use controller::Controller;
26pub use pretty_printer::PrettyPrinter;
26pub use pretty_printer::PrettyPrinter;
27pub use printer::{InteractivePrinter, Printer, SimplePrinter};
27pub use printer::{InteractivePrinter, Printer, SimplePrinter};
28+pub use style::{StyleComponent, StyleComponents};
src/pretty_printer.rs+47−2
@@ -1,7 +1,7 @@
1use std::ffi::OsStr;
1use std::ffi::OsStr;
2
2
3use crate::{
3use crate::{
4− config::{Config, InputFile, OrdinaryFile},
4+ config::{Config, InputFile, OrdinaryFile, StyleComponents, WrappingMode},
5 errors::Result,
5 errors::Result,
6 Controller, HighlightingAssets,
6 Controller, HighlightingAssets,
7};
7};
@@ -24,19 +24,64 @@ impl<'a> PrettyPrinter<'a> {
24 }
24 }
25 }
25 }
26
26
27+ /// Add a file which should be pretty-printed
27− pub fn file(&'a mut self, path: &'a OsStr) -> &'a mut Self {
28+ pub fn file(&mut self, path: &OsStr) -> &mut Self {
28 self.config
29 self.config
29 .files
30 .files
30 .push(InputFile::Ordinary(OrdinaryFile::from_path(path)));
31 .push(InputFile::Ordinary(OrdinaryFile::from_path(path)));
31 self
32 self
32 }
33 }
33
34
35+ /// Add multiple files which should be pretty-printed
36+ pub fn files<I, P>(&mut self, paths: I) -> &mut Self
37+ where
38+ I: IntoIterator<Item = P>,
39+ P: AsRef<OsStr>,
40+ {
41+ for path in paths {
42+ self.config
43+ .files
44+ .push(InputFile::Ordinary(OrdinaryFile::from_path(path.as_ref())));
45+ }
46+ self
47+ }
48+
49+ /// The character width of the terminal (default: unlimited)
50+ pub fn term_width(&mut self, width: usize) -> &mut Self {
51+ self.config.term_width = width;
52+ self
53+ }
54+
55+ /// The width of tab characters (default: None - do not turn tabs to spaces)
56+ pub fn tab_width(&mut self, tab_width: Option<usize>) -> &mut Self {
57+ self.config.tab_width = tab_width.unwrap_or(0);
58+ self
59+ }
60+
34 /// Whether or not the output should be colorized (default: true)
61 /// Whether or not the output should be colorized (default: true)
35 pub fn colored_output(&mut self, yes: bool) -> &mut Self {
62 pub fn colored_output(&mut self, yes: bool) -> &mut Self {
36 self.config.colored_output = yes;
63 self.config.colored_output = yes;
37 self
64 self
38 }
65 }
39
66
67+ /// Whether or not to output 24bit colors (default: true)
68+ pub fn true_color(&mut self, yes: bool) -> &mut Self {
69+ self.config.true_color = yes;
70+ self
71+ }
72+
73+ /// Configure style elements (grid, line numbers, ...)
74+ pub fn style_components(&mut self, components: StyleComponents) -> &mut Self {
75+ self.config.style_components = components;
76+ self
77+ }
78+
79+ /// Text wrapping mode (default: do not wrap)
80+ pub fn wrapping_mode(&mut self, wrapping_mode: WrappingMode) -> &mut Self {
81+ self.config.wrapping_mode = wrapping_mode;
82+ self
83+ }
84+
40 pub fn run(&'a self) -> Result<bool> {
85 pub fn run(&'a self) -> Result<bool> {
41 let controller = Controller::new(&self.config, &self.assets);
86 let controller = Controller::new(&self.config, &self.assets);
42 controller.run()
87 controller.run()
src/printer.rs+6−6
@@ -31,10 +31,10 @@ use crate::inputfile::{InputFile, InputFileReader};
31use crate::line_range::RangeCheckResult;
31use crate::line_range::RangeCheckResult;
32use crate::preprocessor::{expand_tabs, replace_nonprintable};
32use crate::preprocessor::{expand_tabs, replace_nonprintable};
33use crate::terminal::{as_terminal_escaped, to_ansi_color};
33use crate::terminal::{as_terminal_escaped, to_ansi_color};
34−use crate::wrap::OutputWrap;
34+use crate::wrap::WrappingMode;
35
35
36pub trait Printer {
36pub trait Printer {
37− fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()>;
37+ fn print_header(&mut self, handle: &mut dyn Write, file: &InputFile) -> Result<()>;
38 fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()>;
38 fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()>;
39
39
40 fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()>;
40 fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()>;
@@ -57,7 +57,7 @@ impl SimplePrinter {
57}
57}
58
58
59impl Printer for SimplePrinter {
59impl Printer for SimplePrinter {
60− fn print_header(&mut self, _handle: &mut dyn Write, _file: InputFile) -> Result<()> {
60+ fn print_header(&mut self, _handle: &mut dyn Write, _file: &InputFile) -> Result<()> {
61 Ok(())
61 Ok(())
62 }
62 }
63
63
@@ -101,7 +101,7 @@ impl<'a> InteractivePrinter<'a> {
101 pub fn new(
101 pub fn new(
102 config: &'a Config,
102 config: &'a Config,
103 assets: &'a HighlightingAssets,
103 assets: &'a HighlightingAssets,
104− file: InputFile,
104+ file: &InputFile,
105 reader: &mut InputFileReader,
105 reader: &mut InputFileReader,
106 ) -> Self {
106 ) -> Self {
107 let theme = assets.get_theme(&config.theme);
107 let theme = assets.get_theme(&config.theme);
@@ -230,7 +230,7 @@ impl<'a> InteractivePrinter<'a> {
230}
230}
231
231
232impl<'a> Printer for InteractivePrinter<'a> {
232impl<'a> Printer for InteractivePrinter<'a> {
233− fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()> {
233+ fn print_header(&mut self, handle: &mut dyn Write, file: &InputFile) -> Result<()> {
234 if !self.config.style_components.header() {
234 if !self.config.style_components.header() {
235 if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable {
235 if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable {
236 let input = match file {
236 let input = match file {
@@ -415,7 +415,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
415 }
415 }
416
416
417 // Line contents.
417 // Line contents.
418− if self.config.output_wrap == OutputWrap::None {
418+ if self.config.wrapping_mode == WrappingMode::NoWrapping {
419 let true_color = self.config.true_color;
419 let true_color = self.config.true_color;
420 let colored_output = self.config.colored_output;
420 let colored_output = self.config.colored_output;
421 let italics = self.config.use_italic_text;
421 let italics = self.config.use_italic_text;
src/wrap.rs+5−5
@@ -1,11 +1,11 @@
1−#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
1+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2−pub enum OutputWrap {
2+pub enum WrappingMode {
3 Character,
3 Character,
4− None,
4+ NoWrapping,
5}
5}
6
6
7−impl Default for OutputWrap {
7+impl Default for WrappingMode {
8 fn default() -> Self {
8 fn default() -> Self {
9− OutputWrap::None
9+ WrappingMode::NoWrapping
10 }
10 }
11}
11}