diff --git a/src/main.rs b/src/main.rs index 773f3432dc..b3982d1324 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,12 +20,13 @@ extern crate syntect; mod printer; mod terminal; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::env; use std::fs::{self, File}; use std::io::{self, BufRead, BufReader, Write}; use std::path::{Path, PathBuf}; use std::process::{self, Child, Command, Stdio}; +use std::str::FromStr; #[cfg(unix)] use std::os::unix::fs::FileTypeExt; @@ -51,22 +52,92 @@ lazy_static! { mod errors { error_chain! { foreign_links { + Clap(::clap::Error); Io(::std::io::Error); } + + errors { + NoCorrectStylesSpecified { + description("no correct styles specified") + } + + UnknownStyleName(name: String) { + description("unknown style name") + display("unknown style name: '{}'", name) + } + } } } use errors::*; -pub enum OptionsStyle { - Plain, - LineNumbers, +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +pub enum OutputComponent { + Changes, + Grid, + Header, + Numbers, Full, + Plain, +} + +impl OutputComponent { + fn components(&self) -> &'static [OutputComponent] { + match *self { + OutputComponent::Changes => &[OutputComponent::Changes], + OutputComponent::Grid => &[OutputComponent::Grid], + OutputComponent::Header => &[OutputComponent::Header], + OutputComponent::Numbers => &[OutputComponent::Numbers], + OutputComponent::Full => &[ + OutputComponent::Changes, + OutputComponent::Grid, + OutputComponent::Header, + OutputComponent::Numbers, + ], + OutputComponent::Plain => &[], + } + } +} + +impl FromStr for OutputComponent { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "changes" => Ok(OutputComponent::Changes), + "grid" => Ok(OutputComponent::Grid), + "header" => Ok(OutputComponent::Header), + "numbers" => Ok(OutputComponent::Numbers), + "full" => Ok(OutputComponent::Full), + "plain" => Ok(OutputComponent::Plain), + _ => Err(ErrorKind::UnknownStyleName(s.to_owned()).into()), + } + } +} + +pub struct OutputComponents(HashSet); + +impl OutputComponents { + fn changes(&self) -> bool { + self.0.contains(&OutputComponent::Changes) + } + + fn grid(&self) -> bool { + self.0.contains(&OutputComponent::Grid) + } + + fn header(&self) -> bool { + self.0.contains(&OutputComponent::Header) + } + + fn numbers(&self) -> bool { + self.0.contains(&OutputComponent::Numbers) + } } pub struct Options<'a> { pub true_color: bool, - pub style: OptionsStyle, + pub output_components: OutputComponents, pub language: Option<&'a str>, pub colored_output: bool, pub paging: bool, @@ -456,8 +527,10 @@ fn run() -> Result<()> { .arg( Arg::with_name("style") .long("style") - .possible_values(&["auto", "plain", "line-numbers", "full"]) - .default_value("auto") + .use_delimiter(true) + .takes_value(true) + .possible_values(&["full", "plain", "changes", "header", "grid", "numbers"]) + .default_value("full") .help("Additional info to display along with content"), ) .arg( @@ -545,18 +618,17 @@ fn run() -> Result<()> { }) .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()) + .fold(HashSet::new(), |mut acc, components| { + acc.extend(components.iter().cloned()); + acc + }); + let options = Options { true_color: is_truecolor_terminal(), - style: match app_matches.value_of("style") { - Some("plain") => OptionsStyle::Plain, - Some("line-numbers") => OptionsStyle::LineNumbers, - Some("full") => OptionsStyle::Full, - Some("auto") | _ => if interactive_terminal { - OptionsStyle::Full - } else { - OptionsStyle::Plain - }, - }, + output_components: OutputComponents(output_components), language: app_matches.value_of("language"), colored_output: match app_matches.value_of("color") { Some("always") => true, diff --git a/src/printer.rs b/src/printer.rs index 0d0a84c5d1..224702496a 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -3,7 +3,7 @@ use errors::*; use std::io::Write; use syntect::highlighting; use terminal::as_terminal_escaped; -use {Colors, LineChange, LineChanges, Options, OptionsStyle}; +use {Colors, LineChange, LineChanges, Options}; const PANEL_WIDTH: usize = 7; @@ -31,19 +31,20 @@ impl<'a> Printer<'a> { } pub fn print_header(&mut self, filename: Option<&str>) -> Result<()> { - match self.options.style { - OptionsStyle::Full => {} - _ => return Ok(()), + if !self.options.output_components.header() { + return Ok(()); } - self.print_horizontal_line('┬')?; + if self.options.output_components.grid() { + self.print_horizontal_line('┬')?; - write!( - self.handle, - "{}{} ", - " ".repeat(PANEL_WIDTH), - self.colors.grid.paint("│"), - )?; + write!( + self.handle, + "{}{} ", + " ".repeat(PANEL_WIDTH), + self.colors.grid.paint("│"), + )?; + } writeln!( self.handle, @@ -52,11 +53,15 @@ impl<'a> Printer<'a> { self.colors.filename.paint(filename.unwrap_or("STDIN")) )?; - self.print_horizontal_line('┼') + if self.options.output_components.grid() { + self.print_horizontal_line('┼')?; + } + + Ok(()) } pub fn print_footer(&mut self) -> Result<()> { - if let OptionsStyle::Full = self.options.style { + if self.options.output_components.grid() { self.print_horizontal_line('┴') } else { Ok(()) @@ -79,12 +84,17 @@ impl<'a> Printer<'a> { )), ]; + let grid_requested = self.options.output_components.grid(); write!( self.handle, "{}", decorations .into_iter() - .filter_map(|dec| dec) + .filter_map(|dec| if grid_requested { + Some(dec.unwrap_or(" ".to_owned())) + } else { + dec + }) .collect::>() .join(" ") )?; @@ -93,45 +103,48 @@ impl<'a> Printer<'a> { } fn print_line_number(&self, line_number: usize) -> Option { - if let OptionsStyle::Plain = self.options.style { - return None; + if self.options.output_components.numbers() { + Some( + self.colors + .line_number + .paint(format!("{:4}", line_number)) + .to_string(), + ) + } else if self.options.output_components.grid() { + Some(" ".to_owned()) + } else { + None } - - Some( - self.colors - .line_number - .paint(format!("{:4}", line_number)) - .to_string(), - ) } fn print_git_marker(&self, line_number: usize) -> Option { - match self.options.style { - OptionsStyle::Full => {} - _ => return None, - } - - let marker = if let Some(ref changes) = self.line_changes { - match changes.get(&(line_number as u32)) { - Some(&LineChange::Added) => self.colors.git_added.paint("+"), - Some(&LineChange::RemovedAbove) => self.colors.git_removed.paint("‾"), - Some(&LineChange::RemovedBelow) => self.colors.git_removed.paint("_"), - Some(&LineChange::Modified) => self.colors.git_modified.paint("~"), - _ => Style::default().paint(" "), - } + if self.options.output_components.changes() { + Some( + if let Some(ref changes) = self.line_changes { + match changes.get(&(line_number as u32)) { + Some(&LineChange::Added) => self.colors.git_added.paint("+"), + Some(&LineChange::RemovedAbove) => self.colors.git_removed.paint("‾"), + Some(&LineChange::RemovedBelow) => self.colors.git_removed.paint("_"), + Some(&LineChange::Modified) => self.colors.git_modified.paint("~"), + _ => Style::default().paint(" "), + } + } else { + Style::default().paint(" ") + }.to_string(), + ) + } else if self.options.output_components.grid() { + Some(" ".to_owned()) } else { - Style::default().paint(" ") - }; - - Some(marker.to_string()) + None + } } fn print_line_border(&self) -> Option { - if let OptionsStyle::Plain = self.options.style { - return None; + if self.options.output_components.grid() { + Some(self.colors.grid.paint("│").to_string()) + } else { + None } - - Some(self.colors.grid.paint("│").to_string()) } fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> {