bat/src/bin/bat/main.rs

264 lines
7.9 KiB
Rust
Raw Normal View History

2018-04-30 11:09:24 +02:00
// `error_chain!` can recurse deeply
#![recursion_limit = "1024"]
2018-05-10 23:39:13 +02:00
mod app;
2020-03-21 19:40:43 +01:00
mod assets;
mod clap_app;
2019-10-06 03:44:14 +02:00
mod config;
mod directories;
mod input;
2018-04-23 23:56:47 +02:00
use std::collections::{HashMap, HashSet};
2020-02-28 10:27:06 +01:00
use std::ffi::OsStr;
2018-05-21 21:51:41 +02:00
use std::io;
use std::io::{BufReader, Write};
use std::path::Path;
2018-05-21 14:59:42 +02:00
use std::process;
2018-04-21 12:51:43 +02:00
2018-08-23 22:37:27 +02:00
use ansi_term::Colour::Green;
2018-08-27 22:22:36 +02:00
use ansi_term::Style;
2018-08-23 22:37:27 +02:00
2020-04-11 19:40:04 +02:00
use crate::{
app::App,
config::{config_file, generate_config_file},
};
2020-03-21 19:40:43 +01:00
use assets::{assets_from_cache_or_binary, cache_dir, clear_assets, config_dir};
2020-04-21 17:19:07 +02:00
use clap::crate_version;
2020-03-21 19:40:43 +01:00
use directories::PROJECT_DIRS;
use globset::GlobMatcher;
use bat::{
assets::HighlightingAssets,
config::Config,
controller::Controller,
2020-04-22 21:45:47 +02:00
error::*,
input::Input,
style::{StyleComponent, StyleComponents},
MappingTarget,
};
2018-05-21 21:51:41 +02:00
const THEME_PREVIEW_DATA: &[u8] = include_bytes!("../../../assets/theme_preview.rs");
2018-08-22 22:29:12 +02:00
fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
if matches.is_present("build") {
2020-03-21 19:40:43 +01:00
let source_dir = matches
.value_of("source")
.map(Path::new)
.unwrap_or_else(|| PROJECT_DIRS.config_dir());
let target_dir = matches
.value_of("target")
.map(Path::new)
.unwrap_or_else(|| PROJECT_DIRS.cache_dir());
2018-08-22 22:29:12 +02:00
let blank = matches.is_present("blank");
2020-03-21 20:36:00 +01:00
let assets = HighlightingAssets::from_files(source_dir, !blank)?;
2020-04-21 17:19:07 +02:00
assets.save_to_cache(target_dir, crate_version!())?;
2018-08-22 22:29:12 +02:00
} else if matches.is_present("clear") {
clear_assets();
}
Ok(())
}
fn get_syntax_mapping_to_paths(
mappings: Vec<(GlobMatcher, MappingTarget)>,
) -> HashMap<String, Vec<String>> {
let mut map = HashMap::new();
for mapping in mappings {
match mapping.1 {
MappingTarget::MapToUnknown => {}
MappingTarget::MapTo(s) => {
let globs = map.entry(s.into()).or_insert(Vec::new());
globs.push(mapping.0.glob().glob().into());
}
}
}
map
}
pub fn list_languages(config: &Config) -> Result<()> {
let assets = assets_from_cache_or_binary()?;
2018-08-23 22:37:27 +02:00
let mut languages = assets
.syntaxes()
.iter()
.filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
.cloned()
2018-08-23 22:37:27 +02:00
.collect::<Vec<_>>();
languages.sort_by_key(|lang| lang.name.to_uppercase());
let configured_languages =
get_syntax_mapping_to_paths(config.syntax_mapping.mappings().clone());
for lang in languages.iter_mut() {
if configured_languages.contains_key(&lang.name) {
let additional_paths = configured_languages.get(&lang.name).unwrap();
lang.file_extensions
.extend(additional_paths.iter().cloned());
}
}
2018-08-31 21:48:26 +02:00
let stdout = io::stdout();
let mut stdout = stdout.lock();
if config.loop_through {
for lang in languages {
writeln!(stdout, "{}:{}", lang.name, lang.file_extensions.join(","))?;
}
} else {
let longest = languages
.iter()
.map(|syntax| syntax.name.len())
.max()
.unwrap_or(32); // Fallback width if they have no language definitions.
let comma_separator = ", ";
let separator = " ";
// Line-wrapping for the possible file extension overflow.
let desired_width = config.term_width - longest - separator.len();
let style = if config.colored_output {
Green.normal()
} else {
Style::default()
};
2018-08-23 22:37:27 +02:00
for lang in languages {
write!(stdout, "{:width$}{}", lang.name, separator, width = longest)?;
// Number of characters on this line so far, wrap before `desired_width`
let mut num_chars = 0;
let mut extension = lang.file_extensions.iter().peekable();
while let Some(word) = extension.next() {
// If we can't fit this word in, then create a line break and align it in.
let new_chars = word.len() + comma_separator.len();
if num_chars + new_chars >= desired_width {
num_chars = 0;
write!(stdout, "\n{:width$}{}", "", separator, width = longest)?;
}
num_chars += new_chars;
write!(stdout, "{}", style.paint(&word[..]))?;
if extension.peek().is_some() {
write!(stdout, "{}", comma_separator)?;
}
2018-08-23 22:37:27 +02:00
}
writeln!(stdout)?;
2018-08-23 22:37:27 +02:00
}
}
Ok(())
2018-08-23 22:37:27 +02:00
}
fn theme_preview_file<'a>() -> Input<'a> {
Input::from_reader(Box::new(BufReader::new(THEME_PREVIEW_DATA)))
}
pub fn list_themes(cfg: &Config) -> Result<()> {
let assets = assets_from_cache_or_binary()?;
2018-08-27 23:18:15 +02:00
let mut config = cfg.clone();
2018-08-27 22:10:56 +02:00
let mut style = HashSet::new();
style.insert(StyleComponent::Plain);
config.language = Some("Rust");
config.style_components = StyleComponents(style);
2018-08-31 21:48:26 +02:00
let stdout = io::stdout();
let mut stdout = stdout.lock();
if config.colored_output {
for theme in assets.themes() {
writeln!(
stdout,
"Theme: {}\n",
Style::new().bold().paint(theme.to_string())
)?;
config.theme = theme.to_string();
2020-04-21 21:14:44 +02:00
Controller::new(&config, &assets)
.run(vec![theme_preview_file()])
2020-04-21 21:14:44 +02:00
.ok();
writeln!(stdout)?;
}
} else {
for theme in assets.themes() {
writeln!(stdout, "{}", theme)?;
}
2018-08-23 22:37:27 +02:00
}
Ok(())
2018-08-23 22:37:27 +02:00
}
2020-04-21 21:19:06 +02:00
fn run_controller(inputs: Vec<Input>, config: &Config) -> Result<bool> {
let assets = assets_from_cache_or_binary()?;
let controller = Controller::new(&config, &assets);
2020-04-21 21:14:44 +02:00
controller.run(inputs)
}
2018-11-27 02:41:00 +01:00
/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Ok(true)` on full success and
/// `Ok(false)` if any intermediate errors occurred (were printed).
2018-05-19 11:46:41 +02:00
fn run() -> Result<bool> {
2018-10-11 22:50:37 +02:00
let app = App::new()?;
2018-04-21 12:51:43 +02:00
2018-05-10 23:39:13 +02:00
match app.matches.subcommand() {
("cache", Some(cache_matches)) => {
// If there is a file named 'cache' in the current working directory,
// arguments for subcommand 'cache' are not mandatory.
// If there are non-zero arguments, execute the subcommand cache, else, open the file cache.
if !cache_matches.args.is_empty() {
run_cache_subcommand(cache_matches)?;
Ok(true)
} else {
2020-04-22 16:27:34 +02:00
let inputs = vec![Input::ordinary_file(OsStr::new("cache"))];
2020-04-21 21:19:06 +02:00
let config = app.config(&inputs)?;
2020-04-21 21:14:44 +02:00
run_controller(inputs, &config)
}
}
_ => {
2020-04-21 21:14:44 +02:00
let inputs = app.inputs()?;
let config = app.config(&inputs)?;
2018-05-10 23:39:13 +02:00
if app.matches.is_present("list-languages") {
list_languages(&config)?;
2018-08-22 22:29:12 +02:00
Ok(true)
} else if app.matches.is_present("list-themes") {
list_themes(&config)?;
2018-10-17 23:02:53 +02:00
Ok(true)
} else if app.matches.is_present("config-file") {
println!("{}", config_file().to_string_lossy());
Ok(true)
} else if app.matches.is_present("generate-config-file") {
2020-03-26 03:03:10 +01:00
generate_config_file()?;
Ok(true)
} else if app.matches.is_present("config-dir") {
writeln!(io::stdout(), "{}", config_dir())?;
Ok(true)
} else if app.matches.is_present("cache-dir") {
writeln!(io::stdout(), "{}", cache_dir())?;
2018-08-22 22:29:12 +02:00
Ok(true)
} else {
2020-04-21 21:14:44 +02:00
run_controller(inputs, &config)
2018-08-22 22:29:12 +02:00
}
}
}
2018-05-19 11:46:41 +02:00
}
fn main() {
let result = run();
2018-04-21 12:51:43 +02:00
2018-05-19 11:46:41 +02:00
match result {
Err(error) => {
let stderr = std::io::stderr();
default_error_handler(&error, &mut stderr.lock());
2018-05-19 11:46:41 +02:00
process::exit(1);
}
Ok(false) => {
process::exit(1);
}
Ok(true) => {
process::exit(0);
}
2018-04-21 12:51:43 +02:00
}
}