From 057e4eced1a74972dd5aef90c2c054acb7963c60 Mon Sep 17 00:00:00 2001 From: sharkdp Date: Tue, 21 Apr 2020 20:06:09 +0200 Subject: [PATCH] Large refactoring towards a better builder structure --- examples/cat.rs | 35 +++++++------------------------ src/assets.rs | 20 +++++++++--------- src/bin/bat/app.rs | 20 ++++++++++-------- src/config.rs | 8 +++---- src/controller.rs | 8 +++---- src/inputfile.rs | 40 ++++++++++++++++++----------------- src/lib.rs | 1 + src/pretty_printer.rs | 49 +++++++++++++++++++++++++++++++++++++++++-- src/printer.rs | 12 +++++------ src/wrap.rs | 10 ++++----- 10 files changed, 117 insertions(+), 86 deletions(-) diff --git a/examples/cat.rs b/examples/cat.rs index c98270bb..4575add0 100644 --- a/examples/cat.rs +++ b/examples/cat.rs @@ -1,36 +1,17 @@ /// A very simple colorized `cat` clone, using `bat` as a library. /// See `src/bin/bat` for the full `bat` application. -use bat::{ - config::{Config, InputFile, OrdinaryFile, StyleComponent, StyleComponents}, - Controller, HighlightingAssets, -}; +use bat::{PrettyPrinter, StyleComponent, StyleComponents}; use console::Term; -use std::process; fn main() { - let files = std::env::args_os().skip(1).collect::>(); - - if files.is_empty() { - eprintln!("No input files specified"); - process::exit(1); - } - - let config = Config { - term_width: Term::stdout().size().1 as usize, - colored_output: true, - true_color: true, - style_components: StyleComponents::new(&[ + PrettyPrinter::new() + .term_width(Term::stdout().size().1 as usize) + .style_components(StyleComponents::new(&[ StyleComponent::Header, StyleComponent::Grid, StyleComponent::Numbers, - ]), - files: files - .iter() - .map(|file| InputFile::Ordinary(OrdinaryFile::from_path(file))) - .collect(), - ..Default::default() - }; - let assets = HighlightingAssets::from_binary(); - - Controller::new(&config, &assets).run().expect("no errors"); + ])) + .files(std::env::args_os().skip(1)) + .run() + .expect("no errors"); } diff --git a/src/assets.rs b/src/assets.rs index f5bb7c4e..916d03b3 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -188,7 +188,7 @@ impl HighlightingAssets { pub(crate) fn get_syntax( &self, language: Option<&str>, - file: InputFile, + file: &InputFile, reader: &mut InputFileReader, mapping: &SyntaxMapping, ) -> &SyntaxReference { @@ -216,7 +216,7 @@ impl HighlightingAssets { .ok() .and_then(|l| self.syntax_set.find_syntax_by_first_line(&l)), (None, InputFile::StdIn(Some(file_name))) => self - .get_extension_syntax(file_name) + .get_extension_syntax(&file_name) .or(self.get_first_line_syntax(reader)), (_, InputFile::ThemePreviewFile) => self.syntax_set.find_syntax_by_name("Rust"), }; @@ -246,17 +246,17 @@ impl HighlightingAssets { #[cfg(test)] mod tests { - use std::ffi::OsStr; + use super::*; + + use crate::inputfile::OrdinaryFile; + + use std::ffi::{OsStr, OsString}; use std::fs::File; use std::io; use std::io::Write; use tempdir::TempDir; - use crate::assets::HighlightingAssets; - use crate::inputfile::{InputFile, OrdinaryFile}; - use crate::syntax_mapping::{MappingTarget, SyntaxMapping}; - struct SyntaxDetectionTest<'a> { assets: HighlightingAssets, pub syntax_mapping: SyntaxMapping<'a>, @@ -283,7 +283,7 @@ mod tests { let input_file = InputFile::Ordinary(OrdinaryFile::from_path(file_path.as_os_str())); let syntax = self.assets.get_syntax( None, - input_file, + &input_file, &mut input_file.get_reader(io::stdin().lock()).unwrap(), &self.syntax_mapping, ); @@ -304,10 +304,10 @@ mod tests { } fn syntax_for_stdin_with_content(&self, file_name: &str, content: &[u8]) -> String { - let input_file = InputFile::StdIn(Some(OsStr::new(file_name))); + let input_file = InputFile::StdIn(Some(OsString::from(file_name))); let syntax = self.assets.get_syntax( None, - input_file, + &input_file, &mut input_file.get_reader(content).unwrap(), &self.syntax_mapping, ); diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index 4a04f952..5c2031d1 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -16,7 +16,7 @@ use console::Term; use bat::{ config::{ Config, HighlightedLineRanges, InputFile, LineRange, LineRanges, MappingTarget, - OrdinaryFile, OutputWrap, PagingMode, StyleComponent, StyleComponents, SyntaxMapping, + OrdinaryFile, PagingMode, StyleComponent, StyleComponents, SyntaxMapping, WrappingMode, }, errors::*, HighlightingAssets, @@ -143,22 +143,22 @@ impl App { } }), show_nonprintable: self.matches.is_present("show-all"), - output_wrap: if self.interactive_output || maybe_term_width.is_some() { + wrapping_mode: if self.interactive_output || maybe_term_width.is_some() { match self.matches.value_of("wrap") { - Some("character") => OutputWrap::Character, - Some("never") => OutputWrap::None, + Some("character") => WrappingMode::Character, + Some("never") => WrappingMode::NoWrapping, Some("auto") | _ => { if style_components.plain() { - OutputWrap::None + WrappingMode::NoWrapping } else { - OutputWrap::Character + WrappingMode::Character } } } } else { // We don't have the tty width when piping to another program. // There's no point in wrapping when this is the case. - OutputWrap::None + WrappingMode::NoWrapping }, colored_output: match self.matches.value_of("color") { Some("always") => true, @@ -247,7 +247,9 @@ impl App { let files: Option> = self.matches.values_of_os("FILE").map(|vs| vs.collect()); if files.is_none() { - return Ok(vec![InputFile::StdIn(filenames_or_none.nth(0).unwrap())]); + return Ok(vec![InputFile::StdIn( + filenames_or_none.nth(0).unwrap().map(|f| f.to_owned()), + )]); } let files_or_none: Box> = match files { Some(ref files) => Box::new(files.into_iter().map(|name| Some(*name))), @@ -258,7 +260,7 @@ impl App { for (input, name) in files_or_none.zip(filenames_or_none) { if let Some(input) = input { if input.to_str().unwrap_or_default() == "-" { - file_input.push(InputFile::StdIn(name)); + file_input.push(InputFile::StdIn(name.map(|n| n.to_owned()))); } else { let mut ofile = OrdinaryFile::from_path(input); if let Some(path) = name { diff --git a/src/config.rs b/src/config.rs index a2bff63d..b200af57 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,7 +3,7 @@ pub use crate::inputfile::OrdinaryFile; pub use crate::line_range::{HighlightedLineRanges, LineRange, LineRanges}; pub use crate::style::{StyleComponent, StyleComponents}; pub use crate::syntax_mapping::{MappingTarget, SyntaxMapping}; -pub use crate::wrap::OutputWrap; +pub use crate::wrap::WrappingMode; #[derive(Debug, Clone, Copy, PartialEq)] #[cfg(feature = "paging")] @@ -23,7 +23,7 @@ impl Default for PagingMode { #[derive(Debug, Clone, Default)] pub struct Config<'a> { /// List of files to print - pub files: Vec>, + pub files: Vec, /// The explicitly configured language, if any pub language: Option<&'a str>, @@ -50,8 +50,8 @@ pub struct Config<'a> { /// Style elements (grid, line numbers, ...) pub style_components: StyleComponents, - /// Text wrapping mode - pub output_wrap: OutputWrap, + /// If and how text should be wrapped + pub wrapping_mode: WrappingMode, /// Pager or STDOUT #[cfg(feature = "paging")] diff --git a/src/controller.rs b/src/controller.rs index 10230562..f4fc646b 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -65,15 +65,15 @@ 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) } else { let mut printer = InteractivePrinter::new( &self.config, &self.assets, - *input_file, + input_file, &mut reader, ); - self.print_file(reader, &mut printer, writer, *input_file) + self.print_file(reader, &mut printer, writer, input_file) }; if let Err(error) = result { @@ -92,7 +92,7 @@ impl<'b> Controller<'b> { reader: InputFileReader, printer: &mut P, writer: &mut dyn Write, - input_file: InputFile<'a>, + input_file: &InputFile, ) -> Result<()> { if !reader.first_line.is_empty() || self.config.style_components.header() { printer.print_header(writer, input_file)?; diff --git a/src/inputfile.rs b/src/inputfile.rs index e7ed739d..8e29ce57 100644 --- a/src/inputfile.rs +++ b/src/inputfile.rs @@ -1,4 +1,4 @@ -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::fs::File; use std::io::{self, BufRead, BufReader}; @@ -52,42 +52,44 @@ impl<'a> InputFileReader<'a> { } } -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct OrdinaryFile<'a> { - path: &'a OsStr, - user_provided_path: Option<&'a OsStr>, +#[derive(Debug, Clone, PartialEq)] +pub struct OrdinaryFile { + path: OsString, + user_provided_path: Option, } -impl<'a> OrdinaryFile<'a> { - pub fn from_path(path: &'a OsStr) -> OrdinaryFile<'a> { +impl OrdinaryFile { + pub fn from_path(path: &OsStr) -> OrdinaryFile { OrdinaryFile { - path, + path: path.to_os_string(), user_provided_path: None, } } - pub fn set_provided_path(&mut self, user_provided_path: &'a OsStr) { - self.user_provided_path = Some(user_provided_path); + pub fn set_provided_path(&mut self, user_provided_path: &OsStr) { + self.user_provided_path = Some(user_provided_path.to_os_string()); } - pub(crate) fn provided_path(&self) -> &'a OsStr { - self.user_provided_path.unwrap_or_else(|| self.path) + pub(crate) fn provided_path<'a>(&'a self) -> &'a OsStr { + self.user_provided_path + .as_ref() + .unwrap_or_else(|| &self.path) } } -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum InputFile<'a> { - StdIn(Option<&'a OsStr>), - Ordinary(OrdinaryFile<'a>), +#[derive(Debug, Clone, PartialEq)] +pub enum InputFile { + StdIn(Option), + Ordinary(OrdinaryFile), ThemePreviewFile, } -impl<'a> InputFile<'a> { - pub(crate) fn get_reader(&self, stdin: R) -> Result { +impl InputFile { + pub(crate) fn get_reader<'a, R: BufRead + 'a>(&self, stdin: R) -> Result> { match self { InputFile::StdIn(_) => Ok(InputFileReader::new(stdin)), InputFile::Ordinary(ofile) => { - let file = File::open(ofile.path) + let file = File::open(&ofile.path) .map_err(|e| format!("'{}': {}", ofile.path.to_string_lossy(), e))?; if file.metadata()?.is_dir() { diff --git a/src/lib.rs b/src/lib.rs index 7f36c2e0..07f6d191 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,3 +25,4 @@ pub use assets_metadata::AssetsMetadata; pub use controller::Controller; pub use pretty_printer::PrettyPrinter; pub use printer::{InteractivePrinter, Printer, SimplePrinter}; +pub use style::{StyleComponent, StyleComponents}; diff --git a/src/pretty_printer.rs b/src/pretty_printer.rs index 7d8ba97b..fcf60db7 100644 --- a/src/pretty_printer.rs +++ b/src/pretty_printer.rs @@ -1,7 +1,7 @@ use std::ffi::OsStr; use crate::{ - config::{Config, InputFile, OrdinaryFile}, + config::{Config, InputFile, OrdinaryFile, StyleComponents, WrappingMode}, errors::Result, Controller, HighlightingAssets, }; @@ -24,19 +24,64 @@ impl<'a> PrettyPrinter<'a> { } } - pub fn file(&'a mut self, path: &'a OsStr) -> &'a mut Self { + /// Add a file which should be pretty-printed + pub fn file(&mut self, path: &OsStr) -> &mut Self { self.config .files .push(InputFile::Ordinary(OrdinaryFile::from_path(path))); self } + /// Add multiple files which should be pretty-printed + pub fn files(&mut self, paths: I) -> &mut Self + where + I: IntoIterator, + P: AsRef, + { + for path in paths { + self.config + .files + .push(InputFile::Ordinary(OrdinaryFile::from_path(path.as_ref()))); + } + self + } + + /// The character width of the terminal (default: unlimited) + pub fn term_width(&mut self, width: usize) -> &mut Self { + self.config.term_width = width; + self + } + + /// The width of tab characters (default: None - do not turn tabs to spaces) + pub fn tab_width(&mut self, tab_width: Option) -> &mut Self { + self.config.tab_width = tab_width.unwrap_or(0); + self + } + /// Whether or not the output should be colorized (default: true) pub fn colored_output(&mut self, yes: bool) -> &mut Self { self.config.colored_output = yes; self } + /// Whether or not to output 24bit colors (default: true) + pub fn true_color(&mut self, yes: bool) -> &mut Self { + self.config.true_color = yes; + self + } + + /// Configure style elements (grid, line numbers, ...) + pub fn style_components(&mut self, components: StyleComponents) -> &mut Self { + self.config.style_components = components; + self + } + + /// Text wrapping mode (default: do not wrap) + pub fn wrapping_mode(&mut self, wrapping_mode: WrappingMode) -> &mut Self { + self.config.wrapping_mode = wrapping_mode; + self + } + pub fn run(&'a self) -> Result { let controller = Controller::new(&self.config, &self.assets); controller.run() diff --git a/src/printer.rs b/src/printer.rs index e63d5877..d7d5405d 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -31,10 +31,10 @@ use crate::inputfile::{InputFile, InputFileReader}; use crate::line_range::RangeCheckResult; use crate::preprocessor::{expand_tabs, replace_nonprintable}; use crate::terminal::{as_terminal_escaped, to_ansi_color}; -use crate::wrap::OutputWrap; +use crate::wrap::WrappingMode; 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) -> Result<()>; fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()>; fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()>; @@ -57,7 +57,7 @@ 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) -> Result<()> { Ok(()) } @@ -101,7 +101,7 @@ impl<'a> InteractivePrinter<'a> { pub fn new( config: &'a Config, assets: &'a HighlightingAssets, - file: InputFile, + file: &InputFile, reader: &mut InputFileReader, ) -> Self { let theme = assets.get_theme(&config.theme); @@ -230,7 +230,7 @@ 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) -> Result<()> { if !self.config.style_components.header() { if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable { let input = match file { @@ -415,7 +415,7 @@ impl<'a> Printer for InteractivePrinter<'a> { } // Line contents. - if self.config.output_wrap == OutputWrap::None { + if self.config.wrapping_mode == WrappingMode::NoWrapping { let true_color = self.config.true_color; let colored_output = self.config.colored_output; let italics = self.config.use_italic_text; diff --git a/src/wrap.rs b/src/wrap.rs index 211469ab..6138606e 100644 --- a/src/wrap.rs +++ b/src/wrap.rs @@ -1,11 +1,11 @@ -#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] -pub enum OutputWrap { +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WrappingMode { Character, - None, + NoWrapping, } -impl Default for OutputWrap { +impl Default for WrappingMode { fn default() -> Self { - OutputWrap::None + WrappingMode::NoWrapping } }