mirror of
https://github.com/OMGeeky/confique.git
synced 2025-12-27 06:29:27 +01:00
Revert "Replace some manual parsing code with darling helper crate"
This reverts commit e5af4f8efe.
The `darling` crate limits us to `syn::Meta` which is probably not
enough in the long run. There are a couple of other problems I have
with this crate and I think it's currently not worth. I'm considering
writing my own alternative...
This commit is contained in:
@@ -12,8 +12,7 @@ license = "MIT/Apache-2.0"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
darling = "0.13.0"
|
||||
heck = "0.3.2"
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = "1.0"
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
heck = "0.3.2"
|
||||
|
||||
265
macro/src/ir.rs
265
macro/src/ir.rs
@@ -1,5 +1,9 @@
|
||||
//! Definition of the intermediate representation.
|
||||
|
||||
use syn::{Error, Token, parse::{Parse, ParseStream}, spanned::Spanned};
|
||||
|
||||
use crate::util::{is_option, unwrap_option};
|
||||
|
||||
|
||||
/// The parsed input to the `gen_config` macro.
|
||||
pub(crate) struct Input {
|
||||
@@ -53,7 +57,6 @@ impl LeafKind {
|
||||
|
||||
/// The kinds of expressions (just literals) we allow for default or example
|
||||
/// values.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Expr {
|
||||
Str(syn::LitStr),
|
||||
Int(syn::LitInt),
|
||||
@@ -61,3 +64,263 @@ pub(crate) enum Expr {
|
||||
Bool(syn::LitBool),
|
||||
// TODO: arrays?
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub(crate) fn from_ast(mut input: syn::DeriveInput) -> Result<Self, Error> {
|
||||
let fields = match input.data {
|
||||
syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(f), .. }) => f,
|
||||
_ => return Err(Error::new(
|
||||
input.span(),
|
||||
"`confique::Config` can only be derive for structs with named fields",
|
||||
)),
|
||||
};
|
||||
|
||||
let doc = extract_doc(&mut input.attrs);
|
||||
let fields = fields.named.into_iter()
|
||||
.map(Field::from_ast)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
|
||||
Ok(Self {
|
||||
doc,
|
||||
visibility: input.vis,
|
||||
name: input.ident,
|
||||
fields,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Field {
|
||||
fn from_ast(mut field: syn::Field) -> Result<Self, Error> {
|
||||
let doc = extract_doc(&mut field.attrs);
|
||||
let attrs = extract_internal_attrs(&mut field.attrs)?;
|
||||
|
||||
// TODO: check no other attributes are here
|
||||
let kind = if attrs.nested {
|
||||
if is_option(&field.ty) {
|
||||
return Err(Error::new(
|
||||
field.ident.span(),
|
||||
"nested configurations cannot be optional (type `Option<_>`)",
|
||||
));
|
||||
}
|
||||
if attrs.default.is_some() {
|
||||
return Err(Error::new(
|
||||
field.ident.span(),
|
||||
"cannot specify `nested` and `default` attributes at the same time",
|
||||
));
|
||||
}
|
||||
if attrs.env.is_some() {
|
||||
return Err(Error::new(
|
||||
field.ident.span(),
|
||||
"cannot specify `nested` and `env` attributes at the same time",
|
||||
));
|
||||
}
|
||||
|
||||
FieldKind::Nested { ty: field.ty }
|
||||
} else {
|
||||
match unwrap_option(&field.ty) {
|
||||
None => FieldKind::Leaf {
|
||||
env: attrs.env,
|
||||
kind: LeafKind::Required {
|
||||
default: attrs.default,
|
||||
ty: field.ty,
|
||||
},
|
||||
},
|
||||
Some(inner) => {
|
||||
if attrs.default.is_some() {
|
||||
return Err(Error::new(
|
||||
field.ident.span(),
|
||||
"optional fields (type `Option<_>`) cannot have default \
|
||||
values (`#[config(default = ...)]`)",
|
||||
));
|
||||
}
|
||||
|
||||
FieldKind::Leaf {
|
||||
env: attrs.env,
|
||||
kind: LeafKind::Optional {
|
||||
inner_ty: inner.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
doc,
|
||||
name: field.ident.expect("bug: expected named field"),
|
||||
kind,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn is_leaf(&self) -> bool {
|
||||
matches!(self.kind, FieldKind::Leaf { .. })
|
||||
}
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
fn from_lit(lit: syn::Lit) -> Result<Self, Error> {
|
||||
match lit {
|
||||
syn::Lit::Str(l) => Ok(Self::Str(l)),
|
||||
syn::Lit::Int(l) => Ok(Self::Int(l)),
|
||||
syn::Lit::Float(l) => Ok(Self::Float(l)),
|
||||
syn::Lit::Bool(l) => Ok(Self::Bool(l)),
|
||||
|
||||
_ => {
|
||||
let msg = "only string, integer, float and bool literals are allowed here";
|
||||
Err(Error::new(lit.span(), msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts all doc string attributes from the list and return them as list of
|
||||
/// strings (in order).
|
||||
fn extract_doc(attrs: &mut Vec<syn::Attribute>) -> Vec<String> {
|
||||
extract_attrs(attrs, |attr| {
|
||||
match attr.parse_meta().ok()? {
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Str(s),
|
||||
path,
|
||||
..
|
||||
}) if path.is_ident("doc") => Some(s.value()),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_attrs<P, O>(attrs: &mut Vec<syn::Attribute>, mut pred: P) -> Vec<O>
|
||||
where
|
||||
P: FnMut(&syn::Attribute) -> Option<O>,
|
||||
{
|
||||
// TODO: use `Vec::drain_filter` once stabilized. The current impl is O(n²).
|
||||
let mut i = 0;
|
||||
let mut out = Vec::new();
|
||||
while i < attrs.len() {
|
||||
match pred(&attrs[i]) {
|
||||
Some(v) => {
|
||||
out.push(v);
|
||||
attrs.remove(i);
|
||||
}
|
||||
None => i += 1,
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn extract_internal_attrs(
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
) -> Result<InternalAttrs, Error> {
|
||||
let internal_attrs = extract_attrs(attrs, |attr| {
|
||||
if attr.path.is_ident("config") {
|
||||
// TODO: clone not necessary once we use drain_filter
|
||||
Some(attr.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let mut out = InternalAttrs::default();
|
||||
for attr in internal_attrs {
|
||||
type AttrList = syn::punctuated::Punctuated<InternalAttr, Token![,]>;
|
||||
let parsed_list = attr.parse_args_with(AttrList::parse_terminated)?;
|
||||
|
||||
for parsed in parsed_list {
|
||||
let keyword = parsed.keyword();
|
||||
|
||||
macro_rules! duplicate_if {
|
||||
($cond:expr) => {
|
||||
if $cond {
|
||||
let msg = format!("duplicate '{}' confique attribute", keyword);
|
||||
return Err(Error::new(attr.tokens.span(), msg));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match parsed {
|
||||
InternalAttr::Default(expr) => {
|
||||
duplicate_if!(out.default.is_some());
|
||||
out.default = Some(expr);
|
||||
}
|
||||
InternalAttr::Nested => {
|
||||
duplicate_if!(out.nested);
|
||||
out.nested = true;
|
||||
}
|
||||
InternalAttr::Env(key) => {
|
||||
duplicate_if!(out.env.is_some());
|
||||
out.env = Some(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct InternalAttrs {
|
||||
nested: bool,
|
||||
default: Option<Expr>,
|
||||
env: Option<String>
|
||||
}
|
||||
|
||||
enum InternalAttr {
|
||||
Nested,
|
||||
Default(Expr),
|
||||
Env(String),
|
||||
}
|
||||
|
||||
impl InternalAttr {
|
||||
fn keyword(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Nested => "nested",
|
||||
Self::Default(_) => "default",
|
||||
Self::Env(_) => "env",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for InternalAttr {
|
||||
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||
let ident: syn::Ident = input.parse()?;
|
||||
match &*ident.to_string() {
|
||||
"nested" => {
|
||||
assert_empty_or_comma(input)?;
|
||||
Ok(Self::Nested)
|
||||
}
|
||||
|
||||
"default" => {
|
||||
let _: Token![=] = input.parse()?;
|
||||
let expr = Expr::from_lit(input.parse()?)?;
|
||||
assert_empty_or_comma(input)?;
|
||||
Ok(Self::Default(expr))
|
||||
}
|
||||
|
||||
"env" => {
|
||||
let _: Token![=] = input.parse()?;
|
||||
let key: syn::LitStr = input.parse()?;
|
||||
assert_empty_or_comma(input)?;
|
||||
let value = key.value();
|
||||
if value.contains('=') || value.contains('\0') {
|
||||
Err(syn::Error::new(
|
||||
key.span(),
|
||||
"environment variable key must not contain '=' or null bytes",
|
||||
))
|
||||
} else {
|
||||
Ok(Self::Env(value))
|
||||
}
|
||||
}
|
||||
|
||||
_ => Err(syn::Error::new(ident.span(), "unknown confique attribute")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_empty_or_comma(input: ParseStream) -> Result<(), Error> {
|
||||
if input.is_empty() || input.peek(Token![,]) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(input.span(), "unexpected tokens, expected no more tokens in this context"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,14 @@ use proc_macro::TokenStream as TokenStream1;
|
||||
|
||||
mod gen;
|
||||
mod ir;
|
||||
mod parse;
|
||||
mod util;
|
||||
|
||||
|
||||
#[proc_macro_derive(Config, attributes(config))]
|
||||
pub fn config(input: TokenStream1) -> TokenStream1 {
|
||||
let input = match syn::parse2::<syn::DeriveInput>(input.into()) {
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
Ok(i) => i,
|
||||
};
|
||||
|
||||
ir::Input::from_ast(input)
|
||||
syn::parse2::<syn::DeriveInput>(input.into())
|
||||
.and_then(ir::Input::from_ast)
|
||||
.map(gen::gen)
|
||||
.unwrap_or_else(|e| e.write_errors())
|
||||
.unwrap_or_else(|e| e.to_compile_error())
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
use darling::Error;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use crate::{ir, util::{is_option, unwrap_option}};
|
||||
|
||||
|
||||
macro_rules! bail {
|
||||
($span:expr, $msg:expr $(,)?) => {
|
||||
return Err(Error::custom($msg).with_span(&$span))
|
||||
};
|
||||
}
|
||||
|
||||
impl ir::Input {
|
||||
pub(crate) fn from_ast(mut input: syn::DeriveInput) -> Result<Self, Error> {
|
||||
let struct_fields = match input.data {
|
||||
syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(f), .. }) => f,
|
||||
_ => bail!(
|
||||
input.span(),
|
||||
"`confique::Config` can only be derive for structs with named fields",
|
||||
),
|
||||
};
|
||||
|
||||
let doc = extract_doc(&mut input.attrs);
|
||||
|
||||
let mut errors = Vec::new();
|
||||
let mut fields = Vec::new();
|
||||
for field in struct_fields.named {
|
||||
match ir::Field::from_ast(field) {
|
||||
Ok(f) => fields.push(f),
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(Error::multiple(errors));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
doc,
|
||||
visibility: input.vis,
|
||||
name: input.ident,
|
||||
fields,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ir::Field {
|
||||
fn from_ast(field: syn::Field) -> Result<Self, darling::Error> {
|
||||
use darling::FromField;
|
||||
let mut field = Field::from_field(&field)?;
|
||||
let doc = extract_doc(&mut field.attrs);
|
||||
|
||||
let kind = if field.nested {
|
||||
// Nested field.
|
||||
|
||||
if is_option(&field.ty) {
|
||||
bail!(
|
||||
field.ident.span(),
|
||||
"nested configurations cannot be optional (type `Option<_>`)",
|
||||
);
|
||||
}
|
||||
if field.default.is_some() {
|
||||
bail!(
|
||||
field.ident.span(),
|
||||
"cannot specify `nested` and `default` attributes at the same time",
|
||||
);
|
||||
}
|
||||
if field.env.is_some() {
|
||||
bail!(
|
||||
field.ident.span(),
|
||||
"cannot specify `nested` and `env` attributes at the same time",
|
||||
);
|
||||
}
|
||||
|
||||
ir::FieldKind::Nested { ty: field.ty }
|
||||
} else {
|
||||
// Leaf field.
|
||||
|
||||
match unwrap_option(&field.ty) {
|
||||
None => ir::FieldKind::Leaf {
|
||||
env: field.env,
|
||||
kind: ir::LeafKind::Required {
|
||||
default: field.default,
|
||||
ty: field.ty,
|
||||
},
|
||||
},
|
||||
Some(inner) => {
|
||||
if field.default.is_some() {
|
||||
bail!(
|
||||
field.ident.span(),
|
||||
"optional fields (type `Option<_>`) cannot have default \
|
||||
values (`#[config(default = ...)]`)",
|
||||
);
|
||||
}
|
||||
|
||||
ir::FieldKind::Leaf {
|
||||
env: field.env,
|
||||
kind: ir::LeafKind::Optional {
|
||||
inner_ty: inner.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
doc,
|
||||
name: field.ident.expect("bug: expected named field"),
|
||||
kind,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn is_leaf(&self) -> bool {
|
||||
matches!(self.kind, ir::FieldKind::Leaf { .. })
|
||||
}
|
||||
}
|
||||
|
||||
impl darling::FromMeta for ir::Expr {
|
||||
fn from_value(lit: &syn::Lit) -> Result<Self, darling::Error> {
|
||||
match lit {
|
||||
syn::Lit::Str(l) => Ok(Self::Str(l.clone())),
|
||||
syn::Lit::Int(l) => Ok(Self::Int(l.clone())),
|
||||
syn::Lit::Float(l) => Ok(Self::Float(l.clone())),
|
||||
syn::Lit::Bool(l) => Ok(Self::Bool(l.clone())),
|
||||
|
||||
_ => {
|
||||
// let msg = "only string, integer, float and bool literals are allowed here";
|
||||
// Err(Error::new(lit.span(), msg))
|
||||
Err(darling::Error::unexpected_lit_type(lit))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, darling::FromField)]
|
||||
#[darling(attributes(config), forward_attrs(doc))]
|
||||
struct Field {
|
||||
ident: Option<syn::Ident>,
|
||||
ty: syn::Type,
|
||||
attrs: Vec<syn::Attribute>,
|
||||
|
||||
#[darling(default)]
|
||||
nested: bool,
|
||||
|
||||
#[darling(default)]
|
||||
env: Option<String>,
|
||||
|
||||
#[darling(default)]
|
||||
default: Option<ir::Expr>,
|
||||
}
|
||||
|
||||
/// Extracts all doc string attributes from the list and return them as list of
|
||||
/// strings (in order).
|
||||
fn extract_doc(attrs: &mut Vec<syn::Attribute>) -> Vec<String> {
|
||||
extract_attrs(attrs, |attr| {
|
||||
match attr.parse_meta().ok()? {
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Str(s),
|
||||
path,
|
||||
..
|
||||
}) if path.is_ident("doc") => Some(s.value()),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_attrs<P, O>(attrs: &mut Vec<syn::Attribute>, mut pred: P) -> Vec<O>
|
||||
where
|
||||
P: FnMut(&syn::Attribute) -> Option<O>,
|
||||
{
|
||||
// TODO: use `Vec::drain_filter` once stabilized. The current impl is O(n²).
|
||||
let mut i = 0;
|
||||
let mut out = Vec::new();
|
||||
while i < attrs.len() {
|
||||
match pred(&attrs[i]) {
|
||||
Some(v) => {
|
||||
out.push(v);
|
||||
attrs.remove(i);
|
||||
}
|
||||
None => i += 1,
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
Reference in New Issue
Block a user