From 7c2812be4fde41a33edfb94bb4c5ff41f2077feb Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 13 Oct 2019 22:43:58 +0200 Subject: [PATCH] fully ported library --- src/lib.rs | 76 +++++++++++++++++++++++++++++++++++++ src/syntax/bold.rs | 27 +++++++++++++ src/syntax/color.rs | 79 +++++++++++++++++++++++++++++++++++++++ src/syntax/hyperlink.rs | 30 +++++++++++++++ src/syntax/italic.rs | 27 +++++++++++++ src/syntax/mod.rs | 5 +++ src/syntax/underline.rs | 26 +++++++++++++ src/transform/mod.rs | 18 ++++----- src/transform/registry.rs | 6 +-- 9 files changed, 281 insertions(+), 13 deletions(-) create mode 100644 src/lib.rs create mode 100644 src/syntax/bold.rs create mode 100644 src/syntax/color.rs create mode 100644 src/syntax/hyperlink.rs create mode 100644 src/syntax/italic.rs create mode 100644 src/syntax/mod.rs create mode 100644 src/syntax/underline.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3b7101a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,76 @@ +/** the transform module defines generic transformer and + * transformer registry logic + */ + +pub mod transform; + +/** the syntax module implements several transformers */ +pub mod syntax; + +// protect escaped tokens +const DOLLAR_TOKEN: &str = "e4d097183ab04e49f25cb7b0956fb9eb25b90c0316a32cb5afcbcdd9a6692e8d2974919035789d5632b10d799db5b3e5bf8539592c904497f5c356f117ef37382"; +const ASTERISK_TOKEN: &str = "253c3cd0a904d28abc3e601e3557d59ea69da2616079ceef4987d58d55c9820c83026be92a917ee19a298e613ea0b393cc70d4e55dc614a9afc6a020d8f08f37"; +const UNDERSCORE_TOKEN: &str = "2b08e24b7833e90c74ed8e6c27b7b3cd5fe949e0f18b28af813d5f2df863d55f97b0ed7f8fbb26a152eda55ac073331ce11ac10702caca5b3ea4a29f722840b9"; +const SQUARE_BRACKET_TOKEN: &str = "51b06edd58f36003844941916cd3b313979fece55824d89ba02af052a229b2673aafffa541b703472c1a21d8e6a1bb3e844d236fb0e8bf5d62902b24042f4fb5"; + +pub fn _format(args: std::fmt::Arguments) -> String { + // 1. pre-process with format + let pre_formatted = std::fmt::format(args); + + // 2. protect escaped characters with tokens + let escaped = pre_formatted.replace("\\$", DOLLAR_TOKEN); + let escaped = escaped.replace("\\*", ASTERISK_TOKEN); + let escaped = escaped.replace("\\_", UNDERSCORE_TOKEN); + let escaped = escaped.replace("\\[", SQUARE_BRACKET_TOKEN); + + // 3. create transform registry + let mut registry = transform::registry::new(); + registry.add( syntax::bold::get_transformer() ); + registry.add( syntax::italic::get_transformer() ); + registry.add( syntax::underline::get_transformer() ); + registry.add( syntax::hyperlink::get_transformer() ); + registry.add( syntax::color::get_transformer() ); + + // 4. apply transformations + let transformed = registry.transform(&escaped).unwrap(); + + // 5. restore token-protected escapes + let unescaped = transformed.replace(DOLLAR_TOKEN, "\\$"); + let unescaped = unescaped.replace(ASTERISK_TOKEN, "\\*"); + let unescaped = unescaped.replace(UNDERSCORE_TOKEN, "\\_"); + let unescaped = unescaped.replace(SQUARE_BRACKET_TOKEN, "\\["); + + unescaped +} + +pub fn _print(args: std::fmt::Arguments) { + // 1. process with format + let processed = _format(args); + + std::print!("{}", processed); +} +pub fn _println(args: std::fmt::Arguments) { + // 1. process with format + let processed = _format(args); + + std::print!("{}\n", processed); +} + + +#[macro_export] +macro_rules! format { + ($($arg:tt)*) => ($crate::_format(format_args!($($arg)*))) +} + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::_print(format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! println { + () => ($crate::_print!("\n")); + ($($arg:tt)*) => ({ + $crate::_println(format_args!($($arg)*)); + }) +} diff --git a/src/syntax/bold.rs b/src/syntax/bold.rs new file mode 100644 index 0000000..0e2ffec --- /dev/null +++ b/src/syntax/bold.rs @@ -0,0 +1,27 @@ +use regex; +use crate::transform::Transformer; + +pub struct BoldTransformer { + regex: regex::Regex, +} + +pub fn get_transformer() -> BoldTransformer { + BoldTransformer { + regex: regex::Regex::new(r"(?m)\*\*((?:[^\*]+\*?)+)\*\*").unwrap(), + } +} + + +impl Transformer for BoldTransformer { + + fn get_regex(&self) -> ®ex::Regex { &self.regex } + + fn transform(&self, captures: ®ex::Captures) -> Option { + // don't mutate empty catpures + if captures[0].len() < 1 || captures.len() < 2 { + return None; + } + + Some( format!("\x1b[1m{}\x1b[22m", captures[1].replace("\x1b[0m", "\x1b[0m\x1b[1m")) ) + } +} \ No newline at end of file diff --git a/src/syntax/color.rs b/src/syntax/color.rs new file mode 100644 index 0000000..2024072 --- /dev/null +++ b/src/syntax/color.rs @@ -0,0 +1,79 @@ +use regex; +use crate::transform::Transformer; + +pub struct ColorTransformer { + regex: regex::Regex +} + +pub fn get_transformer() -> ColorTransformer { + ColorTransformer { + regex: regex::Regex::new(r"(?m)\$\{([^$]+)\}\(((?:[a-z]+|#(?:[0-9a-f]{3}|[0-9a-f]{6})))?(?::(#(?:[0-9a-f]{3}|[0-9a-f]{6})))?\)").unwrap(), + } +} + + +impl Transformer for ColorTransformer { + + fn get_regex(&self) -> ®ex::Regex { &self.regex } + + fn transform(&self, captures: ®ex::Captures) -> Option { + // don't mutate empty catpures + if captures[0].len() < 1 || captures.len() < 4 { + return None; + } + + let text = captures[1].to_owned(); + let foreground = match captures.get(2) { + Some(fg_text) => hex_to_rgb(&fg_text.as_str()[1..]), + None => None, + }; + let background = match captures.get(3) { + Some(bg_text) => hex_to_rgb(&bg_text.as_str()[1..]), + None => None, + }; + + if let Some(fg) = foreground { + + // both + if let Some(bg) = background { + return Some( format!("\x1b[38;2;{};{};{};48;2;{};{};{}m{}\x1b[0m", fg.0, fg.1, fg.2, bg.0, bg.1, bg.2, text) ); + } + + // only foreground + return Some( format!("\x1b[38;2;{};{};{}m{}\x1b[0m", fg.0, fg.1, fg.2, text) ); + } + + // only bg + if let Some(bg) = background { + return Some( format!("\x1b[48;2;{};{};{}m{}\x1b[0m", bg.0, bg.1, bg.2, text) ); + } + + return None; + } +} + +fn hex_to_rgb(hex: &str) -> Option<(u8, u8, u8)> { + if hex.len() != 3 && hex.len() != 6 { + return None; + } + + let fullhex; + + // 3 char version + if hex.len() == 3 { + fullhex = format!("{0}{0}{1}{1}{2}{2}", hex.chars().nth(0).unwrap(), hex.chars().nth(1).unwrap(), hex.chars().nth(2).unwrap()); + } else { + fullhex = hex.to_owned(); + } + + // ignore on error + if let Ok(r) = u8::from_str_radix(&fullhex[0..2], 16) { + if let Ok(g) = u8::from_str_radix(&fullhex[2..4], 16) { + if let Ok(b) = u8::from_str_radix(&fullhex[4..6], 16) { + return Some( (r,g,b) ); + } + } + } + + return None; +} \ No newline at end of file diff --git a/src/syntax/hyperlink.rs b/src/syntax/hyperlink.rs new file mode 100644 index 0000000..e879554 --- /dev/null +++ b/src/syntax/hyperlink.rs @@ -0,0 +1,30 @@ +use regex; +use crate::transform::Transformer; + +pub struct HyperlinkTransformer { + regex: regex::Regex +} + +pub fn get_transformer() -> HyperlinkTransformer { + HyperlinkTransformer { + regex: regex::Regex::new(r"(?m)(^|[^\x1b])\[([^(?:\]()]+)\]\(([^\)]+)\)").unwrap(), + } +} + +impl Transformer for HyperlinkTransformer { + + fn get_regex(&self) -> ®ex::Regex { &self.regex } + + fn transform(&self, captures: ®ex::Captures) -> Option { + // don't mutate empty catpures + if captures[0].len() < 1 || captures.len() < 3 { + return None; + } + + let previous_char = captures[1].to_owned(); + let label = captures[2].to_owned(); + let target = captures[3].to_owned(); + + Some( format!("{}\x1b]8;;{}\x1b\\{}\x1b]8;;\x1b\\", previous_char, target, label) ) + } +} \ No newline at end of file diff --git a/src/syntax/italic.rs b/src/syntax/italic.rs new file mode 100644 index 0000000..a7814d1 --- /dev/null +++ b/src/syntax/italic.rs @@ -0,0 +1,27 @@ +use regex; +use crate::transform::Transformer; + +pub struct ItalicTransformer { + regex: regex::Regex, +} + +pub fn get_transformer() -> ItalicTransformer{ + ItalicTransformer { + regex: regex::Regex::new(r"(?m)\*([^\*]+)\*").unwrap(), + } +} + +impl Transformer for ItalicTransformer { + + fn get_regex(&self) -> ®ex::Regex { &self.regex } + + fn transform(&self, captures: ®ex::Captures) -> Option { + // don't mutate empty catpures + if captures[0].len() < 1 || captures.len() < 2 { + return None; + } + + Some( format!("\x1b[3m{}\x1b[23m", captures[1].replace("\x1b[0m", "\x1b[0m\x1b[3m")) ) + } + +} diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs new file mode 100644 index 0000000..0ff9791 --- /dev/null +++ b/src/syntax/mod.rs @@ -0,0 +1,5 @@ +pub mod bold; +pub mod italic; +pub mod underline; +pub mod hyperlink; +pub mod color; \ No newline at end of file diff --git a/src/syntax/underline.rs b/src/syntax/underline.rs new file mode 100644 index 0000000..a0b814e --- /dev/null +++ b/src/syntax/underline.rs @@ -0,0 +1,26 @@ +use regex; +use crate::transform::Transformer; + +pub struct UnderlineTransformer { + regex: regex::Regex +} +pub fn get_transformer() -> UnderlineTransformer { + UnderlineTransformer { + regex: regex::Regex::new(r"(?m)_([^_]+)_").unwrap(), + } +} + + +impl Transformer for UnderlineTransformer { + + fn get_regex(&self) -> ®ex::Regex { &self.regex } + + fn transform(&self, captures: ®ex::Captures) -> Option { + // don't mutate empty catpures + if captures[0].len() < 1 || captures.len() < 2 { + return None; + } + + Some( format!("\x1b[4m{}\x1b[24m", captures[1].replace("\x1b[0m", "\x1b[0m\x1b[4m")) ) + } +} diff --git a/src/transform/mod.rs b/src/transform/mod.rs index 3488751..a31d459 100644 --- a/src/transform/mod.rs +++ b/src/transform/mod.rs @@ -3,18 +3,16 @@ use regex; pub mod errors; pub mod registry; -pub struct Transformer { +pub trait Transformer { + // the regex matching text to replace - pub matcher: regex::Regex, + fn get_regex<'t>(&'t self) -> &'t regex::Regex; // transform is called to replace a match by its transformation // it takes as arguments the matched string chunks from the regex - pub transform: fn(®ex::Captures) -> Option -} + fn transform(&self, captures: ®ex::Captures) -> Option; -impl Transformer { - // execute 1 given transformer @t with its @input string and returns the output, - // and the error if one. + // transforms the input and returns an optional error. fn execute(&self, input: &mut String) -> Option { let mut cursor = 0; let original = input.to_owned(); @@ -23,16 +21,16 @@ impl Transformer { input.clear(); // apply transformatione for each match - for re_match in self.matcher.find_iter(&original) { + for re_match in self.get_regex().find_iter(&original) { // 1. append gap between input start OR previous match input.push_str( &original[cursor..re_match.start()] ); cursor = re_match.end(); // 2. execute transformation on captures - for re_captures in self.matcher.captures(re_match.as_str()) { + for re_captures in self.get_regex().captures(re_match.as_str()) { - if let Some(transformed) = (self.transform)(&re_captures) { + if let Some(transformed) = self.transform(&re_captures) { // 3. apply transformation input.push_str(&transformed); } diff --git a/src/transform/registry.rs b/src/transform/registry.rs index adb595f..c2cefff 100644 --- a/src/transform/registry.rs +++ b/src/transform/registry.rs @@ -12,14 +12,14 @@ pub fn new() -> Registry { pub struct Registry { // Transformers represents the transformer stack // ; each one will be executed in ascending order - transformers: vec::Vec + transformers: vec::Vec> } impl Registry { - pub fn add(&mut self, transformer: Transformer) { - self.transformers.push(transformer); + pub fn add(&mut self, transformer: impl Transformer + 'static) { + self.transformers.push( Box::new(transformer) ); } // transform executes each transformer of the stack in ascending order feeding