docbot-derive 0.3.0-alpha.2

Derive macros for docbot
Documentation
use std::collections::HashMap;

use anyhow::anyhow;
use syn::{spanned::Spanned, Fields, Type};

use super::prelude::*;
use crate::{attrs, Result};

pub enum FieldMode {
    Required,
    Optional,
    RestRequired,
    RestOptional,
}

impl FieldMode {
    pub fn required(&self) -> bool { matches!(self, Self::Required | Self::RestRequired) }

    pub fn rest(&self) -> bool { matches!(self, Self::RestRequired | Self::RestOptional) }
}

#[allow(clippy::manual_non_exhaustive)]
pub struct FieldInfo<'a> {
    pub span: Span,
    pub opts: FieldOpts,
    pub name: String,
    pub ty: &'a Type,
    pub mode: FieldMode,
    _priv: (),
}

pub enum FieldInfos<'a> {
    Unit,
    Unnamed(Vec<FieldInfo<'a>>),
    Named(Vec<(Ident, FieldInfo<'a>)>),
}

impl<'a> FieldInfos<'a> {
    pub fn new(span: Span, usage: &CommandUsage, fields: &'a Fields) -> Result<Self> {
        let mut args = usage
            .required
            .iter()
            .map(|n| (FieldMode::Required, n))
            .chain(usage.optional.iter().map(|n| (FieldMode::Optional, n)))
            .chain(match usage.rest {
                RestArg::None => None,
                RestArg::Optional(ref n) => Some((FieldMode::RestOptional, n)),
                RestArg::Required(ref n) => Some((FieldMode::RestRequired, n)),
            });

        let args = match fields {
            Fields::Unit if args.next().is_none() => Ok(FieldInfos::Unit),
            Fields::Unit => Err((anyhow!("could not locate any fields in unit type"), span)),
            Fields::Unnamed(u) => {
                let mut map: HashMap<_, _> = u.unnamed.iter().enumerate().collect();

                args.enumerate()
                    .map(|(i, (mode, name))| {
                        let field = map
                            .remove(&i)
                            .ok_or_else(|| (anyhow!("could not locate field {}", i), span))?;
                        let span = field.span();

                        Ok(FieldInfo {
                            span,
                            opts: attrs::parse_field(&field.attrs, span)?,
                            mode,
                            name: name.into(),
                            ty: &field.ty,
                            _priv: (),
                        })
                    })
                    .collect::<Result<_>>()
                    .map(FieldInfos::Unnamed)
            },
            Fields::Named(n) => {
                let mut map: HashMap<_, _> = n
                    .named
                    .iter()
                    .map(|f| (f.ident.as_ref().unwrap().to_string(), f))
                    .collect();

                args.map(|(mode, name)| {
                    let field = map
                        .remove(name)
                        .ok_or_else(|| (anyhow!("could not locate field {:?}", name), span))?;
                    let span = field.span();

                    Ok((
                        syn::parse_str(name).map_err(|e| (e.into(), span))?,
                        FieldInfo {
                            span,
                            opts: attrs::parse_field(&field.attrs, span)?,
                            mode,
                            name: name.into(),
                            ty: &field.ty,
                            _priv: (),
                        },
                    ))
                })
                .collect::<Result<_>>()
                .map(FieldInfos::Named)
            },
        }?;

        if args.iter().len() != fields.iter().len() {
            return Err((anyhow!("mismatched number of fields and arguments"), span));
        }

        Ok(args)
    }

    pub fn iter(&self) -> Iter {
        match self {
            Self::Unit => Iter::Unit,
            Self::Unnamed(u) => Iter::Unnamed(u.iter()),
            Self::Named(n) => Iter::Named(n.iter()),
        }
    }
}

pub enum Iter<'a> {
    Unit,
    Unnamed(std::slice::Iter<'a, FieldInfo<'a>>),
    Named(std::slice::Iter<'a, (Ident, FieldInfo<'a>)>),
}

impl<'a> Iterator for Iter<'a> {
    type Item = &'a FieldInfo<'a>;

    #[inline]
    fn next(&mut self) -> Option<&'a FieldInfo<'a>> {
        match self {
            Self::Unit => None,
            Self::Unnamed(u) => u.next(),
            Self::Named(n) => n.next().map(|(_, f)| f),
        }
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        let len = match self {
            Self::Unit => 0,
            Self::Unnamed(u) => u.len(),
            Self::Named(n) => n.len(),
        };

        (len, Some(len))
    }
}

impl<'a> ExactSizeIterator for Iter<'a> {}