mirror of
https://github.com/sharkdp/bat
synced 2026-08-07 19:31:42 +00:00
Move src/bin/* into src/bin/bat/
This will limit [[bin]] to *bat* only which will make: - `cargo run` works without specifying --bin - prevent `cargo build --bins` to produce multiple binaries (app,clap_app,...)
This commit is contained in:
committed by
David Peter
parent
710a1df4ff
commit
e981bd88c1
@@ -0,0 +1,335 @@
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::str::FromStr;
|
||||
|
||||
use atty::{self, Stream};
|
||||
|
||||
use crate::clap_app;
|
||||
use clap::ArgMatches;
|
||||
use wild;
|
||||
|
||||
use console::Term;
|
||||
|
||||
#[cfg(windows)]
|
||||
use ansi_term;
|
||||
|
||||
use crate::assets::BAT_THEME_DEFAULT;
|
||||
use crate::config::{get_args_from_config_file, get_args_from_env_var};
|
||||
use crate::errors::*;
|
||||
use crate::inputfile::InputFile;
|
||||
use crate::line_range::{LineRange, LineRanges};
|
||||
use crate::style::{OutputComponent, OutputComponents, OutputWrap};
|
||||
use crate::syntax_mapping::SyntaxMapping;
|
||||
use crate::util::transpose;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum PagingMode {
|
||||
Always,
|
||||
QuitIfOneScreen,
|
||||
Never,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Config<'a> {
|
||||
/// List of files to print
|
||||
pub files: Vec<InputFile<'a>>,
|
||||
|
||||
/// The explicitly configured language, if any
|
||||
pub language: Option<&'a str>,
|
||||
|
||||
/// Whether or not to show/replace non-printable characters like space, tab and newline.
|
||||
pub show_nonprintable: bool,
|
||||
|
||||
/// The character width of the terminal
|
||||
pub term_width: usize,
|
||||
|
||||
/// The width of tab characters.
|
||||
/// Currently, a value of 0 will cause tabs to be passed through without expanding them.
|
||||
pub tab_width: usize,
|
||||
|
||||
/// Whether or not to simply loop through all input (`cat` mode)
|
||||
pub loop_through: bool,
|
||||
|
||||
/// Whether or not the output should be colorized
|
||||
pub colored_output: bool,
|
||||
|
||||
/// Whether or not the output terminal supports true color
|
||||
pub true_color: bool,
|
||||
|
||||
/// Style elements (grid, line numbers, ...)
|
||||
pub output_components: OutputComponents,
|
||||
|
||||
/// Text wrapping mode
|
||||
pub output_wrap: OutputWrap,
|
||||
|
||||
/// Pager or STDOUT
|
||||
pub paging_mode: PagingMode,
|
||||
|
||||
/// Specifies the lines that should be printed
|
||||
pub line_ranges: LineRanges,
|
||||
|
||||
/// The syntax highlighting theme
|
||||
pub theme: String,
|
||||
|
||||
/// File extension/name mappings
|
||||
pub syntax_mapping: SyntaxMapping,
|
||||
|
||||
/// Command to start the pager
|
||||
pub pager: Option<&'a str>,
|
||||
|
||||
/// Whether or not to use ANSI italics
|
||||
pub use_italic_text: bool,
|
||||
|
||||
/// Lines to highlight
|
||||
pub highlight_lines: Vec<usize>,
|
||||
}
|
||||
|
||||
fn is_truecolor_terminal() -> bool {
|
||||
env::var("COLORTERM")
|
||||
.map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub matches: ArgMatches<'static>,
|
||||
interactive_output: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new() -> Result<Self> {
|
||||
#[cfg(windows)]
|
||||
let _ = ansi_term::enable_ansi_support();
|
||||
|
||||
let interactive_output = atty::is(Stream::Stdout);
|
||||
|
||||
Ok(App {
|
||||
matches: Self::matches(interactive_output)?,
|
||||
interactive_output,
|
||||
})
|
||||
}
|
||||
|
||||
fn matches(interactive_output: bool) -> Result<ArgMatches<'static>> {
|
||||
let args = if wild::args_os().nth(1) == Some("cache".into())
|
||||
|| wild::args_os().any(|arg| arg == "--no-config")
|
||||
{
|
||||
// Skip the arguments in bats config file
|
||||
|
||||
wild::args_os().collect::<Vec<_>>()
|
||||
} else {
|
||||
let mut cli_args = wild::args_os();
|
||||
|
||||
// Read arguments from bats config file
|
||||
let mut args = get_args_from_env_var()
|
||||
.unwrap_or_else(get_args_from_config_file)
|
||||
.chain_err(|| "Could not parse configuration file")?;
|
||||
|
||||
// Put the zero-th CLI argument (program name) first
|
||||
args.insert(0, cli_args.next().unwrap());
|
||||
|
||||
// .. and the rest at the end
|
||||
cli_args.for_each(|a| args.push(a));
|
||||
|
||||
args
|
||||
};
|
||||
|
||||
Ok(clap_app::build_app(interactive_output).get_matches_from(args))
|
||||
}
|
||||
|
||||
pub fn config(&self) -> Result<Config> {
|
||||
let files = self.files();
|
||||
let output_components = self.output_components()?;
|
||||
|
||||
let paging_mode = match self.matches.value_of("paging") {
|
||||
Some("always") => PagingMode::Always,
|
||||
Some("never") => PagingMode::Never,
|
||||
Some("auto") | _ => {
|
||||
if self.matches.occurrences_of("plain") > 1 {
|
||||
// If we have -pp as an option when in auto mode, the pager should be disabled.
|
||||
PagingMode::Never
|
||||
} else if files.contains(&InputFile::StdIn) {
|
||||
// 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
|
||||
// terminal.
|
||||
if self.interactive_output && !atty::is(Stream::Stdin) {
|
||||
PagingMode::QuitIfOneScreen
|
||||
} else {
|
||||
PagingMode::Never
|
||||
}
|
||||
} else if self.interactive_output {
|
||||
PagingMode::QuitIfOneScreen
|
||||
} else {
|
||||
PagingMode::Never
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut syntax_mapping = SyntaxMapping::new();
|
||||
|
||||
if let Some(values) = self.matches.values_of("map-syntax") {
|
||||
for from_to in values {
|
||||
let parts: Vec<_> = from_to.split(':').collect();
|
||||
|
||||
if parts.len() != 2 {
|
||||
return Err("Invalid syntax mapping. The format of the -m/--map-syntax option is 'from:to'.".into());
|
||||
}
|
||||
|
||||
syntax_mapping.insert(parts[0], parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Config {
|
||||
true_color: is_truecolor_terminal(),
|
||||
language: self.matches.value_of("language").or_else(|| {
|
||||
if self.matches.is_present("show-all") {
|
||||
Some("show-nonprintable")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
show_nonprintable: self.matches.is_present("show-all"),
|
||||
output_wrap: if !self.interactive_output {
|
||||
// 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
|
||||
} else {
|
||||
match self.matches.value_of("wrap") {
|
||||
Some("character") => OutputWrap::Character,
|
||||
Some("never") => OutputWrap::None,
|
||||
Some("auto") | _ => {
|
||||
if output_components.plain() {
|
||||
OutputWrap::None
|
||||
} else {
|
||||
OutputWrap::Character
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
colored_output: match self.matches.value_of("color") {
|
||||
Some("always") => true,
|
||||
Some("never") => false,
|
||||
Some("auto") | _ => self.interactive_output,
|
||||
},
|
||||
paging_mode,
|
||||
term_width: self
|
||||
.matches
|
||||
.value_of("terminal-width")
|
||||
.and_then(|w| {
|
||||
if w.starts_with('+') || w.starts_with('-') {
|
||||
// Treat argument as a delta to the current terminal width
|
||||
w.parse().ok().map(|delta: i16| {
|
||||
let old_width: u16 = Term::stdout().size().1;
|
||||
let new_width: i32 = i32::from(old_width) + i32::from(delta);
|
||||
|
||||
if new_width <= 0 {
|
||||
old_width as usize
|
||||
} else {
|
||||
new_width as usize
|
||||
}
|
||||
})
|
||||
} else {
|
||||
w.parse().ok()
|
||||
}
|
||||
})
|
||||
.unwrap_or(Term::stdout().size().1 as usize),
|
||||
loop_through: !(self.interactive_output
|
||||
|| self.matches.value_of("color") == Some("always")
|
||||
|| self.matches.value_of("decorations") == Some("always")),
|
||||
files,
|
||||
tab_width: self
|
||||
.matches
|
||||
.value_of("tabs")
|
||||
.map(String::from)
|
||||
.or_else(|| env::var("BAT_TABS").ok())
|
||||
.and_then(|t| t.parse().ok())
|
||||
.unwrap_or(
|
||||
if output_components.plain() && paging_mode == PagingMode::Never {
|
||||
0
|
||||
} else {
|
||||
4
|
||||
},
|
||||
),
|
||||
theme: self
|
||||
.matches
|
||||
.value_of("theme")
|
||||
.map(String::from)
|
||||
.or_else(|| env::var("BAT_THEME").ok())
|
||||
.unwrap_or_else(|| String::from(BAT_THEME_DEFAULT)),
|
||||
line_ranges: LineRanges::from(
|
||||
transpose(
|
||||
self.matches
|
||||
.values_of("line-range")
|
||||
.map(|vs| vs.map(LineRange::from).collect()),
|
||||
)?
|
||||
.unwrap_or_else(|| vec![]),
|
||||
),
|
||||
output_components,
|
||||
syntax_mapping,
|
||||
pager: self.matches.value_of("pager"),
|
||||
use_italic_text: match self.matches.value_of("italic-text") {
|
||||
Some("always") => true,
|
||||
_ => false,
|
||||
},
|
||||
highlight_lines: self
|
||||
.matches
|
||||
.values_of("highlight-line")
|
||||
.and_then(|ws| ws.map(|w| w.parse().ok()).collect())
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn files(&self) -> Vec<InputFile> {
|
||||
self.matches
|
||||
.values_of("FILE")
|
||||
.map(|values| {
|
||||
values
|
||||
.map(|filename| {
|
||||
if filename == "-" {
|
||||
InputFile::StdIn
|
||||
} else {
|
||||
InputFile::Ordinary(filename)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| vec![InputFile::StdIn])
|
||||
}
|
||||
|
||||
fn output_components(&self) -> Result<OutputComponents> {
|
||||
let matches = &self.matches;
|
||||
Ok(OutputComponents(
|
||||
if matches.value_of("decorations") == Some("never") {
|
||||
HashSet::new()
|
||||
} else if matches.is_present("number") {
|
||||
[OutputComponent::Numbers].iter().cloned().collect()
|
||||
} else if matches.is_present("plain") {
|
||||
[OutputComponent::Plain].iter().cloned().collect()
|
||||
} else {
|
||||
let env_style_components: Option<Vec<OutputComponent>> =
|
||||
transpose(env::var("BAT_STYLE").ok().map(|style_str| {
|
||||
style_str
|
||||
.split(',')
|
||||
.map(|x| OutputComponent::from_str(&x))
|
||||
.collect::<Result<Vec<OutputComponent>>>()
|
||||
}))?;
|
||||
|
||||
matches
|
||||
.value_of("style")
|
||||
.map(|styles| {
|
||||
styles
|
||||
.split(',')
|
||||
.map(|style| style.parse::<OutputComponent>())
|
||||
.filter_map(|style| style.ok())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.or(env_style_components)
|
||||
.unwrap_or_else(|| vec![OutputComponent::Full])
|
||||
.into_iter()
|
||||
.map(|style| style.components(self.interactive_output))
|
||||
.fold(HashSet::new(), |mut acc, components| {
|
||||
acc.extend(components.iter().cloned());
|
||||
acc
|
||||
})
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user