init: initial commit

This commit is contained in:
Strix 2023-10-14 22:39:16 +02:00
commit c3c599f8b9
No known key found for this signature in database
GPG key ID: 49B2E37B8915B774
12 changed files with 366 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.idea/
target/

7
Cargo.lock generated Normal file
View 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
View 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
View file

@ -0,0 +1,2 @@
# Milly
Raine's general purpose library

30
src/dom/document.rs Normal file
View 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
}
}

View 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
View 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
View 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
View file

@ -0,0 +1,3 @@
pub mod document;
pub mod element;
pub mod head;

26
src/lib.rs Normal file
View 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
View 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
View 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());
}