First commit

This commit is contained in:
2026-03-02 22:45:57 +01:00
commit 7b6eba8ca5
7 changed files with 227 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/target
.idea
.vscode

54
Cargo.lock generated Normal file
View File

@@ -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",
]

13
Cargo.toml Normal file
View File

@@ -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"

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# Web-Macro
### Simple: the web inside macros in Rust

3
build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
println!("cargo:rustc-env=RUST_TEST_NOCAPTURE=1");
}

97
src/lib.rs Normal file
View File

@@ -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 [<heading$v>](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<Self> {
let name: Ident = input.parse()?;
input.parse::<Token![=]>()?;
let value: Expr = input.parse()?;
Ok(Property { name, value })
}
}
struct Argument {
value: Option<Ident>,
}
impl Parse for Argument {
fn parse(input: ParseStream) -> Result<Self> {
let value: Option<Ident> =
if input.peek(Ident) && input.peek2(Token![:]) && !input.peek3(Token![:]) {
let val = input.parse()?;
input.parse::<Token![:]>()?;
val
} else {
None
};
Ok(Argument { value })
}
}
struct BaseElement {
value: Expr,
params: Punctuated<Property, Token![,]>,
}
impl Parse for BaseElement {
fn parse(input: ParseStream) -> Result<Self> {
let value: Expr = input.parse()?;
let _ = input.parse::<Token![,]>();
let params: Punctuated<Property, Token![,]> = 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::<Vec<String>>()
.join(" ");
let format_string = format!("<h{} {formated_names}>{{}}</h{}>", level, level);
for property in params {
values.push(property.value);
}
quote!(format!(#format_string, #(#values), *, #value))
}
true => {
let format_string = format!("<h{}>{{}}</h{}>", level, level);
quote!(format!(#format_string, #value))
}
};
TokenStream::from(expanded)
}

54
tests/tests.rs Normal file
View File

@@ -0,0 +1,54 @@
#[cfg(test)]
mod tests {
use web_macro::*;
#[test]
fn test_function() {
assert_eq!("<h1>test</h1>", heading1!(String::from("test")));
}
#[test]
fn test_literal() {
assert_eq!("<h1>test</h1>", heading1!("test"));
}
#[test]
fn test_recursive() {
assert_eq!("<h1><h1>test</h1></h1>", heading1!(heading1!("test")));
}
#[test]
fn test_options_literal() {
assert_eq!(
"<h1 id=\"oui\" class=\"test\">test</h1>",
heading1!("test", id = "oui", class = "test")
);
}
#[test]
fn test_options_functional() {
assert_eq!(
"<h1 id=\"oui\" class=\"test\" pseudo=\"adolf\">test</h1>",
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!("<h2>test</h2>", heading2!("test"))
}
#[test]
fn test_level_options() {
assert_eq!("<h3 id=\"oui\">test</h3>", heading3!("test", id = "oui"))
}
}