summaryrefslogtreecommitdiff
path: root/htmlua-parser
diff options
context:
space:
mode:
authorQuin <trypsynth@gmail.com>2025-07-11 16:55:49 -0600
committerQuin <trypsynth@gmail.com>2025-07-11 16:55:49 -0600
commit38be7b73656d70e21118e1a8b7b6b21c8c20a849 (patch)
tree644b2f3c2fb8f09d499b7d3f3a0c55eb5e9541f2 /htmlua-parser
parentc8ffbb2af0eae39b897ff3b1b6c8263181fdd8d4 (diff)
Basic toml config support.
Diffstat (limited to 'htmlua-parser')
-rw-r--r--htmlua-parser/Cargo.toml4
-rw-r--r--htmlua-parser/src/config.rs98
-rw-r--r--htmlua-parser/src/helpers.rs10
-rw-r--r--htmlua-parser/src/lib.rs3
-rw-r--r--htmlua-parser/src/render.rs28
-rw-r--r--htmlua-parser/src/serve.rs33
6 files changed, 147 insertions, 29 deletions
diff --git a/htmlua-parser/Cargo.toml b/htmlua-parser/Cargo.toml
index 28416b7..9e5018e 100644
--- a/htmlua-parser/Cargo.toml
+++ b/htmlua-parser/Cargo.toml
@@ -5,14 +5,16 @@ edition = "2024"
[dependencies]
anyhow = "1.0.98"
+dirs = "6.0.0"
html5ever = "0.35.0"
kuchikiki = "0.8.2"
markup5ever = "0.11.0"
mlua = { version = "0.10.5", features = ["lua54", "vendored", "serialize"] }
pulldown-cmark = "0.13.0"
+serde = { version = "1.0.219", features = ["derive"] }
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
new file mode 100644
index 0000000..70ea7a8
--- /dev/null
+++ b/htmlua-parser/src/config.rs
@@ -0,0 +1,98 @@
+use anyhow::{Context, Result};
+use serde::{Deserialize, Serialize};
+use std::{fs, path::PathBuf};
+
+#[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<Self> {
+ 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(())
+ }
+
+ 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 90439f7..bd99f81 100644
--- a/htmlua-parser/src/helpers.rs
+++ b/htmlua-parser/src/helpers.rs
@@ -1,15 +1,15 @@
-use std::{fs::File, io::{BufRead, BufReader, Read, Seek, SeekFrom}, path::PathBuf};
+use std::{
+ fs::File,
+ io::{BufRead, BufReader, Read, Seek, SeekFrom},
+ path::PathBuf,
+};
use anyhow::Result;
use kuchikiki::NodeRef;
use markup5ever::{LocalName, Namespace, QualName};
use tendril::TendrilSink;
-
-
-
pub fn read_doc_from_file(path: PathBuf) -> Result<NodeRef> {
-
let mut file = File::open(path)?;
let mut reader = BufReader::new(&mut file);
diff --git a/htmlua-parser/src/lib.rs b/htmlua-parser/src/lib.rs
index cd87be2..525486e 100644
--- a/htmlua-parser/src/lib.rs
+++ b/htmlua-parser/src/lib.rs
@@ -1,3 +1,4 @@
+pub mod config;
+pub mod helpers;
pub mod render;
pub mod serve;
-pub mod helpers;
diff --git a/htmlua-parser/src/render.rs b/htmlua-parser/src/render.rs
index 6444d60..9360f16 100644
--- a/htmlua-parser/src/render.rs
+++ b/htmlua-parser/src/render.rs
@@ -1,4 +1,4 @@
-use std::{cell::RefCell, fmt::Write, fs::read_to_string, path::PathBuf, rc::Rc};
+use std::{cell::RefCell, fmt::Write, path::PathBuf, rc::Rc};
use anyhow::{Result, anyhow};
use kuchikiki::{NodeRef, traits::TendrilSink};
@@ -12,7 +12,7 @@ use syntect::{
util::LinesWithEndings,
};
-use crate::helpers::read_doc_from_file;
+use crate::{helpers::read_doc_from_file, serve::get_config};
fn create_htmlua_stdlib(l: &Lua, stdout: &Rc<RefCell<String>>) -> mlua::Result<Table> {
let t = l.create_table()?;
@@ -108,7 +108,11 @@ pub fn expand_template(document: NodeRef, component_path: &PathBuf, include_from
let exported = from_node
.select_first(format!("exportelement.{name}").as_str())
.map_err(|()| anyhow!("Error finding exportelement"))?;
- exported.as_node().children().rev().for_each(|c| i.as_node().insert_after(c));
+ exported
+ .as_node()
+ .children()
+ .rev()
+ .for_each(|c| i.as_node().insert_after(c));
}
}
while let Ok(i) = document.select_first("includeelement") {
@@ -130,8 +134,13 @@ pub fn expand_template(document: NodeRef, component_path: &PathBuf, include_from
item_path.push(include_path);
let new_node = read_doc_from_file(item_path)?;
let replaced_node = expand_template(new_node, component_path, Some(i.as_node()))?;
- replaced_node.select_first("html").map_err(|()| anyhow!("Error finding html"))?.as_node().children().rev().for_each(|c| i.as_node().insert_after(c));
-
+ replaced_node
+ .select_first("html")
+ .map_err(|()| anyhow!("Error finding html"))?
+ .as_node()
+ .children()
+ .rev()
+ .for_each(|c| i.as_node().insert_after(c));
}
}
while let Ok(i) = document.select_first("include") {
@@ -141,16 +150,15 @@ pub fn expand_template(document: NodeRef, component_path: &PathBuf, include_from
}
pub fn process_syntax_highlighting(document: NodeRef) -> Result<NodeRef> {
+ 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() {
- // TODO: read from config
- let themes = PathBuf::from("/var/www/htmlua/themes");
- let _ = ts.add_from_folder(themes);
+ if !syntax_elements.is_empty() && config.syntax_highlighting.load_custom_themes {
+ let _ = ts.add_from_folder(&config.paths.themes);
}
for node in syntax_elements {
let attrs = match node.as_node().as_element() {
@@ -158,7 +166,7 @@ pub fn process_syntax_highlighting(document: NodeRef) -> Result<NodeRef> {
None => continue,
};
let language = attrs.get("lang").unwrap_or("text");
- let theme_name = attrs.get("theme").unwrap_or("base16-ocean.dark");
+ let theme_name = attrs.get("theme").unwrap_or(&config.syntax_highlighting.default_theme);
if let Some(text_node) = node.as_node().first_child() {
if let Some(code_text) = text_node.as_text() {
let syntax = ps
diff --git a/htmlua-parser/src/serve.rs b/htmlua-parser/src/serve.rs
index 06de0ea..9821210 100644
--- a/htmlua-parser/src/serve.rs
+++ b/htmlua-parser/src/serve.rs
@@ -1,20 +1,29 @@
-use crate::helpers::read_doc_from_file;
-
-use super::render::{execute_lua, expand_template, process_markdown, process_syntax_highlighting};
+use crate::{
+ config::Config,
+ helpers::read_doc_from_file,
+ render::{execute_lua, expand_template, process_markdown, process_syntax_highlighting},
+};
use anyhow::Result;
-use std::path::{Path, PathBuf}
-;
+use std::{path::Path, sync::OnceLock};
+
+static CONFIG: OnceLock<Config> = 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()
+ })
+ })
+}
pub fn serve_content(request_uri: &str) -> Result<String> {
- // TODO: read from config
- let pages = PathBuf::from("/var/www/htmlua/pages");
- let components = PathBuf::from("/var/www/htmlua/components");
+ let config = get_config();
let safe_path = Path::new(request_uri).strip_prefix("/")?;
- let page_path = pages.join(safe_path);
-
+ let page_path = config.paths.pages.join(safe_path);
let doc = read_doc_from_file(page_path)?;
-
- let full_doc = expand_template(doc, &components, None)?;
+ let full_doc = expand_template(doc, &config.paths.components, None)?;
let markdown_doc = process_markdown(full_doc)?;
let highlighted_doc = process_syntax_highlighting(markdown_doc)?;
let executed_doc = execute_lua(highlighted_doc)?;