1
0
mirror of https://github.com/sharkdp/bat.git synced 2025-01-18 20:11:03 +00:00

Moved user_provided_filename to be contained within OrdinaryFile struct

This commit is contained in:
Kyle Criddle 2020-04-04 18:49:55 -06:00 committed by David Peter
parent a3f8140fbe
commit 04fa84aea7
9 changed files with 143 additions and 127 deletions

View File

@ -1,7 +1,7 @@
/// 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::{
config::{Config, InputFile, StyleComponent, StyleComponents}, config::{Config, InputFile, OrdinaryFile, StyleComponent, StyleComponents},
Controller, HighlightingAssets, Controller, HighlightingAssets,
}; };
use console::Term; use console::Term;
@ -24,7 +24,10 @@ fn main() {
StyleComponent::Grid, StyleComponent::Grid,
StyleComponent::Numbers, StyleComponent::Numbers,
]), ]),
files: files.iter().map(|file| InputFile::Ordinary(file)).collect(), files: files
.iter()
.map(|file| InputFile::Ordinary(OrdinaryFile::new(file, None)))
.collect(),
..Default::default() ..Default::default()
}; };
let assets = HighlightingAssets::from_binary(); let assets = HighlightingAssets::from_binary();

View File

@ -1,6 +1,6 @@
/// A simple program that prints its own source code using the bat library /// A simple program that prints its own source code using the bat library
use bat::{ use bat::{
config::{Config, InputFile}, config::{Config, InputFile, OrdinaryFile},
Controller, HighlightingAssets, Controller, HighlightingAssets,
}; };
use std::ffi::OsStr; use std::ffi::OsStr;
@ -9,7 +9,10 @@ fn main() {
let path_to_this_file = OsStr::new(file!()); let path_to_this_file = OsStr::new(file!());
let config = Config { let config = Config {
files: vec![InputFile::Ordinary(path_to_this_file)], files: vec![InputFile::Ordinary(OrdinaryFile::new(
path_to_this_file,
None,
))],
colored_output: true, colored_output: true,
true_color: true, true_color: true,
..Default::default() ..Default::default()

View File

@ -177,14 +177,13 @@ impl HighlightingAssets {
&self, &self,
language: Option<&str>, language: Option<&str>,
file: InputFile, file: InputFile,
file_name: Option<&str>,
reader: &mut InputFileReader, reader: &mut InputFileReader,
mapping: &SyntaxMapping, mapping: &SyntaxMapping,
) -> &SyntaxReference { ) -> &SyntaxReference {
let syntax = match (language, file, file_name) { let syntax = match (language, file) {
(Some(language), _, _) => self.syntax_set.find_syntax_by_token(language), (Some(language), _) => self.syntax_set.find_syntax_by_token(language),
(None, InputFile::Ordinary(file), _) => { (None, InputFile::Ordinary(ofile)) => {
let path = Path::new(file); let path = Path::new(ofile.filename());
let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or(""); let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
let extension = path.extension().and_then(|x| x.to_str()).unwrap_or(""); let extension = path.extension().and_then(|x| x.to_str()).unwrap_or("");
@ -208,12 +207,12 @@ impl HighlightingAssets {
None => ext_syntax.or(line_syntax), None => ext_syntax.or(line_syntax),
} }
} }
(None, InputFile::StdIn, None) => String::from_utf8(reader.first_line.clone()) (None, InputFile::StdIn(None)) => String::from_utf8(reader.first_line.clone())
.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
.syntax_set .syntax_set
.find_syntax_by_extension(&file_name) .find_syntax_by_extension(file_name.to_str().unwrap())
.or_else(|| { .or_else(|| {
self.syntax_set.find_syntax_by_extension( self.syntax_set.find_syntax_by_extension(
Path::new(file_name) Path::new(file_name)
@ -225,7 +224,7 @@ impl HighlightingAssets {
.or(String::from_utf8(reader.first_line.clone()) .or(String::from_utf8(reader.first_line.clone())
.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))),
(_, InputFile::ThemePreviewFile, _) => self.syntax_set.find_syntax_by_name("Rust"), (_, InputFile::ThemePreviewFile) => self.syntax_set.find_syntax_by_name("Rust"),
}; };
syntax.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text()) syntax.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text())
@ -242,7 +241,7 @@ mod tests {
use tempdir::TempDir; use tempdir::TempDir;
use crate::assets::HighlightingAssets; use crate::assets::HighlightingAssets;
use crate::inputfile::InputFile; use crate::inputfile::{InputFile, OrdinaryFile};
use crate::syntax_mapping::{MappingTarget, SyntaxMapping}; use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
struct SyntaxDetectionTest<'a> { struct SyntaxDetectionTest<'a> {
@ -261,28 +260,17 @@ mod tests {
} }
} }
fn syntax_for_file_with_content( fn syntax_for_file_with_content(&self, file_name: &str, first_line: &str) -> String {
&self,
file_name: &str,
first_line: &str,
as_stdin: bool,
) -> String {
let file_path = self.temp_dir.path().join(file_name); let file_path = self.temp_dir.path().join(file_name);
{ {
let mut temp_file = File::create(&file_path).unwrap(); let mut temp_file = File::create(&file_path).unwrap();
writeln!(temp_file, "{}", first_line).unwrap(); writeln!(temp_file, "{}", first_line).unwrap();
} }
let input_file = InputFile::Ordinary(OsStr::new(&file_path)); let input_file = InputFile::Ordinary(OrdinaryFile::new(OsStr::new(&file_path), None));
let (file, file_name) = if as_stdin {
(InputFile::StdIn, Some(file_name))
} else {
(input_file, None)
};
let syntax = self.assets.get_syntax( let syntax = self.assets.get_syntax(
None, None,
file, input_file,
file_name,
&mut input_file.get_reader(&io::stdin()).unwrap(), &mut input_file.get_reader(&io::stdin()).unwrap(),
&self.syntax_mapping, &self.syntax_mapping,
); );
@ -291,7 +279,7 @@ mod tests {
} }
fn syntax_for_file(&self, file_name: &str) -> String { fn syntax_for_file(&self, file_name: &str) -> String {
self.syntax_for_file_with_content(file_name, "", false) self.syntax_for_file_with_content(file_name, "")
} }
} }
@ -325,15 +313,15 @@ mod tests {
let test = SyntaxDetectionTest::new(); let test = SyntaxDetectionTest::new();
assert_eq!( assert_eq!(
test.syntax_for_file_with_content("my_script", "#!/bin/bash", false), test.syntax_for_file_with_content("my_script", "#!/bin/bash"),
"Bourne Again Shell (bash)" "Bourne Again Shell (bash)"
); );
assert_eq!( assert_eq!(
test.syntax_for_file_with_content("build", "#!/bin/bash", false), test.syntax_for_file_with_content("build", "#!/bin/bash"),
"Bourne Again Shell (bash)" "Bourne Again Shell (bash)"
); );
assert_eq!( assert_eq!(
test.syntax_for_file_with_content("my_script", "<?php", false), test.syntax_for_file_with_content("my_script", "<?php"),
"PHP" "PHP"
); );
} }
@ -365,13 +353,10 @@ mod tests {
let test = SyntaxDetectionTest::new(); let test = SyntaxDetectionTest::new();
// from file extension // from file extension
assert_eq!( assert_eq!(test.syntax_for_file_with_content("test.cpp", ""), "C++");
test.syntax_for_file_with_content("test.cpp", "", true),
"C++"
);
// from first line (fallback) // from first line (fallback)
assert_eq!( assert_eq!(
test.syntax_for_file_with_content("my_script", "#!/bin/bash", true), test.syntax_for_file_with_content("my_script", "#!/bin/bash"),
"Bourne Again Shell (bash)" "Bourne Again Shell (bash)"
); );
} }

View File

@ -1,5 +1,6 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
use std::ffi::OsStr;
use std::str::FromStr; use std::str::FromStr;
use atty::{self, Stream}; use atty::{self, Stream};
@ -14,8 +15,8 @@ use console::Term;
use bat::{ use bat::{
config::{ config::{
Config, HighlightedLineRanges, InputFile, LineRange, LineRanges, MappingTarget, OutputWrap, Config, HighlightedLineRanges, InputFile, LineRange, LineRanges, MappingTarget,
PagingMode, StyleComponent, StyleComponents, SyntaxMapping, OrdinaryFile, OutputWrap, PagingMode, StyleComponent, StyleComponents, SyntaxMapping,
}, },
errors::*, errors::*,
HighlightingAssets, HighlightingAssets,
@ -73,7 +74,7 @@ impl App {
} }
pub fn config(&self) -> Result<Config> { pub fn config(&self) -> Result<Config> {
let files = self.files(); let files = self.files()?;
let style_components = self.style_components()?; let style_components = self.style_components()?;
let paging_mode = match self.matches.value_of("paging") { let paging_mode = match self.matches.value_of("paging") {
@ -83,7 +84,7 @@ impl App {
if self.matches.occurrences_of("plain") > 1 { if self.matches.occurrences_of("plain") > 1 {
// If we have -pp as an option when in auto mode, the pager should be disabled. // If we have -pp as an option when in auto mode, the pager should be disabled.
PagingMode::Never PagingMode::Never
} else if files.contains(&InputFile::StdIn) { } else if files.contains(&InputFile::StdIn(None)) {
// If we are reading from stdin, only enable paging if we write to an // If we are reading from stdin, only enable paging if we write to an
// interactive terminal and if we do not *read* from an interactive // interactive terminal and if we do not *read* from an interactive
// terminal. // terminal.
@ -132,13 +133,6 @@ impl App {
} }
}); });
match self.matches.values_of("file-name") {
Some(ref filenames) if filenames.len() != files.len() => {
return Err(format!("{} {}", filenames.len(), files.len()).into());
}
_ => {}
}
Ok(Config { Ok(Config {
true_color: is_truecolor_terminal(), true_color: is_truecolor_terminal(),
language: self.matches.value_of("language").or_else(|| { language: self.matches.value_of("language").or_else(|| {
@ -225,28 +219,59 @@ impl App {
.map(LineRanges::from) .map(LineRanges::from)
.map(|lr| HighlightedLineRanges(lr)) .map(|lr| HighlightedLineRanges(lr))
.unwrap_or_default(), .unwrap_or_default(),
filenames: self
.matches
.values_of("file-name")
.map(|values| values.collect()),
}) })
} }
fn files(&self) -> Vec<InputFile> { fn files(&self) -> Result<Vec<InputFile>> {
self.matches // verify equal length of file-names and input FILEs
match self.matches.values_of("file-name") {
Some(filenames)
if self.matches.values_of_os("FILE").is_some()
&& filenames.len() != self.matches.values_of_os("FILE").unwrap().len() =>
{
return Err("Must be one file name per input type.".into());
}
_ => {}
}
let filenames: Option<Vec<&str>> = self
.matches
.values_of("file-name")
.map(|values| values.collect());
let mut filenames_or_none: Box<dyn Iterator<Item = _>> = match filenames {
Some(ref filenames) => {
Box::new(filenames.into_iter().map(|name| Some(OsStr::new(*name))))
}
None => Box::new(std::iter::repeat(None)),
};
let files: Option<Vec<&str>> = self
.matches
.values_of_os("FILE") .values_of_os("FILE")
.map(|values| { .map(|values| values.map(|fname| fname.to_str()).collect())
values .unwrap_or(None);
.map(|filename| {
if filename == "-" { if files.is_none() {
InputFile::StdIn return Ok(vec![InputFile::StdIn(filenames_or_none.nth(0).unwrap())]);
} else { }
InputFile::Ordinary(filename) let files_or_none: Box<dyn Iterator<Item = _>> = match files {
} Some(ref files) => Box::new(files.into_iter().map(|name| Some(OsStr::new(*name)))),
}) None => Box::new(std::iter::repeat(None)),
.collect() };
})
.unwrap_or_else(|| vec![InputFile::StdIn]) let mut file_input = Vec::new();
for (input, name) in files_or_none.zip(filenames_or_none) {
match input {
Some(input) => {
if input.to_str().unwrap() == "-" {
file_input.push(InputFile::StdIn(name));
} else {
file_input.push(InputFile::Ordinary(OrdinaryFile::new(input, name)))
}
}
None => {}
}
}
return Ok(file_input);
} }
fn style_components(&self) -> Result<StyleComponents> { fn style_components(&self) -> Result<StyleComponents> {

View File

@ -26,7 +26,7 @@ use bat::Controller;
use directories::PROJECT_DIRS; use directories::PROJECT_DIRS;
use bat::{ use bat::{
config::{Config, InputFile, StyleComponent, StyleComponents}, config::{Config, InputFile, OrdinaryFile, StyleComponent, StyleComponents},
errors::*, errors::*,
HighlightingAssets, HighlightingAssets,
}; };
@ -167,7 +167,10 @@ fn run() -> Result<bool> {
Ok(true) Ok(true)
} else { } else {
let mut config = app.config()?; let mut config = app.config()?;
config.files = vec![InputFile::Ordinary(OsStr::new("cache"))]; config.files = vec![InputFile::Ordinary(OrdinaryFile::new(
OsStr::new("cache"),
None,
))];
run_controller(&config) run_controller(&config)
} }

View File

@ -1,4 +1,5 @@
pub use crate::inputfile::InputFile; pub use crate::inputfile::InputFile;
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};
@ -73,9 +74,8 @@ pub struct Config<'a> {
/// Ranges of lines which should be highlighted with a special background color /// Ranges of lines which should be highlighted with a special background color
pub highlighted_lines: HighlightedLineRanges, pub highlighted_lines: HighlightedLineRanges,
///// Names of files to display when printing
/// Names of files to display when printing // pub filenames: Option<Vec<&'a str>>,
pub filenames: Option<Vec<&'a str>>,
} }
#[test] #[test]

View File

@ -35,8 +35,8 @@ impl<'b> Controller<'b> {
let mut paging_mode = self.config.paging_mode; let mut paging_mode = self.config.paging_mode;
if self.config.paging_mode != PagingMode::Never { if self.config.paging_mode != PagingMode::Never {
let call_pager = self.config.files.iter().any(|file| { let call_pager = self.config.files.iter().any(|file| {
if let InputFile::Ordinary(path) = file { if let InputFile::Ordinary(ofile) = file {
return Path::new(path).exists(); return Path::new(ofile.filename()).exists();
} else { } else {
return true; return true;
} }
@ -58,12 +58,7 @@ impl<'b> Controller<'b> {
let stdin = io::stdin(); let stdin = io::stdin();
let filenames: Box<dyn Iterator<Item = _>> = match self.config.filenames { for input_file in self.config.files.iter() {
Some(ref filenames) => Box::new(filenames.into_iter().map(|name| Some(*name))),
None => Box::new(std::iter::repeat(None)),
};
for (input_file, file_name) in self.config.files.iter().zip(filenames) {
match input_file.get_reader(&stdin) { match input_file.get_reader(&stdin) {
Err(error) => { Err(error) => {
handle_error(&error); handle_error(&error);
@ -72,16 +67,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, file_name) 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,
file_name,
&mut reader, &mut reader,
); );
self.print_file(reader, &mut printer, writer, *input_file, file_name) self.print_file(reader, &mut printer, writer, *input_file)
}; };
if let Err(error) = result { if let Err(error) = result {
@ -101,10 +95,9 @@ impl<'b> Controller<'b> {
printer: &mut P, printer: &mut P,
writer: &mut dyn Write, writer: &mut dyn Write,
input_file: InputFile<'a>, input_file: InputFile<'a>,
file_name: Option<&str>,
) -> 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, file_name)?; printer.print_header(writer, input_file)?;
} }
if !reader.first_line.is_empty() { if !reader.first_line.is_empty() {

View File

@ -52,23 +52,44 @@ impl<'a> InputFileReader<'a> {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct OrdinaryFile<'a> {
filename: &'a OsStr,
user_provided_name: Option<&'a OsStr>,
}
impl<'a> OrdinaryFile<'a> {
pub fn new(filename: &'a OsStr, user_provided_name: Option<&'a OsStr>) -> OrdinaryFile<'a> {
OrdinaryFile {
filename: filename,
user_provided_name: user_provided_name,
}
}
pub fn filename(&self) -> &'a OsStr {
self.user_provided_name.unwrap_or_else(|| self.filename)
}
}
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum InputFile<'a> { pub enum InputFile<'a> {
StdIn, StdIn(Option<&'a OsStr>),
Ordinary(&'a OsStr), Ordinary(OrdinaryFile<'a>),
ThemePreviewFile, ThemePreviewFile,
} }
impl<'a> InputFile<'a> { impl<'a> InputFile<'a> {
pub(crate) fn get_reader(&self, stdin: &'a io::Stdin) -> Result<InputFileReader> { pub(crate) fn get_reader(&self, stdin: &'a io::Stdin) -> Result<InputFileReader> {
match self { match self {
InputFile::StdIn => Ok(InputFileReader::new(stdin.lock())), InputFile::StdIn(_) => Ok(InputFileReader::new(stdin.lock())),
InputFile::Ordinary(filename) => { InputFile::Ordinary(ofile) => {
let file = File::open(filename) let file = File::open(ofile.filename)
.map_err(|e| format!("'{}': {}", filename.to_string_lossy(), e))?; .map_err(|e| format!("'{}': {}", ofile.filename.to_string_lossy(), e))?;
if file.metadata()?.is_dir() { if file.metadata()?.is_dir() {
return Err(format!("'{}' is a directory.", filename.to_string_lossy()).into()); return Err(
format!("'{}' is a directory.", ofile.filename.to_string_lossy()).into(),
);
} }
Ok(InputFileReader::new(BufReader::new(file))) Ok(InputFileReader::new(BufReader::new(file)))

View File

@ -34,12 +34,7 @@ use crate::terminal::{as_terminal_escaped, to_ansi_color};
use crate::wrap::OutputWrap; use crate::wrap::OutputWrap;
pub trait Printer { pub trait Printer {
fn print_header( fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()>;
&mut self,
handle: &mut dyn Write,
file: InputFile,
file_name: Option<&str>,
) -> 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<()>;
@ -62,12 +57,7 @@ impl SimplePrinter {
} }
impl Printer for SimplePrinter { impl Printer for SimplePrinter {
fn print_header( fn print_header(&mut self, _handle: &mut dyn Write, _file: InputFile) -> Result<()> {
&mut self,
_handle: &mut dyn Write,
_file: InputFile,
_file_name: Option<&str>,
) -> Result<()> {
Ok(()) Ok(())
} }
@ -112,7 +102,6 @@ impl<'a> InteractivePrinter<'a> {
config: &'a Config, config: &'a Config,
assets: &'a HighlightingAssets, assets: &'a HighlightingAssets,
file: InputFile, file: InputFile,
file_name: Option<&str>,
reader: &mut InputFileReader, reader: &mut InputFileReader,
) -> Self { ) -> Self {
let theme = assets.get_theme(&config.theme); let theme = assets.get_theme(&config.theme);
@ -171,20 +160,14 @@ impl<'a> InteractivePrinter<'a> {
#[cfg(feature = "git")] #[cfg(feature = "git")]
{ {
if config.style_components.changes() { if config.style_components.changes() {
if let InputFile::Ordinary(filename) = file { if let InputFile::Ordinary(ofile) = file {
line_changes = get_git_diff(filename); line_changes = get_git_diff(ofile.filename());
} }
} }
} }
// Determine the type of syntax for highlighting // Determine the type of syntax for highlighting
let syntax = assets.get_syntax( let syntax = assets.get_syntax(config.language, file, reader, &config.syntax_mapping);
config.language,
file,
file_name,
reader,
&config.syntax_mapping,
);
Some(HighlightLines::new(syntax, theme)) Some(HighlightLines::new(syntax, theme))
}; };
@ -247,20 +230,16 @@ impl<'a> InteractivePrinter<'a> {
} }
impl<'a> Printer for InteractivePrinter<'a> { impl<'a> Printer for InteractivePrinter<'a> {
fn print_header( fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()> {
&mut self,
handle: &mut dyn Write,
file: InputFile,
file_name: Option<&str>,
) -> 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 {
InputFile::Ordinary(filename) => format!( InputFile::Ordinary(ofile) => {
"file '{}'", format!("file '{}'", &ofile.filename().to_string_lossy())
file_name.unwrap_or(&filename.to_string_lossy()) }
), InputFile::StdIn(Some(name)) => name.to_str().unwrap().to_string(),
_ => file_name.unwrap_or("STDIN").to_owned(), InputFile::StdIn(None) => "STDIN".to_owned(),
InputFile::ThemePreviewFile => "".to_owned(),
}; };
writeln!( writeln!(
@ -295,11 +274,15 @@ impl<'a> Printer for InteractivePrinter<'a> {
} }
let (prefix, name) = match file { let (prefix, name) = match file {
InputFile::Ordinary(filename) => ( InputFile::Ordinary(ofile) => (
"File: ", "File: ",
Cow::from(file_name.unwrap_or(&filename.to_string_lossy()).to_owned()), Cow::from(ofile.filename().to_string_lossy().to_owned()),
), ),
_ => ("File: ", Cow::from(file_name.unwrap_or("STDIN").to_owned())), InputFile::StdIn(Some(name)) => {
("File: ", Cow::from(name.to_string_lossy().to_owned()))
}
InputFile::StdIn(None) => ("File: ", Cow::from("STDIN".to_owned())),
InputFile::ThemePreviewFile => ("", Cow::from("")),
}; };
let mode = match self.content_type { let mode = match self.content_type {