commit 6815f58962d8337ca8590c4af9218ded1ff11752
parent ec8b1a13e4a90e0b25cc6a85a563146933380e38
Author: egor-achkasov <eaachkasov@gmail.com>
Date: Thu, 26 Feb 2026 17:42:20 +0000
Get rid of regex dependency
Diffstat:
2 files changed, 28 insertions(+), 15 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -11,6 +11,5 @@ path = "src/bin/cli/main.rs"
[dependencies]
anyhow = "1.0.102"
clap = { version = "4.5.57", features = ["derive"] }
-regex = "1.12.3"
reqwest = { version = "0.12", features = ["blocking"] }
scraper = "0.25.0"
diff --git a/src/export/html/render.rs b/src/export/html/render.rs
@@ -1,4 +1,4 @@
-use crate::post::{File, Post};
+use crate::thread::{File, Post};
fn html_escape(s: &str) -> String {
s.replace('&', "&")
@@ -12,23 +12,37 @@ fn html_escape(s: &str) -> String {
/// - Lines starting with `>` (not `>>digit`) → greentext span
/// - `\n` → `<br>`
pub fn render_text_to_html(text: &str) -> String {
- static RE_REPLY: std::sync::LazyLock<regex::Regex> = std::sync::LazyLock::new(|| {
- regex::Regex::new(r">>(\d+)").unwrap()
- });
+ let needle = ">>";
let lines: Vec<String> = text.split('\n').map(|line| {
let escaped = html_escape(line);
- // Greentext: starts with > but not >>digit
- let processed = if escaped.starts_with(">") && !escaped.starts_with(">>") {
- format!("<span class=\"quote\">{}</span>", escaped)
+
+ // Replace >>id with reply link anchors
+ let mut processed = String::with_capacity(escaped.len());
+ let mut rest = escaped.as_str();
+ while let Some(pos) = rest.find(needle) {
+ processed.push_str(&rest[..pos]);
+ let after = &rest[pos + needle.len()..];
+ let digit_end = after.find(|c: char| !c.is_ascii_digit()).unwrap_or(after.len());
+ if digit_end > 0 {
+ let id = &after[..digit_end];
+ processed.push_str(&format!("<a href=\"#post{id}\" class=\"reply-link\">>>{id}</a>"));
+ rest = &after[digit_end..];
+ } else {
+ processed.push_str(needle);
+ rest = after;
+ }
+ }
+ processed.push_str(rest);
+
+ // Wrap in greentext span if line starts with > but not >>digit
+ let is_greentext = escaped.starts_with(">")
+ && !escaped.strip_prefix(needle).is_some_and(|s| s.starts_with(|c: char| c.is_ascii_digit()));
+ if is_greentext {
+ format!("<span class=\"quote\">{processed}</span>")
} else {
- escaped
- };
- // Reply links: >>id
- RE_REPLY.replace_all(&processed, |caps: ®ex::Captures| {
- let id = &caps[1];
- format!("<a href=\"#post{}\" class=\"reply-link\">>>{}</a>", id, id)
- }).into_owned()
+ processed
+ }
}).collect();
lines.join("<br>\n")