csv-codegen 0.2.3

A Rust procedural macro that transforms CSV data into safe, zero-cost code. Generate match arms, loops, and nested queries directly from CSV files, ensuring type safety and deterministic code generation.
Documentation
use convert_case::Casing;
use proc_macro2::TokenStream;
use syn::__private::ToTokens;
use syn::{Ident, LitStr};

/// Expression selecting field from a data record, and translating converting it to an output form
#[derive(Clone, Debug, PartialEq)]
pub struct FieldExpression {
    // todo: some more complex expressions, for now - just select a column
    pub(crate) field: Ident,
    pub(crate) syntax_type: OutputFormat,
    pub(crate) prefix: String,
    pub(crate) suffix: String,
}

impl FieldExpression {
    #[cfg(test)]
    pub(crate) fn new(field: Ident) -> Self {
        Self {
            field,
            syntax_type: OutputFormat::LitOrIdent,
            prefix: "".to_string(),
            suffix: "".to_string(),
        }
    }

    /// If the expression need to know about neighbour values to produce its output
    pub fn require_neighbours(&self) -> bool {
        false
    }
    /// Write the value to it's output representation
    pub fn write_output(&self, value: &SelectedValue) -> syn::Result<TokenStream> {
        let output = match self.syntax_type {
            OutputFormat::IdentConverted(case) => {
                let string = format!("{}{}{}", self.prefix, value.0.to_case(case), self.suffix);
                // an empty string will never be a valid ident
                let string = if string.is_empty() {
                    "none".to_string()
                } else {
                    string
                };
                let mut ident = syn::parse_str::<syn::Ident>(&string)
                    .map_err(|err| syn::Error::new(self.field.span(), err))?;
                ident.set_span(self.field.span());
                ident.to_token_stream()
            }
            OutputFormat::LitStr => {
                let string = format!("{}{}{}", self.prefix, value.0, self.suffix);
                LitStr::new(&string, self.field.span()).to_token_stream()
            }
            OutputFormat::LitOrIdent => {
                let string = format!("{}{}{}", self.prefix, value.0, self.suffix);
                if let Ok(mut lit) = syn::parse_str::<syn::Lit>(&string) {
                    lit.set_span(self.field.span());
                    lit.to_token_stream()
                } else if let Ok(mut ident) = syn::parse_str::<syn::Ident>(&string) {
                    ident.set_span(self.field.span());
                    ident.to_token_stream()
                } else {
                    // just token stream it
                    syn::parse_str(&string)
                        .map_err(|err| syn::Error::new(self.field.span(), err))?
                }
            }
        };
        Ok(output)
    }
    // /// Write the value to it's output representation using neighbours as context
    // /// eg. to generate a numeric range pattern to match the nearest, it would generate a range from halfway between prev and value to halfway between next and value
    // pub fn write_output_with_neighbours(
    //     &self,
    //     prev: Option<SelectedValue>,
    //     value: SelectedValue,
    //     next: Option<SelectedValue>,
    // ) -> TokenStream {
    //     todo!()
    // }

    pub(crate) fn field_ident(&self) -> &Ident {
        &self.field
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum OutputFormat {
    /// To be treated as an ident
    IdentConverted(convert_case::Case<'static>),
    /// To be treated as a string literal, ie. ensure surrounding with " and treat as a literal
    LitStr,
    LitOrIdent,
}

/// The Value extracted by an expression from a record, would probably usually just be the content of the field
/// I don't know what the type should be, maybe just even `&str`, but it needs to implement `Ord`, etc. at least for neighbours usage, but also for determinisitic output
#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Ord)]
pub struct SelectedValue(pub String);