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.
/// 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::<Vec<_>>();
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");
}

View File

@ -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,
);

View File

@ -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<Vec<&OsStr>> = 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<dyn Iterator<Item = _>> = 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 {

View File

@ -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<InputFile<'a>>,
pub files: Vec<InputFile>,
/// 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")]

View File

@ -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)?;

View File

@ -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<OsString>,
}
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<OsString>),
Ordinary(OrdinaryFile),
ThemePreviewFile,
}
impl<'a> InputFile<'a> {
pub(crate) fn get_reader<R: BufRead + 'a>(&self, stdin: R) -> Result<InputFileReader> {
impl InputFile {
pub(crate) fn get_reader<'a, R: BufRead + 'a>(&self, stdin: R) -> Result<InputFileReader<'a>> {
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() {

View File

@ -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};

View File

@ -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<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)
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<bool> {
let controller = Controller::new(&self.config, &self.assets);
controller.run()

View File

@ -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;

View File

@ -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
}
}