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