Skip to content

Commit

Permalink
Add default config option
Browse files Browse the repository at this point in the history
I feel like I'm going to run out of Ben Finegold quotes rather soon.
  • Loading branch information
cyqsimon committed Oct 16, 2024
1 parent f243477 commit 1f9f745
Show file tree
Hide file tree
Showing 12 changed files with 295 additions and 32 deletions.
7 changes: 5 additions & 2 deletions documented-macros/src/attr_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ pub fn docs_const_impl(

let (item_vis, item_name, attrs) = get_vis_name_attrs(&item)?;

let docs = get_docs(attrs, config.trim)?
.ok_or_else(|| Error::new_spanned(&item, "Missing doc comments"))?;
let docs = match (get_docs(attrs, config.trim)?, config.default_value) {
(Some(docs), _) => Ok(quote! { #docs }),
(None, Some(default)) => Ok(quote! { #default }),
(None, None) => Err(Error::new_spanned(&item, "Missing doc comments")),
}?;

let const_vis = config.custom_vis.unwrap_or(item_vis);
let const_name = config
Expand Down
7 changes: 6 additions & 1 deletion documented-macros/src/config/attr.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#[cfg(feature = "customise")]
use optfield::optfield;
use syn::Visibility;
#[cfg(feature = "customise")]
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
Token,
};
use syn::{Expr, Visibility};

#[cfg(feature = "customise")]
use crate::config::customise_core::{ensure_unique_options, ConfigOption};
Expand All @@ -28,13 +28,15 @@ pub struct AttrConfig {
// see https://docs.rs/optfield/latest/optfield/#rewrapping-option-fields
pub custom_vis: Option<Visibility>,
pub custom_name: Option<String>,
pub default_value: Option<Expr>,
pub trim: bool,
}
impl Default for AttrConfig {
fn default() -> Self {
Self {
custom_vis: None,
custom_name: None,
default_value: None,
trim: true,
}
}
Expand Down Expand Up @@ -70,6 +72,9 @@ impl Parse for AttrCustomisations {
O::Name(_, val) => {
config.custom_name.replace(val);
}
O::Default(_, mode) => {
config.default_value.replace(mode);
}
O::Trim(_, val) => {
config.trim.replace(val);
}
Expand Down
14 changes: 13 additions & 1 deletion documented-macros/src/config/customise_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ use proc_macro2::Span;
use syn::{
parse::{Parse, ParseStream},
spanned::Spanned,
Error, LitBool, LitStr, Token, Visibility,
Error, Expr, LitBool, LitStr, Token, Visibility,
};

mod kw {
use syn::custom_keyword;

custom_keyword!(vis);
custom_keyword!(name);
custom_keyword!(default);
custom_keyword!(trim);
}

Expand All @@ -36,6 +37,11 @@ pub enum ConfigOption {
/// E.g. `name = "CUSTOM_NAME_DOCS"`.
Name(kw::name, String),

/// Use some default value when doc comments are absent.
///
/// E.g. `default = "not documented"`.
Default(kw::default, Expr),

/// Trim each line or not.
///
/// E.g. `trim = false`.
Expand All @@ -55,6 +61,11 @@ impl Parse for ConfigOption {
input.parse::<Token![=]>()?;
let name = input.parse::<LitStr>()?;
Ok(Self::Name(kw, name.value()))
} else if lookahead.peek(kw::default) {
let kw = input.parse()?;
input.parse::<Token![=]>()?;
let default = input.parse::<Expr>()?;
Ok(Self::Default(kw, default))
} else if lookahead.peek(kw::trim) {
let kw = input.parse()?;
input.parse::<Token![=]>()?;
Expand All @@ -70,6 +81,7 @@ impl ConfigOption {
match self {
Self::Vis(kw, _) => kw.span(),
Self::Name(kw, _) => kw.span(),
Self::Default(kw, _) => kw.span(),
Self::Trim(kw, _) => kw.span(),
}
}
Expand Down
18 changes: 13 additions & 5 deletions documented-macros/src/config/derive.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#[cfg(feature = "customise")]
use optfield::optfield;
use syn::Expr;
#[cfg(feature = "customise")]
use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Error, Meta, Token};

Expand All @@ -17,21 +18,25 @@ use crate::config::customise_core::{ensure_unique_options, ConfigOption};
\n\
Expected parse stream format: `<KW> = <VAL>, <KW> = <VAL>, ...`"
))]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DeriveConfig {
// optfield does not rewrap `Option` by default, which is the desired behavior
// see https://docs.rs/optfield/latest/optfield/#rewrapping-option-fields
pub default_value: Option<Expr>,
pub trim: bool,
}
impl Default for DeriveConfig {
fn default() -> Self {
Self { trim: true }
Self { default_value: None, trim: true }
}
}
#[cfg(feature = "customise")]
impl DeriveConfig {
/// Return a new instance of this config with customisations applied.
pub fn with_customisations(mut self, customisations: DeriveCustomisations) -> Self {
self.apply_customisations(customisations);
self
pub fn with_customisations(&self, customisations: DeriveCustomisations) -> Self {
let mut new = self.clone();
new.apply_customisations(customisations);
new
}
}

Expand All @@ -52,6 +57,9 @@ impl TryFrom<Vec<ConfigOption>> for DeriveCustomisations {
arg.kw_span(),
"This config option is not applicable to derive macros",
))?,
O::Default(_, mode) => {
config.default_value.replace(mode);
}
O::Trim(_, val) => {
config.trim.replace(val);
}
Expand Down
47 changes: 28 additions & 19 deletions documented-macros/src/derive_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
spanned::Spanned, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, Fields, Ident,
spanned::Spanned, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, Expr, Fields,
Ident,
};

#[cfg(feature = "customise")]
Expand Down Expand Up @@ -35,27 +36,33 @@ impl ToTokens for DocType {
}
impl DocType {
/// Get the closure that determines how to handle optional docs.
/// The closure takes two arguments:
/// The closure takes three arguments:
///
/// 1. The optional doc comments on an item
/// 2. The token span on which to report any errors
/// 2. A default value optionally set by the user
/// 3. The token span on which to report any errors
///
/// And fallibly returns the tokenised doc comments.
fn docs_handler_opt<S>(&self) -> Box<dyn Fn(Option<String>, S) -> syn::Result<TokenStream>>
#[allow(clippy::type_complexity)]
fn docs_handler_opt<S>(
&self,
) -> Box<dyn Fn(Option<String>, Option<Expr>, S) -> syn::Result<TokenStream>>
where
S: ToTokens,
{
match self {
Self::Str => Box::new(|docs_opt, span| match docs_opt {
Some(docs) => Ok(quote! { #docs }),
None => Err(Error::new_spanned(span, "Missing doc comments")),
}),
Self::OptStr => Box::new(|docs_opt, _span| {
// quote macro needs some help with `Option`s
// see: https://github.com/dtolnay/quote/issues/213
let tokens = match docs_opt {
Some(docs) => quote! { Some(#docs) },
None => quote! { None },
Self::Str => Box::new(
|docs_opt, default_opt, span| match (docs_opt, default_opt) {
(Some(docs), _) => Ok(quote! { #docs }),
(None, Some(default)) => Ok(quote! { #default }),
(None, None) => Err(Error::new_spanned(span, "Missing doc comments")),
},
),
Self::OptStr => Box::new(|docs_opt, default_opt, _span| {
let tokens = match (docs_opt, default_opt) {
(Some(docs), _) => quote! { Some(#docs) },
(None, Some(default)) => quote! { #default },
(None, None) => quote! { None },
};
Ok(tokens)
}),
Expand Down Expand Up @@ -85,7 +92,7 @@ pub fn documented_impl(input: DeriveInput, docs_ty: DocType) -> syn::Result<Toke
.map(|c| DeriveConfig::default().with_customisations(c))?;

let docs = get_docs(&input.attrs, config.trim)
.and_then(|docs_opt| docs_ty.docs_handler_opt()(docs_opt, &input))?;
.and_then(|docs_opt| docs_ty.docs_handler_opt()(docs_opt, config.default_value, &input))?;

Ok(quote! {
#[automatically_derived]
Expand Down Expand Up @@ -128,12 +135,14 @@ pub fn documented_fields_impl(input: DeriveInput, docs_ty: DocType) -> syn::Resu
.into_iter()
.map(|(span, ident, attrs)| {
#[cfg(not(feature = "customise"))]
let config = base_config;
let config = base_config.clone();
#[cfg(feature = "customise")]
let config = base_config
.with_customisations(get_customisations_from_attrs(&attrs, "documented_fields")?);
get_docs(&attrs, config.trim)
.and_then(|docs_opt| docs_ty.docs_handler_opt()(docs_opt, span))
.and_then(|docs_opt| {
docs_ty.docs_handler_opt()(docs_opt, config.default_value, span)
})
.map(|docs| (ident, docs))
})
.collect::<syn::Result<Vec<_>>>()?
Expand Down Expand Up @@ -196,14 +205,14 @@ pub fn documented_variants_impl(input: DeriveInput, docs_ty: DocType) -> syn::Re
.into_iter()
.map(|v| {
#[cfg(not(feature = "customise"))]
let config = base_config;
let config = base_config.clone();
#[cfg(feature = "customise")]
let config = base_config.with_customisations(get_customisations_from_attrs(
&v.attrs,
"documented_variants",
)?);
get_docs(&v.attrs, config.trim)
.and_then(|docs_opt| docs_ty.docs_handler_opt()(docs_opt, &v))
.and_then(|docs_opt| docs_ty.docs_handler_opt()(docs_opt, config.default_value, &v))
.map(|docs| (v.ident, v.fields, docs))
})
.collect::<syn::Result<Vec<_>>>()?;
Expand Down
91 changes: 87 additions & 4 deletions documented-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,24 @@ use crate::{
/// With the `customise` feature enabled, you can customise this macro's
/// behaviour using the `#[documented(...)]` attribute.
///
/// Currently, you can disable line-trimming like so:
/// Currently, you can:
///
/// ## 1. set a default value when doc comments are absent like so:
///
/// ```rust
/// # use documented::Documented;
/// #[derive(Documented)]
/// #[documented(default = "The answer is fries.")]
/// struct WhosTurnIsIt;
///
/// assert_eq!(WhosTurnIsIt::DOCS, "The answer is fries.");
/// ```
///
/// This option is primarily designed for [`DocumentedFields`] and
/// [`DocumentedVariants`], so it's probably not very useful here. But it could
/// conceivably come in handy in some niche meta-programming contexts.
///
/// ## 2. disable line-trimming like so:
///
/// ```rust
/// # use documented::Documented;
Expand Down Expand Up @@ -138,7 +155,32 @@ pub fn documented_opt(input: TokenStream) -> TokenStream {
/// per-field configurations overriding container configurations, which
/// override the default.
///
/// Currently, you can (selectively) disable line-trimming like so:
/// Currently, you can:
///
/// ## 1. set a default value when doc comments are absent like so:
///
/// ```rust
/// # use documented::DocumentedFields;
/// #[derive(DocumentedFields)]
/// #[documented_fields(default = "Confusing the audience.")]
/// struct SettingUpForTheNextGame {
/// rh8: bool,
/// ng8: bool,
/// /// Always play:
/// bf8: bool,
/// }
///
// assert_eq!(
// SettingUpForTheNextGame::FIELD_DOCS,
// [
// "Confusing the audience.",
// "Confusing the audience.",
// "Always play:"
// ]
// );
/// ```
///
/// ## 2. (selectively) disable line-trimming like so:
///
/// ```rust
/// # use documented::DocumentedFields;
Expand Down Expand Up @@ -209,7 +251,30 @@ pub fn documented_fields_opt(input: TokenStream) -> TokenStream {
/// per-variant configurations overriding container configurations, which
/// override the default.
///
/// Currently, you can (selectively) disable line-trimming like so:
/// Currently, you can:
///
/// ## 1. set a default value when doc comments are absent like so:
///
/// ```rust
/// # use documented::DocumentedVariants;
/// #[derive(DocumentedVariants)]
/// #[documented_variants(default = "Still theory.")]
/// enum OurHeroPlayed {
/// G4Mate,
/// OOOMate,
/// /// Frankly ridiculous.
/// Bf1g2Mate,
/// }
///
/// assert_eq!(OurHeroPlayed::G4Mate.get_variant_docs(), "Still theory.");
/// assert_eq!(OurHeroPlayed::OOOMate.get_variant_docs(), "Still theory.");
/// assert_eq!(
/// OurHeroPlayed::Bf1g2Mate.get_variant_docs(),
/// "Frankly ridiculous."
/// );
/// ```
///
/// ## 2. (selectively) disable line-trimming like so:
///
/// ```rust
/// # use documented::DocumentedVariants;
Expand Down Expand Up @@ -306,7 +371,25 @@ pub fn documented_variants_opt(input: TokenStream) -> TokenStream {
/// assert_eq!(DONT_RAISE_YOUR_HAND, "If you have a question raise your hand");
/// ```
///
/// ## 3. disable line-trimming like so:
/// ## 3. set a default value when doc comments are absent like so:
///
/// ```rust
/// use documented::docs_const;
///
/// #[docs_const(default = "In this position many of you blunder.")]
/// trait StartingPosition {}
///
/// assert_eq!(
/// STARTING_POSITION_DOCS,
/// "In this position many of you blunder."
/// );
/// ```
///
/// This option is primarily designed for [`DocumentedFields`] and
/// [`DocumentedVariants`], so it's probably not very useful here. But it could
/// conceivably come in handy in some niche meta-programming contexts.
///
/// ## 4. disable line-trimming like so:
///
/// ```rust
/// # use documented::docs_const;
Expand Down
Loading

0 comments on commit 1f9f745

Please sign in to comment.