Large refactoring towards a better builder structure

This commit is contained in:
sharkdp 2020-04-21 20:06:09 +02:00 committed by David Peter
parent 27974616bf
commit 057e4eced1
10 changed files with 117 additions and 86 deletions

View File

@ -1,36 +1,17 @@
/// A very simple colorized `cat` clone, using `bat` as a library. /// A very simple colorized `cat` clone, using `bat` as a library.
/// See `src/bin/bat` for the full `bat` application. /// See `src/bin/bat` for the full `bat` application.
use bat::{ use bat::{PrettyPrinter, StyleComponent, StyleComponents};
config::{Config, InputFile, OrdinaryFile, StyleComponent, StyleComponents},
Controller, HighlightingAssets,
};
use console::Term; use console::Term;
use std::process;
fn main() { fn main() {
let files = std::env::args_os().skip(1).collect::<Vec<_>>(); PrettyPrinter::new()
.term_width(Term::stdout().size().1 as usize)
if files.is_empty() { .style_components(StyleComponents::new(&[
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(&[
StyleComponent::Header, StyleComponent::Header,
StyleComponent::Grid, StyleComponent::Grid,
StyleComponent::Numbers, StyleComponent::Numbers,
]), ]))
files: files .files(std::env::args_os().skip(1))
.iter() .run()
.map(|file| InputFile::Ordinary(OrdinaryFile::from_path(file))) .expect("no errors");
.collect(),
..Default::default()
};
let assets = HighlightingAssets::from_binary();
Controller::new(&config, &assets).run().expect("no errors");
} }

View File

@ -188,7 +188,7 @@ impl HighlightingAssets {
pub(crate) fn get_syntax( pub(crate) fn get_syntax(
&self, &self,
language: Option<&str>, language: Option<&str>,
file: InputFile, file: &InputFile,
reader: &mut InputFileReader, reader: &mut InputFileReader,
mapping: &SyntaxMapping, mapping: &SyntaxMapping,
) -> &SyntaxReference { ) -> &SyntaxReference {
@ -216,7 +216,7 @@ impl HighlightingAssets {
.ok() .ok()
.and_then(|l| self.syntax_set.find_syntax_by_first_line(&l)), .and_then(|l| self.syntax_set.find_syntax_by_first_line(&l)),
(None, InputFile::StdIn(Some(file_name))) => self (None, InputFile::StdIn(Some(file_name))) => self
.get_extension_syntax(file_name) .get_extension_syntax(&file_name)
.or(self.get_first_line_syntax(reader)), .or(self.get_first_line_syntax(reader)),
(_, InputFile::ThemePreviewFile) => self.syntax_set.find_syntax_by_name("Rust"), (_, InputFile::ThemePreviewFile) => self.syntax_set.find_syntax_by_name("Rust"),
}; };
@ -246,17 +246,17 @@ impl HighlightingAssets {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::ffi::OsStr; use super::*;
use crate::inputfile::OrdinaryFile;
use std::ffi::{OsStr, OsString};
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::Write; use std::io::Write;
use tempdir::TempDir; use tempdir::TempDir;
use crate::assets::HighlightingAssets;
use crate::inputfile::{InputFile, OrdinaryFile};
use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
struct SyntaxDetectionTest<'a> { struct SyntaxDetectionTest<'a> {
assets: HighlightingAssets, assets: HighlightingAssets,
pub syntax_mapping: SyntaxMapping<'a>, 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 input_file = InputFile::Ordinary(OrdinaryFile::from_path(file_path.as_os_str()));
let syntax = self.assets.get_syntax( let syntax = self.assets.get_syntax(
None, None,
input_file, &input_file,
&mut input_file.get_reader(io::stdin().lock()).unwrap(), &mut input_file.get_reader(io::stdin().lock()).unwrap(),
&self.syntax_mapping, &self.syntax_mapping,
); );
@ -304,10 +304,10 @@ mod tests {
} }
fn syntax_for_stdin_with_content(&self, file_name: &str, content: &[u8]) -> String { 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( let syntax = self.assets.get_syntax(
None, None,
input_file, &input_file,
&mut input_file.get_reader(content).unwrap(), &mut input_file.get_reader(content).unwrap(),
&self.syntax_mapping, &self.syntax_mapping,
); );

View File

@ -16,7 +16,7 @@ use console::Term;
use bat::{ use bat::{
config::{ config::{
Config, HighlightedLineRanges, InputFile, LineRange, LineRanges, MappingTarget, Config, HighlightedLineRanges, InputFile, LineRange, LineRanges, MappingTarget,
OrdinaryFile, OutputWrap, PagingMode, StyleComponent, StyleComponents, SyntaxMapping, OrdinaryFile, PagingMode, StyleComponent, StyleComponents, SyntaxMapping, WrappingMode,
}, },
errors::*, errors::*,
HighlightingAssets, HighlightingAssets,
@ -143,22 +143,22 @@ impl App {
} }
}), }),
show_nonprintable: self.matches.is_present("show-all"), 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") { match self.matches.value_of("wrap") {
Some("character") => OutputWrap::Character, Some("character") => WrappingMode::Character,
Some("never") => OutputWrap::None, Some("never") => WrappingMode::NoWrapping,
Some("auto") | _ => { Some("auto") | _ => {
if style_components.plain() { if style_components.plain() {
OutputWrap::None WrappingMode::NoWrapping
} else { } else {
OutputWrap::Character WrappingMode::Character
} }
} }
} }
} else { } else {
// We don't have the tty width when piping to another program. // We don't have the tty width when piping to another program.
// There's no point in wrapping when this is the case. // There's no point in wrapping when this is the case.
OutputWrap::None WrappingMode::NoWrapping
}, },
colored_output: match self.matches.value_of("color") { colored_output: match self.matches.value_of("color") {
Some("always") => true, Some("always") => true,
@ -247,7 +247,9 @@ impl App {
let files: Option<Vec<&OsStr>> = self.matches.values_of_os("FILE").map(|vs| vs.collect()); let files: Option<Vec<&OsStr>> = self.matches.values_of_os("FILE").map(|vs| vs.collect());
if files.is_none() { 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<dyn Iterator<Item = _>> = match files { let files_or_none: Box<dyn Iterator<Item = _>> = match files {
Some(ref files) => Box::new(files.into_iter().map(|name| Some(*name))), 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) { for (input, name) in files_or_none.zip(filenames_or_none) {
if let Some(input) = input { if let Some(input) = input {
if input.to_str().unwrap_or_default() == "-" { 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 { } else {
let mut ofile = OrdinaryFile::from_path(input); let mut ofile = OrdinaryFile::from_path(input);
if let Some(path) = name { if let Some(path) = name {

View File

@ -3,7 +3,7 @@ pub use crate::inputfile::OrdinaryFile;
pub use crate::line_range::{HighlightedLineRanges, LineRange, LineRanges}; pub use crate::line_range::{HighlightedLineRanges, LineRange, LineRanges};
pub use crate::style::{StyleComponent, StyleComponents}; pub use crate::style::{StyleComponent, StyleComponents};
pub use crate::syntax_mapping::{MappingTarget, SyntaxMapping}; pub use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
pub use crate::wrap::OutputWrap; pub use crate::wrap::WrappingMode;
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
#[cfg(feature = "paging")] #[cfg(feature = "paging")]
@ -23,7 +23,7 @@ impl Default for PagingMode {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Config<'a> { pub struct Config<'a> {
/// List of files to print /// List of files to print
pub files: Vec<InputFile<'a>>, pub files: Vec<InputFile>,
/// The explicitly configured language, if any /// The explicitly configured language, if any
pub language: Option<&'a str>, pub language: Option<&'a str>,
@ -50,8 +50,8 @@ pub struct Config<'a> {
/// Style elements (grid, line numbers, ...) /// Style elements (grid, line numbers, ...)
pub style_components: StyleComponents, pub style_components: StyleComponents,
/// Text wrapping mode /// If and how text should be wrapped
pub output_wrap: OutputWrap, pub wrapping_mode: WrappingMode,
/// Pager or STDOUT /// Pager or STDOUT
#[cfg(feature = "paging")] #[cfg(feature = "paging")]

View File

@ -65,15 +65,15 @@ impl<'b> Controller<'b> {
Ok(mut reader) => { Ok(mut reader) => {
let result = if self.config.loop_through { let result = if self.config.loop_through {
let mut printer = SimplePrinter::new(); let mut printer = SimplePrinter::new();
self.print_file(reader, &mut printer, writer, *input_file) self.print_file(reader, &mut printer, writer, input_file)
} else { } else {
let mut printer = InteractivePrinter::new( let mut printer = InteractivePrinter::new(
&self.config, &self.config,
&self.assets, &self.assets,
*input_file, input_file,
&mut reader, &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 { if let Err(error) = result {
@ -92,7 +92,7 @@ impl<'b> Controller<'b> {
reader: InputFileReader, reader: InputFileReader,
printer: &mut P, printer: &mut P,
writer: &mut dyn Write, writer: &mut dyn Write,
input_file: InputFile<'a>, input_file: &InputFile,
) -> Result<()> { ) -> Result<()> {
if !reader.first_line.is_empty() || self.config.style_components.header() { if !reader.first_line.is_empty() || self.config.style_components.header() {
printer.print_header(writer, input_file)?; printer.print_header(writer, input_file)?;

View File

@ -1,4 +1,4 @@
use std::ffi::OsStr; use std::ffi::{OsStr, OsString};
use std::fs::File; use std::fs::File;
use std::io::{self, BufRead, BufReader}; use std::io::{self, BufRead, BufReader};
@ -52,42 +52,44 @@ impl<'a> InputFileReader<'a> {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct OrdinaryFile<'a> { pub struct OrdinaryFile {
path: &'a OsStr, path: OsString,
user_provided_path: Option<&'a OsStr>, user_provided_path: Option<OsString>,
} }
impl<'a> OrdinaryFile<'a> { impl OrdinaryFile {
pub fn from_path(path: &'a OsStr) -> OrdinaryFile<'a> { pub fn from_path(path: &OsStr) -> OrdinaryFile {
OrdinaryFile { OrdinaryFile {
path, path: path.to_os_string(),
user_provided_path: None, user_provided_path: None,
} }
} }
pub fn set_provided_path(&mut self, user_provided_path: &'a OsStr) { pub fn set_provided_path(&mut self, user_provided_path: &OsStr) {
self.user_provided_path = Some(user_provided_path); self.user_provided_path = Some(user_provided_path.to_os_string());
} }
pub(crate) fn provided_path(&self) -> &'a OsStr { pub(crate) fn provided_path<'a>(&'a self) -> &'a OsStr {
self.user_provided_path.unwrap_or_else(|| self.path) self.user_provided_path
.as_ref()
.unwrap_or_else(|| &self.path)
} }
} }
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum InputFile<'a> { pub enum InputFile {
StdIn(Option<&'a OsStr>), StdIn(Option<OsString>),
Ordinary(OrdinaryFile<'a>), Ordinary(OrdinaryFile),
ThemePreviewFile, ThemePreviewFile,
} }
impl<'a> InputFile<'a> { impl InputFile {
pub(crate) fn get_reader<R: BufRead + 'a>(&self, stdin: R) -> Result<InputFileReader> { pub(crate) fn get_reader<'a, R: BufRead + 'a>(&self, stdin: R) -> Result<InputFileReader<'a>> {
match self { match self {
InputFile::StdIn(_) => Ok(InputFileReader::new(stdin)), InputFile::StdIn(_) => Ok(InputFileReader::new(stdin)),
InputFile::Ordinary(ofile) => { 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))?; .map_err(|e| format!("'{}': {}", ofile.path.to_string_lossy(), e))?;
if file.metadata()?.is_dir() { if file.metadata()?.is_dir() {

View File

@ -25,3 +25,4 @@ pub use assets_metadata::AssetsMetadata;
pub use controller::Controller; pub use controller::Controller;
pub use pretty_printer::PrettyPrinter; pub use pretty_printer::PrettyPrinter;
pub use printer::{InteractivePrinter, Printer, SimplePrinter}; pub use printer::{InteractivePrinter, Printer, SimplePrinter};
pub use style::{StyleComponent, StyleComponents};

View File

@ -1,7 +1,7 @@
use std::ffi::OsStr; use std::ffi::OsStr;
use crate::{ use crate::{
config::{Config, InputFile, OrdinaryFile}, config::{Config, InputFile, OrdinaryFile, StyleComponents, WrappingMode},
errors::Result, errors::Result,
Controller, HighlightingAssets, 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 self.config
.files .files
.push(InputFile::Ordinary(OrdinaryFile::from_path(path))); .push(InputFile::Ordinary(OrdinaryFile::from_path(path)));
self self
} }
/// Add multiple files which should be pretty-printed
pub fn files<I, P>(&mut self, paths: I) -> &mut Self
where
I: IntoIterator<Item = P>,
P: AsRef<OsStr>,
{
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<usize>) -> &mut Self {
self.config.tab_width = tab_width.unwrap_or(0);
self
}
/// Whether or not the output should be colorized (default: true) /// Whether or not the output should be colorized (default: true)
pub fn colored_output(&mut self, yes: bool) -> &mut Self { pub fn colored_output(&mut self, yes: bool) -> &mut Self {
self.config.colored_output = yes; self.config.colored_output = yes;
self 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<bool> { pub fn run(&'a self) -> Result<bool> {
let controller = Controller::new(&self.config, &self.assets); let controller = Controller::new(&self.config, &self.assets);
controller.run() controller.run()

View File

@ -31,10 +31,10 @@ use crate::inputfile::{InputFile, InputFileReader};
use crate::line_range::RangeCheckResult; use crate::line_range::RangeCheckResult;
use crate::preprocessor::{expand_tabs, replace_nonprintable}; use crate::preprocessor::{expand_tabs, replace_nonprintable};
use crate::terminal::{as_terminal_escaped, to_ansi_color}; use crate::terminal::{as_terminal_escaped, to_ansi_color};
use crate::wrap::OutputWrap; use crate::wrap::WrappingMode;
pub trait Printer { 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_footer(&mut self, handle: &mut dyn Write) -> Result<()>;
fn print_snip(&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 { 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(()) Ok(())
} }
@ -101,7 +101,7 @@ impl<'a> InteractivePrinter<'a> {
pub fn new( pub fn new(
config: &'a Config, config: &'a Config,
assets: &'a HighlightingAssets, assets: &'a HighlightingAssets,
file: InputFile, file: &InputFile,
reader: &mut InputFileReader, reader: &mut InputFileReader,
) -> Self { ) -> Self {
let theme = assets.get_theme(&config.theme); let theme = assets.get_theme(&config.theme);
@ -230,7 +230,7 @@ impl<'a> InteractivePrinter<'a> {
} }
impl<'a> Printer for 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 !self.config.style_components.header() {
if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable { if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable {
let input = match file { let input = match file {
@ -415,7 +415,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
} }
// Line contents. // Line contents.
if self.config.output_wrap == OutputWrap::None { if self.config.wrapping_mode == WrappingMode::NoWrapping {
let true_color = self.config.true_color; let true_color = self.config.true_color;
let colored_output = self.config.colored_output; let colored_output = self.config.colored_output;
let italics = self.config.use_italic_text; let italics = self.config.use_italic_text;

View File

@ -1,11 +1,11 @@
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputWrap { pub enum WrappingMode {
Character, Character,
None, NoWrapping,
} }
impl Default for OutputWrap { impl Default for WrappingMode {
fn default() -> Self { fn default() -> Self {
OutputWrap::None WrappingMode::NoWrapping
} }
} }