init: initial commit
This commit is contained in:
commit
c3c599f8b9
12 changed files with 366 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.idea/
|
||||||
|
target/
|
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
|
@ -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"
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -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]
|
2
README.md
Normal file
2
README.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Milly
|
||||||
|
Raine's general purpose library
|
30
src/dom/document.rs
Normal file
30
src/dom/document.rs
Normal file
|
@ -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<S: Into<String>>(lang: S) -> Self {
|
||||||
|
Document {
|
||||||
|
lang: lang.into(),
|
||||||
|
head: Head::default(),
|
||||||
|
body: Element::new("body"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Element> for Document {
|
||||||
|
fn into(self) -> Element {
|
||||||
|
let mut r = Element::new("html");
|
||||||
|
r.set_pre_text("<!DOCTYPE html>");
|
||||||
|
r.set_attribute("lang", self.lang);
|
||||||
|
r.append_child(into!(Element, self.head))
|
||||||
|
.append_child(self.body);
|
||||||
|
r
|
||||||
|
}
|
||||||
|
}
|
41
src/dom/element/datatype.rs
Normal file
41
src/dom/element/datatype.rs
Normal file
|
@ -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<DataType> for $i {
|
||||||
|
fn into(self) -> DataType {
|
||||||
|
DataType::$i(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
create_datatype!(String, Element, i32);
|
||||||
|
|
||||||
|
impl Into<DataType> for &str {
|
||||||
|
fn into(self) -> DataType {
|
||||||
|
DataType::String(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<DataType> for &mut Element {
|
||||||
|
fn into(self) -> DataType {
|
||||||
|
DataType::Element(self.clone())
|
||||||
|
}
|
||||||
|
}
|
104
src/dom/element/mod.rs
Normal file
104
src/dom/element/mod.rs
Normal file
|
@ -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<String, String>,
|
||||||
|
children: Vec<DataType>,
|
||||||
|
pre_text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element {
|
||||||
|
pub fn new<S: Into<String>>(t: S) -> Self {
|
||||||
|
Element {
|
||||||
|
element_type: t.into(),
|
||||||
|
attributes: HashMap::new(),
|
||||||
|
children: vec![],
|
||||||
|
pre_text: String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_pre_text<S: Into<String>>(&mut self, str: S) -> &mut Self {
|
||||||
|
self.pre_text = str.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_child<C: Into<DataType>>(&mut self, child: C) -> &mut Self {
|
||||||
|
self.children.push(child.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_attribute<K: Into<String>, V: Into<String>>(
|
||||||
|
&mut self,
|
||||||
|
key: K,
|
||||||
|
value: V,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.attributes.insert(key.into(), value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_attribute<S: Into<String>>(&self, attribute: S) -> Option<String> {
|
||||||
|
self.attributes
|
||||||
|
.get(attribute.into().as_str())
|
||||||
|
.map(|s| s.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_id<S: Into<String>>(&mut self, id: S) -> &mut Self {
|
||||||
|
self.set_attribute("id", id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_class<S: Into<String>>(&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<S: Into<String>>(&mut self, str: S) {
|
||||||
|
self.children = vec![str.into().into()];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_element_by_id<S: Into<String>>(&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
|
||||||
|
}
|
||||||
|
}
|
75
src/dom/head.rs
Normal file
75
src/dom/head.rs
Normal file
|
@ -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<Asset>,
|
||||||
|
children: Vec<Element>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Head {
|
||||||
|
pub fn set_title<S: Into<String>>(&mut self, title: S) -> &mut Self {
|
||||||
|
self.title = title.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_js<S: Into<String>>(&mut self, file: S, defer: bool) -> &mut Self {
|
||||||
|
self.assets.push(Asset::JS {
|
||||||
|
file: file.into(),
|
||||||
|
defer,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_css<S: Into<String>>(&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<Element> 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
|
||||||
|
}
|
||||||
|
}
|
3
src/dom/mod.rs
Normal file
3
src/dom/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod document;
|
||||||
|
pub mod element;
|
||||||
|
pub mod head;
|
26
src/lib.rs
Normal file
26
src/lib.rs
Normal file
|
@ -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!("<h1></h1>", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_macro() {
|
||||||
|
use crate::into;
|
||||||
|
|
||||||
|
let foo = "foo";
|
||||||
|
assert_eq!(String::from("foo"), into!(String, foo));
|
||||||
|
}
|
||||||
|
}
|
11
src/macros.rs
Normal file
11
src/macros.rs
Normal file
|
@ -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)
|
||||||
|
};
|
||||||
|
}
|
53
src/main.rs
Normal file
53
src/main.rs
Normal file
|
@ -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());
|
||||||
|
}
|
Loading…
Reference in a new issue