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