on f140f6d help sign in
bat/src/main.rs
241 lines 6.6 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 directories;
17extern crate git2;
18extern crate syntect;
19extern crate wild;
21mod app;
22mod assets;
23mod clap_app;
24mod controller;
25mod decorations;
26mod diff;
27mod line_range;
28mod output;
29mod preprocessor;
30mod printer;
31mod style;
32mod terminal;
34use std::collections::HashSet;
35use std::io;
36use std::io::Write;
37use std::path::Path;
38use std::process;
40use ansi_term::Colour::Green;
41use ansi_term::Style;
43use app::{App, Config, InputFile};
44use assets::{clear_assets, config_dir, HighlightingAssets};
45use controller::Controller;
46use style::{OutputComponent, OutputComponents};
48mod errors {
49 error_chain! {
50 foreign_links {
51 Clap(::clap::Error);
52 Io(::std::io::Error);
53 SyntectError(::syntect::LoadingError);
54 ParseIntError(::std::num::ParseIntError);
55 }
56 }
58 pub fn handle_error(error: &Error) {
59 match error {
60 &Error(ErrorKind::Io(ref io_error), _)
61 if io_error.kind() == super::io::ErrorKind::BrokenPipe =>
62 {
63 super::process::exit(0);
64 }
65 _ => {
66 use ansi_term::Colour::Red;
67 eprintln!("{}: {}", Red.paint("[bat error]"), error);
68 }
69 };
70 }
73use errors::*;
75fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
76 if matches.is_present("init") {
77 let source_dir = matches.value_of("source").map(Path::new);
78 let target_dir = matches.value_of("target").map(Path::new);
80 let blank = matches.is_present("blank");
82 let assets = HighlightingAssets::from_files(source_dir, blank)?;
83 assets.save(target_dir)?;
84 } else if matches.is_present("clear") {
85 clear_assets();
86 } else if matches.is_present("config-dir") {
87 writeln!(io::stdout(), "{}", config_dir())?;
88 }
90 Ok(())
93pub fn list_languages(config: &Config) -> Result<()> {
94 let assets = HighlightingAssets::new();
95 let mut languages = assets
96 .syntax_set
97 .syntaxes()
98 .iter()
99 .filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
100 .collect::<Vec<_>>();
101 languages.sort_by_key(|lang| lang.name.to_uppercase());
103 let longest = languages
104 .iter()
105 .map(|syntax| syntax.name.len())
106 .max()
107 .unwrap_or(32); // Fallback width if they have no language definitions.
109 let comma_separator = ", ";
110 let separator = " ";
111 // Line-wrapping for the possible file extension overflow.
112 let desired_width = config.term_width - longest - separator.len();
114 let stdout = io::stdout();
115 let mut stdout = stdout.lock();
117 let style = if config.colored_output {
118 Green.normal()
119 } else {
120 Style::default()
121 };
123 for lang in languages {
124 write!(stdout, "{:width$}{}", lang.name, separator, width = longest)?;
126 // Number of characters on this line so far, wrap before `desired_width`
127 let mut num_chars = 0;
129 let mut extension = lang.file_extensions.iter().peekable();
130 while let Some(word) = extension.next() {
131 // If we can't fit this word in, then create a line break and align it in.
132 let new_chars = word.len() + comma_separator.len();
133 if num_chars + new_chars >= desired_width {
134 num_chars = 0;
135 write!(stdout, "\n{:width$}{}", "", separator, width = longest)?;
136 }
138 num_chars += new_chars;
139 write!(stdout, "{}", style.paint(&word[..]))?;
140 if extension.peek().is_some() {
141 write!(stdout, "{}", comma_separator)?;
142 }
143 }
144 writeln!(stdout)?;
145 }
147 Ok(())
150pub fn list_themes(cfg: &Config) -> Result<()> {
151 let assets = HighlightingAssets::new();
152 let themes = &assets.theme_set.themes;
153 let mut config = cfg.clone();
154 let mut style = HashSet::new();
155 style.insert(OutputComponent::Plain);
156 config.files = vec![InputFile::ThemePreviewFile];
157 config.output_components = OutputComponents(style);
159 let stdout = io::stdout();
160 let mut stdout = stdout.lock();
162 if config.colored_output {
163 for (theme, _) in themes.iter() {
164 writeln!(
165 stdout,
166 "Theme: {}\n",
167 Style::new().bold().paint(theme.to_string())
168 )?;
169 config.theme = theme.to_string();
170 let _controller = Controller::new(&config, &assets).run();
171 writeln!(stdout)?;
172 }
173 } else {
174 for (theme, _) in themes.iter() {
175 writeln!(stdout, "{}", theme)?;
176 }
177 }
179 Ok(())
182fn run_controller(config: &Config) -> Result<bool> {
183 let assets = HighlightingAssets::new();
184 let controller = Controller::new(&config, &assets);
185 controller.run()
188/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Some(true)` on full success and
189/// `Some(false)` if any intermediate errors occurred (were printed).
190fn run() -> Result<bool> {
191 let app = App::new();
193 match app.matches.subcommand() {
194 ("cache", Some(cache_matches)) => {
195 // If there is a file named 'cache' in the current working directory,
196 // arguments for subcommand 'cache' are not mandatory.
197 // If there are non-zero arguments, execute the subcommand cache, else, open the file cache.
198 if !cache_matches.args.is_empty() {
199 run_cache_subcommand(cache_matches)?;
200 Ok(true)
201 } else {
202 let mut config = app.config()?;
203 config.files = vec![InputFile::Ordinary(&"cache")];
205 run_controller(&config)
206 }
207 }
208 _ => {
209 let config = app.config()?;
211 if app.matches.is_present("list-languages") {
212 list_languages(&config)?;
214 Ok(true)
215 } else if app.matches.is_present("list-themes") {
216 list_themes(&config)?;
218 Ok(true)
219 } else {
220 run_controller(&config)
221 }
222 }
223 }
226fn main() {
227 let result = run();
229 match result {
230 Err(error) => {
231 handle_error(&error);
232 process::exit(1);
233 }
234 Ok(false) => {
235 process::exit(1);
236 }
237 Ok(true) => {
238 process::exit(0);
239 }
240 }