fully ported library
This commit is contained in:
parent
3385594089
commit
7c2812be4f
|
@ -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)*));
|
||||||
|
})
|
||||||
|
}
|
|
@ -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<String> {
|
||||||
|
// 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")) )
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String> {
|
||||||
|
// 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;
|
||||||
|
}
|
|
@ -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<String> {
|
||||||
|
// 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) )
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String> {
|
||||||
|
// 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")) )
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod bold;
|
||||||
|
pub mod italic;
|
||||||
|
pub mod underline;
|
||||||
|
pub mod hyperlink;
|
||||||
|
pub mod color;
|
|
@ -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<String> {
|
||||||
|
// 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")) )
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,18 +3,16 @@ use regex;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
|
|
||||||
pub struct Transformer {
|
pub trait Transformer {
|
||||||
|
|
||||||
// the regex matching text to replace
|
// 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
|
// transform is called to replace a match by its transformation
|
||||||
// it takes as arguments the matched string chunks from the regex
|
// it takes as arguments the matched string chunks from the regex
|
||||||
pub transform: fn(®ex::Captures) -> Option<String>
|
fn transform(&self, captures: ®ex::Captures) -> Option<String>;
|
||||||
}
|
|
||||||
|
|
||||||
impl Transformer {
|
// transforms the input and returns an optional error.
|
||||||
// execute 1 given transformer @t with its @input string and returns the output,
|
|
||||||
// and the error if one.
|
|
||||||
fn execute(&self, input: &mut String) -> Option<errors::Error> {
|
fn execute(&self, input: &mut String) -> Option<errors::Error> {
|
||||||
let mut cursor = 0;
|
let mut cursor = 0;
|
||||||
let original = input.to_owned();
|
let original = input.to_owned();
|
||||||
|
@ -23,16 +21,16 @@ impl Transformer {
|
||||||
input.clear();
|
input.clear();
|
||||||
|
|
||||||
// apply transformatione for each match
|
// 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
|
// 1. append gap between input start OR previous match
|
||||||
input.push_str( &original[cursor..re_match.start()] );
|
input.push_str( &original[cursor..re_match.start()] );
|
||||||
cursor = re_match.end();
|
cursor = re_match.end();
|
||||||
|
|
||||||
// 2. execute transformation on captures
|
// 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
|
// 3. apply transformation
|
||||||
input.push_str(&transformed);
|
input.push_str(&transformed);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,14 +12,14 @@ pub fn new() -> Registry {
|
||||||
pub struct Registry {
|
pub struct Registry {
|
||||||
// Transformers represents the transformer stack
|
// Transformers represents the transformer stack
|
||||||
// ; each one will be executed in ascending order
|
// ; each one will be executed in ascending order
|
||||||
transformers: vec::Vec<Transformer>
|
transformers: vec::Vec<Box<dyn Transformer>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Registry {
|
impl Registry {
|
||||||
|
|
||||||
pub fn add(&mut self, transformer: Transformer) {
|
pub fn add(&mut self, transformer: impl Transformer + 'static) {
|
||||||
self.transformers.push(transformer);
|
self.transformers.push( Box::new(transformer) );
|
||||||
}
|
}
|
||||||
|
|
||||||
// transform executes each transformer of the stack in ascending order feeding
|
// transform executes each transformer of the stack in ascending order feeding
|
||||||
|
|
Loading…
Reference in New Issue