on a736889 help sign in
bat/src/main.rs
265 lines 7.5 kB history raw
1// `error_chain!` can recurse deeply
2#![recursion_limit = "1024"]
4#[macro_use]
5extern crate error_chain;
7#[macro_use]
8extern crate clap;
10#[macro_use]
11extern crate lazy_static;
13extern crate ansi_term;
14extern crate atty;
15extern crate console;
16extern crate content_inspector;
17extern crate dirs as dirs_rs;
18extern crate encoding;
19extern crate git2;
20extern crate shell_words;
21extern crate syntect;
22extern crate wild;
24mod app;
25mod assets;
26mod clap_app;
27mod config;
28mod controller;
29mod decorations;
30mod diff;
31mod dirs;
32mod inputfile;
33mod line_range;
34mod output;
35mod preprocessor;
36mod printer;
37mod style;
38mod syntax_mapping;
39mod terminal;
40mod util;
42use std::collections::HashSet;
43use std::io;
44use std::io::Write;
45use std::path::Path;
46use std::process;
48use ansi_term::Colour::Green;
49use ansi_term::Style;
51use crate::app::{App, Config};
52use crate::assets::{cache_dir, clear_assets, config_dir, HighlightingAssets};
53use crate::config::config_file;
54use crate::controller::Controller;
55use crate::inputfile::InputFile;
56use crate::style::{OutputComponent, OutputComponents};
58mod errors {
59 error_chain! {
60 foreign_links {
61 Clap(::clap::Error);
62 Io(::std::io::Error);
63 SyntectError(::syntect::LoadingError);
64 ParseIntError(::std::num::ParseIntError);
65 }
66 }
68 pub fn handle_error(error: &Error) {
69 match error {
70 Error(ErrorKind::Io(ref io_error), _)
71 if io_error.kind() == super::io::ErrorKind::BrokenPipe =>
72 {
73 super::process::exit(0);
74 }
75 _ => {
76 use ansi_term::Colour::Red;
77 eprintln!("{}: {}", Red.paint("[bat error]"), error);
78 }
79 };
80 }
83use crate::errors::*;
85fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
86 if matches.is_present("build") {
87 let source_dir = matches.value_of("source").map(Path::new);
88 let target_dir = matches.value_of("target").map(Path::new);
90 let blank = matches.is_present("blank");
92 let assets = HighlightingAssets::from_files(source_dir, blank)?;
93 assets.save(target_dir)?;
94 } else if matches.is_present("clear") {
95 clear_assets();
96 }
98 Ok(())
101pub fn list_languages(config: &Config) -> Result<()> {
102 let assets = HighlightingAssets::new();
103 let mut languages = assets
104 .syntax_set
105 .syntaxes()
106 .iter()
107 .filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
108 .collect::<Vec<_>>();
109 languages.sort_by_key(|lang| lang.name.to_uppercase());
111 let stdout = io::stdout();
112 let mut stdout = stdout.lock();
114 if config.loop_through {
115 for lang in languages {
116 write!(stdout, "{}:{}\n", lang.name, lang.file_extensions.join(","))?;
117 }
118 } else {
119 let longest = languages
120 .iter()
121 .map(|syntax| syntax.name.len())
122 .max()
123 .unwrap_or(32); // Fallback width if they have no language definitions.
125 let comma_separator = ", ";
126 let separator = " ";
127 // Line-wrapping for the possible file extension overflow.
128 let desired_width = config.term_width - longest - separator.len();
130 let style = if config.colored_output {
131 Green.normal()
132 } else {
133 Style::default()
134 };
136 for lang in languages {
137 write!(stdout, "{:width$}{}", lang.name, separator, width = longest)?;
139 // Number of characters on this line so far, wrap before `desired_width`
140 let mut num_chars = 0;
142 let mut extension = lang.file_extensions.iter().peekable();
143 while let Some(word) = extension.next() {
144 // If we can't fit this word in, then create a line break and align it in.
145 let new_chars = word.len() + comma_separator.len();
146 if num_chars + new_chars >= desired_width {
147 num_chars = 0;
148 write!(stdout, "\n{:width$}{}", "", separator, width = longest)?;
149 }
151 num_chars += new_chars;
152 write!(stdout, "{}", style.paint(&word[..]))?;
153 if extension.peek().is_some() {
154 write!(stdout, "{}", comma_separator)?;
155 }
156 }
157 writeln!(stdout)?;
158 }
159 }
161 Ok(())
164pub fn list_themes(cfg: &Config) -> Result<()> {
165 let assets = HighlightingAssets::new();
166 let themes = &assets.theme_set.themes;
167 let mut config = cfg.clone();
168 let mut style = HashSet::new();
169 style.insert(OutputComponent::Plain);
170 config.files = vec![InputFile::ThemePreviewFile];
171 config.output_components = OutputComponents(style);
173 let stdout = io::stdout();
174 let mut stdout = stdout.lock();
176 if config.colored_output {
177 for (theme, _) in themes.iter() {
178 writeln!(
179 stdout,
180 "Theme: {}\n",
181 Style::new().bold().paint(theme.to_string())
182 )?;
183 config.theme = theme.to_string();
184 let _controller = Controller::new(&config, &assets).run();
185 writeln!(stdout)?;
186 }
187 } else {
188 for (theme, _) in themes.iter() {
189 writeln!(stdout, "{}", theme)?;
190 }
191 }
193 Ok(())
196fn run_controller(config: &Config) -> Result<bool> {
197 let assets = HighlightingAssets::new();
198 let controller = Controller::new(&config, &assets);
199 controller.run()
202/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Ok(true)` on full success and
203/// `Ok(false)` if any intermediate errors occurred (were printed).
204fn run() -> Result<bool> {
205 let app = App::new()?;
207 match app.matches.subcommand() {
208 ("cache", Some(cache_matches)) => {
209 // If there is a file named 'cache' in the current working directory,
210 // arguments for subcommand 'cache' are not mandatory.
211 // If there are non-zero arguments, execute the subcommand cache, else, open the file cache.
212 if !cache_matches.args.is_empty() {
213 run_cache_subcommand(cache_matches)?;
214 Ok(true)
215 } else {
216 let mut config = app.config()?;
217 config.files = vec![InputFile::Ordinary(&"cache")];
219 run_controller(&config)
220 }
221 }
222 _ => {
223 let config = app.config()?;
225 if app.matches.is_present("list-languages") {
226 list_languages(&config)?;
228 Ok(true)
229 } else if app.matches.is_present("list-themes") {
230 list_themes(&config)?;
232 Ok(true)
233 } else if app.matches.is_present("config-file") {
234 println!("{}", config_file().to_string_lossy());
236 Ok(true)
237 } else if app.matches.is_present("config-dir") {
238 writeln!(io::stdout(), "{}", config_dir())?;
239 Ok(true)
240 } else if app.matches.is_present("cache-dir") {
241 writeln!(io::stdout(), "{}", cache_dir())?;
242 Ok(true)
243 } else {
244 run_controller(&config)
245 }
246 }
247 }
250fn main() {
251 let result = run();
253 match result {
254 Err(error) => {
255 handle_error(&error);
256 process::exit(1);
257 }
258 Ok(false) => {
259 process::exit(1);
260 }
261 Ok(true) => {
262 process::exit(0);
263 }
264 }