mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-17 22:07:47 +00:00
Generate environment variables doc from code (#8493)
## Summary Resolves #8417 I've just begun learning procedural macros, so this PR is more of a proof of concept. It's still a work in progress, and I welcome any assistance or feedback.
This commit is contained in:
parent
545a55f58f
commit
3dfedf1fef
13 changed files with 491 additions and 135 deletions
|
@ -2,7 +2,7 @@ mod options_metadata;
|
|||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
use syn::{parse_macro_input, Attribute, DeriveInput, ImplItem, ItemImpl, LitStr};
|
||||
|
||||
#[proc_macro_derive(OptionsMetadata, attributes(option, doc, option_group))]
|
||||
pub fn derive_options_metadata(input: TokenStream) -> TokenStream {
|
||||
|
@ -49,3 +49,92 @@ fn impl_combine(ast: &DeriveInput) -> TokenStream {
|
|||
};
|
||||
gen.into()
|
||||
}
|
||||
|
||||
fn get_doc_comment(attrs: &[Attribute]) -> String {
|
||||
attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
if attr.path().is_ident("doc") {
|
||||
if let syn::Meta::NameValue(meta) = &attr.meta {
|
||||
if let syn::Expr::Lit(expr) = &meta.value {
|
||||
if let syn::Lit::Str(str) = &expr.lit {
|
||||
return Some(str.value().trim().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn get_env_var_pattern_from_attr(attrs: &[Attribute]) -> Option<String> {
|
||||
attrs
|
||||
.iter()
|
||||
.find(|attr| attr.path().is_ident("attr_env_var_pattern"))
|
||||
.and_then(|attr| attr.parse_args::<LitStr>().ok())
|
||||
.map(|lit_str| lit_str.value())
|
||||
}
|
||||
|
||||
fn is_hidden(attrs: &[Attribute]) -> bool {
|
||||
attrs.iter().any(|attr| attr.path().is_ident("attr_hidden"))
|
||||
}
|
||||
|
||||
/// This attribute is used to generate environment variables metadata for [`uv_static::EnvVars`].
|
||||
#[proc_macro_attribute]
|
||||
pub fn attribute_env_vars_metadata(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as ItemImpl);
|
||||
|
||||
let constants: Vec<_> = ast
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
ImplItem::Const(item) if !is_hidden(&item.attrs) => {
|
||||
let name = item.ident.to_string();
|
||||
let doc = get_doc_comment(&item.attrs);
|
||||
Some((name, doc))
|
||||
}
|
||||
ImplItem::Fn(item) if !is_hidden(&item.attrs) => {
|
||||
// Extract the environment variable patterns.
|
||||
if let Some(pattern) = get_env_var_pattern_from_attr(&item.attrs) {
|
||||
let doc = get_doc_comment(&item.attrs);
|
||||
Some((pattern, doc))
|
||||
} else {
|
||||
None // Skip if pattern extraction fails.
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let struct_name = &ast.self_ty;
|
||||
let pairs = constants.iter().map(|(name, doc)| {
|
||||
quote! {
|
||||
(#name, #doc)
|
||||
}
|
||||
});
|
||||
|
||||
let expanded = quote! {
|
||||
#ast
|
||||
|
||||
impl #struct_name {
|
||||
/// Returns a list of pairs of env var and their documentation defined in this impl block.
|
||||
pub fn metadata<'a>() -> &'a [(&'static str, &'static str)] {
|
||||
&[#(#pairs),*]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
expanded.into()
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn attr_hidden(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
item
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn attr_env_var_pattern(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
item
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue