diff --git a/.gitignore b/.gitignore
index eb5a316..9087ecb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
target
+.aider*
diff --git a/Cargo.lock b/Cargo.lock
index 50f4d18..fd806ca 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8,6 +8,20 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+[[package]]
+name = "ahash"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+dependencies = [
+ "cfg-if",
+ "const-random",
+ "getrandom",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
[[package]]
name = "aho-corasick"
version = "1.1.3"
@@ -66,6 +80,12 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
[[package]]
name = "base64"
version = "0.22.1"
@@ -207,6 +227,26 @@ dependencies = [
"xdg",
]
+[[package]]
+name = "const-random"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
+dependencies = [
+ "const-random-macro",
+]
+
+[[package]]
+name = "const-random-macro"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "tiny-keccak",
+]
+
[[package]]
name = "crc32fast"
version = "1.4.2"
@@ -216,6 +256,12 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
[[package]]
name = "darling"
version = "0.20.10"
@@ -302,6 +348,8 @@ name = "draftsmith_render"
version = "0.1.0"
dependencies = [
"comrak",
+ "regex",
+ "rhai",
]
[[package]]
@@ -352,6 +400,17 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
[[package]]
name = "hashbrown"
version = "0.15.0"
@@ -380,6 +439,15 @@ dependencies = [
"hashbrown",
]
+[[package]]
+name = "instant"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
+dependencies = [
+ "cfg-if",
+]
+
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
@@ -437,6 +505,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "once_cell"
version = "1.20.2"
@@ -546,6 +623,34 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+[[package]]
+name = "rhai"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61797318be89b1a268a018a92a7657096d83f3ecb31418b9e9c16dcbb043b702"
+dependencies = [
+ "ahash",
+ "bitflags 2.6.0",
+ "instant",
+ "num-traits",
+ "once_cell",
+ "rhai_codegen",
+ "smallvec",
+ "smartstring",
+ "thin-vec",
+]
+
+[[package]]
+name = "rhai_codegen"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "rustix"
version = "0.38.38"
@@ -628,6 +733,29 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "smartstring"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
+dependencies = [
+ "autocfg",
+ "static_assertions",
+ "version_check",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
[[package]]
name = "strsim"
version = "0.11.1"
@@ -678,6 +806,12 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "thin-vec"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b"
+
[[package]]
name = "thiserror"
version = "1.0.65"
@@ -729,6 +863,15 @@ dependencies = [
"time-core",
]
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
[[package]]
name = "tinyvec"
version = "1.8.0"
@@ -777,6 +920,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
[[package]]
name = "walkdir"
version = "2.5.0"
@@ -787,6 +936,12 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
[[package]]
name = "wasm-bindgen"
version = "0.2.95"
@@ -947,3 +1102,23 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/Cargo.toml b/Cargo.toml
index ea8bb0a..c466efc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,3 +5,5 @@ edition = "2021"
[dependencies]
comrak = "0.29.0"
+regex = "1.11.1"
+rhai = "1.19.0"
diff --git a/src/bin/md_example.rs b/src/bin/md_example.rs
index 85a2520..2048828 100644
--- a/src/bin/md_example.rs
+++ b/src/bin/md_example.rs
@@ -1,15 +1,54 @@
use draftsmith_render::replace_text;
const DOC: &str = r#"
+
+this is inline html
+
+
+:::foo
+
+
+this is an admonition block
+
+
+:::
+
+
+
# Heading
# Admonitions
-
-
Also maps closer to tailwind css (easier for me)
-
+:::tip
+This works
+
+ :::foo
+ Also maps closer to tailwind css (easier for me)
+ :::
+
+```rust
+fn main() {
+ println!("Hello, world!");
+}
+```
+
+:::
+
+This is some code:
+
+```markdown
+:::tip
+This works
+
+:::
+Also maps closer to tailwind css (easier for me)
+:::
+
+:::
+
+```
# Basic Text
@@ -74,14 +113,9 @@ fn main() {
let doc = DOC;
let orig = "my";
let repl = "your";
+ let test_string = std::fs::read_to_string("tests/fixtures/input_divs_code_and_inline_code.md").unwrap();
+ let expected = std::fs::read_to_string("tests/fixtures/expected_output_divs_code_and_inline_code.md").unwrap().trim_end_matches('\n').to_string();
let html = replace_text(&doc, &orig, &repl);
- println!("{}", html);
- // Output:
- //
- // This is your input.
- //
- // - Also your input.
- // - Certainly your input.
- //
+ // println!("{}", html);
}
diff --git a/src/lib.rs b/src/lib.rs
index 649a9bf..80cf65c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,9 @@
+mod processor;
+
use comrak::nodes::NodeValue;
use comrak::{format_html, parse_document, Arena, Options};
use comrak::{ComrakOptions, ExtensionOptions, ParseOptions, RenderOptions};
+use processor::Processor;
pub fn add(left: u64, right: u64) -> u64 {
left + right
@@ -32,6 +35,11 @@ pub fn replace_text(document: &str, orig_string: &str, replacement: &str) -> Str
let mut options = Options::default();
config_opts(&mut options);
+ // Preprocess the document
+ let mut processor = Processor::default();
+ let document = processor.process(document).as_str();
+
+
// get the AST
let root = parse_document(&arena, document, &options);
diff --git a/src/processor.rs b/src/processor.rs
new file mode 100644
index 0000000..de523fb
--- /dev/null
+++ b/src/processor.rs
@@ -0,0 +1,245 @@
+//! This module provides functionality for processing markdown-like text
+//! with custom admonitions and code blocks.
+
+use regex::Regex;
+use rhai::{Engine, Scope};
+
+const ADMONITION_START_PATTERN: &str = r"^\s*:::([\w!\{\}-]+)$";
+const ADMONITION_END_PATTERN: &str = r"^\s*(:::)$";
+const CODE_START_PATTERN: &str = r"^\s*```\{rhai\}$";
+const RHAI_DISPLAY_START_PATTERN: &str = r"^\s*```\{rhai-display\}$";
+const CODE_END_PATTERN: &str = r"^\s*```$";
+const LAMBDA_PATTERN: &str = r"λ#\(((?s).*?)\)#";
+
+/// A processor for handling custom markdown-like syntax.
+pub struct Processor<'a> {
+ admonition_start_regex: Regex,
+ admonition_end_regex: Regex,
+ code_start_regex: Regex,
+ rhai_display_start_regex: Regex,
+ code_end_regex: Regex,
+ lambda_regex: Regex,
+ div_stack: Vec,
+ eval_stack: bool,
+ is_rhai_display: bool,
+ contents: Vec,
+ rhai_engine: Engine,
+ rhai_scope: Scope<'a>,
+}
+
+impl<'a> Default for Processor<'a> {
+ fn default() -> Self {
+ Self {
+ admonition_start_regex: Regex::new(ADMONITION_START_PATTERN)
+ .expect("Failed to compile regex"),
+ admonition_end_regex: Regex::new(ADMONITION_END_PATTERN)
+ .expect("Failed to compile regex"),
+ code_start_regex: Regex::new(CODE_START_PATTERN).expect("Failed to compile regex"),
+ rhai_display_start_regex: Regex::new(RHAI_DISPLAY_START_PATTERN)
+ .expect("Failed to compile regex"),
+ code_end_regex: Regex::new(CODE_END_PATTERN).expect("Failed to compile regex"),
+ lambda_regex: Regex::new(LAMBDA_PATTERN).expect("Failed to compile regex"),
+ div_stack: Vec::new(),
+ eval_stack: false,
+ is_rhai_display: false,
+ contents: Vec::new(),
+ rhai_engine: Engine::new(),
+ rhai_scope: Scope::new(),
+ }
+ }
+}
+
+impl<'a> Processor<'a> {
+ /// Processes the input string and returns the transformed output.
+ ///
+ /// # Arguments
+ ///
+ /// * `input` - A string slice that holds the text to be processed.
+ ///
+ /// # Returns
+ ///
+ /// A `String` containing the processed text with custom syntax transformed.
+ pub fn process(&mut self, input: &str) -> String {
+ input
+ .lines()
+ .map(|line| self.process_line(line))
+ .collect::>()
+ .join("")
+ .trim_end_matches('\n')
+ .to_string()
+ }
+
+ /// Evaluates Rhai code and returns a formatted string of the results.
+ ///
+ /// # Arguments
+ ///
+ /// * `engine` - A reference to the Rhai Engine.
+ /// * `scope` - A mutable reference to the Rhai Scope.
+ /// * `captured` - A string slice containing the Rhai code to evaluate.
+ ///
+ /// # Returns
+ ///
+ /// A `String` containing the formatted evaluation results.
+ fn process_lambda(engine: &Engine, scope: &mut Scope, captured: &str) -> String {
+ match engine.eval_with_scope::(scope, captured) {
+ Ok(result) => format!("{}", result),
+ Err(err) => format!("Error: {}", err),
+ }
+ }
+
+ /// Processes a single line of text.
+ ///
+ /// # Arguments
+ ///
+ /// * `line` - A string slice that holds the line to be processed.
+ ///
+ /// # Returns
+ ///
+ /// A `String` containing the processed line.
+ fn process_line(&mut self, line: &str) -> String {
+ if self.code_start_regex.is_match(line) {
+ self.handle_code_start(false)
+ } else if self.rhai_display_start_regex.is_match(line) {
+ self.handle_code_start(true)
+ } else if self.code_end_regex.is_match(line) {
+ self.handle_code_end()
+ } else if self.admonition_start_regex.is_match(line) {
+ if let Some(caps) = self.admonition_start_regex.captures(line) {
+ self.handle_admonition_start(&caps[1])
+ } else {
+ String::new()
+ }
+ } else if self.admonition_end_regex.is_match(line) {
+ self.handle_admonition_end()
+ } else {
+ self.handle_regular_line(line)
+ }
+ }
+
+ /// Handles the start of an admonition block.
+ ///
+ /// # Arguments
+ ///
+ /// * `class` - The class of the admonition.
+ ///
+ /// # Returns
+ ///
+ /// A `String` containing the opening HTML div tag for the admonition.
+ fn handle_admonition_start(&mut self, class: &str) -> String {
+ if class.is_empty() {
+ return String::new(); // Return an empty string for empty admonitions
+ }
+ let html = match class {
+ "alert" => format!(""),
+ "info" => format!("
"),
+ "success" => format!("
"),
+ "warning" => format!("
"),
+ "error" => format!("
"),
+ "tip" => format!("
"),
+ "fold" => format!("
"),
+ "summary" => format!(""),
+ "col" => format!(""),
+ "card" => format!("
"),
+ _ => format!("
", class),
+ };
+ self.div_stack.push(class.to_string());
+ format!("{}\n", html)
+ }
+
+ /// Handles the end of an admonition block.
+ ///
+ /// # Returns
+ ///
+ /// A `String` containing the closing HTML div tag for the admonition.
+ fn handle_admonition_end(&mut self) -> String {
+ if self.div_stack.pop().is_some() {
+ "
\n".to_string()
+ } else {
+ String::from(":::\n")
+ }
+ }
+
+ /// Handles the start of a code block.
+ ///
+ /// # Returns
+ ///
+ /// An empty `String` as the code block start is not directly output.
+ fn handle_code_start(&mut self, is_display: bool) -> String {
+ self.eval_stack = true;
+ self.is_rhai_display = is_display;
+ self.contents.clear(); // Clear any previous contents
+ String::new()
+ }
+
+ /// Handles the end of a code block.
+ ///
+ /// # Returns
+ ///
+ /// A `String` containing the evaluated code output wrapped in HTML.
+ fn handle_code_end(&mut self) -> String {
+ if self.eval_stack {
+ self.eval_stack = false;
+ if !self.contents.is_empty() {
+ let code = self.contents.join("\n");
+ let results = Self::process_lambda(&self.rhai_engine, &mut self.rhai_scope, &code);
+ self.contents.clear();
+
+ if self.is_rhai_display {
+ if results.trim().is_empty() {
+ return String::new();
+ } else {
+ format!(
+ "
\n```rust\n{}\n```\n
\n```\n{}\n```\n
\n
\n",
+ code, results
+ )
+ }
+ } else {
+ return String::new();
+ }
+ } else {
+ String::new()
+ }
+ } else {
+ String::new()
+ }
+ }
+
+ /// Handles a regular line of text.
+ ///
+ /// # Arguments
+ ///
+ /// * `line` - A string slice that holds the line to be processed.
+ ///
+ /// # Returns
+ ///
+ /// A `String` containing the processed line.
+ fn handle_regular_line(&mut self, line: &str) -> String {
+ if self.eval_stack {
+ self.contents.push(line.to_string());
+ String::new() // Return an empty string when in eval_stack mode
+ } else {
+ let mut result = String::new();
+ let mut last_end = 0;
+ let mut scope = self.rhai_scope.clone();
+ let engine = &self.rhai_engine;
+
+ for cap in self.lambda_regex.captures_iter(line) {
+ let whole_match = cap.get(0).unwrap();
+ let captured = &cap[1];
+ result.push_str(&line[last_end..whole_match.start()]);
+ result.push_str(&Self::process_lambda(engine, &mut scope, captured));
+ last_end = whole_match.end();
+ }
+ result.push_str(&line[last_end..]);
+
+ // Update the main scope with any changes from the cloned scope
+ self.rhai_scope = scope;
+
+ if result.trim().is_empty() {
+ "\n".to_string() // Always return a newline for empty lines
+ } else {
+ format!("{}\n", result) // Add a newline after each non-empty line
+ }
+ }
+ }
+}