2018-04-30 11:09:24 +02:00
|
|
|
// `error_chain!` can recurse deeply
|
|
|
|
#![recursion_limit = "1024"]
|
|
|
|
|
|
|
|
#[macro_use]
|
|
|
|
extern crate clap;
|
|
|
|
|
2020-03-21 16:48:27 +01:00
|
|
|
extern crate dirs as dirs_rs;
|
|
|
|
|
2018-05-10 23:39:13 +02:00
|
|
|
mod app;
|
2020-03-21 19:40:43 +01:00
|
|
|
mod assets;
|
2018-10-03 09:39:30 +02:00
|
|
|
mod clap_app;
|
2019-10-06 03:44:14 +02:00
|
|
|
mod config;
|
2020-03-21 16:48:27 +01:00
|
|
|
mod directories;
|
2018-04-23 23:56:47 +02:00
|
|
|
|
2018-08-28 19:48:31 +02:00
|
|
|
use std::collections::HashSet;
|
2020-02-28 10:27:06 +01:00
|
|
|
use std::ffi::OsStr;
|
2018-05-21 21:51:41 +02:00
|
|
|
use std::io;
|
2018-08-31 11:27:29 +02:00
|
|
|
use std::io::Write;
|
2018-05-16 21:22:16 +02:00
|
|
|
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
|
|
|
|
2019-10-06 03:44:14 +02:00
|
|
|
use crate::{app::App, config::config_file};
|
2020-03-21 19:40:43 +01:00
|
|
|
use assets::{assets_from_cache_or_binary, cache_dir, clear_assets, config_dir};
|
2020-03-21 21:15:12 +01:00
|
|
|
use bat::Controller;
|
2020-03-21 19:40:43 +01:00
|
|
|
use directories::PROJECT_DIRS;
|
2019-10-06 04:10:03 +02:00
|
|
|
|
|
|
|
use bat::{
|
2020-03-21 21:03:57 +01:00
|
|
|
config::{Config, InputFile, StyleComponent, StyleComponents},
|
2019-10-06 04:10:03 +02:00
|
|
|
errors::*,
|
2020-03-21 21:15:12 +01:00
|
|
|
HighlightingAssets,
|
2019-10-06 04:10:03 +02:00
|
|
|
};
|
2018-05-21 21:51:41 +02:00
|
|
|
|
2018-08-22 22:29:12 +02:00
|
|
|
fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
|
2019-02-07 22:31:37 +01:00
|
|
|
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)?;
|
|
|
|
assets.save_to_cache(target_dir)?;
|
2018-08-22 22:29:12 +02:00
|
|
|
} else if matches.is_present("clear") {
|
|
|
|
clear_assets();
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-09-05 23:22:12 +02:00
|
|
|
pub fn list_languages(config: &Config) -> Result<()> {
|
2020-03-21 16:48:27 +01:00
|
|
|
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())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
languages.sort_by_key(|lang| lang.name.to_uppercase());
|
|
|
|
|
2018-08-31 21:48:26 +02:00
|
|
|
let stdout = io::stdout();
|
|
|
|
let mut stdout = stdout.lock();
|
|
|
|
|
2019-05-10 23:38:48 +02:00
|
|
|
if config.loop_through {
|
|
|
|
for lang in languages {
|
2019-05-14 23:42:23 +02:00
|
|
|
write!(stdout, "{}:{}\n", lang.name, lang.file_extensions.join(","))?;
|
2019-05-10 23:38:48 +02:00
|
|
|
}
|
2018-08-31 21:57:18 +02:00
|
|
|
} else {
|
2019-05-10 23:38:48 +02:00
|
|
|
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
|
|
|
|
2019-05-10 23:38:48 +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
|
|
|
}
|
2019-05-10 23:38:48 +02:00
|
|
|
writeln!(stdout)?;
|
2018-08-23 22:37:27 +02:00
|
|
|
}
|
|
|
|
}
|
2018-08-31 11:27:29 +02:00
|
|
|
|
|
|
|
Ok(())
|
2018-08-23 22:37:27 +02:00
|
|
|
}
|
|
|
|
|
2018-09-05 23:22:12 +02:00
|
|
|
pub fn list_themes(cfg: &Config) -> Result<()> {
|
2020-03-21 16:48:27 +01:00
|
|
|
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();
|
2020-03-21 20:54:16 +01:00
|
|
|
style.insert(StyleComponent::Plain);
|
2018-08-28 20:12:45 +02:00
|
|
|
config.files = vec![InputFile::ThemePreviewFile];
|
2020-03-21 20:54:16 +01:00
|
|
|
config.style_components = StyleComponents(style);
|
2018-08-31 21:48:26 +02:00
|
|
|
|
|
|
|
let stdout = io::stdout();
|
|
|
|
let mut stdout = stdout.lock();
|
|
|
|
|
2018-08-31 21:57:18 +02:00
|
|
|
if config.colored_output {
|
2020-03-21 20:16:32 +01:00
|
|
|
for theme in assets.themes() {
|
2018-08-31 21:57:18 +02:00
|
|
|
writeln!(
|
|
|
|
stdout,
|
|
|
|
"Theme: {}\n",
|
|
|
|
Style::new().bold().paint(theme.to_string())
|
|
|
|
)?;
|
|
|
|
config.theme = theme.to_string();
|
|
|
|
let _controller = Controller::new(&config, &assets).run();
|
|
|
|
writeln!(stdout)?;
|
|
|
|
}
|
|
|
|
} else {
|
2020-03-21 20:16:32 +01:00
|
|
|
for theme in assets.themes() {
|
2018-08-31 21:57:18 +02:00
|
|
|
writeln!(stdout, "{}", theme)?;
|
|
|
|
}
|
2018-08-23 22:37:27 +02:00
|
|
|
}
|
2018-08-31 11:27:29 +02:00
|
|
|
|
|
|
|
Ok(())
|
2018-08-23 22:37:27 +02:00
|
|
|
}
|
|
|
|
|
2018-09-05 23:22:12 +02:00
|
|
|
fn run_controller(config: &Config) -> Result<bool> {
|
2020-03-21 16:48:27 +01:00
|
|
|
let assets = assets_from_cache_or_binary();
|
2018-09-05 23:22:12 +02:00
|
|
|
let controller = Controller::new(&config, &assets);
|
|
|
|
controller.run()
|
|
|
|
}
|
|
|
|
|
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() {
|
2018-05-08 19:48:10 +02:00
|
|
|
("cache", Some(cache_matches)) => {
|
2018-09-05 23:22:12 +02:00
|
|
|
// 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 {
|
|
|
|
let mut config = app.config()?;
|
2020-02-12 23:23:49 +01:00
|
|
|
config.files = vec![InputFile::Ordinary(OsStr::new("cache"))];
|
2018-09-05 23:22:12 +02:00
|
|
|
|
|
|
|
run_controller(&config)
|
|
|
|
}
|
2018-04-30 15:08:04 +02:00
|
|
|
}
|
|
|
|
_ => {
|
2018-08-27 21:43:22 +02:00
|
|
|
let config = app.config()?;
|
2018-04-30 15:08:04 +02:00
|
|
|
|
2018-05-10 23:39:13 +02:00
|
|
|
if app.matches.is_present("list-languages") {
|
2018-09-05 23:22:12 +02:00
|
|
|
list_languages(&config)?;
|
2018-05-07 18:25:47 +02:00
|
|
|
|
2018-08-22 22:29:12 +02:00
|
|
|
Ok(true)
|
|
|
|
} else if app.matches.is_present("list-themes") {
|
2018-09-05 23:22:12 +02:00
|
|
|
list_themes(&config)?;
|
2018-05-11 13:53:17 +02:00
|
|
|
|
2018-10-17 23:02:53 +02:00
|
|
|
Ok(true)
|
|
|
|
} else if app.matches.is_present("config-file") {
|
|
|
|
println!("{}", config_file().to_string_lossy());
|
|
|
|
|
2019-02-07 22:37:12 +01:00
|
|
|
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 {
|
2018-09-05 23:22:12 +02:00
|
|
|
run_controller(&config)
|
2018-08-22 22:29:12 +02:00
|
|
|
}
|
2018-04-30 15:08:04 +02:00
|
|
|
}
|
|
|
|
}
|
2018-05-19 11:46:41 +02:00
|
|
|
}
|
2018-04-30 15:08:04 +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) => {
|
2020-03-21 19:51:59 +01:00
|
|
|
default_error_handler(&error);
|
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
|
|
|
}
|
|
|
|
}
|