bat/src/main.rs

261 lines
7.0 KiB
Rust
Raw Normal View History

2018-04-30 11:09:24 +02:00
// `error_chain!` can recurse deeply
#![recursion_limit = "1024"]
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate clap;
#[macro_use]
extern crate lazy_static;
2018-04-21 12:51:43 +02:00
extern crate ansi_term;
extern crate atty;
extern crate console;
extern crate content_inspector;
extern crate dirs as dirs_rs;
2018-10-07 16:44:59 +02:00
extern crate encoding;
2018-04-21 17:12:25 +02:00
extern crate git2;
2018-10-11 21:39:44 +02:00
extern crate shell_words;
2018-04-21 12:51:43 +02:00
extern crate syntect;
extern crate wild;
2018-04-21 12:51:43 +02:00
2018-05-10 23:39:13 +02:00
mod app;
mod assets;
mod clap_app;
mod config;
2018-08-23 22:37:27 +02:00
mod controller;
mod decorations;
mod diff;
mod dirs;
2018-10-07 11:21:41 +02:00
mod inputfile;
2018-08-22 22:29:12 +02:00
mod line_range;
2018-05-21 14:59:42 +02:00
mod output;
mod preprocessor;
mod printer;
2018-05-11 02:32:31 +02:00
mod style;
mod syntax_mapping;
2018-04-23 23:56:47 +02:00
mod terminal;
2018-10-11 22:27:42 +02:00
mod util;
2018-04-23 23:56:47 +02:00
2018-08-28 19:48:31 +02:00
use std::collections::HashSet;
2018-05-21 21:51:41 +02:00
use std::io;
use std::io::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
2018-10-07 11:21:41 +02:00
use app::{App, Config};
2019-02-07 21:19:20 +01:00
use assets::{cache_dir, clear_assets, config_dir, HighlightingAssets};
2018-10-17 23:02:53 +02:00
use config::config_file;
2018-08-23 22:37:27 +02:00
use controller::Controller;
2018-10-07 11:21:41 +02:00
use inputfile::InputFile;
2018-08-27 22:10:56 +02:00
use style::{OutputComponent, OutputComponents};
2018-04-23 23:56:47 +02:00
2018-04-30 11:09:24 +02:00
mod errors {
error_chain! {
2018-04-30 11:09:24 +02:00
foreign_links {
Clap(::clap::Error);
2018-04-30 11:09:24 +02:00
Io(::std::io::Error);
2018-05-25 22:12:30 +02:00
SyntectError(::syntect::LoadingError);
2018-06-05 01:07:01 +02:00
ParseIntError(::std::num::ParseIntError);
2018-04-30 11:09:24 +02:00
}
errors {
ImmediateEOF
}
2018-04-30 11:09:24 +02:00
}
2018-04-21 12:51:43 +02:00
2018-05-21 21:51:41 +02:00
pub fn handle_error(error: &Error) {
match error {
&Error(ErrorKind::Io(ref io_error), _)
if io_error.kind() == super::io::ErrorKind::BrokenPipe =>
{
super::process::exit(0);
}
_ => {
use ansi_term::Colour::Red;
eprintln!("{}: {}", Red.paint("[bat error]"), error);
}
};
2018-04-21 12:51:43 +02:00
}
}
2018-05-21 21:51:41 +02:00
use errors::*;
2018-08-22 22:29:12 +02:00
fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
if matches.is_present("init") {
let source_dir = matches.value_of("source").map(Path::new);
let target_dir = matches.value_of("target").map(Path::new);
let blank = matches.is_present("blank");
let assets = HighlightingAssets::from_files(source_dir, blank)?;
assets.save(target_dir)?;
} else if matches.is_present("clear") {
clear_assets();
} else if matches.is_present("config-dir") {
2018-08-31 21:48:26 +02:00
writeln!(io::stdout(), "{}", config_dir())?;
2019-02-07 21:19:20 +01:00
} else if matches.is_present("cache-dir") {
writeln!(io::stdout(), "{}", cache_dir())?;
2018-08-22 22:29:12 +02:00
}
Ok(())
}
pub fn list_languages(config: &Config) -> Result<()> {
let assets = HighlightingAssets::new();
2018-08-23 22:37:27 +02:00
let mut languages = assets
.syntax_set
.syntaxes()
.iter()
.filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
.collect::<Vec<_>>();
languages.sort_by_key(|lang| lang.name.to_uppercase());
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();
2018-08-23 22:37:27 +02:00
2018-08-31 21:48:26 +02:00
let stdout = io::stdout();
let mut stdout = stdout.lock();
let style = if config.colored_output {
Green.normal()
} else {
Style::default()
};
2018-08-23 22:37:27 +02:00
for lang in languages {
2018-08-31 21:48:26 +02:00
write!(stdout, "{:width$}{}", lang.name, separator, width = longest)?;
2018-08-23 22:37:27 +02:00
// 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;
2018-08-31 21:48:26 +02:00
write!(stdout, "\n{:width$}{}", "", separator, width = longest)?;
2018-08-23 22:37:27 +02:00
}
num_chars += new_chars;
write!(stdout, "{}", style.paint(&word[..]))?;
2018-08-23 22:37:27 +02:00
if extension.peek().is_some() {
2018-08-31 21:48:26 +02:00
write!(stdout, "{}", comma_separator)?;
2018-08-23 22:37:27 +02:00
}
}
2018-08-31 21:48:26 +02:00
writeln!(stdout)?;
2018-08-23 22:37:27 +02:00
}
Ok(())
2018-08-23 22:37:27 +02:00
}
pub fn list_themes(cfg: &Config) -> Result<()> {
let assets = HighlightingAssets::new();
2018-08-23 22:37:27 +02:00
let themes = &assets.theme_set.themes;
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(OutputComponent::Plain);
2018-08-28 20:12:45 +02:00
config.files = vec![InputFile::ThemePreviewFile];
2018-08-27 22:10:56 +02:00
config.output_components = OutputComponents(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 themes.iter() {
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 {
for (theme, _) in themes.iter() {
writeln!(stdout, "{}", theme)?;
}
2018-08-23 22:37:27 +02:00
}
Ok(())
2018-08-23 22:37:27 +02:00
}
fn run_controller(config: &Config) -> Result<bool> {
let assets = HighlightingAssets::new();
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() {
("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 {
let mut config = app.config()?;
config.files = vec![InputFile::Ordinary(&"cache")];
run_controller(&config)
}
}
_ => {
2018-08-27 21:43:22 +02:00
let config = app.config()?;
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-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());
2018-08-22 22:29:12 +02:00
Ok(true)
} else {
run_controller(&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) => {
handle_error(&error);
process::exit(1);
}
Ok(false) => {
process::exit(1);
}
Ok(true) => {
process::exit(0);
}
2018-04-21 12:51:43 +02:00
}
}