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