From 64a9341b73b1e677f4fca752033af1fd01dde761 Mon Sep 17 00:00:00 2001 From: Ezinwa Okpoechi Date: Thu, 10 May 2018 23:39:13 +0200 Subject: [PATCH] Split app to separate module --- src/app.rs | 194 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 172 ++++--------------------------------------- src/printer.rs | 37 +++++----- 3 files changed, 226 insertions(+), 177 deletions(-) create mode 100644 src/app.rs diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 00000000..a7f0018f --- /dev/null +++ b/src/app.rs @@ -0,0 +1,194 @@ +use atty::{self, Stream}; +use clap::{App as ClapApp, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand}; +use console::Term; +use errors::*; +use std::collections::HashSet; +use std::env; +use {OutputComponent, OutputComponents}; + +pub struct App { + pub matches: ArgMatches<'static>, + interactive_output: bool, +} + +impl App { + pub fn new() -> Self { + let interactive_output = atty::is(Stream::Stdout); + + App { + matches: Self::matches(interactive_output), + interactive_output, + } + } + + fn matches(interactive_output: bool) -> ArgMatches<'static> { + let clap_color_setting = if interactive_output { + AppSettings::ColoredHelp + } else { + AppSettings::ColorNever + }; + + ClapApp::new(crate_name!()) + .version(crate_version!()) + .global_setting(clap_color_setting) + .global_setting(AppSettings::DeriveDisplayOrder) + .global_setting(AppSettings::UnifiedHelpMessage) + .global_setting(AppSettings::NextLineHelp) + .setting(AppSettings::InferSubcommands) + .setting(AppSettings::ArgsNegateSubcommands) + .setting(AppSettings::DisableHelpSubcommand) + .setting(AppSettings::VersionlessSubcommands) + .max_term_width(90) + .about(crate_description!()) + .arg( + Arg::with_name("language") + .short("l") + .long("language") + .help("Set the language for highlighting") + .takes_value(true), + ) + .arg( + Arg::with_name("FILE") + .help("File(s) to print") + .multiple(true) + .empty_values(false), + ) + .arg( + Arg::with_name("style") + .long("style") + .use_delimiter(true) + .takes_value(true) + .possible_values(&[ + "auto", "full", "plain", "changes", "header", "grid", "numbers", + ]) + .default_value("auto") + .help("Additional info to display along with content"), + ) + .arg( + Arg::with_name("color") + .long("color") + .takes_value(true) + .possible_values(&["auto", "never", "always"]) + .default_value("auto") + .help("When to use colors"), + ) + .arg( + Arg::with_name("paging") + .long("paging") + .takes_value(true) + .possible_values(&["auto", "never", "always"]) + .default_value("auto") + .help("When to use the pager"), + ) + .arg( + Arg::with_name("list-languages") + .long("list-languages") + .help("Displays supported languages"), + ) + .subcommand( + SubCommand::with_name("cache") + .about("Modify the syntax-definition and theme cache") + .arg( + Arg::with_name("init") + .long("init") + .short("i") + .help("Initialize the cache by loading from the config dir"), + ) + .arg( + Arg::with_name("clear") + .long("clear") + .short("c") + .help("Reset the cache"), + ) + .arg( + Arg::with_name("config-dir") + .long("config-dir") + .short("d") + .help("Show the configuration directory"), + ) + .group( + ArgGroup::with_name("cache-actions") + .args(&["init", "clear", "config-dir"]) + .required(true), + ), + ) + .help_message("Print this help message.") + .version_message("Show version information.") + .get_matches() + } + + pub fn config(&self) -> Result { + let files = self.files(); + + Ok(Config { + true_color: is_truecolor_terminal(), + output_components: self.output_components()?, + language: self.matches.value_of("language"), + colored_output: match self.matches.value_of("color") { + Some("always") => true, + Some("never") => false, + Some("auto") | _ => self.interactive_output, + }, + paging: match self.matches.value_of("paging") { + Some("always") => true, + Some("never") => false, + Some("auto") | _ => if files.contains(&None) { + // If we are reading from stdin, only enable paging if we write to an + // interactive terminal and if we do not *read* from an interactive + // terminal. + self.interactive_output && !atty::is(Stream::Stdin) + } else { + self.interactive_output + }, + }, + term_width: Term::stdout().size().1 as usize, + files, + }) + } + + fn files(&self) -> Vec> { + self.matches + .values_of("FILE") + .map(|values| { + values + .map(|filename| { + if filename == "-" { + None + } else { + Some(filename) + } + }) + .collect() + }) + .unwrap_or_else(|| vec![None]) // read from stdin (None) if no args are given + } + + fn output_components(&self) -> Result { + let matches = &self.matches; + Ok(OutputComponents( + values_t!(matches.values_of("style"), OutputComponent)? + .into_iter() + .map(|style| style.components(self.interactive_output)) + .fold(HashSet::new(), |mut acc, components| { + acc.extend(components.iter().cloned()); + acc + }), + )) + } +} + +pub struct Config<'a> { + pub true_color: bool, + pub output_components: OutputComponents, + pub language: Option<&'a str>, + pub colored_output: bool, + pub paging: bool, + pub term_width: usize, + pub files: Vec>, +} + +fn is_truecolor_terminal() -> bool { + env::var("COLORTERM") + .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit") + .unwrap_or(false) +} diff --git a/src/main.rs b/src/main.rs index 36376200..6ece21a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,13 +17,13 @@ extern crate directories; extern crate git2; extern crate syntect; +mod app; mod assets; mod diff; mod printer; mod terminal; use std::collections::HashSet; -use std::env; use std::fs::{self, File}; use std::io::{self, BufRead, BufReader, Write}; use std::process::{self, Child, Command, Stdio}; @@ -34,13 +34,12 @@ use std::os::unix::fs::FileTypeExt; use ansi_term::Colour::{Fixed, Green, Red, White, Yellow}; use ansi_term::Style; -use atty::Stream; -use clap::{App, AppSettings, Arg, ArgGroup, SubCommand}; use syntect::easy::HighlightLines; use syntect::highlighting::Theme; use syntect::parsing::SyntaxSet; +use app::{App, Config}; use assets::{config_dir, syntax_set_path, theme_set_path, HighlightingAssets}; use diff::get_git_diff; use printer::Printer; @@ -215,7 +214,7 @@ impl Colors { } fn print_file( - options: &Options, + config: &Config, theme: &Theme, syntax_set: &SyntaxSet, printer: &mut Printer, @@ -228,7 +227,7 @@ fn print_file( Some(filename) => Box::new(BufReader::new(File::open(filename)?)), }; - let syntax = match (options.language, filename) { + let syntax = match (config.language, filename) { (Some(language), _) => syntax_set.find_syntax_by_token(language), (None, Some(filename)) => { #[cfg(not(unix))] @@ -292,110 +291,10 @@ fn get_output_type(paging: bool) -> OutputType { } } -fn is_truecolor_terminal() -> bool { - env::var("COLORTERM") - .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit") - .unwrap_or(false) -} - fn run() -> Result<()> { - let interactive_terminal = atty::is(Stream::Stdout); + let app = App::new(); - let clap_color_setting = if interactive_terminal { - AppSettings::ColoredHelp - } else { - AppSettings::ColorNever - }; - - let app_matches = App::new(crate_name!()) - .version(crate_version!()) - .global_setting(clap_color_setting) - .global_setting(AppSettings::DeriveDisplayOrder) - .global_setting(AppSettings::UnifiedHelpMessage) - .global_setting(AppSettings::NextLineHelp) - .setting(AppSettings::InferSubcommands) - .setting(AppSettings::ArgsNegateSubcommands) - .setting(AppSettings::DisableHelpSubcommand) - .setting(AppSettings::VersionlessSubcommands) - .max_term_width(90) - .about(crate_description!()) - .arg( - Arg::with_name("language") - .short("l") - .long("language") - .help("Set the language for highlighting") - .takes_value(true), - ) - .arg( - Arg::with_name("FILE") - .help("File(s) to print") - .multiple(true) - .empty_values(false), - ) - .arg( - Arg::with_name("style") - .long("style") - .use_delimiter(true) - .takes_value(true) - .possible_values(&[ - "auto", "full", "plain", "changes", "header", "grid", "numbers", - ]) - .default_value("auto") - .help("Additional info to display along with content"), - ) - .arg( - Arg::with_name("color") - .long("color") - .takes_value(true) - .possible_values(&["auto", "never", "always"]) - .default_value("auto") - .help("When to use colors"), - ) - .arg( - Arg::with_name("paging") - .long("paging") - .takes_value(true) - .possible_values(&["auto", "never", "always"]) - .default_value("auto") - .help("When to use the pager"), - ) - .arg( - Arg::with_name("list-languages") - .long("list-languages") - .help("Displays supported languages"), - ) - .subcommand( - SubCommand::with_name("cache") - .about("Modify the syntax-definition and theme cache") - .arg( - Arg::with_name("init") - .long("init") - .short("i") - .help("Initialize the cache by loading from the config dir"), - ) - .arg( - Arg::with_name("clear") - .long("clear") - .short("c") - .help("Reset the cache"), - ) - .arg( - Arg::with_name("config-dir") - .long("config-dir") - .short("d") - .help("Show the configuration directory"), - ) - .group( - ArgGroup::with_name("cache-actions") - .args(&["init", "clear", "config-dir"]) - .required(true), - ), - ) - .help_message("Print this help message.") - .version_message("Show version information.") - .get_matches(); - - match app_matches.subcommand() { + match app.matches.subcommand() { ("cache", Some(cache_matches)) => { if cache_matches.is_present("init") { let assets = HighlightingAssets::from_files()?; @@ -413,57 +312,12 @@ fn run() -> Result<()> { } } _ => { - let files: Vec> = app_matches - .values_of("FILE") - .map(|values| { - values - .map(|filename| { - if filename == "-" { - None - } else { - Some(filename) - } - }) - .collect() - }) - .unwrap_or_else(|| vec![None]); // read from stdin (None) if no args are given - - let output_components = values_t!(app_matches.values_of("style"), OutputComponent)? - .into_iter() - .map(|style| style.components(interactive_terminal)) - .fold(HashSet::new(), |mut acc, components| { - acc.extend(components.iter().cloned()); - acc - }); - - let options = Options { - true_color: is_truecolor_terminal(), - output_components: OutputComponents(output_components), - language: app_matches.value_of("language"), - colored_output: match app_matches.value_of("color") { - Some("always") => true, - Some("never") => false, - _ => interactive_terminal, - }, - paging: match app_matches.value_of("paging") { - Some("always") => true, - Some("never") => false, - Some("auto") | _ => if files.contains(&None) { - // If we are reading from stdin, only enable paging if we write to an - // interactive terminal and if we do not *read* from an interactive - // terminal. - interactive_terminal && !atty::is(Stream::Stdin) - } else { - interactive_terminal - }, - }, - term_width: console::Term::stdout().size().1 as usize, - }; + let config = app.config()?; let assets = HighlightingAssets::new(); let theme = assets.default_theme()?; - if app_matches.is_present("list-languages") { + if app.matches.is_present("list-languages") { let languages = assets.syntax_set.syntaxes(); let longest = languages @@ -481,7 +335,7 @@ fn run() -> Result<()> { print!("{:width$}{}", lang.name, separator, width = longest); // Line-wrapping for the possible file extension overflow. - let desired_width = options.term_width - longest - separator.len(); + let desired_width = config.term_width - longest - separator.len(); // Number of characters on this line so far, wrap before `desired_width` let mut num_chars = 0; @@ -507,14 +361,14 @@ fn run() -> Result<()> { return Ok(()); } - let mut output_type = get_output_type(options.paging); + let mut output_type = get_output_type(config.paging); let handle = output_type.handle()?; - let mut printer = Printer::new(handle, &options); + let mut printer = Printer::new(handle, &config); - for file in files { + for file in &config.files { printer.line_changes = file.and_then(|filename| get_git_diff(filename)); - print_file(&options, theme, &assets.syntax_set, &mut printer, file)?; + print_file(&config, theme, &assets.syntax_set, &mut printer, *file)?; } } } diff --git a/src/printer.rs b/src/printer.rs index 30e06a66..ed36ddac 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,23 +1,24 @@ use ansi_term::Style; +use app::Config; use diff::{LineChange, LineChanges}; use errors::*; use std::io::Write; use syntect::highlighting; use terminal::as_terminal_escaped; -use {Colors, Options}; +use Colors; const PANEL_WIDTH: usize = 7; pub struct Printer<'a> { handle: &'a mut Write, colors: Colors, - options: &'a Options<'a>, + config: &'a Config<'a>, pub line_changes: Option, } impl<'a> Printer<'a> { - pub fn new(handle: &'a mut Write, options: &'a Options) -> Self { - let colors = if options.colored_output { + pub fn new(handle: &'a mut Write, config: &'a Config) -> Self { + let colors = if config.colored_output { Colors::colored() } else { Colors::plain() @@ -26,17 +27,17 @@ impl<'a> Printer<'a> { Printer { handle, colors, - options, + config, line_changes: None, } } pub fn print_header(&mut self, filename: Option<&str>) -> Result<()> { - if !self.options.output_components.header() { + if !self.config.output_components.header() { return Ok(()); } - if self.options.output_components.grid() { + if self.config.output_components.grid() { self.print_horizontal_line('┬')?; write!( @@ -54,7 +55,7 @@ impl<'a> Printer<'a> { self.colors.filename.paint(filename.unwrap_or("STDIN")) )?; - if self.options.output_components.grid() { + if self.config.output_components.grid() { self.print_horizontal_line('┼')?; } @@ -62,7 +63,7 @@ impl<'a> Printer<'a> { } pub fn print_footer(&mut self) -> Result<()> { - if self.options.output_components.grid() { + if self.config.output_components.grid() { self.print_horizontal_line('┴') } else { Ok(()) @@ -80,12 +81,12 @@ impl<'a> Printer<'a> { self.print_line_border(), Some(as_terminal_escaped( ®ions, - self.options.true_color, - self.options.colored_output, + self.config.true_color, + self.config.colored_output, )), ]; - let grid_requested = self.options.output_components.grid(); + let grid_requested = self.config.output_components.grid(); write!( self.handle, "{}", @@ -104,14 +105,14 @@ impl<'a> Printer<'a> { } fn print_line_number(&self, line_number: usize) -> Option { - if self.options.output_components.numbers() { + if self.config.output_components.numbers() { Some( self.colors .line_number .paint(format!("{:4}", line_number)) .to_string(), ) - } else if self.options.output_components.grid() { + } else if self.config.output_components.grid() { Some(" ".to_owned()) } else { None @@ -119,7 +120,7 @@ impl<'a> Printer<'a> { } fn print_git_marker(&self, line_number: usize) -> Option { - if self.options.output_components.changes() { + if self.config.output_components.changes() { Some( if let Some(ref changes) = self.line_changes { match changes.get(&(line_number as u32)) { @@ -133,7 +134,7 @@ impl<'a> Printer<'a> { Style::default().paint(" ") }.to_string(), ) - } else if self.options.output_components.grid() { + } else if self.config.output_components.grid() { Some(" ".to_owned()) } else { None @@ -141,7 +142,7 @@ impl<'a> Printer<'a> { } fn print_line_border(&self) -> Option { - if self.options.output_components.grid() { + if self.config.output_components.grid() { Some(self.colors.grid.paint("│").to_string()) } else { None @@ -149,7 +150,7 @@ impl<'a> Printer<'a> { } fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> { - let hline = "─".repeat(self.options.term_width - (PANEL_WIDTH + 1)); + let hline = "─".repeat(self.config.term_width - (PANEL_WIDTH + 1)); let hline = format!("{}{}{}", "─".repeat(PANEL_WIDTH), grid_char, hline); writeln!(self.handle, "{}", self.colors.grid.paint(hline))?;