~eanyanwu/toph

c72453e8956ba387c645deb043ebf39ccbed44e8 — Eze 2 months ago 4af540b
Implement a futile attempt to scope css rulesets

Futile because i somehow forgot that clases cascade.
8 files changed, 105 insertions(+), 40 deletions(-)

M Cargo.lock
M Cargo.toml
M components.html
M src/css/stack.css
M src/lib.rs
M src/node.rs
M src/node/variable.rs
M src/node/visitor.rs
M Cargo.lock => Cargo.lock +7 -0
@@ 3,6 3,12 @@
version = 3

[[package]]
name = "fastrand"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"

[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 52,6 58,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
name = "toph"
version = "0.4.0"
dependencies = [
 "fastrand",
 "paste",
 "url",
]

M Cargo.toml => Cargo.toml +1 -0
@@ 12,5 12,6 @@ keywords = ["template", "html", "layout"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
fastrand = "2.0.1"
paste = "1.0.14"
url = "2.5.0"

M components.html => components.html +34 -19
@@ 1,35 1,50 @@
<!DOCTYPE html><html><head><style>t-stack {
<!DOCTYPE html><html><head><style>
button {
  padding: 0.5rem 1.25rem;
  font-weight: bold;
  border: 2px solid black;
  box-shadow: 2px 2px black;
}</style><style>.card { padding: 1rem; border: 5px solid black; }</style><style>.t-cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--t-cluster-gap-4145495461);
  justify-content: flex-start;
  align-items: center;
}
</style><style>.t-stack {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}

t-stack>* {
.t-stack>* {
  margin-block: 0;
}

t-stack>*+* {
  margin-block-start: var(--t-stack-space);
.t-stack>*+* {
  margin-block-start: var(--t-stack-space-2737476295);
}

t-stack:only-child {
.t-stack:only-child {
  block-size: 100%;
}

t-stack> :nth-child(3) {
  margin-block-end: auto;
}
</style><style>.card { padding: 1rem; border: 5px solid black; }</style><style>
button {
  padding: 0.5rem 1.25rem;
  font-weight: bold;
  border: 2px solid black;
  box-shadow: 2px 2px black;
}</style><style>t-cluster {
</style><style>.t-stack {
  display: flex;
  flex-wrap: wrap;
  gap: var(--t-cluster-gap);
  flex-direction: column;
  justify-content: flex-start;
  align-items: center;
}
</style></head><body><t-cluster style="--t-cluster-gap: 1.096875rem;"><div class="card"><t-stack style="--t-stack-space: 1.096875rem;"><button>Hello</button><button>World</button></t-stack></div><div class="card"><t-stack style="--t-stack-space: 1.096875rem;"><button>Goodbye</button><button>World</button></t-stack></div></t-cluster></body></html>
\ No newline at end of file

.t-stack>* {
  margin-block: 0;
}

.t-stack>*+* {
  margin-block-start: var(--t-stack-space-3138295301);
}

.t-stack:only-child {
  block-size: 100%;
}

</style></head><body><div class="t-cluster" style="--t-cluster-gap-4145495461: 1.096875rem;"><div class="card"><div class="t-stack" style="--t-stack-space-2737476295: 1.096875rem;"><button>Hello</button><button>World</button></div></div><div class="card"><div class="t-stack" style="--t-stack-space-3138295301: 1.096875rem;"><button>Goodbye</button><button>World</button></div></div></div></body></html>
\ No newline at end of file

M src/css/stack.css => src/css/stack.css +0 -3
@@ 16,6 16,3 @@
  block-size: 100%;
}

.t-stack> :nth-child(3) {
  margin-block-end: auto;
}

M src/lib.rs => src/lib.rs +6 -4
@@ 1,10 1,10 @@
//! An API for building HTML documents in Rust.
//!
//! - Macro use kept to a minimum (there is just one macro for setting attributes). It's Just Rust
//! Code.
//! - It's Just Rust Code.
//! - [Safely](#xss-prevention) set attributes and content on HTML elements.
//! - Link [css](crate::Node::stylesheet) & [javascript](crate::Node::js) snippets to HTML
//! elements, such that those snippets appear when the linked element is displayed.
//!   - CSS & JS assets are included in the final HTML output.
//!
//! The crate also implements [a set of core css components](crate::component) so you don't have to
//!


@@ 120,6 120,8 @@
//!
//! // Set snippets using string literals
//! // Parameterize with css custom variables & `var()`
//! # use fastrand;
//! # fastrand::seed(1);
//! let css = "p { font-size: var(--font-size); }";
//! let mut html = html_.set([
//!     head_,


@@ 130,10 132,10 @@
//!   r#"<html>
//!   <head>
//!     <style>
//!       p { font-size: var(--font-size); }
//!       p { font-size: var(--font-size-3791187243); }
//!     </style>
//!   </head>
//!   <p style="--font-size: 1rem;">
//!   <p style="--font-size-3791187243: 1rem;">
//!   </p>
//! </html>
//!"#);

M src/node.rs => src/node.rs +12 -3
@@ 200,7 200,13 @@ impl Node {
    /// define additional variables.
    ///
    /// # Example
    ///
    /// Variable names are suffixed with a random number to prevent descendant nested elements from
    /// overriding ancestor values
    ///
    /// ```
    /// # use fastrand;
    /// # fastrand::seed(1);
    /// use toph::{tag::*, Node};
    ///
    /// let css = "div { color: var(--text-color); border: 1px solid var(--div-color); }";


@@ 222,13 228,16 @@ impl Node {
    /// r#"<html>
    ///   <head>
    ///     <style>
    ///       div { color: var(--text-color); border: 1px solid var(--div-color); }
    ///       div { color: var(--text-color-3791187243); border: 1px solid var(--div-color-479898943); }
    ///     </style>
    ///     <style>
    ///       div { color: var(--text-color-787217118); border: 1px solid var(--div-color-4275851145); }
    ///     </style>
    ///   </head>
    ///   <body>
    ///     <div style="--div-color: black;--text-color: white;">
    ///     <div style="--div-color-479898943: black;--text-color-3791187243: white;">
    ///     </div>
    ///     <div style="--div-color: pink;--text-color: brown;">
    ///     <div style="--div-color-4275851145: pink;--text-color-787217118: brown;">
    ///     </div>
    ///   </body>
    /// </html>

M src/node/variable.rs => src/node/variable.rs +30 -3
@@ 1,10 1,22 @@
use crate::encode;
use fastrand;
use std::collections::BTreeMap;
use std::fmt::{Display, Write};

/// The value associated with a variable + a random suffix that will be appended
/// to the variable name to make it unique to Node instance
///
/// This addition is necessary because in the case of a nested component, the
/// ancestor variable value gets  overridden in descendants.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Entry {
    pub value: String,
    pub suffix: u32,
}

/// Map of custom CSS variable name to value
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct CSSVariableMap(BTreeMap<&'static str, String>);
pub struct CSSVariableMap(BTreeMap<&'static str, Entry>);

impl CSSVariableMap {
    /// Add a new custom CSS variable to the map


@@ 12,7 24,14 @@ impl CSSVariableMap {
    /// The value will be attribute encoded  
    pub fn insert(&mut self, key: &'static str, value: &str) {
        let encoded = encode::attr(value);
        self.0.insert(key, encoded);
        let suffix = fastrand::u32(0..u32::MAX);
        self.0.insert(
            key,
            Entry {
                value: encoded,
                suffix,
            },
        );
    }

    /// Create a new variable map


@@ 26,6 45,14 @@ impl CSSVariableMap {
    }
}

impl<'a> IntoIterator for &'a CSSVariableMap {
    type Item = (&'a &'static str, &'a Entry);
    type IntoIter = std::collections::btree_map::Iter<'a, &'static str, Entry>;
    fn into_iter(self) -> Self::IntoIter {
        self.0.iter()
    }
}

impl Display for CSSVariableMap {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.0.is_empty() {


@@ 33,7 60,7 @@ impl Display for CSSVariableMap {
        }
        let mut result = String::new();
        for (k, v) in self.0.iter() {
            write!(result, "--{}: {};", k, v)?;
            write!(result, "--{}-{}: {};", k, v.suffix, v.value)?;
        }
        write!(f, "{}", result)
    }

M src/node/visitor.rs => src/node/visitor.rs +15 -8
@@ 1,7 1,7 @@
use super::{tag::*, Asset, Node};
use std::borrow::Cow;
use std::collections::btree_map::Entry;
use std::collections::HashSet;
use std::collections::BTreeSet;
use std::fmt;

enum Tag<'n> {


@@ 19,7 19,7 @@ pub fn include_assets(node: &mut Node) {
    let script_fragments = collector
        .js
        .into_iter()
        .map(|j| script_.dangerously_set_html(&j))
        .map(|j| script_.dangerously_set_html(j))
        .collect::<Vec<_>>();
    let style_fragments = collector
        .css


@@ 227,16 227,17 @@ impl NodeVisitor for AssetInserter {
}

// A visitor that collects all css & js snippets from the Node tree
// Using btreeset because the iteration order is defined, which makes it possible to test
pub struct SnippetCollector {
    pub css: HashSet<String>,
    pub js: HashSet<String>,
    pub css: BTreeSet<String>,
    pub js: BTreeSet<&'static str>,
}

impl SnippetCollector {
    pub fn new() -> Self {
        Self {
            css: HashSet::new(),
            js: HashSet::new(),
            css: BTreeSet::new(),
            js: BTreeSet::new(),
        }
    }
}


@@ 248,10 249,16 @@ impl NodeVisitor for &mut SnippetCollector {
        for asset in el.assets.iter_mut() {
            match asset {
                Asset::StyleSheet(css) => {
                    self.css.insert(css.to_string());
                    let mut localized_css = String::from(*css);
                    for (k, v) in el.variables.into_iter() {
                        let pattern = format!("var(--{})", k);
                        let replacement = format!("var(--{}-{})", k, v.suffix);
                        localized_css = localized_css.replace(&pattern, &replacement);
                    }
                    self.css.insert(localized_css);
                }
                Asset::JavaScript(js) => {
                    self.js.insert(js.to_string());
                    self.js.insert(js);
                }
            }
        }