Split app to separate module

This commit is contained in:
Ezinwa Okpoechi 2018-05-10 23:39:13 +02:00 committed by David Peter
parent ccf88fd5d8
commit 64a9341b73
3 changed files with 226 additions and 177 deletions

194
src/app.rs Normal file
View File

@ -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<Config> {
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<Option<&str>> {
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<OutputComponents> {
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<Option<&'a str>>,
}
fn is_truecolor_terminal() -> bool {
env::var("COLORTERM")
.map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
.unwrap_or(false)
}

View File

@ -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<Option<&str>> = 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)?;
}
}
}

View File

@ -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<LineChanges>,
}
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(
&regions,
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<String> {
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<String> {
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<String> {
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))?;