commit c3c599f8b9422bd1812730714af41feae81a5d97 Author: Raine Date: Sat Oct 14 22:39:16 2023 +0200 init: initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92322c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..243a38f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "milly" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..88cf077 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "milly" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["dom"] +dom = [] + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..056febb --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Milly +Raine's general purpose library \ No newline at end of file diff --git a/src/dom/document.rs b/src/dom/document.rs new file mode 100644 index 0000000..d5d038e --- /dev/null +++ b/src/dom/document.rs @@ -0,0 +1,30 @@ +use crate::dom::element::Element; +use crate::dom::head::Head; +use crate::into; + +pub struct Document { + lang: String, + pub head: Head, + pub body: Element, +} + +impl Document { + pub fn new>(lang: S) -> Self { + Document { + lang: lang.into(), + head: Head::default(), + body: Element::new("body"), + } + } +} + +impl Into for Document { + fn into(self) -> Element { + let mut r = Element::new("html"); + r.set_pre_text(""); + r.set_attribute("lang", self.lang); + r.append_child(into!(Element, self.head)) + .append_child(self.body); + r + } +} diff --git a/src/dom/element/datatype.rs b/src/dom/element/datatype.rs new file mode 100644 index 0000000..6a8c56f --- /dev/null +++ b/src/dom/element/datatype.rs @@ -0,0 +1,41 @@ +use crate::dom::element::Element; + +macro_rules! create_datatype { + ($($i: ident),+) => { + #[allow(non_camel_case_types)] + #[derive(Debug, Clone)] + pub enum DataType { + $($i($i),)* + } + impl ToString for DataType { + fn to_string(&self) -> String { + match self { + $( + DataType::$i(v) => v.to_string(), + )* + } + } + } + $( + impl Into for $i { + fn into(self) -> DataType { + DataType::$i(self) + } + } + )* + }; +} + +create_datatype!(String, Element, i32); + +impl Into for &str { + fn into(self) -> DataType { + DataType::String(self.to_string()) + } +} + +impl Into for &mut Element { + fn into(self) -> DataType { + DataType::Element(self.clone()) + } +} diff --git a/src/dom/element/mod.rs b/src/dom/element/mod.rs new file mode 100644 index 0000000..e43fce8 --- /dev/null +++ b/src/dom/element/mod.rs @@ -0,0 +1,104 @@ +use crate::dom::element::datatype::DataType; +use std::collections::HashMap; + +pub mod datatype; + +#[derive(Debug, Clone)] +pub struct Element { + element_type: String, + attributes: HashMap, + children: Vec, + pre_text: String, +} + +impl Element { + pub fn new>(t: S) -> Self { + Element { + element_type: t.into(), + attributes: HashMap::new(), + children: vec![], + pre_text: String::new() + } + } + + pub fn set_pre_text>(&mut self, str: S) -> &mut Self { + self.pre_text = str.into(); + self + } + + pub fn append_child>(&mut self, child: C) -> &mut Self { + self.children.push(child.into()); + self + } + + pub fn set_attribute, V: Into>( + &mut self, + key: K, + value: V, + ) -> &mut Self { + self.attributes.insert(key.into(), value.into()); + self + } + + pub fn get_attribute>(&self, attribute: S) -> Option { + self.attributes + .get(attribute.into().as_str()) + .map(|s| s.clone()) + } + + pub fn set_id>(&mut self, id: S) -> &mut Self { + self.set_attribute("id", id); + self + } + + pub fn add_class>(&mut self, class: S) -> &mut Self { + let classes = match self.get_attribute("class") { + Some(s) => s + format!(" {}", class.into()).as_str(), + None => class.into(), + }; + self.set_attribute("class", classes); + self + } + + pub fn set_string>(&mut self, str: S) { + self.children = vec![str.into().into()]; + } + + pub fn get_element_by_id>(&mut self, needle: S) -> Option<&mut Element> { + let needle = needle.into(); + let mut el = None; + let elements: Vec<&mut Element> = self + .children + .iter_mut() + .filter_map(|d| match d { + DataType::Element(e) => Some(e), + _ => None, + }) + .collect(); + for e in elements { + if e.get_attribute("id").unwrap_or(String::new()) == needle.clone() { + return Some(e); + } else { + el = e.get_element_by_id(needle.clone()); + } + } + el + } +} + +impl ToString for Element { + fn to_string(&self) -> String { + let mut s = String::new(); + s.push_str(self.pre_text.as_str()); + s.push_str(format!("<{}", self.element_type).as_str()); + for (k, v) in &self.attributes { + s.push_str(format!(" {}=\"{}\"", k, v).as_str()); + } + s.push('>'); + for c in &self.children { + s.push_str(c.to_string().as_str()); + } + s.push_str(format!("", self.element_type).as_str()); + s + } +} diff --git a/src/dom/head.rs b/src/dom/head.rs new file mode 100644 index 0000000..8dcf73a --- /dev/null +++ b/src/dom/head.rs @@ -0,0 +1,75 @@ + +use crate::dom::element::Element; + +pub enum Asset { + JS { file: String, defer: bool }, + CSS { file: String }, +} + +pub struct Head { + title: String, + assets: Vec, + children: Vec +} + +impl Head { + pub fn set_title>(&mut self, title: S) -> &mut Self { + self.title = title.into(); + self + } + + pub fn add_js>(&mut self, file: S, defer: bool) -> &mut Self { + self.assets.push(Asset::JS { + file: file.into(), + defer, + }); + self + } + + pub fn add_css>(&mut self, file: S) -> &mut Self { + self.assets.push(Asset::CSS { file: file.into() }); + self + } + + pub fn add_element(&mut self, e: Element) -> &mut Self { + self.children.push(e); + self + } +} + +impl Default for Head { + fn default() -> Self { + Head { + title: String::default(), + assets: vec![], + children: vec![] + } + } +} + +impl Into for Head { + fn into(self) -> Element { + let mut r = Element::new("head"); + self.children + .into_iter() + .for_each(|e| { + r.append_child(e); + }); + r.append_child(Element::new("title").append_child(self.title)); + for a in self.assets { + match a { + Asset::JS { file, defer } => r.append_child( + Element::new("script") + .set_attribute("src", file) + .set_attribute("defer", if defer { "true" } else { "false" }), + ), + Asset::CSS { file } => r.append_child( + Element::new("link") + .set_attribute("rel", "stylesheet") + .set_attribute("href", file), + ), + }; + } + r + } +} diff --git a/src/dom/mod.rs b/src/dom/mod.rs new file mode 100644 index 0000000..64a335c --- /dev/null +++ b/src/dom/mod.rs @@ -0,0 +1,3 @@ +pub mod document; +pub mod element; +pub mod head; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d7710be --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,26 @@ +#[cfg(feature = "dom")] +/// Create a virtual DOM. +pub mod dom; + +/// All sorts of general use macros +pub mod macros; + +#[cfg(test)] +mod tests { + #[cfg(feature = "dom")] + #[test] + fn dom() { + use crate::dom; + + let s = dom::element::Element::new("h1").to_string(); + assert_eq!("

", s); + } + + #[test] + fn into_macro() { + use crate::into; + + let foo = "foo"; + assert_eq!(String::from("foo"), into!(String, foo)); + } +} diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..beafc9b --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,11 @@ +/// Perform an into but specify what into +/// ```rs +/// let foo = "foo"; +/// assert_eq!(String::from("foo"), into!(String, foo)); +/// ``` +#[macro_export] +macro_rules! into { + ($i: ident, $e: expr) => { + Into::<$i>::into($e) + }; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..812ae8d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,53 @@ +use milly::dom::element::Element; +use milly::into; + +macro_rules! doc { + ($title: expr, $lang: expr) => { + { + use milly::dom::document::Document; + use milly::dom::element::Element; + let mut d = Document::new($lang); + d.head + .set_title($title) + .add_css("https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css") + .add_element( + Element::new("meta") + .set_attribute("name", "viewport") + .set_attribute("content", "width=device-width,initial-scale=1") + .clone() + ); + d.body + .append_child( + Element::new("nav") + .add_class("container-fluid") + .append_child( + Element::new("ul") + .set_id("nav-titles") + .append_child(Element::new("li").append_child( + $title + )) + ) + ) + .append_child( + Element::new("main") + .set_id("main") + .add_class("container") + ); + d + } + } +} + +fn main() { + let mut doc = doc!("Main", "en"); + + doc.body + .get_element_by_id("main") + .unwrap() + .append_child( + Element::new("h1") + .append_child("Hello") + ); + + println!("{}", into!(Element, doc).to_string()); +}