From 7b6eba8ca5012d072a33cb2d4e08efe3e490119e Mon Sep 17 00:00:00 2001 From: AINDUSTRIES Date: Mon, 2 Mar 2026 22:45:57 +0100 Subject: [PATCH] First commit --- .gitignore | 3 ++ Cargo.lock | 54 ++++++++++++++++++++++++++++ Cargo.toml | 13 +++++++ README.md | 3 ++ build.rs | 3 ++ src/lib.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/tests.rs | 54 ++++++++++++++++++++++++++++ 7 files changed, 227 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 build.rs create mode 100644 src/lib.rs create mode 100644 tests/tests.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06a5314 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +.idea +.vscode diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e30a52a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,54 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "web-macro" +version = "0.1.0" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4edf054 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "web-macro" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0.44" +syn = {version="2.0.117", features=["full"]} +proc-macro2 = "1.0.103" +paste = "1.0.15" diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e0b630 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Web-Macro + +### Simple: the web inside macros in Rust diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..f9a898a --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rustc-env=RUST_TEST_NOCAPTURE=1"); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3424f94 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,97 @@ +extern crate proc_macro; +use paste::paste; +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::{Expr, Ident, Token, parse_macro_input}; + +macro_rules! generate_headings { + ($($v:expr),*) => { + $( + paste! { + #[proc_macro] + pub fn [](item: TokenStream) -> TokenStream { + heading(stringify!($v), item) + } + } + )* + }; +} + +generate_headings!(1, 2, 3, 4, 5, 6); + +struct Property { + name: Ident, + value: Expr, +} + +impl Parse for Property { + fn parse(input: ParseStream) -> Result { + let name: Ident = input.parse()?; + input.parse::()?; + let value: Expr = input.parse()?; + Ok(Property { name, value }) + } +} + +struct Argument { + value: Option, +} + +impl Parse for Argument { + fn parse(input: ParseStream) -> Result { + let value: Option = + if input.peek(Ident) && input.peek2(Token![:]) && !input.peek3(Token![:]) { + let val = input.parse()?; + input.parse::()?; + val + } else { + None + }; + Ok(Argument { value }) + } +} + +struct BaseElement { + value: Expr, + params: Punctuated, +} + +impl Parse for BaseElement { + fn parse(input: ParseStream) -> Result { + let value: Expr = input.parse()?; + let _ = input.parse::(); + let params: Punctuated = Punctuated::parse_terminated(input)?; + Ok(BaseElement { value, params }) + } +} + +fn heading(level: &str, item: TokenStream) -> TokenStream { + let BaseElement { value, params } = parse_macro_input!(item as BaseElement); + + let expanded = match params.is_empty() { + false => { + let mut values = Vec::new(); + + let formated_names = params + .iter() + .map(|x| format!("{}=\"{{}}\"", x.name)) + .collect::>() + .join(" "); + + let format_string = format!("{{}}", level, level); + + for property in params { + values.push(property.value); + } + + quote!(format!(#format_string, #(#values), *, #value)) + } + true => { + let format_string = format!("{{}}", level, level); + quote!(format!(#format_string, #value)) + } + }; + TokenStream::from(expanded) +} diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 0000000..8f18255 --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,54 @@ +#[cfg(test)] +mod tests { + use web_macro::*; + + #[test] + fn test_function() { + assert_eq!("

test

", heading1!(String::from("test"))); + } + + #[test] + fn test_literal() { + assert_eq!("

test

", heading1!("test")); + } + + #[test] + fn test_recursive() { + assert_eq!("

test

", heading1!(heading1!("test"))); + } + + #[test] + fn test_options_literal() { + assert_eq!( + "

test

", + heading1!("test", id = "oui", class = "test") + ); + } + + #[test] + fn test_options_functional() { + assert_eq!( + "

test

", + heading1!( + "test", + id = String::from("oui"), + class = String::from("test"), + pseudo = { + let mut s = String::new(); + s.push_str("adolf"); + s + } + ) + ); + } + + #[test] + fn test_level() { + assert_eq!("

test

", heading2!("test")) + } + + #[test] + fn test_level_options() { + assert_eq!("

test

", heading3!("test", id = "oui")) + } +}