on master help sign in
Split app to separate module
commit64a9341b73b1e677f4fca752033af1fd01dde761 authorEzinwa Okpoechi <brainmaestro@outlook.com> dateThu 10 May 2018, 23:39 (8 years ago) parentccf88fd
3 files changed+226−177
src/app.rs+194 src/main.rs+13−159 src/printer.rs+19−18
src/app.rsnew file+194
@@ -0,0 +1,194 @@
1+use atty::{self, Stream};
2+use clap::{App as ClapApp, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand};
3+use console::Term;
4+use errors::*;
5+use std::collections::HashSet;
6+use std::env;
7+use {OutputComponent, OutputComponents};
8+
9+pub struct App {
10+ pub matches: ArgMatches<'static>,
11+ interactive_output: bool,
12+}
13+
14+impl App {
15+ pub fn new() -> Self {
16+ let interactive_output = atty::is(Stream::Stdout);
17+
18+ App {
19+ matches: Self::matches(interactive_output),
20+ interactive_output,
21+ }
22+ }
23+
24+ fn matches(interactive_output: bool) -> ArgMatches<'static> {
25+ let clap_color_setting = if interactive_output {
26+ AppSettings::ColoredHelp
27+ } else {
28+ AppSettings::ColorNever
29+ };
30+
31+ ClapApp::new(crate_name!())
32+ .version(crate_version!())
33+ .global_setting(clap_color_setting)
34+ .global_setting(AppSettings::DeriveDisplayOrder)
35+ .global_setting(AppSettings::UnifiedHelpMessage)
36+ .global_setting(AppSettings::NextLineHelp)
37+ .setting(AppSettings::InferSubcommands)
38+ .setting(AppSettings::ArgsNegateSubcommands)
39+ .setting(AppSettings::DisableHelpSubcommand)
40+ .setting(AppSettings::VersionlessSubcommands)
41+ .max_term_width(90)
42+ .about(crate_description!())
43+ .arg(
44+ Arg::with_name("language")
45+ .short("l")
46+ .long("language")
47+ .help("Set the language for highlighting")
48+ .takes_value(true),
49+ )
50+ .arg(
51+ Arg::with_name("FILE")
52+ .help("File(s) to print")
53+ .multiple(true)
54+ .empty_values(false),
55+ )
56+ .arg(
57+ Arg::with_name("style")
58+ .long("style")
59+ .use_delimiter(true)
60+ .takes_value(true)
61+ .possible_values(&[
62+ "auto", "full", "plain", "changes", "header", "grid", "numbers",
63+ ])
64+ .default_value("auto")
65+ .help("Additional info to display along with content"),
66+ )
67+ .arg(
68+ Arg::with_name("color")
69+ .long("color")
70+ .takes_value(true)
71+ .possible_values(&["auto", "never", "always"])
72+ .default_value("auto")
73+ .help("When to use colors"),
74+ )
75+ .arg(
76+ Arg::with_name("paging")
77+ .long("paging")
78+ .takes_value(true)
79+ .possible_values(&["auto", "never", "always"])
80+ .default_value("auto")
81+ .help("When to use the pager"),
82+ )
83+ .arg(
84+ Arg::with_name("list-languages")
85+ .long("list-languages")
86+ .help("Displays supported languages"),
87+ )
88+ .subcommand(
89+ SubCommand::with_name("cache")
90+ .about("Modify the syntax-definition and theme cache")
91+ .arg(
92+ Arg::with_name("init")
93+ .long("init")
94+ .short("i")
95+ .help("Initialize the cache by loading from the config dir"),
96+ )
97+ .arg(
98+ Arg::with_name("clear")
99+ .long("clear")
100+ .short("c")
101+ .help("Reset the cache"),
102+ )
103+ .arg(
104+ Arg::with_name("config-dir")
105+ .long("config-dir")
106+ .short("d")
107+ .help("Show the configuration directory"),
108+ )
109+ .group(
110+ ArgGroup::with_name("cache-actions")
111+ .args(&["init", "clear", "config-dir"])
112+ .required(true),
113+ ),
114+ )
115+ .help_message("Print this help message.")
116+ .version_message("Show version information.")
117+ .get_matches()
118+ }
119+
120+ pub fn config(&self) -> Result<Config> {
121+ let files = self.files();
122+
123+ Ok(Config {
124+ true_color: is_truecolor_terminal(),
125+ output_components: self.output_components()?,
126+ language: self.matches.value_of("language"),
127+ colored_output: match self.matches.value_of("color") {
128+ Some("always") => true,
129+ Some("never") => false,
130+ Some("auto") | _ => self.interactive_output,
131+ },
132+ paging: match self.matches.value_of("paging") {
133+ Some("always") => true,
134+ Some("never") => false,
135+ Some("auto") | _ => if files.contains(&None) {
136+ // If we are reading from stdin, only enable paging if we write to an
137+ // interactive terminal and if we do not *read* from an interactive
138+ // terminal.
139+ self.interactive_output && !atty::is(Stream::Stdin)
140+ } else {
141+ self.interactive_output
142+ },
143+ },
144+ term_width: Term::stdout().size().1 as usize,
145+ files,
146+ })
147+ }
148+
149+ fn files(&self) -> Vec<Option<&str>> {
150+ self.matches
151+ .values_of("FILE")
152+ .map(|values| {
153+ values
154+ .map(|filename| {
155+ if filename == "-" {
156+ None
157+ } else {
158+ Some(filename)
159+ }
160+ })
161+ .collect()
162+ })
163+ .unwrap_or_else(|| vec![None]) // read from stdin (None) if no args are given
164+ }
165+
166+ fn output_components(&self) -> Result<OutputComponents> {
167+ let matches = &self.matches;
168+ Ok(OutputComponents(
169+ values_t!(matches.values_of("style"), OutputComponent)?
170+ .into_iter()
171+ .map(|style| style.components(self.interactive_output))
172+ .fold(HashSet::new(), |mut acc, components| {
173+ acc.extend(components.iter().cloned());
174+ acc
175+ }),
176+ ))
177+ }
178+}
179+
180+pub struct Config<'a> {
181+ pub true_color: bool,
182+ pub output_components: OutputComponents,
183+ pub language: Option<&'a str>,
184+ pub colored_output: bool,
185+ pub paging: bool,
186+ pub term_width: usize,
187+ pub files: Vec<Option<&'a str>>,
188+}
189+
190+fn is_truecolor_terminal() -> bool {
191+ env::var("COLORTERM")
192+ .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
193+ .unwrap_or(false)
194+}
src/main.rs+13−159
@@ -17,13 +17,13 @@ extern crate directories;
1717extern crate git2;
1818extern crate syntect;
1919
20+mod app;
2021mod assets;
2122mod diff;
2223mod printer;
2324mod terminal;
2425
2526use std::collections::HashSet;
26−use std::env;
2727use std::fs::{self, File};
2828use std::io::{self, BufRead, BufReader, Write};
2929use std::process::{self, Child, Command, Stdio};
@@ -34,13 +34,12 @@ use std::os::unix::fs::FileTypeExt;
3434
3535use ansi_term::Colour::{Fixed, Green, Red, White, Yellow};
3636use ansi_term::Style;
37−use atty::Stream;
38−use clap::{App, AppSettings, Arg, ArgGroup, SubCommand};
3937
4038use syntect::easy::HighlightLines;
4139use syntect::highlighting::Theme;
4240use syntect::parsing::SyntaxSet;
4341
42+use app::{App, Config};
4443use assets::{config_dir, syntax_set_path, theme_set_path, HighlightingAssets};
4544use diff::get_git_diff;
4645use printer::Printer;
@@ -215,7 +214,7 @@ impl Colors {
215214}
216215
217216fn print_file(
218− options: &Options,
217+ config: &Config,
219218 theme: &Theme,
220219 syntax_set: &SyntaxSet,
221220 printer: &mut Printer,
@@ -228,7 +227,7 @@ fn print_file(
228227 Some(filename) => Box::new(BufReader::new(File::open(filename)?)),
229228 };
230229
231− let syntax = match (options.language, filename) {
230+ let syntax = match (config.language, filename) {
232231 (Some(language), _) => syntax_set.find_syntax_by_token(language),
233232 (None, Some(filename)) => {
234233 #[cfg(not(unix))]
@@ -292,110 +291,10 @@ fn get_output_type(paging: bool) -> OutputType {
292291 }
293292}
294293
295−fn is_truecolor_terminal() -> bool {
296− env::var("COLORTERM")
297− .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
298− .unwrap_or(false)
299−}
300−
301294fn run() -> Result<()> {
302− let interactive_terminal = atty::is(Stream::Stdout);
303−
304− let clap_color_setting = if interactive_terminal {
305− AppSettings::ColoredHelp
306− } else {
307− AppSettings::ColorNever
308− };
295+ let app = App::new();
309296
310− let app_matches = App::new(crate_name!())
311− .version(crate_version!())
312− .global_setting(clap_color_setting)
313− .global_setting(AppSettings::DeriveDisplayOrder)
314− .global_setting(AppSettings::UnifiedHelpMessage)
315− .global_setting(AppSettings::NextLineHelp)
316− .setting(AppSettings::InferSubcommands)
317− .setting(AppSettings::ArgsNegateSubcommands)
318− .setting(AppSettings::DisableHelpSubcommand)
319− .setting(AppSettings::VersionlessSubcommands)
320− .max_term_width(90)
321− .about(crate_description!())
322− .arg(
323− Arg::with_name("language")
324− .short("l")
325− .long("language")
326− .help("Set the language for highlighting")
327− .takes_value(true),
328− )
329− .arg(
330− Arg::with_name("FILE")
331− .help("File(s) to print")
332− .multiple(true)
333− .empty_values(false),
334− )
335− .arg(
336− Arg::with_name("style")
337− .long("style")
338− .use_delimiter(true)
339− .takes_value(true)
340− .possible_values(&[
341− "auto", "full", "plain", "changes", "header", "grid", "numbers",
342− ])
343− .default_value("auto")
344− .help("Additional info to display along with content"),
345− )
346− .arg(
347− Arg::with_name("color")
348− .long("color")
349− .takes_value(true)
350− .possible_values(&["auto", "never", "always"])
351− .default_value("auto")
352− .help("When to use colors"),
353− )
354− .arg(
355− Arg::with_name("paging")
356− .long("paging")
357− .takes_value(true)
358− .possible_values(&["auto", "never", "always"])
359− .default_value("auto")
360− .help("When to use the pager"),
361− )
362− .arg(
363− Arg::with_name("list-languages")
364− .long("list-languages")
365− .help("Displays supported languages"),
366− )
367− .subcommand(
368− SubCommand::with_name("cache")
369− .about("Modify the syntax-definition and theme cache")
370− .arg(
371− Arg::with_name("init")
372− .long("init")
373− .short("i")
374− .help("Initialize the cache by loading from the config dir"),
375− )
376− .arg(
377− Arg::with_name("clear")
378− .long("clear")
379− .short("c")
380− .help("Reset the cache"),
381− )
382− .arg(
383− Arg::with_name("config-dir")
384− .long("config-dir")
385− .short("d")
386− .help("Show the configuration directory"),
387− )
388− .group(
389− ArgGroup::with_name("cache-actions")
390− .args(&["init", "clear", "config-dir"])
391− .required(true),
392− ),
393− )
394− .help_message("Print this help message.")
395− .version_message("Show version information.")
396− .get_matches();
397−
398− match app_matches.subcommand() {
297+ match app.matches.subcommand() {
399298 ("cache", Some(cache_matches)) => {
400299 if cache_matches.is_present("init") {
401300 let assets = HighlightingAssets::from_files()?;
@@ -413,57 +312,12 @@ fn run() -> Result<()> {
413312 }
414313 }
415314 _ => {
416− let files: Vec<Option<&str>> = app_matches
417− .values_of("FILE")
418− .map(|values| {
419− values
420− .map(|filename| {
421− if filename == "-" {
422− None
423− } else {
424− Some(filename)
425− }
426− })
427− .collect()
428− })
429− .unwrap_or_else(|| vec![None]); // read from stdin (None) if no args are given
430−
431− let output_components = values_t!(app_matches.values_of("style"), OutputComponent)?
432− .into_iter()
433− .map(|style| style.components(interactive_terminal))
434− .fold(HashSet::new(), |mut acc, components| {
435− acc.extend(components.iter().cloned());
436− acc
437− });
438−
439− let options = Options {
440− true_color: is_truecolor_terminal(),
441− output_components: OutputComponents(output_components),
442− language: app_matches.value_of("language"),
443− colored_output: match app_matches.value_of("color") {
444− Some("always") => true,
445− Some("never") => false,
446− _ => interactive_terminal,
447− },
448− paging: match app_matches.value_of("paging") {
449− Some("always") => true,
450− Some("never") => false,
451− Some("auto") | _ => if files.contains(&None) {
452− // If we are reading from stdin, only enable paging if we write to an
453− // interactive terminal and if we do not *read* from an interactive
454− // terminal.
455− interactive_terminal && !atty::is(Stream::Stdin)
456− } else {
457− interactive_terminal
458− },
459− },
460− term_width: console::Term::stdout().size().1 as usize,
461− };
315+ let config = app.config()?;
462316
463317 let assets = HighlightingAssets::new();
464318 let theme = assets.default_theme()?;
465319
466− if app_matches.is_present("list-languages") {
320+ if app.matches.is_present("list-languages") {
467321 let languages = assets.syntax_set.syntaxes();
468322
469323 let longest = languages
@@ -481,7 +335,7 @@ fn run() -> Result<()> {
481335 print!("{:width$}{}", lang.name, separator, width = longest);
482336
483337 // Line-wrapping for the possible file extension overflow.
484− let desired_width = options.term_width - longest - separator.len();
338+ let desired_width = config.term_width - longest - separator.len();
485339 // Number of characters on this line so far, wrap before `desired_width`
486340 let mut num_chars = 0;
487341
@@ -507,14 +361,14 @@ fn run() -> Result<()> {
507361 return Ok(());
508362 }
509363
510− let mut output_type = get_output_type(options.paging);
364+ let mut output_type = get_output_type(config.paging);
511365 let handle = output_type.handle()?;
512− let mut printer = Printer::new(handle, &options);
366+ let mut printer = Printer::new(handle, &config);
513367
514− for file in files {
368+ for file in &config.files {
515369 printer.line_changes = file.and_then(|filename| get_git_diff(filename));
516370
517− print_file(&options, theme, &assets.syntax_set, &mut printer, file)?;
371+ print_file(&config, theme, &assets.syntax_set, &mut printer, *file)?;
518372 }
519373 }
520374 }
src/printer.rs+19−18
@@ -1,23 +1,24 @@
11use ansi_term::Style;
2+use app::Config;
23use diff::{LineChange, LineChanges};
34use errors::*;
45use std::io::Write;
56use syntect::highlighting;
67use terminal::as_terminal_escaped;
7−use {Colors, Options};
8+use Colors;
89
910const PANEL_WIDTH: usize = 7;
1011
1112pub struct Printer<'a> {
1213 handle: &'a mut Write,
1314 colors: Colors,
14− options: &'a Options<'a>,
15+ config: &'a Config<'a>,
1516 pub line_changes: Option<LineChanges>,
1617}
1718
1819impl<'a> Printer<'a> {
19− pub fn new(handle: &'a mut Write, options: &'a Options) -> Self {
20− let colors = if options.colored_output {
20+ pub fn new(handle: &'a mut Write, config: &'a Config) -> Self {
21+ let colors = if config.colored_output {
2122 Colors::colored()
2223 } else {
2324 Colors::plain()
@@ -26,17 +27,17 @@ impl<'a> Printer<'a> {
2627 Printer {
2728 handle,
2829 colors,
29− options,
30+ config,
3031 line_changes: None,
3132 }
3233 }
3334
3435 pub fn print_header(&mut self, filename: Option<&str>) -> Result<()> {
35− if !self.options.output_components.header() {
36+ if !self.config.output_components.header() {
3637 return Ok(());
3738 }
3839
39− if self.options.output_components.grid() {
40+ if self.config.output_components.grid() {
4041 self.print_horizontal_line('┬')?;
4142
4243 write!(
@@ -54,7 +55,7 @@ impl<'a> Printer<'a> {
5455 self.colors.filename.paint(filename.unwrap_or("STDIN"))
5556 )?;
5657
57− if self.options.output_components.grid() {
58+ if self.config.output_components.grid() {
5859 self.print_horizontal_line('┼')?;
5960 }
6061
@@ -62,7 +63,7 @@ impl<'a> Printer<'a> {
6263 }
6364
6465 pub fn print_footer(&mut self) -> Result<()> {
65− if self.options.output_components.grid() {
66+ if self.config.output_components.grid() {
6667 self.print_horizontal_line('┴')
6768 } else {
6869 Ok(())
@@ -80,12 +81,12 @@ impl<'a> Printer<'a> {
8081 self.print_line_border(),
8182 Some(as_terminal_escaped(
8283 &regions,
83− self.options.true_color,
84− self.options.colored_output,
84+ self.config.true_color,
85+ self.config.colored_output,
8586 )),
8687 ];
8788
88− let grid_requested = self.options.output_components.grid();
89+ let grid_requested = self.config.output_components.grid();
8990 write!(
9091 self.handle,
9192 "{}",
@@ -104,14 +105,14 @@ impl<'a> Printer<'a> {
104105 }
105106
106107 fn print_line_number(&self, line_number: usize) -> Option<String> {
107− if self.options.output_components.numbers() {
108+ if self.config.output_components.numbers() {
108109 Some(
109110 self.colors
110111 .line_number
111112 .paint(format!("{:4}", line_number))
112113 .to_string(),
113114 )
114− } else if self.options.output_components.grid() {
115+ } else if self.config.output_components.grid() {
115116 Some(" ".to_owned())
116117 } else {
117118 None
@@ -119,7 +120,7 @@ impl<'a> Printer<'a> {
119120 }
120121
121122 fn print_git_marker(&self, line_number: usize) -> Option<String> {
122− if self.options.output_components.changes() {
123+ if self.config.output_components.changes() {
123124 Some(
124125 if let Some(ref changes) = self.line_changes {
125126 match changes.get(&(line_number as u32)) {
@@ -133,7 +134,7 @@ impl<'a> Printer<'a> {
133134 Style::default().paint(" ")
134135 }.to_string(),
135136 )
136− } else if self.options.output_components.grid() {
137+ } else if self.config.output_components.grid() {
137138 Some(" ".to_owned())
138139 } else {
139140 None
@@ -141,7 +142,7 @@ impl<'a> Printer<'a> {
141142 }
142143
143144 fn print_line_border(&self) -> Option<String> {
144− if self.options.output_components.grid() {
145+ if self.config.output_components.grid() {
145146 Some(self.colors.grid.paint("│").to_string())
146147 } else {
147148 None
@@ -149,7 +150,7 @@ impl<'a> Printer<'a> {
149150 }
150151
151152 fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> {
152− let hline = "─".repeat(self.options.term_width - (PANEL_WIDTH + 1));
153+ let hline = "─".repeat(self.config.term_width - (PANEL_WIDTH + 1));
153154 let hline = format!("{}{}{}", "─".repeat(PANEL_WIDTH), grid_char, hline);
154155
155156 writeln!(self.handle, "{}", self.colors.grid.paint(hline))?;