From faba33490d2e820ab73c8d6727ee4b3f4b9b0cf7 Mon Sep 17 00:00:00 2001 From: dalance Date: Fri, 6 Apr 2018 20:19:11 +0900 Subject: [PATCH] Refactoring --- src/colorize.rs | 220 +++++++++++++++++++++++++++++++++++++ src/main.rs | 281 ++++++++++-------------------------------------- 2 files changed, 279 insertions(+), 222 deletions(-) create mode 100644 src/colorize.rs diff --git a/src/colorize.rs b/src/colorize.rs new file mode 100644 index 0000000..bc4fb0c --- /dev/null +++ b/src/colorize.rs @@ -0,0 +1,220 @@ +use regex::Regex; +use termion::color; +use termion::color::Color; +use toml; + +// ------------------------------------------------------------------------------------------------- +// Config +// ------------------------------------------------------------------------------------------------- + +#[derive(Deserialize)] +pub struct Config { + pub lines: Vec, +} + +#[derive(Deserialize)] +pub struct Line { + #[serde(with = "regex_serde")] + pub pat: Regex, + + pub colors: Vec, + + pub tokens: Vec, +} + +#[derive(Deserialize)] +pub struct Token { + #[serde(with = "regex_serde")] + pub pat: Regex, + + pub colors: Vec, +} + +mod regex_serde { + use regex::Regex; + use serde::{self, Deserialize, Deserializer}; + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let r = Regex::new(&s).map_err(serde::de::Error::custom)?; + Ok(r) + } +} + +// ------------------------------------------------------------------------------------------------- +// Error +// ------------------------------------------------------------------------------------------------- + +error_chain! { + foreign_links { + } +} + +// ------------------------------------------------------------------------------------------------- +// Functions +// ------------------------------------------------------------------------------------------------- + +pub fn colorize(mut s: String, config: &Config) -> Result<(String, Option)> { + #[derive(Debug)] + enum PosType { + Start, + End, + } + + let mut pos = Vec::new(); + let mut line_idx = None; + + for (i,line) in config.lines.iter().enumerate() { + let cap = line.pat.captures(&s); + if let Some(cap) = cap { + line_idx = Some(i); + for (j, mat) in cap.iter().enumerate() { + if let Some(mat) = mat { + pos.push((PosType::Start, mat.start(), line.colors[j].clone())); + pos.push((PosType::End, mat.end(), line.colors[j].clone())); + } + } + for token in &line.tokens { + let cap = token.pat.captures(&s); + if let Some(cap) = cap { + for (j, mat) in cap.iter().enumerate() { + if let Some(mat) = mat { + pos.push((PosType::Start, mat.start(), token.colors[j].clone())); + pos.insert(0, (PosType::End, mat.end(), token.colors[j].clone())); + } + } + } + } + break; + } + } + + pos.sort_by_key(|&(_, p, _)| p); + + let mut current_color = vec![String::from("Default")]; + let mut ret = String::new(); + let mut idx = 0; + for (t, p, color) in pos { + match t { + PosType::Start => { + current_color.push(color); + } + PosType::End => { + current_color.pop(); + } + } + let rest = s.split_off(p - idx); + + ret.push_str(&format!( + "{}{}", + s, + color::Fg(&*conv_color(¤t_color.last())?) + )); + idx += s.len(); + s = rest; + } + + ret.push_str(&s); + Ok((ret, line_idx)) +} + +fn conv_color(s: &Option<&String>) -> Result> { + let ret: Box = if let &Some(ref s) = s { + match s.as_ref() { + "Black" => Box::new(color::Black), + "Blue" => Box::new(color::Blue), + "Cyan" => Box::new(color::Cyan), + "Default" => Box::new(color::Reset), + "Green" => Box::new(color::Green), + "LightBlack" => Box::new(color::LightBlack), + "LightBlue" => Box::new(color::LightBlue), + "LightCyan" => Box::new(color::LightCyan), + "LightGreen" => Box::new(color::LightGreen), + "LightMagenta" => Box::new(color::LightMagenta), + "LightRed" => Box::new(color::LightRed), + "LightWhite" => Box::new(color::LightWhite), + "LightYellow" => Box::new(color::LightYellow), + "Magenta" => Box::new(color::Magenta), + "Red" => Box::new(color::Red), + "White" => Box::new(color::White), + "Yellow" => Box::new(color::Yellow), + _ => { + bail!(format!("failed to parse color name '{}'", s)); + }, + } + } else { + Box::new(color::Reset) + }; + Ok(ret) +} + +// ------------------------------------------------------------------------------------------------- +// Test +// ------------------------------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + pub static TEST_CONFIG: &'static str = r#" + [[lines]] + pat = "A(.*) (.*) (.*) .*" + colors = ["Black", "Blue", "Cyan", "Default"] + [[lines.tokens]] + pat = "A" + colors = ["Green"] + [[lines]] + pat = "B(.*) (.*) (.*) .*" + colors = ["LightBlack", "LightBlue", "LightCyan", "LightGreen"] + tokens = [] + [[lines]] + pat = "C(.*) (.*) (.*) .*" + colors = ["LightMagenta", "LightRed", "LightWhite", "LightYellow"] + tokens = [] + [[lines]] + pat = "D(.*) (.*) (.*) .*" + colors = ["Magenta", "Red", "White", "Yellow"] + tokens = [] + "#; + + pub static TEST_CONFIG2: &'static str = r#" + [[lines]] + pat = "A(.*) (.*) (.*) .*" + colors = ["xxx", "Blue", "Cyan", "Default"] + tokens = [] + "#; + + #[test] + fn test_colorize() { + let config: Config = toml::from_str(TEST_CONFIG).unwrap(); + let (ret, idx) = colorize(String::from("A123 456 789 xyz"), &config).unwrap(); + assert_eq!(ret, "\u{1b}[38;5;0m\u{1b}[38;5;2mA\u{1b}[38;5;0m\u{1b}[38;5;4m123\u{1b}[38;5;0m \u{1b}[38;5;6m456\u{1b}[38;5;0m \u{1b}[39m789\u{1b}[38;5;0m xyz\u{1b}[39m"); + assert_eq!(idx, Some(0)); + + let (ret, idx) = colorize(String::from("B123 456 789 xyz"), &config).unwrap(); + assert_eq!(ret, "\u{1b}[38;5;8mB\u{1b}[38;5;12m123\u{1b}[38;5;8m \u{1b}[38;5;14m456\u{1b}[38;5;8m \u{1b}[38;5;10m789\u{1b}[38;5;8m xyz\u{1b}[39m"); + assert_eq!(idx, Some(1)); + + let (ret, idx) = colorize(String::from("C123 456 789 xyz"), &config).unwrap(); + assert_eq!(ret, "\u{1b}[38;5;13mC\u{1b}[38;5;9m123\u{1b}[38;5;13m \u{1b}[38;5;15m456\u{1b}[38;5;13m \u{1b}[38;5;11m789\u{1b}[38;5;13m xyz\u{1b}[39m"); + assert_eq!(idx, Some(2)); + + let (ret, idx) = colorize(String::from("D123 456 789 xyz"), &config).unwrap(); + assert_eq!(ret, "\u{1b}[38;5;5mD\u{1b}[38;5;1m123\u{1b}[38;5;5m \u{1b}[38;5;7m456\u{1b}[38;5;5m \u{1b}[38;5;3m789\u{1b}[38;5;5m xyz\u{1b}[39m"); + assert_eq!(idx, Some(3)); + + let (ret, idx) = colorize(String::from("E123 456 789 xyz"), &config).unwrap(); + assert_eq!(ret, "E123 456 789 xyz"); + assert_eq!(idx, None); + } + + #[test] + fn test_colorize_fail() { + let config: Config = toml::from_str(TEST_CONFIG2).unwrap(); + let ret = colorize(String::from("A123 456 789 xyz"), &config); + assert_eq!(&format!("{:?}", ret)[0..50], "Err(Error(Msg(\"failed to parse color name \\\'xxx\\\'\""); + } +} diff --git a/src/main.rs b/src/main.rs index 9ba53bc..66424ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,15 +10,15 @@ extern crate structopt; extern crate termion; extern crate toml; +mod colorize; + +use colorize::{colorize, Config}; use atty::Stream; -use regex::Regex; use std::env::home_dir; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use std::path::{Path, PathBuf}; use structopt::{clap, StructOpt}; -use termion::color; -use termion::color::Color; // ------------------------------------------------------------------------------------------------- // Option @@ -51,43 +51,6 @@ pub struct Opt { // Config // ------------------------------------------------------------------------------------------------- -#[derive(Deserialize)] -pub struct Config { - pub lines: Vec, -} - -#[derive(Deserialize)] -pub struct Line { - #[serde(with = "regex_serde")] - pub pat: Regex, - - pub colors: Vec, - - pub tokens: Vec, -} - -#[derive(Deserialize)] -pub struct Token { - #[serde(with = "regex_serde")] - pub pat: Regex, - - pub colors: Vec, -} - -mod regex_serde { - use regex::Regex; - use serde::{self, Deserialize, Deserializer}; - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - let r = Regex::new(&s).map_err(serde::de::Error::custom)?; - Ok(r) - } -} - pub static DEFAULT_CONFIG: &'static str = r#" [[lines]] pat = "(Error).*" @@ -108,6 +71,9 @@ pub static DEFAULT_CONFIG: &'static str = r#" // ------------------------------------------------------------------------------------------------- error_chain! { + links { + Colorize(::colorize::Error, ::colorize::ErrorKind); + } foreign_links { Io(::std::io::Error); Toml(::toml::de::Error); @@ -127,6 +93,18 @@ fn get_reader_stdin() -> Result> { Ok(Box::new(BufReader::new(stdin()))) } +fn get_config_path(opt: &Opt) -> Option { + if let Some(ref p) = opt.config { + return Some(p.clone()); + } else if let Some(mut p) = home_dir() { + p.push(".pipecolor.toml"); + if p.exists() { + return Some(p); + } + } + None +} + fn output(reader: &mut BufRead, writer: &mut Write, use_color: bool, config: &Config, opt: &Opt) -> Result<()> { let mut s = String::new(); loop { @@ -134,7 +112,13 @@ fn output(reader: &mut BufRead, writer: &mut Write, use_color: bool, config: &Co Ok(0) => break, Ok(_) => { if use_color { - s = apply_style(s, config, opt)?; + let (s2, i) = colorize(s, config)?; + s = s2; + if opt.verbose { + if let Some(i) = i { + eprintln!("pipecolor: line matched to '{:?}'", config.lines[i].pat); + } + } } let _ = writer.write(s.as_bytes()); //let _ = writer.flush(); @@ -146,113 +130,6 @@ fn output(reader: &mut BufRead, writer: &mut Write, use_color: bool, config: &Co Ok(()) } -fn apply_style(mut s: String, config: &Config, opt: &Opt) -> Result { - #[derive(Debug)] - enum PosType { - Start, - End, - } - - let mut pos = Vec::new(); - - for line in &config.lines { - let cap = line.pat.captures(&s); - if let Some(cap) = cap { - if opt.verbose { - eprintln!("pipecolor: line matched to '{:?}'", line.pat); - } - for (j, mat) in cap.iter().enumerate() { - if let Some(mat) = mat { - pos.push((PosType::Start, mat.start(), line.colors[j].clone())); - pos.push((PosType::End, mat.end(), line.colors[j].clone())); - } - } - for token in &line.tokens { - let cap = token.pat.captures(&s); - if let Some(cap) = cap { - for (j, mat) in cap.iter().enumerate() { - if let Some(mat) = mat { - pos.push((PosType::Start, mat.start(), token.colors[j].clone())); - pos.insert(0, (PosType::End, mat.end(), token.colors[j].clone())); - } - } - } - } - break; - } - } - - pos.sort_by_key(|&(_, p, _)| p); - - let mut current_color = vec![String::from("Default")]; - let mut ret = String::new(); - let mut idx = 0; - for (t, p, color) in pos { - match t { - PosType::Start => { - current_color.push(color); - } - PosType::End => { - current_color.pop(); - } - } - let rest = s.split_off(p - idx); - - ret.push_str(&format!( - "{}{}", - s, - color::Fg(&*conv_color(¤t_color.last())?) - )); - idx += s.len(); - s = rest; - } - - ret.push_str(&s); - Ok(ret) -} - -fn conv_color(s: &Option<&String>) -> Result> { - let ret: Box = if let &Some(ref s) = s { - match s.as_ref() { - "Black" => Box::new(color::Black), - "Blue" => Box::new(color::Blue), - "Cyan" => Box::new(color::Cyan), - "Default" => Box::new(color::Reset), - "Green" => Box::new(color::Green), - "LightBlack" => Box::new(color::LightBlack), - "LightBlue" => Box::new(color::LightBlue), - "LightCyan" => Box::new(color::LightCyan), - "LightGreen" => Box::new(color::LightGreen), - "LightMagenta" => Box::new(color::LightMagenta), - "LightRed" => Box::new(color::LightRed), - "LightWhite" => Box::new(color::LightWhite), - "LightYellow" => Box::new(color::LightYellow), - "Magenta" => Box::new(color::Magenta), - "Red" => Box::new(color::Red), - "White" => Box::new(color::White), - "Yellow" => Box::new(color::Yellow), - _ => { - bail!(format!("failed to parse color name '{}'", s)); - }, - } - } else { - Box::new(color::Reset) - }; - Ok(ret) -} - -fn get_config_path(opt: &Opt) -> Option { - if let Some(ref p) = opt.config { - return Some(p.clone()); - } else if let Some(mut p) = home_dir() { - p.push(".pipecolor.toml"); - if p.exists() { - return Some(p); - } - } - None -} - // ------------------------------------------------------------------------------------------------- // Main // ------------------------------------------------------------------------------------------------- @@ -312,43 +189,6 @@ fn run_opt(opt: &Opt) -> Result<()> { mod tests { use super::*; - pub static TEST_CONFIG: &'static str = r#" - [[lines]] - pat = "A(.*) (.*) (.*) .*" - colors = ["Black", "Blue", "Cyan", "Default"] - [[lines.tokens]] - pat = "A" - colors = ["Green"] - [[lines]] - pat = "B(.*) (.*) (.*) .*" - colors = ["LightBlack", "LightBlue", "LightCyan", "LightGreen"] - tokens = [] - [[lines]] - pat = "C(.*) (.*) (.*) .*" - colors = ["LightMagenta", "LightRed", "LightWhite", "LightYellow"] - tokens = [] - [[lines]] - pat = "D(.*) (.*) (.*) .*" - colors = ["Magenta", "Red", "White", "Yellow"] - tokens = [] - "#; - - pub static TEST_CONFIG2: &'static str = r#" - [[lines]] - pat = "A(.*) (.*) (.*) .*" - colors = ["xxx", "Blue", "Cyan", "Default"] - tokens = [] - "#; - - pub static TEST_DATA: &'static str = r#" -A123 456 789 xyz -B123 456 789 xyz -C123 456 789 xyz -D123 456 789 xyz - "#; - - pub static TEST_RESULT: &'static str = "\n\u{1b}[38;5;0m\u{1b}[38;5;2mA\u{1b}[38;5;0m\u{1b}[38;5;4m123\u{1b}[38;5;0m \u{1b}[38;5;6m456\u{1b}[38;5;0m \u{1b}[39m789\u{1b}[38;5;0m xyz\u{1b}[39m\n\u{1b}[38;5;8mB\u{1b}[38;5;12m123\u{1b}[38;5;8m \u{1b}[38;5;14m456\u{1b}[38;5;8m \u{1b}[38;5;10m789\u{1b}[38;5;8m xyz\u{1b}[39m\n\u{1b}[38;5;13mC\u{1b}[38;5;9m123\u{1b}[38;5;13m \u{1b}[38;5;15m456\u{1b}[38;5;13m \u{1b}[38;5;11m789\u{1b}[38;5;13m xyz\u{1b}[39m\n\u{1b}[38;5;5mD\u{1b}[38;5;1m123\u{1b}[38;5;5m \u{1b}[38;5;7m456\u{1b}[38;5;5m \u{1b}[38;5;3m789\u{1b}[38;5;5m xyz\u{1b}[39m\n "; - #[test] fn test_run() { let args = vec![ @@ -378,52 +218,49 @@ D123 456 789 xyz } #[test] - fn test_read_config_fail() { - let args = vec!["pipecolor", "-c", "test", "sample/access_log"]; + fn test_mode() { + let args = vec![ + "pipecolor", + "-m", + "always", + "-c", + "sample/pipecolor.toml", + "sample/access_log", + ]; let opt = Opt::from_iter(args.iter()); let ret = run_opt(&opt); - assert!(ret.is_err()); - } + assert!(ret.is_ok()); - #[test] - fn test_output() { - let args = vec!["pipecolor"]; + let args = vec![ + "pipecolor", + "-m", + "auto", + "-c", + "sample/pipecolor.toml", + "sample/access_log", + ]; let opt = Opt::from_iter(args.iter()); - let config: Config = toml::from_str(TEST_CONFIG).unwrap(); - let mut reader = BufReader::new(TEST_DATA.as_bytes()); - let out = String::new(); - let mut writer = BufWriter::new(out.into_bytes()); - let _ = output(&mut reader, writer.get_mut(), true, &config, &opt); - assert_eq!( - TEST_RESULT, - &String::from_utf8(writer.get_ref().to_vec()).unwrap() - ); - } + let ret = run_opt(&opt); + assert!(ret.is_ok()); - #[test] - fn test_output_disable() { - let args = vec!["pipecolor"]; + let args = vec![ + "pipecolor", + "-m", + "disable", + "-c", + "sample/pipecolor.toml", + "sample/access_log", + ]; let opt = Opt::from_iter(args.iter()); - let config: Config = toml::from_str(TEST_CONFIG).unwrap(); - let mut reader = BufReader::new(TEST_DATA.as_bytes()); - let out = String::new(); - let mut writer = BufWriter::new(out.into_bytes()); - let _ = output(&mut reader, writer.get_mut(), false, &config, &opt); - assert_eq!( - TEST_DATA, - &String::from_utf8(writer.get_ref().to_vec()).unwrap() - ); + let ret = run_opt(&opt); + assert!(ret.is_ok()); } #[test] - fn test_output_color_fail() { - let args = vec!["pipecolor"]; + fn test_read_config_fail() { + let args = vec!["pipecolor", "-c", "test", "sample/access_log"]; let opt = Opt::from_iter(args.iter()); - let config: Config = toml::from_str(TEST_CONFIG2).unwrap(); - let mut reader = BufReader::new(TEST_DATA.as_bytes()); - let out = String::new(); - let mut writer = BufWriter::new(out.into_bytes()); - let ret = output(&mut reader, writer.get_mut(), true, &config, &opt); - assert_eq!(&format!("{:?}", ret)[0..50], "Err(Error(Msg(\"failed to parse color name \\\'xxx\\\'\""); + let ret = run_opt(&opt); + assert!(ret.is_err()); } }