on 720a2fb help sign in
bat/src/main.rs
222 lines 6.0 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;
20mod app;
21mod assets;
22mod controller;
23mod decorations;
24mod diff;
25mod line_range;
26mod output;
27mod printer;
28mod style;
29mod terminal;
31use std::collections::HashSet;
32use std::io;
33use std::io::Write;
34use std::path::Path;
35use std::process;
37use ansi_term::Colour::Green;
38use ansi_term::Style;
40use app::{App, Config, InputFile};
41use assets::{clear_assets, config_dir, HighlightingAssets};
42use controller::Controller;
43use style::{OutputComponent, OutputComponents};
45mod errors {
46 error_chain! {
47 foreign_links {
48 Clap(::clap::Error);
49 Io(::std::io::Error);
50 SyntectError(::syntect::LoadingError);
51 ParseIntError(::std::num::ParseIntError);
52 }
53 }
55 pub fn handle_error(error: &Error) {
56 match error {
57 &Error(ErrorKind::Io(ref io_error), _)
58 if io_error.kind() == super::io::ErrorKind::BrokenPipe =>
59 {
60 super::process::exit(0);
61 }
62 _ => {
63 use ansi_term::Colour::Red;
64 eprintln!("{}: {}", Red.paint("[bat error]"), error);
65 }
66 };
67 }
70use errors::*;
72fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
73 if matches.is_present("init") {
74 let source_dir = matches.value_of("source").map(Path::new);
75 let target_dir = matches.value_of("target").map(Path::new);
77 let blank = matches.is_present("blank");
79 let assets = HighlightingAssets::from_files(source_dir, blank)?;
80 assets.save(target_dir)?;
81 } else if matches.is_present("clear") {
82 clear_assets();
83 } else if matches.is_present("config-dir") {
84 writeln!(io::stdout(), "{}", config_dir())?;
85 }
87 Ok(())
90pub fn list_languages(assets: &HighlightingAssets, config: &Config) -> Result<()> {
91 let mut languages = assets
92 .syntax_set
93 .syntaxes()
94 .iter()
95 .filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
96 .collect::<Vec<_>>();
97 languages.sort_by_key(|lang| lang.name.to_uppercase());
99 let longest = languages
100 .iter()
101 .map(|syntax| syntax.name.len())
102 .max()
103 .unwrap_or(32); // Fallback width if they have no language definitions.
105 let comma_separator = ", ";
106 let separator = " ";
107 // Line-wrapping for the possible file extension overflow.
108 let desired_width = config.term_width - longest - separator.len();
110 let stdout = io::stdout();
111 let mut stdout = stdout.lock();
113 let style = if config.colored_output {
114 Green.normal()
115 } else {
116 Style::default()
117 };
119 for lang in languages {
120 write!(stdout, "{:width$}{}", lang.name, separator, width = longest)?;
122 // Number of characters on this line so far, wrap before `desired_width`
123 let mut num_chars = 0;
125 let mut extension = lang.file_extensions.iter().peekable();
126 while let Some(word) = extension.next() {
127 // If we can't fit this word in, then create a line break and align it in.
128 let new_chars = word.len() + comma_separator.len();
129 if num_chars + new_chars >= desired_width {
130 num_chars = 0;
131 write!(stdout, "\n{:width$}{}", "", separator, width = longest)?;
132 }
134 num_chars += new_chars;
135 write!(stdout, "{}", style.paint(&word[..]))?;
136 if extension.peek().is_some() {
137 write!(stdout, "{}", comma_separator)?;
138 }
139 }
140 writeln!(stdout)?;
141 }
143 Ok(())
146pub fn list_themes(assets: &HighlightingAssets, cfg: &Config) -> Result<()> {
147 let themes = &assets.theme_set.themes;
148 let mut config = cfg.clone();
149 let mut style = HashSet::new();
150 style.insert(OutputComponent::Plain);
151 config.files = vec![InputFile::ThemePreviewFile];
152 config.output_components = OutputComponents(style);
154 let stdout = io::stdout();
155 let mut stdout = stdout.lock();
157 if config.colored_output {
158 for (theme, _) in themes.iter() {
159 writeln!(
160 stdout,
161 "Theme: {}\n",
162 Style::new().bold().paint(theme.to_string())
163 )?;
164 config.theme = theme.to_string();
165 let _controller = Controller::new(&config, &assets).run();
166 writeln!(stdout)?;
167 }
168 } else {
169 for (theme, _) in themes.iter() {
170 writeln!(stdout, "{}", theme)?;
171 }
172 }
174 Ok(())
177/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Some(true)` on full success and
178/// `Some(false)` if any intermediate errors occurred (were printed).
179fn run() -> Result<bool> {
180 let app = App::new();
182 match app.matches.subcommand() {
183 ("cache", Some(cache_matches)) => {
184 run_cache_subcommand(cache_matches)?;
185 Ok(true)
186 }
187 _ => {
188 let config = app.config()?;
189 let assets = HighlightingAssets::new();
191 if app.matches.is_present("list-languages") {
192 list_languages(&assets, &config)?;
194 Ok(true)
195 } else if app.matches.is_present("list-themes") {
196 list_themes(&assets, &config)?;
198 Ok(true)
199 } else {
200 let controller = Controller::new(&config, &assets);
201 controller.run()
202 }
203 }
204 }
207fn main() {
208 let result = run();
210 match result {
211 Err(error) => {
212 handle_error(&error);
213 process::exit(1);
214 }
215 Ok(false) => {
216 process::exit(1);
217 }
218 Ok(true) => {
219 process::exit(0);
220 }
221 }