libcnb_proc_macros/
lib.rs1#![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#[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}