From 5b4a7e654f66dcec8232377bb045aa3641cd433d Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 7 Nov 2025 09:26:02 -0500 Subject: [PATCH 01/32] chore: add handlebars-rs crate --- Cargo.lock | 166 ++++++++++++++++++++++++++++++++---- crates/quellcode/Cargo.toml | 2 +- 2 files changed, 152 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2813050..fe30ae8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1038,14 +1038,38 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + [[package]] name = "darling" version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.98", ] [[package]] @@ -1062,13 +1086,24 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.98", +] + [[package]] name = "darling_macro" version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "darling_core", + "darling_core 0.21.3", "quote", "syn 2.0.98", ] @@ -1113,6 +1148,37 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.98", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -2005,6 +2071,22 @@ dependencies = [ "crunchy", ] +[[package]] +name = "handlebars" +version = "6.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" +dependencies = [ + "derive_builder", + "log", + "num-order", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror 2.0.17", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -3061,6 +3143,21 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-modular" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" + +[[package]] +name = "num-order" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" +dependencies = [ + "num-modular", +] + [[package]] name = "num-rational" version = "0.4.2" @@ -3532,6 +3629,49 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "pest_meta" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a" +dependencies = [ + "pest", + "sha2", +] + [[package]] name = "petgraph" version = "0.6.5" @@ -3921,6 +4061,7 @@ dependencies = [ "dotenvy", "env_logger", "evalexpr", + "handlebars", "image", "keyring", "log", @@ -3944,7 +4085,6 @@ dependencies = [ "test-log", "thiserror 2.0.17", "time", - "tinytemplate", "tokio", "toml 0.8.20", "toml_edit 0.22.24", @@ -4888,7 +5028,7 @@ version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" dependencies = [ - "darling", + "darling 0.21.3", "proc-macro2", "quote", "syn 2.0.98", @@ -5855,16 +5995,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "tinyvec" version = "1.8.1" @@ -6207,6 +6337,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "uds_windows" version = "1.1.0" diff --git a/crates/quellcode/Cargo.toml b/crates/quellcode/Cargo.toml index c052409..81fed21 100644 --- a/crates/quellcode/Cargo.toml +++ b/crates/quellcode/Cargo.toml @@ -23,7 +23,6 @@ once_cell = "1.21.1" toml = "0.8.20" toml_edit = "0.22.24" evalexpr = "12.0.2" -tinytemplate = "1.2.1" reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json", "blocking"] } tokio = { version = "1", features = ["rt-multi-thread", "macros"] } time = { version = "0.3.41", features = ["macros", "serde", "parsing", "formatting"] } @@ -41,6 +40,7 @@ ts-rs = "11.0.1" tauri-plugin-store = "2" tauri-plugin-dialog = "2" tauri-plugin-clipboard-manager = "2.3.0" +handlebars = "6.3.2" [lib] name = "quellcode_lib" From 91893b10219757446b16417886dbbe36174d2393 Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 7 Nov 2025 09:38:47 -0500 Subject: [PATCH 02/32] refactor: add deserialization support to property info --- crates/quellcode/src/property.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/quellcode/src/property.rs b/crates/quellcode/src/property.rs index 45c6082..80e70f6 100644 --- a/crates/quellcode/src/property.rs +++ b/crates/quellcode/src/property.rs @@ -20,7 +20,7 @@ pub enum PropertyValue { Bool(bool), } -#[derive(Debug, Clone, Serialize, TS)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] pub enum StringPropertySubtype { @@ -28,7 +28,7 @@ pub enum StringPropertySubtype { Template, } -#[derive(Debug, Clone, Serialize, TS)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase", rename_all_fields = "camelCase", tag = "kind")] #[ts(export)] pub enum PropertyInfo { From 9654fec8fb0a54158e710360bcb296e2d50a41bc Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 7 Nov 2025 09:44:08 -0500 Subject: [PATCH 03/32] feat: create template module with basic logic --- .../frontend/src/lib/bindings/Template.ts | 3 + crates/quellcode/src/lib.rs | 1 + crates/quellcode/src/template.rs | 420 ++++++++++++++++++ 3 files changed, 424 insertions(+) create mode 100644 crates/quellcode/frontend/src/lib/bindings/Template.ts create mode 100644 crates/quellcode/src/template.rs diff --git a/crates/quellcode/frontend/src/lib/bindings/Template.ts b/crates/quellcode/frontend/src/lib/bindings/Template.ts new file mode 100644 index 0000000..7d68ad0 --- /dev/null +++ b/crates/quellcode/frontend/src/lib/bindings/Template.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type Template = { name: string, template: string, }; diff --git a/crates/quellcode/src/lib.rs b/crates/quellcode/src/lib.rs index d4e7bb3..3fc0f1a 100644 --- a/crates/quellcode/src/lib.rs +++ b/crates/quellcode/src/lib.rs @@ -31,6 +31,7 @@ mod app; pub mod asset_store; pub mod dir; pub mod generator; +mod template; pub mod property; pub mod scraping; mod settings; diff --git a/crates/quellcode/src/template.rs b/crates/quellcode/src/template.rs new file mode 100644 index 0000000..138875e --- /dev/null +++ b/crates/quellcode/src/template.rs @@ -0,0 +1,420 @@ +use ::handlebars::{handlebars_helper, HelperDef}; +use handlebars::RenderErrorReason; +use serde::{Deserialize, Serialize}; +use std::{path::PathBuf, str::FromStr}; +use ts_rs::TS; + +use crate::property::PropertyInfo; + +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct Template { + pub name: String, + pub template: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TemplateData { + pub font_settings: FontSettings, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FontSettings { + pub family_name: String, + pub fonts: Vec, + pub size: f32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FontFace { + pub name: String, + pub path: PathBuf, + pub weight: Weight, + pub style: Style, + pub monospaced: bool, +} + +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)] +pub struct Weight(pub u16); + +impl Weight { + pub const THIN: Weight = Weight(100); + pub const EXTRA_LIGHT: Weight = Weight(200); + pub const LIGHT: Weight = Weight(300); + pub const NORMAL: Weight = Weight(400); + pub const MEDIUM: Weight = Weight(500); + pub const SEMIBOLD: Weight = Weight(600); + pub const BOLD: Weight = Weight(700); + pub const EXTRA_BOLD: Weight = Weight(800); + pub const BLACK: Weight = Weight(900); +} + +impl FromStr for Weight { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "thin" => Ok(Weight::THIN), + "extra-light" | "extralight" => Ok(Weight::EXTRA_LIGHT), + "light" => Ok(Weight::LIGHT), + "normal" => Ok(Weight::NORMAL), + "medium" => Ok(Weight::MEDIUM), + "semibold" => Ok(Weight::SEMIBOLD), + "bold" => Ok(Weight::BOLD), + "extra-bold" | "extrabold" => Ok(Weight::EXTRA_BOLD), + "black" => Ok(Weight::BLACK), + _ => u16::from_str(s) + .map(Weight) + .map_err(|e| format!("Could not convert to Weight: \"{e}\"")), + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)] +pub enum Style { + Normal, + Italic, + Oblique, +} + +impl FromStr for Style { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "normal" => Ok(Style::Normal), + "italic" => Ok(Style::Italic), + "oblique" => Ok(Style::Oblique), + _ => Err("Could not convert to Style".to_string()), + } + } +} + +impl std::fmt::Display for Style { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Style::Normal => write!(f, "normal"), + Style::Italic => write!(f, "italic"), + Style::Oblique => write!(f, "oblique"), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct FontSettingsQuery { + weight: Option, + style: Option, + monospaced: Option, +} + +fn find_font_from_helper( + h: &::handlebars::Helper<'_>, +) -> Result, RenderErrorReason> { + let font_family: FontSettings = serde_json::from_value(h.param(0).unwrap().value().clone()) + .map_err(|e| RenderErrorReason::NestedError(e.into()))?; + + let query = FontSettingsQuery { + weight: h + .hash_get("weight") + .as_ref() + .map(|v| v.value().as_str().unwrap().to_string()), + + style: h + .hash_get("style") + .as_ref() + .map(|v| v.value().as_str().unwrap().to_string()), + + monospaced: h + .hash_get("monospaced") + .as_ref() + .map(|v| v.value().as_bool().unwrap()), + }; + + Ok(font_family + .fonts + .iter() + .find(|f| { + query + .weight + .as_ref() + .is_none_or(|w| Weight::from_str(w).expect("Invalid weight") == f.weight) + && query + .style + .as_ref() + .is_none_or(|w| Style::from_str(w).expect("Invalid style") == f.style) + && query.monospaced.as_ref().is_none_or(|m| *m == f.monospaced) + && (query.style.is_some() || query.weight.is_some() || query.monospaced.is_some()) + }) + .cloned()) +} + +fn get_font_face_path_helper( + h: &::handlebars::Helper<'_>, + _: &::handlebars::Handlebars, + _: &::handlebars::Context, + _: &mut ::handlebars::RenderContext, + out: &mut dyn ::handlebars::Output, +) -> ::handlebars::HelperResult { + let font = find_font_from_helper(h)?; + + out.write( + &font + .map(|f| f.path.to_string_lossy().to_string()) + .unwrap_or_default(), + )?; + + Ok(()) +} + +fn get_font_face_name_helper( + h: &::handlebars::Helper<'_>, + _: &::handlebars::Handlebars, + _: &::handlebars::Context, + _: &mut ::handlebars::RenderContext, + out: &mut dyn ::handlebars::Output, +) -> ::handlebars::HelperResult { + let font = find_font_from_helper(h)?; + + out.write(&font.map(|f| f.name.to_string()).unwrap_or_default())?; + + Ok(()) +} + +fn get_font_face_weight_helper( + h: &::handlebars::Helper<'_>, + _: &::handlebars::Handlebars, + _: &::handlebars::Context, + _: &mut ::handlebars::RenderContext, + out: &mut dyn ::handlebars::Output, +) -> ::handlebars::HelperResult { + let font = find_font_from_helper(h)?; + + out.write(&font.map(|f| f.weight.0.to_string()).unwrap_or_default())?; + + Ok(()) +} + +fn get_font_face_style_helper( + h: &::handlebars::Helper<'_>, + _: &::handlebars::Handlebars, + _: &::handlebars::Context, + _: &mut ::handlebars::RenderContext, + out: &mut dyn ::handlebars::Output, +) -> ::handlebars::HelperResult { + let font = find_font_from_helper(h)?; + out.write(&font.map(|f| f.style.to_string()).unwrap_or_default())?; + + Ok(()) +} + +fn get_font_face_monospaced_helper( + h: &::handlebars::Helper<'_>, + _: &::handlebars::Handlebars, + _: &::handlebars::Context, + _: &mut ::handlebars::RenderContext, + out: &mut dyn ::handlebars::Output, +) -> ::handlebars::HelperResult { + let font = find_font_from_helper(h)?; + out.write(&font.map(|f| f.monospaced.to_string()).unwrap_or_default())?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + use test_log::test; + + use super::*; + + #[test] + fn test_get_font_face_weight() { + let font = FontSettings { + size: 10.0, + family_name: "test".to_string(), + fonts: vec![FontFace { + name: "test".to_string(), + path: PathBuf::from("test.ttf"), + weight: Weight::NORMAL, + style: Style::Normal, + monospaced: false, + }], + }; + + let template = "{{fontFaceWeight fontSettings weight=\"normal\" style=\"normal\"}}"; + let empty_template = "{{fontFaceWeight fontSettings weight=\"BOLD\" style=\"normal\"}}"; + + let mut handlebars = ::handlebars::Handlebars::new(); + handlebars.set_strict_mode(true); + + handlebars.register_helper("fontFaceWeight", Box::new(get_font_face_weight_helper)); + handlebars.register_template_string("t1", template).unwrap(); + handlebars + .register_template_string("t2", empty_template) + .unwrap(); + + let data = json!({"fontSettings": font}); + + let t1 = handlebars.render("t1", &data).unwrap(); + let t2 = handlebars.render("t2", &data).unwrap(); + + assert_eq!(t1, "400"); + assert_eq!(t2, ""); + } + + #[test] + fn test_get_font_face_style() { + let font = FontSettings { + size: 10.0, + family_name: "test".to_string(), + fonts: vec![FontFace { + name: "test".to_string(), + path: PathBuf::from("test.ttf"), + weight: Weight::NORMAL, + style: Style::Normal, + monospaced: false, + }], + }; + + let template = "{{fontFaceStyle fontSettings weight=\"normal\" style=\"normal\"}}"; + let empty_template = "{{fontFaceStyle fontSettings weight=\"BOLD\" style=\"normal\"}}"; + + let mut handlebars = ::handlebars::Handlebars::new(); + handlebars.set_strict_mode(true); + + handlebars.register_helper("fontFaceStyle", Box::new(get_font_face_style_helper)); + handlebars.register_template_string("t1", template).unwrap(); + handlebars + .register_template_string("t2", empty_template) + .unwrap(); + + let data = json!({"fontSettings": font}); + + let t1 = handlebars.render("t1", &data).unwrap(); + let t2 = handlebars.render("t2", &data).unwrap(); + + assert_eq!(t1, "normal"); + assert_eq!(t2, ""); + } + + #[test] + fn test_get_font_face_name() { + let font = FontSettings { + size: 10.0, + family_name: "test".to_string(), + fonts: vec![FontFace { + name: "test".to_string(), + path: PathBuf::from("test.ttf"), + weight: Weight::NORMAL, + style: Style::Normal, + monospaced: false, + }], + }; + + let template = "{{fontFaceName fontSettings weight=\"normal\" style=\"normal\"}}"; + let empty_template = "{{fontFaceName fontSettings weight=\"BOLD\" style=\"normal\"}}"; + + let mut handlebars = ::handlebars::Handlebars::new(); + handlebars.set_strict_mode(true); + + handlebars.register_helper("fontFaceName", Box::new(get_font_face_name_helper)); + handlebars.register_template_string("t1", template).unwrap(); + handlebars + .register_template_string("t2", empty_template) + .unwrap(); + + let data = json!({"fontSettings": font}); + + let rendered = handlebars.render("t1", &data).unwrap(); + let empty_render = handlebars.render("t2", &data).unwrap(); + + assert_eq!(rendered, "test"); + assert_eq!(empty_render, ""); + } + + #[test] + fn test_get_font_face_monospaced() { + let font = FontSettings { + size: 10.0, + family_name: "test".to_string(), + fonts: vec![FontFace { + name: "test".to_string(), + path: PathBuf::from("test.ttf"), + weight: Weight::NORMAL, + style: Style::Normal, + monospaced: true, + }], + }; + + let template = "{{fontFaceMonospaced fontSettings weight=\"normal\" style=\"normal\"}}"; + let empty_template = "{{fontFaceMonospaced fontSettings weight=\"BOLD\" style=\"normal\"}}"; + + let mut handlebars = ::handlebars::Handlebars::new(); + handlebars.set_strict_mode(true); + + handlebars.register_helper( + "fontFaceMonospaced", + Box::new(get_font_face_monospaced_helper), + ); + handlebars.register_template_string("t1", template).unwrap(); + handlebars + .register_template_string("t2", empty_template) + .unwrap(); + + let data = json!({"fontSettings": font}); + + let rendered = handlebars.render("t1", &data).unwrap(); + let empty_render = handlebars.render("t2", &data).unwrap(); + + assert_eq!(rendered, "true"); + assert_eq!(empty_render, ""); + } + + #[test] + fn test_get_font_face_path() { + let font = FontSettings { + size: 10.0, + family_name: "test".to_string(), + fonts: vec![FontFace { + name: "test".to_string(), + path: PathBuf::from("test.ttf"), + weight: Weight::NORMAL, + style: Style::Normal, + monospaced: false, + }], + }; + + let template = "{{fontFacePath fontSettings weight=\"normal\"}}"; + let template2 = "{{fontFacePath fontSettings weight=\"Normal\"}}"; + let template3 = "{{fontFacePath fontSettings weight=\"Bold\"}}"; + + let mut handlebars = ::handlebars::Handlebars::new(); + handlebars.set_strict_mode(true); + + handlebars.register_helper("fontFacePath", Box::new(get_font_face_path_helper)); + handlebars.register_template_string("t1", template).unwrap(); + handlebars + .register_template_string("t2", template2) + .unwrap(); + + handlebars + .register_template_string("t3", template3) + .unwrap(); + + let data = json!({ + "fontSettings": font, + }); + + let rendered = handlebars.render("t1", &data).expect("Failed to render"); + let rendered2 = handlebars.render("t2", &data).expect("Failed to render"); + let empty_render = handlebars.render("t3", &data).expect("Failed to render"); + + assert_eq!(rendered, "test.ttf".to_string()); + assert_eq!(rendered2, "test.ttf".to_string()); + assert!(empty_render.is_empty()); + } +} From b85acb36308df5d4b2613d69baa4e5019943268c Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 7 Nov 2025 09:45:23 -0500 Subject: [PATCH 04/32] refactor: remove pub from fields in AppState --- crates/quellcode/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/quellcode/src/lib.rs b/crates/quellcode/src/lib.rs index 3fc0f1a..25d3353 100644 --- a/crates/quellcode/src/lib.rs +++ b/crates/quellcode/src/lib.rs @@ -68,10 +68,10 @@ impl ThemeFormat { } pub struct AppState { - pub theme_files: HashMap, - pub syntect_themes: ThemeSet, - pub syntect_syntaxes: SyntaxSet, - pub generators: Vec<(GeneratorInfo, Arc)>, + theme_files: HashMap, + syntect_themes: ThemeSet, + syntect_syntaxes: SyntaxSet, + generators: Vec<(GeneratorInfo, Arc)>, generator_context: GeneratorContext, } From 8738f644dc886b3a5cd7ef773acf8df9316be184 Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 7 Nov 2025 10:07:23 -0500 Subject: [PATCH 05/32] refactor: allow cache to be written to --- crates/quellcode/src/lib.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/quellcode/src/lib.rs b/crates/quellcode/src/lib.rs index 25d3353..56536b9 100644 --- a/crates/quellcode/src/lib.rs +++ b/crates/quellcode/src/lib.rs @@ -21,20 +21,21 @@ use time::{format_description::well_known::Rfc3339, OffsetDateTime}; use ts_rs::TS; use crate::{ - dir::config_dir, + dir::{cache_dir, config_dir}, generator::{ FusionGenerator, Generator, GeneratorContext, GeneratorExt, GeneratorInfo, SvgGenerator, }, + template::Template, }; mod app; pub mod asset_store; pub mod dir; pub mod generator; -mod template; pub mod property; pub mod scraping; mod settings; +mod template; pub mod util; use app::generate_code; @@ -73,6 +74,7 @@ pub struct AppState { syntect_syntaxes: SyntaxSet, generators: Vec<(GeneratorInfo, Arc)>, generator_context: GeneratorContext, + templates: HashMap, } #[cfg_attr(mobile, tauri::mobile_entry_point)] @@ -115,12 +117,18 @@ pub fn run() { .setup(|app| { let syntax_set = SyntaxSet::load_defaults_nonewlines(); let scope = app.fs_scope(); - let _ = scope.allow_directory(config_dir(app.app_handle()), true); + + let config_dir = config_dir(app.app_handle()); + let cache_dir = cache_dir(app.app_handle()); + + let _ = scope.allow_directory(&config_dir, true); + let _ = scope.allow_directory(&cache_dir, true); for path in [ dir::code_theme_dir(app.app_handle()), dir::code_syntax_dir(app.app_handle()), - dir::config_dir(app.app_handle()), + cache_dir, + config_dir, ] { if !path.exists() { std::fs::create_dir_all(&path).expect("Failed to ensure directory exists"); From 2a586358b0130932dbcb4d844b592673a69df541 Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 7 Nov 2025 10:08:02 -0500 Subject: [PATCH 06/32] feat: add templates to AppState and templates directory --- crates/quellcode/src/dir.rs | 4 +++ crates/quellcode/src/lib.rs | 59 ++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/crates/quellcode/src/dir.rs b/crates/quellcode/src/dir.rs index 98655ae..44da556 100644 --- a/crates/quellcode/src/dir.rs +++ b/crates/quellcode/src/dir.rs @@ -36,6 +36,10 @@ pub fn code_syntax_dir(app_handle: &tauri::AppHandle) -> PathBuf { data_dir(app_handle).join("syntaxes") } +pub fn templates_dir(app_handle: &tauri::AppHandle) -> PathBuf { + data_dir(app_handle).join("templates") +} + pub fn store_cache_dir(app_handle: &tauri::AppHandle) -> PathBuf { cache_dir(app_handle).join("asset_store") } diff --git a/crates/quellcode/src/lib.rs b/crates/quellcode/src/lib.rs index 56536b9..5eaf450 100644 --- a/crates/quellcode/src/lib.rs +++ b/crates/quellcode/src/lib.rs @@ -21,7 +21,7 @@ use time::{format_description::well_known::Rfc3339, OffsetDateTime}; use ts_rs::TS; use crate::{ - dir::{cache_dir, config_dir}, + dir::{cache_dir, config_dir, templates_dir}, generator::{ FusionGenerator, Generator, GeneratorContext, GeneratorExt, GeneratorInfo, SvgGenerator, }, @@ -127,6 +127,7 @@ pub fn run() { for path in [ dir::code_theme_dir(app.app_handle()), dir::code_syntax_dir(app.app_handle()), + dir::templates_dir(app.app_handle()), cache_dir, config_dir, ] { @@ -161,6 +162,7 @@ pub fn run() { theme_files, generators, generator_context: GeneratorContext::new(tx.clone()), + templates: template_files(app.app_handle()), })); Ok(()) @@ -262,6 +264,38 @@ fn generate_html( Ok(generator.finalize()) } +#[tauri::command] +fn add_template(app: tauri::AppHandle, state: State>, template: Template) { + state + .lock() + .expect("Failed to lock state") + .templates + .insert( + templates_dir(&app).join(format!("{}.json", template.name)), + template, + ); +} + +#[tauri::command] +fn remove_template(app: tauri::AppHandle, state: State>, name: String) { + state + .lock() + .expect("Failed to lock state") + .templates + .remove(&templates_dir(&app).join(format!("{}.json", name))); +} + +#[tauri::command] +fn templates(state: State>) -> Vec