fully ported library

This commit is contained in:
Adrien Marquès 2019-10-13 22:43:58 +02:00
parent 3385594089
commit 7c2812be4f
9 changed files with 281 additions and 13 deletions

76
src/lib.rs Normal file
View File

@ -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)*));
})
}

27
src/syntax/bold.rs Normal file
View File

@ -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) -> &regex::Regex { &self.regex }
fn transform(&self, captures: &regex::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")) )
}
}

79
src/syntax/color.rs Normal file
View File

@ -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) -> &regex::Regex { &self.regex }
fn transform(&self, captures: &regex::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;
}

30
src/syntax/hyperlink.rs Normal file
View File

@ -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) -> &regex::Regex { &self.regex }
fn transform(&self, captures: &regex::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) )
}
}

27
src/syntax/italic.rs Normal file
View File

@ -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) -> &regex::Regex { &self.regex }
fn transform(&self, captures: &regex::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")) )
}
}

5
src/syntax/mod.rs Normal file
View File

@ -0,0 +1,5 @@
pub mod bold;
pub mod italic;
pub mod underline;
pub mod hyperlink;
pub mod color;

26
src/syntax/underline.rs Normal file
View File

@ -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) -> &regex::Regex { &self.regex }
fn transform(&self, captures: &regex::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")) )
}
}

View File

@ -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(&regex::Captures) -> Option<String>
}
fn transform(&self, captures: &regex::Captures) -> Option<String>;
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<errors::Error> {
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);
}

View File

@ -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<Transformer>
transformers: vec::Vec<Box<dyn Transformer>>
}
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