Last active
December 11, 2025 17:33
-
-
Save rybla/8a810e5f32af42e64af90f7a2ae43faf to your computer and use it in GitHub Desktop.
In Rust, using Maud library to generate a single-page HTML app for fuzzy-filtering a list read from a text file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [package] | |
| name = "maud-example-fuzzy-filter-list" | |
| version = "0.1.0" | |
| edition = "2024" | |
| [dependencies] | |
| anyhow = "1.0.100" | |
| maud = "0.27.0" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Haskell | |
| Rust | |
| TypeScript | |
| Agda | |
| PureScript | |
| Python | |
| OCaml | |
| Scala | |
| Erlang | |
| Elixir | |
| Clojure | |
| F# | |
| Racket | |
| Scheme | |
| Common Lisp | |
| Coq | |
| Idris | |
| Lean | |
| Mercury | |
| Standard ML |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| ORGANIZATION AND LOGIC: | |
| This program demonstrates a Static Site Generator (SSG) pattern using Rust and the Maud library. | |
| 1. Data Ingestion: The program reads a plaintext file (`items.txt`) expected to be in the current directory. | |
| It uses standard `std::fs` and iterator methods to parse non-empty lines into a vector of strings. | |
| 2. HTML Generation (Maud): | |
| - We use the `maud::html!` macro to construct the DOM. This macro compiles HTML directly into Rust code | |
| for performance and type safety. | |
| - CSS is embedded directly in the `<head>` to keep the output as a single, self-contained file. | |
| - The list of items is rendered dynamically using a Rust `for` loop inside the `html!` macro. | |
| 3. Interactivity (JavaScript): | |
| - Since the Rust program runs ahead-of-time to generate the file, the "fuzzy filter" logic is implemented | |
| in client-side JavaScript embedded in the `script` tag. | |
| - The "fuzzy" algorithm used here is a Subsequence Match (characters must appear in order, but not necessarily adjacent). | |
| - An event listener on the search input toggles the `display` style of list items based on the match result. | |
| 4. Output: The resulting HTML markup is written to `index.html`. | |
| */ | |
| use anyhow::Result; | |
| use maud::{DOCTYPE, PreEscaped, html}; | |
| use std::fs; | |
| use std::io::Write; | |
| fn main() -> Result<()> { | |
| let content = fs::read_to_string("items.txt")?; | |
| let items: Vec<&str> = content.lines().filter(|l| !l.is_empty()).collect(); | |
| let markup = html! { | |
| (DOCTYPE) | |
| html lang="en" { | |
| head { | |
| meta charset="utf-8"; | |
| meta name="viewport" content="width=device-width, initial-scale=1.0"; | |
| title { "Rust + Maud Filter" } | |
| style { (PreEscaped(r#" | |
| body { font-family: system-ui, sans-serif; max-width: 600px; margin: 2rem auto; padding: 0 1rem; background: #f4f4f5; color: #18181b; } | |
| h1 { font-size: 1.5rem; margin-bottom: 1rem; } | |
| input { width: 100%; padding: 0.75rem; font-size: 1rem; border: 1px solid #d4d4d8; border-radius: 0.5rem; margin-bottom: 1.5rem; box-sizing: border-box; } | |
| ul { list-style: none; padding: 0; background: white; border-radius: 0.5rem; box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1); overflow: hidden; } | |
| li { padding: 0.75rem 1rem; border-bottom: 1px solid #f4f4f5; } | |
| li:last-child { border-bottom: none; } | |
| .hidden { display: none; } | |
| "#)) } | |
| } | |
| body { | |
| h1 { "Item Filter" } | |
| input type="text" id="search" placeholder="Fuzzy search items..."; | |
| ul id="list" { | |
| @for item in &items { | |
| li { (item) } | |
| } | |
| } | |
| script { (PreEscaped(r#" | |
| const searchInput = document.getElementById('search'); | |
| const listItems = document.querySelectorAll('#list li'); | |
| // Simple subsequence fuzzy match | |
| function isFuzzyMatch(text, query) { | |
| text = text.toLowerCase(); | |
| query = query.toLowerCase(); | |
| let queryIdx = 0; | |
| for (let char of text) { | |
| if (char === query[queryIdx]) queryIdx++; | |
| if (queryIdx === query.length) return true; | |
| } | |
| return false; | |
| } | |
| searchInput.addEventListener('input', (e) => { | |
| const query = e.target.value; | |
| listItems.forEach(li => { | |
| if (isFuzzyMatch(li.textContent, query)) { | |
| li.classList.remove('hidden'); | |
| } else { | |
| li.classList.add('hidden'); | |
| } | |
| }); | |
| }); | |
| "#)) } | |
| } | |
| } | |
| }; | |
| let mut file = fs::File::create("index.html")?; | |
| write!(file, "{}", markup.into_string())?; | |
| // file.write_all(markup.into_string().as_bytes())?; | |
| println!( | |
| "Successfully generated index.html with {} items.", | |
| items.len() | |
| ); | |
| Ok(()) | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Created by Gemini 3: https://gemini.google.com/share/334dfd4451e1