on f9fd5e4 help sign in
bat/src/app.rs
243 lines 8.0 kB history raw
1use std::collections::HashSet;
2use std::env;
3use std::str::FromStr;
5use atty::{self, Stream};
7use clap::ArgMatches;
8use clap_app;
9use wild;
11use console::Term;
13#[cfg(windows)]
14use ansi_term;
16use assets::BAT_THEME_DEFAULT;
17use errors::*;
18use line_range::LineRange;
19use style::{OutputComponent, OutputComponents, OutputWrap};
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub enum PagingMode {
23 Always,
24 QuitIfOneScreen,
25 Never,
28#[derive(Debug, Clone, Copy, PartialEq)]
29pub enum InputFile<'a> {
30 StdIn,
31 Ordinary(&'a str),
32 ThemePreviewFile,
35#[derive(Clone)]
36pub struct Config<'a> {
37 /// List of files to print
38 pub files: Vec<InputFile<'a>>,
40 /// The explicitly configured language, if any
41 pub language: Option<&'a str>,
43 /// The character width of the terminal
44 pub term_width: usize,
46 /// The width of tab characters.
47 /// Currently, a value of 0 will cause tabs to be passed through without expanding them.
48 pub tab_width: usize,
50 /// Whether or not to simply loop through all input (`cat` mode)
51 pub loop_through: bool,
53 /// Whether or not the output should be colorized
54 pub colored_output: bool,
56 /// Whether or not the output terminal supports true color
57 pub true_color: bool,
59 /// Style elements (grid, line numbers, ...)
60 pub output_components: OutputComponents,
62 /// Text wrapping mode
63 pub output_wrap: OutputWrap,
65 /// Pager or STDOUT
66 pub paging_mode: PagingMode,
68 /// The range lines that should be printed, if specified
69 pub line_range: Option<LineRange>,
71 /// The syntax highlighting theme
72 pub theme: String,
75fn is_truecolor_terminal() -> bool {
76 env::var("COLORTERM")
77 .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
78 .unwrap_or(false)
81/// Helper function that might appear in Rust stable at some point
82/// (https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.transpose)
83fn transpose<T>(opt: Option<Result<T>>) -> Result<Option<T>> {
84 opt.map_or(Ok(None), |res| res.map(Some))
87pub struct App {
88 pub matches: ArgMatches<'static>,
89 interactive_output: bool,
92impl App {
93 pub fn new() -> Self {
94 #[cfg(windows)]
95 let _ = ansi_term::enable_ansi_support();
97 let interactive_output = atty::is(Stream::Stdout);
99 App {
100 matches: Self::matches(interactive_output),
101 interactive_output,
102 }
103 }
105 fn matches(interactive_output: bool) -> ArgMatches<'static> {
106 clap_app::build_app(interactive_output).get_matches_from(wild::args())
107 }
109 pub fn config(&self) -> Result<Config> {
110 let files = self.files();
111 let output_components = self.output_components()?;
113 let paging_mode = match self.matches.value_of("paging") {
114 Some("always") => PagingMode::Always,
115 Some("never") => PagingMode::Never,
116 Some("auto") | _ => {
117 if files.contains(&InputFile::StdIn) {
118 // If we are reading from stdin, only enable paging if we write to an
119 // interactive terminal and if we do not *read* from an interactive
120 // terminal.
121 if self.interactive_output && !atty::is(Stream::Stdin) {
122 PagingMode::QuitIfOneScreen
123 } else {
124 PagingMode::Never
125 }
126 } else {
127 if self.interactive_output {
128 PagingMode::QuitIfOneScreen
129 } else {
130 PagingMode::Never
131 }
132 }
133 }
134 };
136 Ok(Config {
137 true_color: is_truecolor_terminal(),
138 language: self.matches.value_of("language"),
139 output_wrap: if !self.interactive_output {
140 // We don't have the tty width when piping to another program.
141 // There's no point in wrapping when this is the case.
142 OutputWrap::None
143 } else {
144 match self.matches.value_of("wrap") {
145 Some("character") => OutputWrap::Character,
146 Some("never") => OutputWrap::None,
147 Some("auto") | _ => {
148 if output_components.plain() {
149 OutputWrap::None
150 } else {
151 OutputWrap::Character
152 }
153 }
154 }
155 },
156 colored_output: match self.matches.value_of("color") {
157 Some("always") => true,
158 Some("never") => false,
159 Some("auto") | _ => self.interactive_output,
160 },
161 paging_mode,
162 term_width: self
163 .matches
164 .value_of("terminal-width")
165 .and_then(|w| w.parse().ok())
166 .unwrap_or(Term::stdout().size().1 as usize),
167 loop_through: !(self.interactive_output
168 || self.matches.value_of("color") == Some("always")
169 || self.matches.value_of("decorations") == Some("always")),
170 files,
171 tab_width: self
172 .matches
173 .value_of("tabs")
174 .map(String::from)
175 .or_else(|| env::var("BAT_TABS").ok())
176 .and_then(|t| t.parse().ok())
177 .unwrap_or(
178 if output_components.plain() && paging_mode == PagingMode::Never {
179 0
180 } else {
181 8
182 },
183 ),
184 theme: self
185 .matches
186 .value_of("theme")
187 .map(String::from)
188 .or_else(|| env::var("BAT_THEME").ok())
189 .unwrap_or(String::from(BAT_THEME_DEFAULT)),
190 line_range: transpose(self.matches.value_of("line-range").map(LineRange::from))?,
191 output_components,
192 })
193 }
195 fn files(&self) -> Vec<InputFile> {
196 self.matches
197 .values_of("FILE")
198 .map(|values| {
199 values
200 .map(|filename| {
201 if filename == "-" {
202 InputFile::StdIn
203 } else {
204 InputFile::Ordinary(filename)
205 }
206 })
207 .collect()
208 })
209 .unwrap_or_else(|| vec![InputFile::StdIn])
210 }
212 fn output_components(&self) -> Result<OutputComponents> {
213 let matches = &self.matches;
214 Ok(OutputComponents(
215 if matches.value_of("decorations") == Some("never") {
216 HashSet::new()
217 } else if matches.is_present("number") {
218 [OutputComponent::Numbers].iter().cloned().collect()
219 } else if matches.is_present("plain") {
220 [OutputComponent::Plain].iter().cloned().collect()
221 } else {
222 let env_style_components: Option<Vec<OutputComponent>> =
223 transpose(env::var("BAT_STYLE").ok().map(|style_str| {
224 style_str
225 .split(",")
226 .map(|x| OutputComponent::from_str(&x))
227 .collect::<Result<Vec<OutputComponent>>>()
228 }))?;
230 values_t!(matches.values_of("style"), OutputComponent)
231 .ok()
232 .or(env_style_components)
233 .unwrap_or(vec![OutputComponent::Full])
234 .into_iter()
235 .map(|style| style.components(self.interactive_output))
236 .fold(HashSet::new(), |mut acc, components| {
237 acc.extend(components.iter().cloned());
238 acc
239 })
240 },
241 ))
242 }