vite-static-derive 0.5.0

Derive macro support for `vite-static`
Documentation
use std::env;

use anyhow::Context;
use proc_macro2::Span;
use syn::{DeriveInput, Error, Expr, Lit, Meta, spanned::Spanned};

pub struct ManifestSettings {
    pub vite_dist: (String, Span),
    pub no_embedding: bool,
}

enum MacroOption {
    ViteDist((String, Span)),
    NoEmbedding,
}

fn resolve_env(value: String) -> anyhow::Result<String> {
    if value.starts_with("env:") {
        env::var(value.trim_start_matches("env:")).context("failed to get environment variable")
    } else {
        Ok(value)
    }
}

fn parse_macro_option(meta: &Meta) -> syn::Result<MacroOption> {
    #[allow(clippy::collapsible_match)] // remove when more options are added
    match meta {
        Meta::NameValue(option) => {
            if option.path.is_ident("vite_dist") {
                let Expr::Lit(expr_lit) = &option.value else {
                    return Err(Error::new(option.value.span(), "expected string literal"));
                };

                let Lit::Str(value) = &expr_lit.lit else {
                    return Err(Error::new(option.value.span(), "expected string literal"));
                };

                let value_str = resolve_env(value.value())
                    .map_err(|err| Error::new(value.span(), format!("{err:#}")))?;

                return Ok(MacroOption::ViteDist((value_str, value.span())));
            }
        }
        Meta::Path(path) => {
            if path.is_ident("no_embedding") {
                return Ok(MacroOption::NoEmbedding);
            }
        }
        _ => {}
    }

    Err(Error::new(
        meta.path().span(),
        "expected Manifest macro option (available: vite_dist = \"...\", no_embedding)",
    ))
}

pub fn parse_manifest_input(input: &DeriveInput) -> syn::Result<ManifestSettings> {
    let mut vite_dist = ("./dist".to_string(), input.span());
    let mut no_embedding = false;

    for attr in &input.attrs {
        let attr = parse_macro_option(&attr.meta)?;

        match attr {
            MacroOption::ViteDist(value) => vite_dist = value,
            MacroOption::NoEmbedding => no_embedding = true,
        }
    }

    Ok(ManifestSettings {
        vite_dist,
        no_embedding,
    })
}