libcnb_proc_macros/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use proc_macro::TokenStream;
4use quote::quote;
5use std::path::PathBuf;
6use syn::Token;
7use syn::parse::{Parse, ParseStream};
8use syn::parse_macro_input;
9
10/// Compiles the given regex using the `fancy_regex` crate and tries to match the given value. If
11/// the value matches the regex, the macro will expand to the first expression. Otherwise it will
12/// expand to the second expression.
13///
14/// It is designed to be used within other macros to produce compile time errors when the regex
15/// doesn't match but it might work for other use-cases as well.
16///
17/// ```
18/// libcnb_proc_macros::verify_regex!(
19///     "^A-Z+$",
20///     "foobar",
21///     println!("It did match!"),
22///     println!("It did not match!")
23/// );
24/// ```
25#[proc_macro]
26pub fn verify_regex(input: TokenStream) -> TokenStream {
27    let input = parse_macro_input!(input as VerifyRegexInput);
28
29    let token_stream = match fancy_regex::Regex::new(&input.regex.value()) {
30        Ok(regex) => {
31            let regex_matches = regex.is_match(&input.value.value()).unwrap_or(false);
32
33            let expression = if regex_matches {
34                input.expression_when_matched
35            } else {
36                input.expression_when_unmatched
37            };
38
39            quote! { #expression }
40        }
41        Err(err) => syn::Error::new(
42            input.regex.span(),
43            format!("Couldn't compile regular expression: {err}"),
44        )
45        .to_compile_error(),
46    };
47
48    token_stream.into()
49}
50
51struct VerifyRegexInput {
52    regex: syn::LitStr,
53    value: syn::LitStr,
54    expression_when_matched: syn::Expr,
55    expression_when_unmatched: syn::Expr,
56}
57
58impl Parse for VerifyRegexInput {
59    fn parse(input: ParseStream) -> syn::Result<Self> {
60        let regex: syn::LitStr = input.parse()?;
61        input.parse::<Token![,]>()?;
62        let value: syn::LitStr = input.parse()?;
63        input.parse::<Token![,]>()?;
64        let expression_when_matched: syn::Expr = input.parse()?;
65        input.parse::<Token![,]>()?;
66        let expression_when_unmatched: syn::Expr = input.parse()?;
67
68        Ok(Self {
69            regex,
70            value,
71            expression_when_matched,
72            expression_when_unmatched,
73        })
74    }
75}
76
77#[proc_macro]
78pub fn verify_bin_target_exists(input: TokenStream) -> TokenStream {
79    let input = parse_macro_input!(input as VerifyBinTargetExistsInput);
80
81    let cargo_metadata = std::env::var("CARGO_MANIFEST_DIR")
82        .map(PathBuf::from)
83        .ok()
84        .map(|cargo_manifest_dir| {
85            cargo_metadata::MetadataCommand::new()
86                .manifest_path(cargo_manifest_dir.join("Cargo.toml"))
87                .exec()
88        })
89        .transpose();
90
91    let token_stream = if let Ok(Some(cargo_metadata)) = cargo_metadata {
92        if let Some(root_package) = cargo_metadata.root_package() {
93            let valid_target = root_package
94                .targets
95                .iter()
96                .any(|target| target.name == input.target_name.value());
97
98            let expression = if valid_target {
99                input.expression_when_matched
100            } else {
101                input.expression_when_unmatched
102            };
103
104            quote! {
105                #expression
106            }
107        } else {
108            quote! {
109                compile_error!("Couldn't read root package for this crate!")
110            }
111        }
112    } else {
113        quote! {
114            compile_error!("Couldn't read Cargo metadata!")
115        }
116    };
117
118    token_stream.into()
119}
120
121struct VerifyBinTargetExistsInput {
122    target_name: syn::LitStr,
123    expression_when_matched: syn::Expr,
124    expression_when_unmatched: syn::Expr,
125}
126
127impl Parse for VerifyBinTargetExistsInput {
128    fn parse(input: ParseStream) -> syn::Result<Self> {
129        let target_name: syn::LitStr = input.parse()?;
130        input.parse::<Token![,]>()?;
131        let expression_when_matched: syn::Expr = input.parse()?;
132        input.parse::<Token![,]>()?;
133        let expression_when_unmatched: syn::Expr = input.parse()?;
134
135        Ok(Self {
136            target_name,
137            expression_when_matched,
138            expression_when_unmatched,
139        })
140    }
141}