diff --git a/.rustfmt.toml b/.rustfmt.toml index 25b733d..21ebfc1 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -9,5 +9,3 @@ brace_style = "PreferSameLine" blank_lines_upper_bound = 5 combine_control_expr = false wrap_comments = false -group_imports = "StdExternalCrate" -imports_granularity = "Crate" diff --git a/Cargo.lock b/Cargo.lock index d31c5bd..2ae966a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,27 +225,6 @@ dependencies = [ "syn 2.0.104", ] -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.60.2", -] - [[package]] name = "displaydoc" version = "0.2.5" @@ -579,7 +558,6 @@ name = "htmlua-parser" version = "0.1.0" dependencies = [ "anyhow", - "dirs", "html5ever 0.35.0", "httptest", "kuchikiki", @@ -591,7 +569,6 @@ dependencies = [ "serde_json", "syntect", "tendril", - "toml", ] [[package]] @@ -942,16 +919,6 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" -[[package]] -name = "libredox" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" -dependencies = [ - "bitflags 2.9.1", - "libc", -] - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1242,12 +1209,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "ordered-float" version = "2.10.1" @@ -1436,9 +1397,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plist" -version = "1.7.4" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" +checksum = "546b279bf0638ee811d9e47de2ca5b66575a543035d79fdf83959dd2f5c3b4c3" dependencies = [ "base64", "indexmap 2.10.0", @@ -1513,9 +1474,9 @@ checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" [[package]] name = "quick-xml" -version = "0.38.0" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] @@ -1625,17 +1586,6 @@ dependencies = [ "bitflags 2.9.1", ] -[[package]] -name = "redox_users" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 2.0.12", -] - [[package]] name = "regex" version = "1.11.1" @@ -1915,15 +1865,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_spanned" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" -dependencies = [ - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2082,7 +2023,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 1.0.69", + "thiserror", "walkdir", "yaml-rust", ] @@ -2144,16 +2085,7 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl", ] [[package]] @@ -2167,17 +2099,6 @@ dependencies = [ "syn 2.0.104", ] -[[package]] -name = "thiserror-impl" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "time" version = "0.3.41" @@ -2281,45 +2202,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" -dependencies = [ - "indexmap 2.10.0", - "serde", - "serde_spanned", - "toml_datetime", - "toml_parser", - "toml_writer", - "winnow", -] - -[[package]] -name = "toml_datetime" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_parser" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" -dependencies = [ - "winnow", -] - -[[package]] -name = "toml_writer" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" - [[package]] name = "tower" version = "0.5.2" @@ -2793,12 +2675,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" -[[package]] -name = "winnow" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" - [[package]] name = "winsafe" version = "0.0.19" diff --git a/cspell.json b/cspell.json index 44ee262..e8c260e 100644 --- a/cspell.json +++ b/cspell.json @@ -2,9 +2,7 @@ "words": [ "htmlua", "headermap", - "reqwest", - "includeelement", - "exportelement" + "reqwest" ], "userWords": [ "mlua" diff --git a/htmlua-apache-cgi/src/main.rs b/htmlua-apache-cgi/src/main.rs index 5998ff6..ab91cb1 100644 --- a/htmlua-apache-cgi/src/main.rs +++ b/htmlua-apache-cgi/src/main.rs @@ -1,6 +1,5 @@ -use std::env; - use htmlua_parser::serve::serve_content; +use std::env; fn main() { println!("Content-Type: text/html\n"); diff --git a/htmlua-parser/Cargo.toml b/htmlua-parser/Cargo.toml index 8200513..4ee6be3 100644 --- a/htmlua-parser/Cargo.toml +++ b/htmlua-parser/Cargo.toml @@ -5,7 +5,6 @@ edition = "2024" [dependencies] anyhow = "1.0.98" -dirs = "6.0.0" html5ever = "0.35.0" httptest = "0.16.3" kuchikiki = "0.8.2" @@ -17,7 +16,7 @@ serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" syntect = "5.2.0" tendril = "0.4.3" -toml = "0.9.2" + [lints.clippy] pedantic = { level = "warn", priority = -1 } diff --git a/htmlua-parser/src/config.rs b/htmlua-parser/src/config.rs deleted file mode 100644 index de734ed..0000000 --- a/htmlua-parser/src/config.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::{fs, path::PathBuf}; - -use anyhow::{Context, Result}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct Config { - pub paths: PathConfig, - pub server: ServerConfig, - pub syntax_highlighting: SyntaxConfig, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct PathConfig { - pub pages: PathBuf, - pub components: PathBuf, - pub themes: PathBuf, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct ServerConfig { - pub host: String, - pub port: u16, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct SyntaxConfig { - pub default_theme: String, - pub load_custom_themes: bool, -} - -impl Default for Config { - fn default() -> Self { - Self { - paths: PathConfig { - pages: PathBuf::from("/var/www/htmlua/pages"), - components: PathBuf::from("/var/www/htmlua/components"), - themes: PathBuf::from("/var/www/htmlua/themes"), - }, - server: ServerConfig { - host: "127.0.0.1".to_string(), - port: 8080, - }, - syntax_highlighting: SyntaxConfig { - default_theme: "base16-ocean.dark".to_string(), - load_custom_themes: true, - }, - } - } -} - -impl Config { - pub fn load() -> Result { - let config_path = Self::get_config_path(); - if config_path.exists() { - let config_content = fs::read_to_string(&config_path) - .with_context(|| format!("Failed to read config file: {}", config_path.display()))?; - let config: Config = toml::from_str(&config_content) - .with_context(|| format!("Failed to parse config file: {}", config_path.display()))?; - Ok(config) - } else { - let default_config = Config::default(); - if let Some(parent) = config_path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("Failed to create config directory: {}", parent.display()))?; - } - let config_content = - toml::to_string_pretty(&default_config).context("Failed to serialize default config")?; - fs::write(&config_path, config_content) - .with_context(|| format!("Failed to write default config to: {}", config_path.display()))?; - Ok(default_config) - } - } - - fn get_config_path() -> PathBuf { - if cfg!(windows) { - dirs::config_dir() - .unwrap_or_else(|| PathBuf::from("C:\\ProgramData")) - .join("htmlua") - .join("config.toml") - } else { - PathBuf::from("/etc/htmlua.toml") - } - } - - pub fn save(&self) -> Result<()> { - let config_path = Self::get_config_path(); - if let Some(parent) = config_path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("Failed to create config directory: {}", parent.display()))?; - } - let config_content = toml::to_string_pretty(self).context("Failed to serialize config")?; - fs::write(&config_path, config_content) - .with_context(|| format!("Failed to write config to: {}", config_path.display()))?; - Ok(()) - } - - #[must_use] - pub fn config_file_path() -> PathBuf { Self::get_config_path() } -} diff --git a/htmlua-parser/src/helpers.rs b/htmlua-parser/src/helpers.rs index 2992f70..512ebb7 100644 --- a/htmlua-parser/src/helpers.rs +++ b/htmlua-parser/src/helpers.rs @@ -6,10 +6,12 @@ use std::{ use anyhow::Result; use kuchikiki::NodeRef; -use markup5ever::{LocalName, QualName, namespace_url, ns}; +use markup5ever::{LocalName, Namespace, QualName}; use tendril::TendrilSink; + + pub fn read_doc_from_file(path: PathBuf) -> Result { let mut file = File::open(path)?; let mut reader = BufReader::new(&mut file); @@ -27,7 +29,7 @@ pub fn read_doc_from_file(path: PathBuf) -> Result { if is_whole_doc { Ok(kuchikiki::parse_html().one(page_string)) } else { - let ctx_name = QualName::new(None, ns!(), LocalName::from("div")); + let ctx_name = QualName::new(None, Namespace::from("http://www.w3.org/1999/xhtml"), LocalName::from("div")); Ok(kuchikiki::parse_fragment(ctx_name, Vec::new()).one(page_string)) } } diff --git a/htmlua-parser/src/lib.rs b/htmlua-parser/src/lib.rs index d2d5599..062fcb4 100644 --- a/htmlua-parser/src/lib.rs +++ b/htmlua-parser/src/lib.rs @@ -1,4 +1,3 @@ -pub mod config; pub mod helpers; pub mod htmlua_stdlib; pub mod render; diff --git a/htmlua-parser/src/render.rs b/htmlua-parser/src/render.rs index 6a70315..a92e7c5 100644 --- a/htmlua-parser/src/render.rs +++ b/htmlua-parser/src/render.rs @@ -7,7 +7,6 @@ use std::{ use anyhow::{Result, anyhow}; use kuchikiki::{NodeRef, traits::TendrilSink}; -use markup5ever::{LocalName, Namespace, QualName}; use mlua::Lua; use pulldown_cmark::{Options, Parser, html}; use syntect::{ @@ -18,7 +17,7 @@ use syntect::{ util::LinesWithEndings, }; -use crate::{helpers::read_doc_from_file, htmlua_stdlib::create_htmlua_stdlib, serve::get_config}; +use crate::{helpers::read_doc_from_file, htmlua_stdlib::create_htmlua_stdlib}; fn build_lua_with_stdout(stdout: &Rc>) -> Result { @@ -135,15 +134,16 @@ pub fn expand_template(document: NodeRef, component_path: &PathBuf, include_from } pub fn process_syntax_highlighting(document: NodeRef) -> Result { - let config = get_config(); let ps = SyntaxSet::load_defaults_newlines(); let mut ts = ThemeSet::load_defaults(); let syntax_elements: Vec<_> = match document.select("syntaxhighlight") { Ok(e) => e.collect(), Err(()) => return Err(anyhow!("Unable to find syntaxhighlight elements")), }; - if !syntax_elements.is_empty() && config.syntax_highlighting.load_custom_themes { - let _ = ts.add_from_folder(&config.paths.themes); + if !syntax_elements.is_empty() { + // TODO: read from config + let themes = PathBuf::from("/var/www/htmlua/themes"); + let _ = ts.add_from_folder(themes); } for node in syntax_elements { let attrs = match node.as_node().as_element() { @@ -151,7 +151,7 @@ pub fn process_syntax_highlighting(document: NodeRef) -> Result { None => continue, }; let language = attrs.get("lang").unwrap_or("text"); - let theme_name = attrs.get("theme").unwrap_or(&config.syntax_highlighting.default_theme); + let theme_name = attrs.get("theme").unwrap_or("base16-ocean.dark"); if let Some(text_node) = node.as_node().first_child() { if let Some(code_text) = text_node.as_text() { let syntax = ps @@ -181,41 +181,9 @@ pub fn process_syntax_highlighting(document: NodeRef) -> Result { Ok(document) } -pub fn generate_footnotes(document: NodeRef) -> Result { - let Ok(footnote_container) = document.select_first("footnotecontainer") else { - return Ok(document); - }; - - let ctx_name = QualName::new(None, Namespace::from("http://www.w3.org/1999/xhtml"), LocalName::from("div")); - for (i, footnote) in document - .select("footnote") - .map_err(|()| anyhow!("Failed to get footnote"))? - .enumerate() - { - let i = i + 1; - let fn_text = footnote.text_contents(); - let sup_tag = kuchikiki::parse_fragment(ctx_name.clone(), Vec::new()) - .one(format!("{i}")) - .select_first("a") - .map_err(|()| anyhow!("parse err"))?; - footnote.as_node().insert_after(sup_tag.as_node().clone()); - let text_tag = kuchikiki::parse_fragment(ctx_name.clone(), Vec::new()) - .one(format!("

{i}: {fn_text}

")) - .select_first("p") - .map_err(|()| anyhow!("parse err"))?; - footnote_container.as_node().insert_before(text_tag.as_node().clone()); - } - while let Ok(i) = document.select_first("footnote") { - i.as_node().detach(); - } - footnote_container.as_node().detach(); - Ok(document) -} - #[cfg(test)] mod tests { use httptest::{Expectation, ServerPool, matchers::*, responders::*}; - use markup5ever::{namespace_url, ns}; use super::*; @@ -347,6 +315,7 @@ mod tests { let document = kuchikiki::parse_html().one(page); let d = execute_lua(expand_template(document, &p, None).unwrap()).unwrap(); let text = d.select_first("span").unwrap().as_node().text_contents(); + println!("{}", d.to_string()); assert_eq!(text, "Test from Lua!\n"); assert!(d.select_first("lua").is_err()); let text = d.select_first("#inc1").unwrap().as_node().text_contents(); @@ -404,74 +373,6 @@ mod tests { assert!(d.select_first("includeelement").is_err()); } - #[test] - fn include_base() { - let page = r#" - - title_test - body_el - - "#; - let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - p.push("tests/components"); - let ctx_name = QualName::new(None, ns!(html), LocalName::from("div")); - let document = kuchikiki::parse_fragment(ctx_name, Vec::new()).one(page); - let d = expand_template(document, &p, None).unwrap(); - let text = d - .select_first("head") - .unwrap() - .as_node() - .select_first("title") - .unwrap() - .as_node() - .text_contents(); - assert_eq!(text, "title_test"); - let text = d.select_first("#tb").unwrap().as_node().text_contents(); - assert_eq!(text, "body_el"); - assert!(d.select_first("exportelement").is_err()); - assert!(d.select_first("includeelement").is_err()); - } - - #[test] - fn footnotes() { - let page = r" - - - - Basic HTML Page - - -
-

asdf

-

asdfum actually

-

asdf

-

asdfno

-
-
- -
- - "; - let document = kuchikiki::parse_html().one(page); - let d = generate_footnotes(document).unwrap(); - let sup1 = d.select_first("#ft-sup-1").unwrap(); - assert_eq!(sup1.attributes.borrow().get("title").unwrap(), "um actually"); - assert_eq!(sup1.text_contents(), "1"); - - let sup2 = d.select_first("#ft-sup-2").unwrap(); - assert_eq!(sup2.attributes.borrow().get("title").unwrap(), "no"); - assert_eq!(sup2.text_contents(), "2"); - - let ft_text_1 = d.select_first("#ft-text-1").unwrap(); - assert!(ft_text_1.text_contents().contains("um actually")); - - let ft_text_2 = d.select_first("#ft-text-2").unwrap(); - assert!(ft_text_2.text_contents().contains("no")); - - assert!(d.select_first("footnote").is_err()); - assert!(d.select_first("footnotecontainer").is_err()); - } - #[test] fn basic_markdown() { let page = r" diff --git a/htmlua-parser/src/serve.rs b/htmlua-parser/src/serve.rs index 375e885..cfb3811 100644 --- a/htmlua-parser/src/serve.rs +++ b/htmlua-parser/src/serve.rs @@ -1,33 +1,19 @@ -use std::{path::Path, sync::OnceLock}; +use crate::helpers::read_doc_from_file; -use anyhow::{Result, anyhow}; - -use crate::{ - config::Config, - helpers::read_doc_from_file, - render::{execute_lua, expand_template, process_markdown, process_syntax_highlighting}, -}; - -static CONFIG: OnceLock = OnceLock::new(); - -pub fn get_config() -> &'static Config { - CONFIG.get_or_init(|| { - Config::load().unwrap_or_else(|e| { - eprintln!("Warning: Failed to load config: {e}"); - eprintln!("Using default configuration"); - Config::default() - }) - }) -} +use super::render::{execute_lua, expand_template, process_markdown, process_syntax_highlighting}; +use anyhow::Result; +use std::path::{Path, PathBuf}; pub fn serve_content(request_uri: &str) -> Result { - let config = get_config(); + // TODO: read from config + let pages = PathBuf::from("/var/www/htmlua/pages"); + let components = PathBuf::from("/var/www/htmlua/components"); let safe_path = Path::new(request_uri).strip_prefix("/")?; - let page_path = config.paths.pages.join(safe_path); - let doc = read_doc_from_file(page_path)? - .select_first("html") - .map_err(|()| anyhow!("failed to read doc"))?; - let full_doc = expand_template(doc.as_node().to_owned(), &config.paths.components, None)?; + let page_path = pages.join(safe_path); + + let doc = read_doc_from_file(page_path)?; + + let full_doc = expand_template(doc, &components, None)?; let markdown_doc = process_markdown(full_doc)?; let highlighted_doc = process_syntax_highlighting(markdown_doc)?; let executed_doc = execute_lua(highlighted_doc)?; diff --git a/htmlua-parser/tests/components/include_base.html b/htmlua-parser/tests/components/include_base.html deleted file mode 100644 index fa86fe6..0000000 --- a/htmlua-parser/tests/components/include_base.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - -

Hello World

- - -