From 96f7d648b601cee5c3f5917b3c3249e1404c2d3b Mon Sep 17 00:00:00 2001 From: yggverse Date: Mon, 17 Mar 2025 02:09:52 +0200 Subject: [PATCH] remove regex dependency, rename constructor, implement zero-copy trait --- src/line/code/inline.rs | 51 +++++++++++++++++++++------------ src/line/code/inline/gemtext.rs | 38 ++++++++++++++++++++++++ tests/integration.rs | 2 +- 3 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 src/line/code/inline/gemtext.rs diff --git a/src/line/code/inline.rs b/src/line/code/inline.rs index 3569a29..a3cbc01 100644 --- a/src/line/code/inline.rs +++ b/src/line/code/inline.rs @@ -1,5 +1,7 @@ +pub mod gemtext; +pub use gemtext::Gemtext; + use super::TAG; -use glib::{Regex, RegexCompileFlags, RegexMatchFlags}; /// Inline [preformatted](https://geminiprotocol.net/docs/gemtext-specification.gmi#in-pre-formatted-mode) entity holder pub struct Inline { @@ -10,24 +12,35 @@ impl Inline { // Constructors /// Parse `Self` from line string - pub fn from(line: &str) -> Option { - // Skip next operations on prefix and postfix mismatch `TAG` - // * replace regex implementation @TODO - if !line.starts_with(TAG) && !line.ends_with(TAG) { - return None; - } - - // Parse line - let regex = Regex::split_simple( - r"^`{3}([^`]+)`{3}$", - line, - RegexCompileFlags::DEFAULT, - RegexMatchFlags::DEFAULT, - ); - - // Extract formatted value - Some(Self { - value: regex.get(1)?.trim().to_string(), + pub fn parse(line: &str) -> Option { + line.as_value().map(|v| Self { + value: v.to_string(), }) } + + // Converters + + /// Convert `Self` to [Gemtext](https://geminiprotocol.net/docs/gemtext-specification.gmi) line + pub fn to_source(&self) -> String { + self.value.to_source() + } +} + +#[test] +fn test() { + fn assert(source: &str, value: &str) { + let list = Inline::parse(source).unwrap(); + assert_eq!(list.value, value); + assert_eq!(list.to_source(), format!("```{value}```")); + } + assert("```inline```", "inline"); + assert("```inline ```", "inline"); + assert("``` inline ```", "inline"); + assert("``` inline```", "inline"); + assert("``` inline``` ", "inline"); + assert("``````inline``` ", "```inline"); + assert("``````inline`````` ", "```inline```"); + assert("```inline`````` ", "inline```"); + assert!("```inline".as_value().is_none()); + assert!("```inline``` ne".as_value().is_none()); } diff --git a/src/line/code/inline/gemtext.rs b/src/line/code/inline/gemtext.rs new file mode 100644 index 0000000..ba53570 --- /dev/null +++ b/src/line/code/inline/gemtext.rs @@ -0,0 +1,38 @@ +use super::TAG; + +pub trait Gemtext { + /// Get [Gemtext](https://geminiprotocol.net/docs/gemtext-specification.gmi) value for `Self` + fn as_value(&self) -> Option<&str>; + /// Convert `Self` to [Gemtext](https://geminiprotocol.net/docs/gemtext-specification.gmi) line + fn to_source(&self) -> String; +} + +impl Gemtext for str { + fn as_value(&self) -> Option<&str> { + if let Some(p) = self.strip_prefix(TAG) { + return p.trim().strip_suffix(TAG).map(|s| s.trim()); + } + None + } + fn to_source(&self) -> String { + format!("{TAG}{}{TAG}", self.trim()) + } +} + +#[test] +fn test() { + fn assert(source: &str, value: &str) { + assert_eq!(source.as_value(), Some(value)); + assert_eq!(value.to_source(), format!("```{value}```")); + } + assert("```inline```", "inline"); + assert("```inline ```", "inline"); + assert("``` inline ```", "inline"); + assert("``` inline```", "inline"); + assert("``` inline``` ", "inline"); + assert("``````inline``` ", "```inline"); + assert("``````inline`````` ", "```inline```"); + assert("```inline`````` ", "inline```"); + assert!("```inline".as_value().is_none()); + assert!("```inline``` ne".as_value().is_none()); +} diff --git a/tests/integration.rs b/tests/integration.rs index 70924f6..c9168fe 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -36,7 +36,7 @@ fn gemtext() { // Parse document by line for line in gemtext.lines() { // Inline code - if let Some(result) = Inline::from(line) { + if let Some(result) = Inline::parse(line) { code_inline.push(result); continue; }