on master help sign in
Large refactoring towards a better builder structure
commit057e4eced1a74972dd5aef90c2c054acb7963c60 authorsharkdp <davidpeter@web.de> dateTue 21 Apr 2020, 20:06 (7 years ago) parent2797461
10 files changed+117−86
examples/cat.rs+8−27 src/assets.rs+10−10 src/bin/bat/app.rs+11−9 src/config.rs+4−4 src/controller.rs+4−4 src/inputfile.rs+21−19 src/lib.rs+1 src/pretty_printer.rs+47−2 src/printer.rs+6−6 src/wrap.rs+5−5
examples/cat.rs+8−27
@@ -1,36 +1,17 @@
11/// A very simple colorized `cat` clone, using `bat` as a library.
22/// See `src/bin/bat` for the full `bat` application.
3−use bat::{
4− config::{Config, InputFile, OrdinaryFile, StyleComponent, StyleComponents},
5− Controller, HighlightingAssets,
6−};
3+use bat::{PrettyPrinter, StyleComponent, StyleComponents};
74use console::Term;
8−use std::process;
95
106fn main() {
11− let files = std::env::args_os().skip(1).collect::<Vec<_>>();
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,
20− colored_output: true,
21− true_color: true,
22− style_components: StyleComponents::new(&[
7+ PrettyPrinter::new()
8+ .term_width(Term::stdout().size().1 as usize)
9+ .style_components(StyleComponents::new(&[
2310 StyleComponent::Header,
2411 StyleComponent::Grid,
2512 StyleComponent::Numbers,
26− ]),
27− files: files
28− .iter()
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");
13+ ]))
14+ .files(std::env::args_os().skip(1))
15+ .run()
16+ .expect("no errors");
3617}
src/assets.rs+10−10
@@ -188,7 +188,7 @@ impl HighlightingAssets {
188188 pub(crate) fn get_syntax(
189189 &self,
190190 language: Option<&str>,
191− file: InputFile,
191+ file: &InputFile,
192192 reader: &mut InputFileReader,
193193 mapping: &SyntaxMapping,
194194 ) -> &SyntaxReference {
@@ -216,7 +216,7 @@ impl HighlightingAssets {
216216 .ok()
217217 .and_then(|l| self.syntax_set.find_syntax_by_first_line(&l)),
218218 (None, InputFile::StdIn(Some(file_name))) => self
219− .get_extension_syntax(file_name)
219+ .get_extension_syntax(&file_name)
220220 .or(self.get_first_line_syntax(reader)),
221221 (_, InputFile::ThemePreviewFile) => self.syntax_set.find_syntax_by_name("Rust"),
222222 };
@@ -246,17 +246,17 @@ impl HighlightingAssets {
246246
247247#[cfg(test)]
248248mod tests {
249− use std::ffi::OsStr;
249+ use super::*;
250+
251+ use crate::inputfile::OrdinaryFile;
252+
253+ use std::ffi::{OsStr, OsString};
250254 use std::fs::File;
251255 use std::io;
252256 use std::io::Write;
253257
254258 use tempdir::TempDir;
255259
256− use crate::assets::HighlightingAssets;
257− use crate::inputfile::{InputFile, OrdinaryFile};
258− use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
259−
260260 struct SyntaxDetectionTest<'a> {
261261 assets: HighlightingAssets,
262262 pub syntax_mapping: SyntaxMapping<'a>,
@@ -283,7 +283,7 @@ mod tests {
283283 let input_file = InputFile::Ordinary(OrdinaryFile::from_path(file_path.as_os_str()));
284284 let syntax = self.assets.get_syntax(
285285 None,
286− input_file,
286+ &input_file,
287287 &mut input_file.get_reader(io::stdin().lock()).unwrap(),
288288 &self.syntax_mapping,
289289 );
@@ -304,10 +304,10 @@ mod tests {
304304 }
305305
306306 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)));
308308 let syntax = self.assets.get_syntax(
309309 None,
310− input_file,
310+ &input_file,
311311 &mut input_file.get_reader(content).unwrap(),
312312 &self.syntax_mapping,
313313 );
src/bin/bat/app.rs+11−9
@@ -16,7 +16,7 @@ use console::Term;
1616use bat::{
1717 config::{
1818 Config, HighlightedLineRanges, InputFile, LineRange, LineRanges, MappingTarget,
19− OrdinaryFile, OutputWrap, PagingMode, StyleComponent, StyleComponents, SyntaxMapping,
19+ OrdinaryFile, PagingMode, StyleComponent, StyleComponents, SyntaxMapping, WrappingMode,
2020 },
2121 errors::*,
2222 HighlightingAssets,
@@ -143,22 +143,22 @@ impl App {
143143 }
144144 }),
145145 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() {
147147 match self.matches.value_of("wrap") {
148− Some("character") => OutputWrap::Character,
149− Some("never") => OutputWrap::None,
148+ Some("character") => WrappingMode::Character,
149+ Some("never") => WrappingMode::NoWrapping,
150150 Some("auto") | _ => {
151151 if style_components.plain() {
152− OutputWrap::None
152+ WrappingMode::NoWrapping
153153 } else {
154− OutputWrap::Character
154+ WrappingMode::Character
155155 }
156156 }
157157 }
158158 } else {
159159 // We don't have the tty width when piping to another program.
160160 // There's no point in wrapping when this is the case.
161− OutputWrap::None
161+ WrappingMode::NoWrapping
162162 },
163163 colored_output: match self.matches.value_of("color") {
164164 Some("always") => true,
@@ -247,7 +247,9 @@ impl App {
247247 let files: Option<Vec<&OsStr>> = self.matches.values_of_os("FILE").map(|vs| vs.collect());
248248
249249 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+ )]);
251253 }
252254 let files_or_none: Box<dyn Iterator<Item = _>> = match files {
253255 Some(ref files) => Box::new(files.into_iter().map(|name| Some(*name))),
@@ -258,7 +260,7 @@ impl App {
258260 for (input, name) in files_or_none.zip(filenames_or_none) {
259261 if let Some(input) = input {
260262 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())));
262264 } else {
263265 let mut ofile = OrdinaryFile::from_path(input);
264266 if let Some(path) = name {
src/config.rs+4−4
@@ -3,7 +3,7 @@ pub use crate::inputfile::OrdinaryFile;
33pub use crate::line_range::{HighlightedLineRanges, LineRange, LineRanges};
44pub use crate::style::{StyleComponent, StyleComponents};
55pub use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
6−pub use crate::wrap::OutputWrap;
6+pub use crate::wrap::WrappingMode;
77
88#[derive(Debug, Clone, Copy, PartialEq)]
99#[cfg(feature = "paging")]
@@ -23,7 +23,7 @@ impl Default for PagingMode {
2323#[derive(Debug, Clone, Default)]
2424pub struct Config<'a> {
2525 /// List of files to print
26− pub files: Vec<InputFile<'a>>,
26+ pub files: Vec<InputFile>,
2727
2828 /// The explicitly configured language, if any
2929 pub language: Option<&'a str>,
@@ -50,8 +50,8 @@ pub struct Config<'a> {
5050 /// Style elements (grid, line numbers, ...)
5151 pub style_components: StyleComponents,
5252
53− /// Text wrapping mode
54− pub output_wrap: OutputWrap,
53+ /// If and how text should be wrapped
54+ pub wrapping_mode: WrappingMode,
5555
5656 /// Pager or STDOUT
5757 #[cfg(feature = "paging")]
src/controller.rs+4−4
@@ -65,15 +65,15 @@ impl<'b> Controller<'b> {
6565 Ok(mut reader) => {
6666 let result = if self.config.loop_through {
6767 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)
6969 } else {
7070 let mut printer = InteractivePrinter::new(
7171 &self.config,
7272 &self.assets,
73− *input_file,
73+ input_file,
7474 &mut reader,
7575 );
76− self.print_file(reader, &mut printer, writer, *input_file)
76+ self.print_file(reader, &mut printer, writer, input_file)
7777 };
7878
7979 if let Err(error) = result {
@@ -92,7 +92,7 @@ impl<'b> Controller<'b> {
9292 reader: InputFileReader,
9393 printer: &mut P,
9494 writer: &mut dyn Write,
95− input_file: InputFile<'a>,
95+ input_file: &InputFile,
9696 ) -> Result<()> {
9797 if !reader.first_line.is_empty() || self.config.style_components.header() {
9898 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};
22use std::fs::File;
33use std::io::{self, BufRead, BufReader};
44
@@ -52,42 +52,44 @@ impl<'a> InputFileReader<'a> {
5252 }
5353}
5454
55−#[derive(Debug, Clone, Copy, PartialEq)]
56−pub struct OrdinaryFile<'a> {
57− path: &'a OsStr,
58− user_provided_path: Option<&'a OsStr>,
55+#[derive(Debug, Clone, PartialEq)]
56+pub struct OrdinaryFile {
57+ path: OsString,
58+ user_provided_path: Option<OsString>,
5959}
6060
61−impl<'a> OrdinaryFile<'a> {
62− pub fn from_path(path: &'a OsStr) -> OrdinaryFile<'a> {
61+impl OrdinaryFile {
62+ pub fn from_path(path: &OsStr) -> OrdinaryFile {
6363 OrdinaryFile {
64− path,
64+ path: path.to_os_string(),
6565 user_provided_path: None,
6666 }
6767 }
6868
69− pub fn set_provided_path(&mut self, user_provided_path: &'a OsStr) {
70− self.user_provided_path = Some(user_provided_path);
69+ pub fn set_provided_path(&mut self, user_provided_path: &OsStr) {
70+ self.user_provided_path = Some(user_provided_path.to_os_string());
7171 }
7272
73− pub(crate) fn provided_path(&self) -> &'a OsStr {
74− self.user_provided_path.unwrap_or_else(|| self.path)
73+ pub(crate) fn provided_path<'a>(&'a self) -> &'a OsStr {
74+ self.user_provided_path
75+ .as_ref()
76+ .unwrap_or_else(|| &self.path)
7577 }
7678}
7779
78−#[derive(Debug, Clone, Copy, PartialEq)]
79−pub enum InputFile<'a> {
80− StdIn(Option<&'a OsStr>),
81− Ordinary(OrdinaryFile<'a>),
80+#[derive(Debug, Clone, PartialEq)]
81+pub enum InputFile {
82+ StdIn(Option<OsString>),
83+ Ordinary(OrdinaryFile),
8284 ThemePreviewFile,
8385}
8486
85−impl<'a> InputFile<'a> {
86− pub(crate) fn get_reader<R: BufRead + 'a>(&self, stdin: R) -> Result<InputFileReader> {
87+impl InputFile {
88+ pub(crate) fn get_reader<'a, R: BufRead + 'a>(&self, stdin: R) -> Result<InputFileReader<'a>> {
8789 match self {
8890 InputFile::StdIn(_) => Ok(InputFileReader::new(stdin)),
8991 InputFile::Ordinary(ofile) => {
90− let file = File::open(ofile.path)
92+ let file = File::open(&ofile.path)
9193 .map_err(|e| format!("'{}': {}", ofile.path.to_string_lossy(), e))?;
9294
9395 if file.metadata()?.is_dir() {
src/lib.rs+1
@@ -25,3 +25,4 @@ pub use assets_metadata::AssetsMetadata;
2525pub use controller::Controller;
2626pub use pretty_printer::PrettyPrinter;
2727pub use printer::{InteractivePrinter, Printer, SimplePrinter};
28+pub use style::{StyleComponent, StyleComponents};
src/pretty_printer.rs+47−2
@@ -1,7 +1,7 @@
11use std::ffi::OsStr;
22
33use crate::{
4− config::{Config, InputFile, OrdinaryFile},
4+ config::{Config, InputFile, OrdinaryFile, StyleComponents, WrappingMode},
55 errors::Result,
66 Controller, HighlightingAssets,
77};
@@ -24,19 +24,64 @@ impl<'a> PrettyPrinter<'a> {
2424 }
2525 }
2626
27− pub fn file(&'a mut self, path: &'a OsStr) -> &'a mut Self {
27+ /// Add a file which should be pretty-printed
28+ pub fn file(&mut self, path: &OsStr) -> &mut Self {
2829 self.config
2930 .files
3031 .push(InputFile::Ordinary(OrdinaryFile::from_path(path)));
3132 self
3233 }
3334
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+
3461 /// Whether or not the output should be colorized (default: true)
3562 pub fn colored_output(&mut self, yes: bool) -> &mut Self {
3663 self.config.colored_output = yes;
3764 self
3865 }
3966
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+
4085 pub fn run(&'a self) -> Result<bool> {
4186 let controller = Controller::new(&self.config, &self.assets);
4287 controller.run()
src/printer.rs+6−6
@@ -31,10 +31,10 @@ use crate::inputfile::{InputFile, InputFileReader};
3131use crate::line_range::RangeCheckResult;
3232use crate::preprocessor::{expand_tabs, replace_nonprintable};
3333use crate::terminal::{as_terminal_escaped, to_ansi_color};
34−use crate::wrap::OutputWrap;
34+use crate::wrap::WrappingMode;
3535
3636pub 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<()>;
3838 fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()>;
3939
4040 fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()>;
@@ -57,7 +57,7 @@ impl SimplePrinter {
5757}
5858
5959impl 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<()> {
6161 Ok(())
6262 }
6363
@@ -101,7 +101,7 @@ impl<'a> InteractivePrinter<'a> {
101101 pub fn new(
102102 config: &'a Config,
103103 assets: &'a HighlightingAssets,
104− file: InputFile,
104+ file: &InputFile,
105105 reader: &mut InputFileReader,
106106 ) -> Self {
107107 let theme = assets.get_theme(&config.theme);
@@ -230,7 +230,7 @@ impl<'a> InteractivePrinter<'a> {
230230}
231231
232232impl<'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<()> {
234234 if !self.config.style_components.header() {
235235 if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable {
236236 let input = match file {
@@ -415,7 +415,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
415415 }
416416
417417 // Line contents.
418− if self.config.output_wrap == OutputWrap::None {
418+ if self.config.wrapping_mode == WrappingMode::NoWrapping {
419419 let true_color = self.config.true_color;
420420 let colored_output = self.config.colored_output;
421421 let italics = self.config.use_italic_text;
src/wrap.rs+5−5
@@ -1,11 +1,11 @@
1−#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
2−pub enum OutputWrap {
1+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2+pub enum WrappingMode {
33 Character,
4− None,
4+ NoWrapping,
55}
66
7−impl Default for OutputWrap {
7+impl Default for WrappingMode {
88 fn default() -> Self {
9− OutputWrap::None
9+ WrappingMode::NoWrapping
1010 }
1111}