diff --git a/macro/src/ast.rs b/macro/src/ast.rs index 081ea80..7afa732 100644 --- a/macro/src/ast.rs +++ b/macro/src/ast.rs @@ -1,8 +1,12 @@ //! Definition of the intermediate representation or AST. +use proc_macro2::TokenStream; + + /// The parsed input to the `gen_config` macro. pub(crate) struct Input { pub(crate) root: Node, + pub(crate) visibility: Option, } /// One node in the tree of the configuration format. Can either be a leaf node diff --git a/macro/src/gen.rs b/macro/src/gen.rs index e15bbcc..8a2c583 100644 --- a/macro/src/gen.rs +++ b/macro/src/gen.rs @@ -7,7 +7,7 @@ use crate::ast::{Expr, Input, Node}; pub(crate) fn gen(input: Input) -> TokenStream { - let visibility = quote! { pub(crate) }; + let visibility = input.visibility.clone().unwrap_or(quote! { pub(crate) }); let toml = gen_toml(&input); let root_mod = gen_root_mod(&input, &visibility); let raw_mod = gen_raw_mod(&input, &visibility); diff --git a/macro/src/parse.rs b/macro/src/parse.rs index a89cd38..939b1a2 100644 --- a/macro/src/parse.rs +++ b/macro/src/parse.rs @@ -14,8 +14,14 @@ impl Parse for Input { fn parse(input: ParseStream) -> Result { let mut outer_attrs = input.call(syn::Attribute::parse_inner)?; let doc = extract_doc(&mut outer_attrs)?; - let children = input.call(>::parse_terminated)?; + + // `#![visibility = "..."]` + let visibility = extract_single_name_value_attr("visibility", &mut outer_attrs)? + .map(|v| Ok::<_, syn::Error>(assert_string_lit(v)?.parse::()?)) + .transpose()?; + assert_no_extra_attrs(&outer_attrs)?; + let children = input.call(>::parse_terminated)?; let root = Node::Internal { doc, @@ -23,7 +29,7 @@ impl Parse for Input { children: children.into_iter().collect(), }; - Ok(Self { root }) + Ok(Self { root, visibility }) } } @@ -141,3 +147,39 @@ fn extract_doc(attrs: &mut Vec) -> Result, Error> { Ok(out) } + +fn extract_single_name_value_attr( + name: &str, + attrs: &mut Vec, +) -> Result, Error> { + let mut filtered = attrs.iter().filter(|attr| attr.path.is_ident(name)); + let meta = match filtered.next() { + None => return Ok(None), + Some(attr) => attr.parse_meta()?, + }; + + let nv = match meta { + syn::Meta::NameValue(nv) => nv, + other => { + let msg = format!(r#"expected `name = "value"` attribute syntax for `{}`"#, name); + return Err(Error::new(other.span(), msg)); + } + }; + + if let Some(dupe) = filtered.next() { + let msg = format!("duplicate `{}` attribute", name); + return Err(Error::new(dupe.span(), msg)); + } + + // Remove the attribute from the vector + attrs.retain(|attr| !attr.path.is_ident(name)); + + Ok(Some(nv.lit)) +} + +fn assert_string_lit(lit: syn::Lit) -> Result { + match lit { + syn::Lit::Str(s) => Ok(s.value()), + _ => Err(Error::new(lit.span(), "expected string literal")), + } +} diff --git a/src/example.rs b/src/example.rs index 6bf1b9a..aefdac3 100644 --- a/src/example.rs +++ b/src/example.rs @@ -12,6 +12,9 @@ use std::{net::IpAddr, path::PathBuf}; use crate as confique; crate::config! { + //! An example configuration. + #![visibility = "pub"] + dns: { /// The DNS server IP address. #[example = "1.1.1.1"]