First commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/target
|
||||
.idea
|
||||
.vscode
|
||||
54
Cargo.lock
generated
Normal file
54
Cargo.lock
generated
Normal 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
13
Cargo.toml
Normal 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
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Web-Macro
|
||||
|
||||
### Simple: the web inside macros in Rust
|
||||
3
build.rs
Normal file
3
build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("cargo:rustc-env=RUST_TEST_NOCAPTURE=1");
|
||||
}
|
||||
97
src/lib.rs
Normal file
97
src/lib.rs
Normal 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
54
tests/tests.rs
Normal 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"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user