mirror of
https://github.com/sharkdp/bat.git
synced 2025-01-31 10:11:07 +00:00
Split app to separate module
This commit is contained in:
parent
ccf88fd5d8
commit
64a9341b73
194
src/app.rs
Normal file
194
src/app.rs
Normal file
@ -0,0 +1,194 @@
|
||||
use atty::{self, Stream};
|
||||
use clap::{App as ClapApp, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand};
|
||||
use console::Term;
|
||||
use errors::*;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use {OutputComponent, OutputComponents};
|
||||
|
||||
pub struct App {
|
||||
pub matches: ArgMatches<'static>,
|
||||
interactive_output: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new() -> Self {
|
||||
let interactive_output = atty::is(Stream::Stdout);
|
||||
|
||||
App {
|
||||
matches: Self::matches(interactive_output),
|
||||
interactive_output,
|
||||
}
|
||||
}
|
||||
|
||||
fn matches(interactive_output: bool) -> ArgMatches<'static> {
|
||||
let clap_color_setting = if interactive_output {
|
||||
AppSettings::ColoredHelp
|
||||
} else {
|
||||
AppSettings::ColorNever
|
||||
};
|
||||
|
||||
ClapApp::new(crate_name!())
|
||||
.version(crate_version!())
|
||||
.global_setting(clap_color_setting)
|
||||
.global_setting(AppSettings::DeriveDisplayOrder)
|
||||
.global_setting(AppSettings::UnifiedHelpMessage)
|
||||
.global_setting(AppSettings::NextLineHelp)
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.setting(AppSettings::ArgsNegateSubcommands)
|
||||
.setting(AppSettings::DisableHelpSubcommand)
|
||||
.setting(AppSettings::VersionlessSubcommands)
|
||||
.max_term_width(90)
|
||||
.about(crate_description!())
|
||||
.arg(
|
||||
Arg::with_name("language")
|
||||
.short("l")
|
||||
.long("language")
|
||||
.help("Set the language for highlighting")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("FILE")
|
||||
.help("File(s) to print")
|
||||
.multiple(true)
|
||||
.empty_values(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("style")
|
||||
.long("style")
|
||||
.use_delimiter(true)
|
||||
.takes_value(true)
|
||||
.possible_values(&[
|
||||
"auto", "full", "plain", "changes", "header", "grid", "numbers",
|
||||
])
|
||||
.default_value("auto")
|
||||
.help("Additional info to display along with content"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("color")
|
||||
.long("color")
|
||||
.takes_value(true)
|
||||
.possible_values(&["auto", "never", "always"])
|
||||
.default_value("auto")
|
||||
.help("When to use colors"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("paging")
|
||||
.long("paging")
|
||||
.takes_value(true)
|
||||
.possible_values(&["auto", "never", "always"])
|
||||
.default_value("auto")
|
||||
.help("When to use the pager"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("list-languages")
|
||||
.long("list-languages")
|
||||
.help("Displays supported languages"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("cache")
|
||||
.about("Modify the syntax-definition and theme cache")
|
||||
.arg(
|
||||
Arg::with_name("init")
|
||||
.long("init")
|
||||
.short("i")
|
||||
.help("Initialize the cache by loading from the config dir"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("clear")
|
||||
.long("clear")
|
||||
.short("c")
|
||||
.help("Reset the cache"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("config-dir")
|
||||
.long("config-dir")
|
||||
.short("d")
|
||||
.help("Show the configuration directory"),
|
||||
)
|
||||
.group(
|
||||
ArgGroup::with_name("cache-actions")
|
||||
.args(&["init", "clear", "config-dir"])
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.help_message("Print this help message.")
|
||||
.version_message("Show version information.")
|
||||
.get_matches()
|
||||
}
|
||||
|
||||
pub fn config(&self) -> Result<Config> {
|
||||
let files = self.files();
|
||||
|
||||
Ok(Config {
|
||||
true_color: is_truecolor_terminal(),
|
||||
output_components: self.output_components()?,
|
||||
language: self.matches.value_of("language"),
|
||||
colored_output: match self.matches.value_of("color") {
|
||||
Some("always") => true,
|
||||
Some("never") => false,
|
||||
Some("auto") | _ => self.interactive_output,
|
||||
},
|
||||
paging: match self.matches.value_of("paging") {
|
||||
Some("always") => true,
|
||||
Some("never") => false,
|
||||
Some("auto") | _ => if files.contains(&None) {
|
||||
// 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.
|
||||
self.interactive_output && !atty::is(Stream::Stdin)
|
||||
} else {
|
||||
self.interactive_output
|
||||
},
|
||||
},
|
||||
term_width: Term::stdout().size().1 as usize,
|
||||
files,
|
||||
})
|
||||
}
|
||||
|
||||
fn files(&self) -> Vec<Option<&str>> {
|
||||
self.matches
|
||||
.values_of("FILE")
|
||||
.map(|values| {
|
||||
values
|
||||
.map(|filename| {
|
||||
if filename == "-" {
|
||||
None
|
||||
} else {
|
||||
Some(filename)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| vec![None]) // read from stdin (None) if no args are given
|
||||
}
|
||||
|
||||
fn output_components(&self) -> Result<OutputComponents> {
|
||||
let matches = &self.matches;
|
||||
Ok(OutputComponents(
|
||||
values_t!(matches.values_of("style"), OutputComponent)?
|
||||
.into_iter()
|
||||
.map(|style| style.components(self.interactive_output))
|
||||
.fold(HashSet::new(), |mut acc, components| {
|
||||
acc.extend(components.iter().cloned());
|
||||
acc
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Config<'a> {
|
||||
pub true_color: bool,
|
||||
pub output_components: OutputComponents,
|
||||
pub language: Option<&'a str>,
|
||||
pub colored_output: bool,
|
||||
pub paging: bool,
|
||||
pub term_width: usize,
|
||||
pub files: Vec<Option<&'a str>>,
|
||||
}
|
||||
|
||||
fn is_truecolor_terminal() -> bool {
|
||||
env::var("COLORTERM")
|
||||
.map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
|
||||
.unwrap_or(false)
|
||||
}
|
172
src/main.rs
172
src/main.rs
@ -17,13 +17,13 @@ extern crate directories;
|
||||
extern crate git2;
|
||||
extern crate syntect;
|
||||
|
||||
mod app;
|
||||
mod assets;
|
||||
mod diff;
|
||||
mod printer;
|
||||
mod terminal;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, BufRead, BufReader, Write};
|
||||
use std::process::{self, Child, Command, Stdio};
|
||||
@ -34,13 +34,12 @@ use std::os::unix::fs::FileTypeExt;
|
||||
|
||||
use ansi_term::Colour::{Fixed, Green, Red, White, Yellow};
|
||||
use ansi_term::Style;
|
||||
use atty::Stream;
|
||||
use clap::{App, AppSettings, Arg, ArgGroup, SubCommand};
|
||||
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::Theme;
|
||||
use syntect::parsing::SyntaxSet;
|
||||
|
||||
use app::{App, Config};
|
||||
use assets::{config_dir, syntax_set_path, theme_set_path, HighlightingAssets};
|
||||
use diff::get_git_diff;
|
||||
use printer::Printer;
|
||||
@ -215,7 +214,7 @@ impl Colors {
|
||||
}
|
||||
|
||||
fn print_file(
|
||||
options: &Options,
|
||||
config: &Config,
|
||||
theme: &Theme,
|
||||
syntax_set: &SyntaxSet,
|
||||
printer: &mut Printer,
|
||||
@ -228,7 +227,7 @@ fn print_file(
|
||||
Some(filename) => Box::new(BufReader::new(File::open(filename)?)),
|
||||
};
|
||||
|
||||
let syntax = match (options.language, filename) {
|
||||
let syntax = match (config.language, filename) {
|
||||
(Some(language), _) => syntax_set.find_syntax_by_token(language),
|
||||
(None, Some(filename)) => {
|
||||
#[cfg(not(unix))]
|
||||
@ -292,110 +291,10 @@ fn get_output_type(paging: bool) -> OutputType {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_truecolor_terminal() -> bool {
|
||||
env::var("COLORTERM")
|
||||
.map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn run() -> Result<()> {
|
||||
let interactive_terminal = atty::is(Stream::Stdout);
|
||||
let app = App::new();
|
||||
|
||||
let clap_color_setting = if interactive_terminal {
|
||||
AppSettings::ColoredHelp
|
||||
} else {
|
||||
AppSettings::ColorNever
|
||||
};
|
||||
|
||||
let app_matches = App::new(crate_name!())
|
||||
.version(crate_version!())
|
||||
.global_setting(clap_color_setting)
|
||||
.global_setting(AppSettings::DeriveDisplayOrder)
|
||||
.global_setting(AppSettings::UnifiedHelpMessage)
|
||||
.global_setting(AppSettings::NextLineHelp)
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.setting(AppSettings::ArgsNegateSubcommands)
|
||||
.setting(AppSettings::DisableHelpSubcommand)
|
||||
.setting(AppSettings::VersionlessSubcommands)
|
||||
.max_term_width(90)
|
||||
.about(crate_description!())
|
||||
.arg(
|
||||
Arg::with_name("language")
|
||||
.short("l")
|
||||
.long("language")
|
||||
.help("Set the language for highlighting")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("FILE")
|
||||
.help("File(s) to print")
|
||||
.multiple(true)
|
||||
.empty_values(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("style")
|
||||
.long("style")
|
||||
.use_delimiter(true)
|
||||
.takes_value(true)
|
||||
.possible_values(&[
|
||||
"auto", "full", "plain", "changes", "header", "grid", "numbers",
|
||||
])
|
||||
.default_value("auto")
|
||||
.help("Additional info to display along with content"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("color")
|
||||
.long("color")
|
||||
.takes_value(true)
|
||||
.possible_values(&["auto", "never", "always"])
|
||||
.default_value("auto")
|
||||
.help("When to use colors"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("paging")
|
||||
.long("paging")
|
||||
.takes_value(true)
|
||||
.possible_values(&["auto", "never", "always"])
|
||||
.default_value("auto")
|
||||
.help("When to use the pager"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("list-languages")
|
||||
.long("list-languages")
|
||||
.help("Displays supported languages"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("cache")
|
||||
.about("Modify the syntax-definition and theme cache")
|
||||
.arg(
|
||||
Arg::with_name("init")
|
||||
.long("init")
|
||||
.short("i")
|
||||
.help("Initialize the cache by loading from the config dir"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("clear")
|
||||
.long("clear")
|
||||
.short("c")
|
||||
.help("Reset the cache"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("config-dir")
|
||||
.long("config-dir")
|
||||
.short("d")
|
||||
.help("Show the configuration directory"),
|
||||
)
|
||||
.group(
|
||||
ArgGroup::with_name("cache-actions")
|
||||
.args(&["init", "clear", "config-dir"])
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.help_message("Print this help message.")
|
||||
.version_message("Show version information.")
|
||||
.get_matches();
|
||||
|
||||
match app_matches.subcommand() {
|
||||
match app.matches.subcommand() {
|
||||
("cache", Some(cache_matches)) => {
|
||||
if cache_matches.is_present("init") {
|
||||
let assets = HighlightingAssets::from_files()?;
|
||||
@ -413,57 +312,12 @@ fn run() -> Result<()> {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let files: Vec<Option<&str>> = app_matches
|
||||
.values_of("FILE")
|
||||
.map(|values| {
|
||||
values
|
||||
.map(|filename| {
|
||||
if filename == "-" {
|
||||
None
|
||||
} else {
|
||||
Some(filename)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| vec![None]); // read from stdin (None) if no args are given
|
||||
|
||||
let output_components = values_t!(app_matches.values_of("style"), OutputComponent)?
|
||||
.into_iter()
|
||||
.map(|style| style.components(interactive_terminal))
|
||||
.fold(HashSet::new(), |mut acc, components| {
|
||||
acc.extend(components.iter().cloned());
|
||||
acc
|
||||
});
|
||||
|
||||
let options = Options {
|
||||
true_color: is_truecolor_terminal(),
|
||||
output_components: OutputComponents(output_components),
|
||||
language: app_matches.value_of("language"),
|
||||
colored_output: match app_matches.value_of("color") {
|
||||
Some("always") => true,
|
||||
Some("never") => false,
|
||||
_ => interactive_terminal,
|
||||
},
|
||||
paging: match app_matches.value_of("paging") {
|
||||
Some("always") => true,
|
||||
Some("never") => false,
|
||||
Some("auto") | _ => if files.contains(&None) {
|
||||
// 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.
|
||||
interactive_terminal && !atty::is(Stream::Stdin)
|
||||
} else {
|
||||
interactive_terminal
|
||||
},
|
||||
},
|
||||
term_width: console::Term::stdout().size().1 as usize,
|
||||
};
|
||||
let config = app.config()?;
|
||||
|
||||
let assets = HighlightingAssets::new();
|
||||
let theme = assets.default_theme()?;
|
||||
|
||||
if app_matches.is_present("list-languages") {
|
||||
if app.matches.is_present("list-languages") {
|
||||
let languages = assets.syntax_set.syntaxes();
|
||||
|
||||
let longest = languages
|
||||
@ -481,7 +335,7 @@ fn run() -> Result<()> {
|
||||
print!("{:width$}{}", lang.name, separator, width = longest);
|
||||
|
||||
// Line-wrapping for the possible file extension overflow.
|
||||
let desired_width = options.term_width - longest - separator.len();
|
||||
let desired_width = config.term_width - longest - separator.len();
|
||||
// Number of characters on this line so far, wrap before `desired_width`
|
||||
let mut num_chars = 0;
|
||||
|
||||
@ -507,14 +361,14 @@ fn run() -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut output_type = get_output_type(options.paging);
|
||||
let mut output_type = get_output_type(config.paging);
|
||||
let handle = output_type.handle()?;
|
||||
let mut printer = Printer::new(handle, &options);
|
||||
let mut printer = Printer::new(handle, &config);
|
||||
|
||||
for file in files {
|
||||
for file in &config.files {
|
||||
printer.line_changes = file.and_then(|filename| get_git_diff(filename));
|
||||
|
||||
print_file(&options, theme, &assets.syntax_set, &mut printer, file)?;
|
||||
print_file(&config, theme, &assets.syntax_set, &mut printer, *file)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,24 @@
|
||||
use ansi_term::Style;
|
||||
use app::Config;
|
||||
use diff::{LineChange, LineChanges};
|
||||
use errors::*;
|
||||
use std::io::Write;
|
||||
use syntect::highlighting;
|
||||
use terminal::as_terminal_escaped;
|
||||
use {Colors, Options};
|
||||
use Colors;
|
||||
|
||||
const PANEL_WIDTH: usize = 7;
|
||||
|
||||
pub struct Printer<'a> {
|
||||
handle: &'a mut Write,
|
||||
colors: Colors,
|
||||
options: &'a Options<'a>,
|
||||
config: &'a Config<'a>,
|
||||
pub line_changes: Option<LineChanges>,
|
||||
}
|
||||
|
||||
impl<'a> Printer<'a> {
|
||||
pub fn new(handle: &'a mut Write, options: &'a Options) -> Self {
|
||||
let colors = if options.colored_output {
|
||||
pub fn new(handle: &'a mut Write, config: &'a Config) -> Self {
|
||||
let colors = if config.colored_output {
|
||||
Colors::colored()
|
||||
} else {
|
||||
Colors::plain()
|
||||
@ -26,17 +27,17 @@ impl<'a> Printer<'a> {
|
||||
Printer {
|
||||
handle,
|
||||
colors,
|
||||
options,
|
||||
config,
|
||||
line_changes: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_header(&mut self, filename: Option<&str>) -> Result<()> {
|
||||
if !self.options.output_components.header() {
|
||||
if !self.config.output_components.header() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.options.output_components.grid() {
|
||||
if self.config.output_components.grid() {
|
||||
self.print_horizontal_line('┬')?;
|
||||
|
||||
write!(
|
||||
@ -54,7 +55,7 @@ impl<'a> Printer<'a> {
|
||||
self.colors.filename.paint(filename.unwrap_or("STDIN"))
|
||||
)?;
|
||||
|
||||
if self.options.output_components.grid() {
|
||||
if self.config.output_components.grid() {
|
||||
self.print_horizontal_line('┼')?;
|
||||
}
|
||||
|
||||
@ -62,7 +63,7 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
|
||||
pub fn print_footer(&mut self) -> Result<()> {
|
||||
if self.options.output_components.grid() {
|
||||
if self.config.output_components.grid() {
|
||||
self.print_horizontal_line('┴')
|
||||
} else {
|
||||
Ok(())
|
||||
@ -80,12 +81,12 @@ impl<'a> Printer<'a> {
|
||||
self.print_line_border(),
|
||||
Some(as_terminal_escaped(
|
||||
®ions,
|
||||
self.options.true_color,
|
||||
self.options.colored_output,
|
||||
self.config.true_color,
|
||||
self.config.colored_output,
|
||||
)),
|
||||
];
|
||||
|
||||
let grid_requested = self.options.output_components.grid();
|
||||
let grid_requested = self.config.output_components.grid();
|
||||
write!(
|
||||
self.handle,
|
||||
"{}",
|
||||
@ -104,14 +105,14 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
|
||||
fn print_line_number(&self, line_number: usize) -> Option<String> {
|
||||
if self.options.output_components.numbers() {
|
||||
if self.config.output_components.numbers() {
|
||||
Some(
|
||||
self.colors
|
||||
.line_number
|
||||
.paint(format!("{:4}", line_number))
|
||||
.to_string(),
|
||||
)
|
||||
} else if self.options.output_components.grid() {
|
||||
} else if self.config.output_components.grid() {
|
||||
Some(" ".to_owned())
|
||||
} else {
|
||||
None
|
||||
@ -119,7 +120,7 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
|
||||
fn print_git_marker(&self, line_number: usize) -> Option<String> {
|
||||
if self.options.output_components.changes() {
|
||||
if self.config.output_components.changes() {
|
||||
Some(
|
||||
if let Some(ref changes) = self.line_changes {
|
||||
match changes.get(&(line_number as u32)) {
|
||||
@ -133,7 +134,7 @@ impl<'a> Printer<'a> {
|
||||
Style::default().paint(" ")
|
||||
}.to_string(),
|
||||
)
|
||||
} else if self.options.output_components.grid() {
|
||||
} else if self.config.output_components.grid() {
|
||||
Some(" ".to_owned())
|
||||
} else {
|
||||
None
|
||||
@ -141,7 +142,7 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
|
||||
fn print_line_border(&self) -> Option<String> {
|
||||
if self.options.output_components.grid() {
|
||||
if self.config.output_components.grid() {
|
||||
Some(self.colors.grid.paint("│").to_string())
|
||||
} else {
|
||||
None
|
||||
@ -149,7 +150,7 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
|
||||
fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> {
|
||||
let hline = "─".repeat(self.options.term_width - (PANEL_WIDTH + 1));
|
||||
let hline = "─".repeat(self.config.term_width - (PANEL_WIDTH + 1));
|
||||
let hline = format!("{}{}{}", "─".repeat(PANEL_WIDTH), grid_char, hline);
|
||||
|
||||
writeln!(self.handle, "{}", self.colors.grid.paint(hline))?;
|
||||
|
Loading…
x
Reference in New Issue
Block a user