summaryrefslogtreecommitdiffstats
path: root/src/parse_style.rs
diff options
context:
space:
mode:
authorDan Davison <dandavison7@gmail.com>2020-06-26 18:50:47 -0400
committerDan Davison <dandavison7@gmail.com>2020-06-26 18:50:47 -0400
commit472c5a509d5cc9e57bad476e4cd852e4b9459a01 (patch)
treeee97403ba99466e065f50a32ae9e9196e143300f /src/parse_style.rs
parentb83cee3707159c4b6bc0c5070a84d78f73017578 (diff)
Refactor: parse_style module
Diffstat (limited to 'src/parse_style.rs')
-rw-r--r--src/parse_style.rs702
1 files changed, 702 insertions, 0 deletions
diff --git a/src/parse_style.rs b/src/parse_style.rs
new file mode 100644
index 00000000..ffe514d5
--- /dev/null
+++ b/src/parse_style.rs
@@ -0,0 +1,702 @@
+use std::process;
+
+use bitflags::bitflags;
+
+use crate::color;
+use crate::config::unreachable;
+use crate::style::{DecorationStyle, Style};
+
+impl Style {
+ /// Construct Style from style and decoration-style strings supplied on command line, together
+ /// with defaults. A style string is a space-separated string containing 0, 1, or 2 colors
+ /// (foreground and then background) and an arbitrary number of style attributes. See `delta
+ /// --help` for more precise spec.
+ pub fn from_str(
+ style_string: &str,
+ foreground_default: Option<ansi_term::Color>,
+ background_default: Option<ansi_term::Color>,
+ decoration_style_string: Option<&str>,
+ true_color: bool,
+ is_emph: bool,
+ ) -> Self {
+ let (ansi_term_style, is_omitted, is_raw, is_syntax_highlighted) = parse_ansi_term_style(
+ &style_string,
+ foreground_default,
+ background_default,
+ true_color,
+ );
+ let decoration_style =
+ DecorationStyle::from_str(decoration_style_string.unwrap_or(""), true_color);
+ Self {
+ ansi_term_style,
+ is_emph,
+ is_omitted,
+ is_raw,
+ is_syntax_highlighted,
+ decoration_style,
+ }
+ }
+
+ /// Construct Style but interpreting 'ul', 'box', etc as applying to the decoration style.
+ fn from_str_with_handling_of_special_decoration_attributes(
+ style_string: &str,
+ foreground_default: Option<ansi_term::Color>,
+ background_default: Option<ansi_term::Color>,
+ decoration_style_string: Option<&str>,
+ true_color: bool,
+ is_emph: bool,
+ ) -> Self {
+ let (special_attributes_from_style_string, style_string) =
+ extract_special_decoration_attributes_from_non_decoration_style_string(style_string);
+ let mut style = Style::from_str(
+ &style_string,
+ foreground_default,
+ background_default,
+ decoration_style_string.as_deref(),
+ true_color,
+ is_emph,
+ );
+ // TODO: box in this context resulted in box-with-underline for commit and file
+ style.decoration_style = DecorationStyle::apply_special_decoration_attributes(
+ &mut style,
+ special_attributes_from_style_string,
+ );
+ style
+ }
+
+ /// As from_str_with_handling_of_special_decoration_attributes but respecting an optional
+ /// foreground color which has precedence when present.
+ pub fn from_str_with_handling_of_special_decoration_attributes_and_respecting_deprecated_foreground_color_arg(
+ style_string: &str,
+ foreground_default: Option<ansi_term::Color>,
+ background_default: Option<ansi_term::Color>,
+ decoration_style_string: Option<&str>,
+ deprecated_foreground_color_arg: Option<&str>,
+ true_color: bool,
+ is_emph: bool,
+ ) -> Self {
+ let mut style = Self::from_str_with_handling_of_special_decoration_attributes(
+ style_string,
+ foreground_default,
+ background_default,
+ decoration_style_string,
+ true_color,
+ is_emph,
+ );
+ if let Some(s) = deprecated_foreground_color_arg {
+ // The deprecated --{commit,file,hunk}-color args functioned to set the decoration
+ // foreground color. In the case of file, it set the text foreground color also.
+ let foreground_from_deprecated_arg = parse_ansi_term_style(s, None, None, true_color)
+ .0
+ .foreground;
+ style.ansi_term_style.foreground = foreground_from_deprecated_arg;
+ style.decoration_style = match style.decoration_style {
+ DecorationStyle::Box(mut ansi_term_style) => {
+ ansi_term_style.foreground = foreground_from_deprecated_arg;
+ DecorationStyle::Box(ansi_term_style)
+ }
+ DecorationStyle::Underline(mut ansi_term_style) => {
+ ansi_term_style.foreground = foreground_from_deprecated_arg;
+ DecorationStyle::Underline(ansi_term_style)
+ }
+ DecorationStyle::Overline(mut ansi_term_style) => {
+ ansi_term_style.foreground = foreground_from_deprecated_arg;
+ DecorationStyle::Overline(ansi_term_style)
+ }
+ DecorationStyle::UnderOverline(mut ansi_term_style) => {
+ ansi_term_style.foreground = foreground_from_deprecated_arg;
+ DecorationStyle::UnderOverline(ansi_term_style)
+ }
+ DecorationStyle::BoxWithUnderline(mut ansi_term_style) => {
+ ansi_term_style.foreground = foreground_from_deprecated_arg;
+ DecorationStyle::BoxWithUnderline(ansi_term_style)
+ }
+ DecorationStyle::BoxWithOverline(mut ansi_term_style) => {
+ ansi_term_style.foreground = foreground_from_deprecated_arg;
+ DecorationStyle::BoxWithOverline(ansi_term_style)
+ }
+ DecorationStyle::BoxWithUnderOverline(mut ansi_term_style) => {
+ ansi_term_style.foreground = foreground_from_deprecated_arg;
+ DecorationStyle::BoxWithUnderOverline(ansi_term_style)
+ }
+ DecorationStyle::NoDecoration => style.decoration_style,
+ };
+ }
+ style
+ }
+}
+
+bitflags! {
+ struct DecorationAttributes: u8 {
+ const EMPTY = 0b00000000;
+ const BOX = 0b00000001;
+ const OVERLINE = 0b00000010;
+ const UNDERLINE = 0b00000100;
+ }
+}
+
+impl DecorationStyle {
+ pub fn from_str(style_string: &str, true_color: bool) -> Self {
+ let (special_attributes, style_string) =
+ extract_special_decoration_attributes(&style_string);
+ let (style, is_omitted, is_raw, is_syntax_highlighted) =
+ parse_ansi_term_style(&style_string, None, None, true_color);
+ if is_raw {
+ eprintln!("'raw' may not be used in a decoration style.");
+ process::exit(1);
+ };
+ if is_syntax_highlighted {
+ eprintln!("'syntax' may not be used in a decoration style.");
+ process::exit(1);
+ };
+ #[allow(non_snake_case)]
+ let (BOX, UL, OL, EMPTY) = (
+ DecorationAttributes::BOX,
+ DecorationAttributes::UNDERLINE,
+ DecorationAttributes::OVERLINE,
+ DecorationAttributes::EMPTY,
+ );
+ match special_attributes {
+ bits if bits == EMPTY => DecorationStyle::NoDecoration,
+ bits if bits == BOX => DecorationStyle::Box(style),
+ bits if bits == UL => DecorationStyle::Underline(style),
+ bits if bits == OL => DecorationStyle::Overline(style),
+ bits if bits == UL | OL => DecorationStyle::UnderOverline(style),
+ bits if bits == BOX | UL => DecorationStyle::BoxWithUnderline(style),
+ bits if bits == BOX | OL => DecorationStyle::BoxWithOverline(style),
+ bits if bits == BOX | UL | OL => DecorationStyle::BoxWithUnderOverline(style),
+ _ if is_omitted => DecorationStyle::NoDecoration,
+ _ => unreachable("Unreachable code path reached in parse_decoration_style."),
+ }
+ }
+
+ fn apply_special_decoration_attributes(
+ style: &mut Style,
+ special_attributes: DecorationAttributes,
+ ) -> DecorationStyle {
+ let ansi_term_style = match style.decoration_style {
+ DecorationStyle::Box(ansi_term_style) => ansi_term_style,
+ DecorationStyle::Underline(ansi_term_style) => ansi_term_style,
+ DecorationStyle::Overline(ansi_term_style) => ansi_term_style,
+ DecorationStyle::UnderOverline(ansi_term_style) => ansi_term_style,
+ DecorationStyle::BoxWithUnderline(ansi_term_style) => ansi_term_style,
+ DecorationStyle::BoxWithOverline(ansi_term_style) => ansi_term_style,
+ DecorationStyle::BoxWithUnderOverline(ansi_term_style) => ansi_term_style,
+ DecorationStyle::NoDecoration => ansi_term::Style::new(),
+ };
+ #[allow(non_snake_case)]
+ let (BOX, UL, OL, EMPTY) = (
+ DecorationAttributes::BOX,
+ DecorationAttributes::UNDERLINE,
+ DecorationAttributes::OVERLINE,
+ DecorationAttributes::EMPTY,
+ );
+ match special_attributes {
+ bits if bits == EMPTY => style.decoration_style,
+ bits if bits == BOX => DecorationStyle::Box(ansi_term_style),
+ bits if bits == UL => DecorationStyle::Underline(ansi_term_style),
+ bits if bits == OL => DecorationStyle::Overline(ansi_term_style),
+ bits if bits == UL | OL => DecorationStyle::UnderOverline(ansi_term_style),
+ bits if bits == BOX | UL => DecorationStyle::BoxWithUnderline(ansi_term_style),
+ bits if bits == BOX | OL => DecorationStyle::BoxWithOverline(ansi_term_style),
+ bits if bits == BOX | UL | OL => DecorationStyle::BoxWithUnderOverline(ansi_term_style),
+ _ => DecorationStyle::NoDecoration,
+ }
+ }
+}
+
+fn parse_ansi_term_style(
+ s: &str,
+ foreground_default: Option<ansi_term::Color>,
+ background_default: Option<ansi_term::Color>,
+ true_color: bool,
+) -> (ansi_term::Style, bool, bool, bool) {
+ let mut style = ansi_term::Style::new();
+ let mut seen_foreground = false;
+ let mut seen_background = false;
+ let mut is_omitted = false;
+ let mut is_raw = false;
+ let mut is_syntax_highlighted = false;
+ for word in s
+ .to_lowercase()
+ .split_whitespace()
+ .map(|word| word.trim_matches(|c| c == '"' || c == '\''))
+ {
+ if word == "blink" {
+ style.is_blink = true;
+ } else if word == "bold" {
+ style.is_bold = true;
+ } else if word == "dim" {
+ style.is_dimmed = true;
+ } else if word == "hidden" {
+ style.is_hidden = true;
+ } else if word == "italic" {
+ style.is_italic = true;
+ } else if word == "omit" {
+ is_omitted = true;
+ } else if word == "reverse" {
+ style.is_reverse = true;
+ } else if word == "raw" {
+ is_raw = true;
+ } else if word == "strike" {
+ style.is_strikethrough = true;
+ } else if word == "ul" || word == "underline" {
+ style.is_underline = true;
+ } else if !seen_foreground {
+ if word == "syntax" {
+ is_syntax_highlighted = true;
+ } else {
+ style.foreground = color::color_from_rgb_or_ansi_code_with_default(
+ word,
+ foreground_default,
+ true_color,
+ );
+ }
+ seen_foreground = true;
+ } else if !seen_background {
+ if word == "syntax" {
+ eprintln!(
+ "You have used the special color 'syntax' as a background color \
+ (second color in a style string). It may only be used as a foreground \
+ color (first color in a style string)."
+ );
+ process::exit(1);
+ } else {
+ style.background = color::color_from_rgb_or_ansi_code_with_default(
+ word,
+ background_default,
+ true_color,
+ );
+ }
+ seen_background = true;
+ } else {
+ eprintln!(
+ "Invalid style string: {}. See the STYLES section of delta --help.",
+ s
+ );
+ process::exit(1);
+ }
+ }
+ (style, is_omitted, is_raw, is_syntax_highlighted)
+}
+
+/// Extract set of 'special decoration attributes' and return it along with modified style string.
+fn extract_special_decoration_attributes(style_string: &str) -> (DecorationAttributes, String) {
+ _extract_special_decoration_attributes(style_string, true)
+}
+
+fn extract_special_decoration_attributes_from_non_decoration_style_string(
+ style_string: &str,
+) -> (DecorationAttributes, String) {
+ _extract_special_decoration_attributes(style_string, false)
+}
+
+// If this is being called in the context of processing a decoration style string then we treat
+// ul/ol as a request for an underline/overline decoration respectively. Otherwise they are
+// conventional character style attributes.
+fn _extract_special_decoration_attributes(
+ style_string: &str,
+ is_decoration_style_string: bool,
+) -> (DecorationAttributes, String) {
+ let mut attributes = DecorationAttributes::EMPTY;
+ let mut new_style_string = Vec::new();
+ let style_string = style_string.to_lowercase();
+ for token in style_string
+ .split_whitespace()
+ .map(|word| word.trim_matches(|c| c == '"' || c == '\''))
+ {
+ match token {
+ "box" => attributes |= DecorationAttributes::BOX,
+ token if token == "overline" || is_decoration_style_string && token == "ol" => {
+ attributes |= DecorationAttributes::OVERLINE
+ }
+ token if token == "underline" || is_decoration_style_string && token == "ul" => {
+ attributes |= DecorationAttributes::UNDERLINE
+ }
+ token if token == "none" || token == "plain" => {}
+ _ => new_style_string.push(token),
+ }
+ }
+ (attributes, new_style_string.join(" "))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use ansi_term;
+
+ use crate::color::ansi_color_name_to_number;
+
+ #[test]
+ fn test_parse_ansi_term_style() {
+ assert_eq!(
+ parse_ansi_term_style("", None, None, false),
+ (ansi_term::Style::new(), false, false, false)
+ );
+ assert_eq!(
+ parse_ansi_term_style("red", None, None, false),
+ (
+ ansi_term::Style {
+ foreground: Some(ansi_term::Color::Fixed(
+ ansi_color_name_to_number("red").unwrap()
+ )),
+ ..ansi_term::Style::new()
+ },
+ false,
+ false,
+ false
+ )
+ );
+ assert_eq!(
+ parse_ansi_term_style("red green", None, None, false),
+ (
+ ansi_term::Style {
+ foreground: Some(ansi_term::Color::Fixed(
+ ansi_color_name_to_number("red").unwrap()
+ )),
+ background: Some(ansi_term::Color::Fixed(
+ ansi_color_name_to_number("green").unwrap()
+ )),
+ ..ansi_term::Style::new()
+ },
+ false,
+ false,
+ false
+ )
+ );
+ assert_eq!(
+ parse_ansi_term_style("bold red underline green blink", None, None, false),
+ (
+ ansi_term::Style {
+ foreground: Some(ansi_term::Color::Fixed(
+ ansi_color_name_to_number("red").unwrap()
+ )),
+ background: Some(ansi_term::Color::Fixed(
+ ansi_color_name_to_number("green").unwrap()
+ )),
+ is_blink: true,
+ is_bold: true,
+ is_underline: true,
+ ..ansi_term::Style::new()
+ },
+ false,
+ false,
+ false
+ )
+ );
+ }
+
+ #[test]
+ fn test_parse_ansi_term_style_with_special_syntax_color() {
+ assert_eq!(
+ parse_ansi_term_style("syntax", None, None, false),
+ (ansi_term::Style::new(), false, false, true)
+ );
+ assert_eq!(
+ parse_ansi_term_style("syntax italic white hidden", None, None, false),
+ (
+ ansi_term::Style {
+ background: Some(ansi_term::Color::Fixed(
+ ansi_color_name_to_number("white").unwrap()
+ )),
+ is_italic: true,
+ is_hidden: true,
+ ..ansi_term::Style::new()
+ },
+ false,
+ false,
+ true
+ )
+ );
+ assert_eq!(
+ parse_ansi_term_style("bold syntax italic white hidden", None, None, false),
+ (
+ ansi_term::Style {
+ background: Some(ansi_term::Color::Fixed(
+ ansi_color_name_to_number("white").unwrap()
+ )),
+ is_bold: true,
+ is_italic: true,
+ is_hidden: true,
+ ..ansi_term::Style::new()
+ },
+ false,
+ false,
+ true
+ )
+ );
+ }
+
+ #[test]
+ fn test_parse_ansi_term_style_with_special_omit_attribute() {
+ assert_eq!(
+ parse_ansi_term_style("omit", None, None, false),
+ (ansi_term::Style::new(), true, false, false)
+ );
+ // It doesn't make sense for omit to be combined with anything else, but it is not an error.
+ assert_eq!(
+ parse_ansi_term_style("omit syntax italic white hidden", None, None, false),
+ (
+ ansi_term::Style {
+ background: Some(ansi_term::Color::Fixed(
+ ansi_color_name_to_number("white").unwrap()
+ )),
+ is_italic: true,
+ is_hidden: true,
+ ..ansi_term::Style::new()
+ },
+ true,
+ false,
+ true
+ )
+ );
+ }
+
+ #[test]
+ fn test_parse_ansi_term_style_with_special_raw_attribute() {
+ assert_eq!(
+ parse_ansi_term_style("raw", None, None, false),
+ (ansi_term::Style::new(), false, true, false)
+ );
+ // It doesn't make sense for raw to be combined with anything else, but it is not an error.
+ assert_eq!(
+ parse_ansi_term_style("raw syntax italic white hidden", None, None, false),
+ (
+ ansi_term::Style {
+ background: Some(ansi_term::Color::Fixed(
+ ansi_color_name_to_number("white").unwrap()
+ )),
+ is_italic: true,
+ is_hidden: true,
+ ..ansi_term::Style::new()
+ },
+ false,
+ true,
+ true
+ )
+ );
+ }
+
+ #[test]
+ fn test_extract_special_decoration_attribute() {
+ #[allow(non_snake_case)]
+ let (BOX, UL, OL, EMPTY) = (
+ DecorationAttributes::BOX,
+ DecorationAttributes::UNDERLINE,
+ DecorationAttributes::OVERLINE,
+ DecorationAttributes::EMPTY,
+ );
+ assert_eq!(
+ extract_special_decoration_attributes(""),
+ (EMPTY, "".to_string(),)
+ );
+ assert_eq!(
+ extract_special_decoration_attributes("box"),
+ (BOX, "".to_string())
+ );
+ assert_eq!(
+ extract_special_decoration_attributes("ul"),
+ (UL, "".to_string())
+ );
+ assert_eq!(
+ extract_special_decoration_attributes("ol"),
+ (OL, "".to_string())
+ );
+ assert_eq!(
+ extract_special_decoration_attributes("box ul"),
+ (BOX | UL, "".to_string())
+ );
+ assert_eq!(
+ extract_special_decoration_attributes("box ol"),
+ (BOX | OL, "".to_string())
+ );
+ assert_eq!(
+ extract_special_decoration_attributes("ul box ol"),
+ (BOX | UL | OL, "".to_string())
+ );
+ assert_eq!(
+ extract_special_decoration_attributes("ol ul"),
+ (UL | OL, "".to_string())
+ );
+ }
+
+ #[test]
+ fn test_decoration_style_from_str_empty_string() {
+ assert_eq!(
+ DecorationStyle::from_str("", true),
+ DecorationStyle::NoDecoration,
+ )
+ }
+
+ #[test]
+ fn test_decoration_style_from_str() {
+ assert_eq!(
+ DecorationStyle::from_str("ol red box bold green ul", true),
+ DecorationStyle::BoxWithUnderOverline(ansi_term::Style {
+ foreground: Some(ansi_term::Color::Fixed(1)),
+ background: Some(ansi_term::Color::Fixed(2)),
+ is_bold: true,
+ ..ansi_term::Style::new()
+ })
+ )
+ }
+
+ #[test]
+ fn test_style_from_str() {
+ let actual_style = Style::from_str(
+ "red green bold",
+ None,
+ None,
+ Some("ol red box bold green ul"),
+ true,
+ false,
+ );
+ let red_green_bold = ansi_term::Style {
+ foreground: Some(ansi_term::Color::Fixed(1)),
+ background: Some(ansi_term::Color::Fixed(2)),
+ is_bold: true,
+ ..ansi_term::Style::new()
+ };
+ assert_eq!(
+ actual_style,
+ Style {
+ ansi_term_style: red_green_bold,
+ decoration_style: DecorationStyle::BoxWithUnderOverline(red_green_bold),
+ ..Style::new()
+ }
+ )
+ }
+
+ #[test]
+ fn test_style_from_str_raw_with_box() {
+ let actual_style = Style::from_str("raw", None, None, Some("box"), true, false);
+ let empty_ansi_term_style = ansi_term::Style::new();
+ assert_eq!(
+ actual_style,
+ Style {
+ ansi_term_style: empty_ansi_term_style,
+ decoration_style: DecorationStyle::Box(empty_ansi_term_style),
+ is_raw: true,
+ ..Style::new()
+ }
+ )
+ }
+
+ #[test]
+ fn test_style_from_str_decoration_style_only() {
+ let actual_style = Style::from_str(
+ "",
+ None,
+ None,
+ Some("ol red box bold green ul"),
+ true,
+ false,
+ );
+ let red_green_bold = ansi_term::Style {
+ foreground: Some(ansi_term::Color::Fixed(1)),
+ background: Some(ansi_term::Color::Fixed(2)),
+ is_bold: true,
+ ..ansi_term::Style::new()
+ };
+ assert_eq!(
+ actual_style,
+ Style {
+ decoration_style: DecorationStyle::BoxWithUnderOverline(red_green_bold),
+ ..Style::new()
+ }
+ )
+ }
+
+ #[test]
+ fn test_style_from_str_with_handling_of_special_decoration_attributes() {
+ let actual_style = Style::from_str_with_handling_of_special_decoration_attributes(
+ "",
+ None,
+ None,
+ Some("ol red box bold green ul"),
+ true,
+ false,
+ );
+ let expected_decoration_style = DecorationStyle::BoxWithUnderOverline(ansi_term::Style {
+ foreground: Some(ansi_term::Color::Fixed(1)),
+ background: Some(ansi_term::Color::Fixed(2)),
+ is_bold: true,
+ ..ansi_term::Style::new()
+ });
+ assert_eq!(
+ actual_style,
+ Style {
+ decoration_style: expected_decoration_style,
+ ..Style::new()
+ }
+ )
+ }
+
+ #[test]
+ fn test_style_from_str_with_handling_of_special_decoration_attributes_raw_with_box() {
+ let actual_style = Style::from_str_with_handling_of_special_decoration_attributes(
+ "raw",
+ None,
+ None,
+ Some("box"),
+ true,
+ false,
+ );
+ let empty_ansi_term_style = ansi_term::Style::new();
+ assert_eq!(
+ actual_style,
+ Style {
+ ansi_term_style: empty_ansi_term_style,
+ decoration_style: DecorationStyle::Box(empty_ansi_term_style),
+ is_raw: true,
+ ..Style::new()
+ }
+ )
+ }
+
+ #[test]
+ fn test_style_from_str_with_handling_of_special_decoration_attributes_and_respecting_deprecated_foreground_color_arg(
+ ) {
+ let expected_decoration_style = DecorationStyle::BoxWithUnderOverline(ansi_term::Style {
+ foreground: Some(ansi_term::Color::Fixed(1)),
+ background: Some(ansi_term::Color::Fixed(2)),
+ is_bold: true,
+ ..ansi_term::Style::new()
+ });
+ let actual_style = Style::from_str_with_handling_of_special_decoration_attributes_and_respecting_deprecated_foreground_color_arg(
+ "", None, None, Some("ol red box bold green ul"), None, true, false
+ );
+ assert_eq!(
+ actual_style,
+ Style {
+ decoration_style: expected_decoration_style,
+ ..Style::new()
+ }
+ )
+ }
+
+ #[test]
+ fn test_style_from_str_with_handling_of_special_decoration_attributes_and_respecting_deprecated_foreground_color_arg_raw_with_box(
+ ) {
+ let actual_style = Style::from_str_with_handling_of_special_decoration_attributes_and_respecting_deprecated_foreground_color_arg(
+ "raw",
+ None,
+ None,
+ Some("box"),
+ None,
+ true,
+ false,
+ );
+ let empty_ansi_term_style = ansi_term::Style::new();
+ assert_eq!(
+ actual_style,
+ Style {
+ ansi_term_style: empty_ansi_term_style,
+ decoration_style: DecorationStyle::Box(empty_ansi_term_style),
+ is_raw: true,
+ ..Style::new()
+ }
+ )
+ }
+}