valence_ident_macros/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use proc_macro::TokenStream as StdTokenStream;
4use proc_macro2::TokenStream;
5use quote::quote;
6use syn::{parse2, Error, LitStr, Result};
7
8#[proc_macro]
9pub fn parse_ident_str(item: StdTokenStream) -> StdTokenStream {
10    parse_ident_str_inner(item.into())
11        .unwrap_or_else(Error::into_compile_error)
12        .into()
13}
14
15fn parse_ident_str_inner(item: TokenStream) -> Result<TokenStream> {
16    let ident_lit: LitStr = parse2(item)?;
17    let mut ident = ident_lit.value();
18
19    match ident.split_once(':') {
20        Some((namespace, path)) if check_namespace(namespace) && check_path(path) => {}
21        None if check_path(&ident) => {
22            ident = format!("minecraft:{ident}");
23        }
24        _ => {
25            return Err(syn::Error::new(
26                ident_lit.span(),
27                "string cannot be parsed as a resource identifier",
28            ));
29        }
30    }
31
32    Ok(quote!(#ident))
33}
34
35fn check_namespace(s: &str) -> bool {
36    !s.is_empty()
37        && s.chars()
38            .all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-'))
39}
40
41fn check_path(s: &str) -> bool {
42    !s.is_empty()
43        && s.chars()
44            .all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-' | '/'))
45}