@@ -14,6 +14,7 @@ extern crate ansi_term;
1414extern crate atty;
1515extern crate console;
1616extern crate directories;
17+extern crate either;
1718extern crate git2;
1819extern crate syntect;
1920
@@ -26,6 +27,7 @@ use std::fs::{self, File};
2627use std::io::{self, BufRead, BufReader, Write};
2728use std::path::{Path, PathBuf};
2829use std::process::{self, Child, Command, Stdio};
30+use std::str::FromStr;
2931
3032#[cfg(unix)]
3133use std::os::unix::fs::FileTypeExt;
@@ -35,6 +37,7 @@ use ansi_term::Style;
3537use atty::Stream;
3638use clap::{App, AppSettings, Arg, ArgGroup, SubCommand};
3739use directories::ProjectDirs;
40+use either::Either;
3841use git2::{DiffOptions, IntoCString, Repository};
3942
4043use syntect::dumps::{dump_to_file, from_binary, from_reader};
@@ -53,20 +56,79 @@ mod errors {
5356 foreign_links {
5457 Io(::std::io::Error);
5558 }
59+
60+ errors {
61+ NoCorrectStylesSpecified {
62+ description("no correct styles specified")
63+ }
64+
65+ UnknownStyleName(name: String) {
66+ description("unknown style name")
67+ display("unknown style name: '{}'", name)
68+ }
69+ }
5670 }
5771}
5872
5973use errors::*;
6074
61−pub enum OptionsStyle {
62− Plain,
63− LineNumbers,
75+#[derive(Debug, Eq, PartialEq)]
76+pub enum OutputComponent {
77+ Changes,
78+ Grid,
79+ Header,
80+ Numbers,
81+}
82+
83+impl FromStr for OutputComponent {
84+ type Err = Error;
85+
86+ fn from_str(s: &str) -> Result<Self> {
87+ match s {
88+ "changes" => Ok(OutputComponent::Changes),
89+ "grid" => Ok(OutputComponent::Grid),
90+ "header" => Ok(OutputComponent::Header),
91+ "numbers" => Ok(OutputComponent::Numbers),
92+ _ => Err(ErrorKind::UnknownStyleName(s.to_owned()).into()),
93+ }
94+ }
95+}
96+
97+#[derive(Debug, Eq, PartialEq)]
98+enum PredefinedStyle {
6499 Full,
100+ Plain,
101+}
102+
103+impl PredefinedStyle {
104+ fn components(&self) -> &'static [OutputComponent] {
105+ match *self {
106+ PredefinedStyle::Full => &[
107+ OutputComponent::Changes,
108+ OutputComponent::Grid,
109+ OutputComponent::Header,
110+ OutputComponent::Numbers,
111+ ],
112+ PredefinedStyle::Plain => &[],
113+ }
114+ }
115+}
116+
117+impl FromStr for PredefinedStyle {
118+ type Err = Error;
119+
120+ fn from_str(s: &str) -> Result<Self> {
121+ match s {
122+ "full" => Ok(PredefinedStyle::Full),
123+ "plain" => Ok(PredefinedStyle::Plain),
124+ _ => Err(ErrorKind::UnknownStyleName(s.to_owned()).into()),
125+ }
126+ }
65127}
66128
67129pub struct Options<'a> {
68130 pub true_color: bool,
69− pub style: OptionsStyle,
131+ pub output_components: &'a [OutputComponent],
70132 pub language: Option<&'a str>,
71133 pub colored_output: bool,
72134 pub paging: bool,
@@ -456,8 +518,10 @@ fn run() -> Result<()> {
456518 .arg(
457519 Arg::with_name("style")
458520 .long("style")
459− .possible_values(&["auto", "plain", "line-numbers", "full"])
460− .default_value("auto")
521+ .use_delimiter(true)
522+ .takes_value(true)
523+ .possible_values(&["full", "plain", "changes", "header", "grid", "numbers"])
524+ .default_value("full")
461525 .help("Additional info to display along with content"),
462526 )
463527 .arg(
@@ -545,18 +609,63 @@ fn run() -> Result<()> {
545609 })
546610 .unwrap_or_else(|| vec![None]); // read from stdin (None) if no args are given
547611
612+ // Split the user-supplied parameter on commas (','), then go over each part and map it
613+ // to either a predefined style or a simple output style. Once this mapping is done,
614+ // split it up into two lists: one for the matched predefined styles and one for the
615+ // simple output styles.
616+ let (_predefined_styles, _output_components): (Vec<_>, Vec<_>) = app_matches
617+ .values_of("style")
618+ .unwrap()
619+ .map(|s| {
620+ let predefined_style = PredefinedStyle::from_str(s);
621+ if let Ok(style) = predefined_style {
622+ Some(Either::Left(style))
623+ } else {
624+ match OutputComponent::from_str(s) {
625+ Ok(style) => Some(Either::Right(style)),
626+ Err(error) => {
627+ eprintln!("{}: {}", Red.paint("[bat error]"), error);
628+ None
629+ }
630+ }
631+ }
632+ })
633+ .filter_map(|s| s)
634+ .partition(|style| match *style {
635+ Either::Left(_) => true,
636+ Either::Right(_) => false,
637+ });
638+
639+ // Once we have the two lists of predefined styles and simple output styles, we take the
640+ // first matched predefined style and use that as our style, discarding every other
641+ // predefined or simple output style. If we have no predefined styles though, we take
642+ // all matched simple output styles and pass those to the `Options` struct.
643+ let output_components: Vec<OutputComponent>;
644+ let output_components = if let Some(predefined_style) = _predefined_styles
645+ .into_iter()
646+ .map(|e| e.left().unwrap())
647+ .next()
648+ {
649+ predefined_style.components()
650+ } else {
651+ output_components = _output_components
652+ .into_iter()
653+ .map(|e| e.right().unwrap())
654+ .collect::<Vec<_>>();
655+
656+ // If we are in this else-block, `output_components` is empty and the requested style
657+ // wasn't the empty string, the user supplied only wrong style arguments. In this
658+ // case we inform them about this.
659+ if output_components.is_empty() && app_matches.value_of("style").unwrap() != "" {
660+ return Err(ErrorKind::NoCorrectStylesSpecified.into());
661+ }
662+
663+ &output_components
664+ };
665+
548666 let options = Options {
549667 true_color: is_truecolor_terminal(),
550− style: match app_matches.value_of("style") {
551− Some("plain") => OptionsStyle::Plain,
552− Some("line-numbers") => OptionsStyle::LineNumbers,
553− Some("full") => OptionsStyle::Full,
554− Some("auto") | _ => if interactive_terminal {
555− OptionsStyle::Full
556− } else {
557− OptionsStyle::Plain
558− },
559− },
668+ output_components,
560669 language: app_matches.value_of("language"),
561670 colored_output: match app_matches.value_of("color") {
562671 Some("always") => true,