csv_template!() { /* proc-macro */ }Expand description
Generates Rust code from CSV data using a templating syntax.
This macro reads a CSV file at compile time and generates code by substituting CSV field values into a template. It supports filtering, pivoting, and various transformations to convert CSV data into valid Rust identifiers, literals, and other tokens.
§Syntax
csv_codegen::csv_template!(
"path/to/file.csv",
[pivot(column_range, key_column, value_column),]
{
template_code
}
)§Arguments
- CSV path: Relative path to the file the macro is invoked from, like
include!() - pivot() (optional): Transforms specified columns into key-value pairs
column_range: Range of columns to pivot (e.g.,5..=9,"column_a"..)key_column: Name for the generated key fieldvalue_column: Name for the generated value field
- Template body : Direct code generation without wrapper (top-level only)
- #each() : Iterates over rows, optionally filtering which rows are included
- Can be used at any level with optional conditions
- Condition uses CSV field names and supports
==,!=comparisons
- #find(condition) : Finds exactly one matching row, with optional #else fallback
- Always requires a condition to specify which row to find
- Must match exactly one row, compilation error otherwise
§Field Substitution
Fields from the CSV can be substituted into the template using several syntaxes:
§Identifier transformations
#ident(expression)- Converts to a valid Rust identifier- Removes spaces, special characters, converts to snake_case
- Example: “Green Apple” →
#ident(get_{field_name}_value)→get_green_apple_value
#CONST(expression)- Converts to a valid Rust constant identifier (SCREAMING_SNAKE_CASE)#Type(expression)- Converts to a valid Rust type identifier (PascalCase)
§Literal formatting
#({field_name}_suffix)- Appends suffix to create typed literals#{price}_f64converts “42” to42_f64(float literal)#{count}_u32converts “10” to10_u32(unsigned integer literal)
#("{field_name}")- Format as literal string
§Repetition and Filtering
Use #each(condition) { template_code } to repeat template code for each matching row:
#each(status == "active") {
const #CONST({name}): u32 = #({value});
}The condition can reference any CSV field and supports:
==equality comparison!=inequality comparison- String comparisons
§Pivoting
The pivot() modifier transforms multiple columns into key-value pairs:
let product = "Laptop Stand";
let quarter = "q3_sales";
let sales = csv_codegen::csv_template!("../tests/sales.csv", pivot("q1_sales"..="q4_sales", quarter, amount), {
match (product, quarter) {
#each{
// Each row becomes multiple rows with metric/amount pairs
(#("{product}"), #("{quarter}")) => #({amount}),
}
_ => panic!(),
}
});
assert_eq!(sales, 320);Given CSV columns [name, age, height_cm, weight_kg, score_math, score_english],
pivot("height_cm"..="score_english", subject, value) would create pairs like:
subject="height_cm", value="175"subject="weight_kg", value="70"subject="score_math", value="95"subject="score_english", value="88"
§Examples
§Basic code generation
// CSV: name,price,category
// apple,1,fruit
// carrot,0.80,vegetable
csv_codegen::csv_template!("../tests/products.csv", #each {
pub const #CONST({name}_PRICE): f64 = #({price}_f64);
});
assert_eq!(WIRELESS_HEADPHONES_PRICE, 89.99);Generates:
pub const APPLE_PRICE: f64 = 1_f64;
pub const CARROT_PRICE: f64 = 0.80_f64;§Function generation with filtering
csv_codegen::csv_template!("../tests/products.csv", {
#each(category == "fruit"){
pub fn #ident(get_{name}_price)() -> f64 {
#({price}_f64)
}
}
})Generates:
pub fn get_apple_price() -> f64 {
1.20_f64
}§Match arms with nested filtering
csv_codegen::csv_template!("../tests/products.csv", {
fn get_price(name: &str) -> Option<f64> {
match name {
#each(price != ""){
#("{name}") => Some(#({price}_f64)),
}
_ => None,
}
}
});
assert_eq!(get_price("Ergonomic Chair").unwrap(), 399.99);§Pivoting example
// CSV: product,q1_sales,q2_sales,q3_sales,q4_sales
// widget,100,150,120,200
csv_codegen::csv_template!("../tests/sales.csv", pivot("q1_sales"..="q4_sales", quarter, sales), {
#each{
struct #Type({product}Product);
impl #Type({product}Product) {
#each{
pub const #CONST({quarter}): u32 = #({sales}_u32);
}
}
}
});
assert_eq!(SmartWatchProduct::Q_2_SALES, 180);§Notes
- CSV files are read at compile time - changes require recompilation
- Field names are derived from CSV headers
- Empty cells are treated as empty strings