use convert_case::{Case, Casing}; use gtmpl_derive::Gtmpl; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; use std::io::Write; use std::path::PathBuf; #[derive(Deserialize, Serialize, Gtmpl)] pub struct PowerSystemStructs { pub auto_generated_structs: Vec, } #[derive(Clone, Deserialize, Serialize, Gtmpl)] pub struct AutoGeneratedStruct { pub struct_name: String, pub docstring: Option, pub fields: Vec, pub supertype: String, pub struct_name_snake: Option, } #[derive(Clone, Deserialize, Serialize, Gtmpl)] pub struct Field { pub name: String, pub comment: Option, // pub null_value: Option, pub data_type: String, pub rename: Option, } const TEMPLATE: &str = r#" //use crate::common::*; use serde::{Deserialize, Serialize}; {{ range $st := .auto_generated_structs }} {{ with .docstring }}/// {{.}}{{ end }} #[derive(Clone, Deserialize, Serialize, Default, PartialEq, Debug)] pub struct {{ .struct_name }} { #[serde(rename = "__metadata__")] pub metadata: Metadata, {{- range $fld := .fields }} {{- with .comment }} /// {{.}} {{- end}} {{- with .rename }} #[serde(rename = "{{.}}")] {{- end}} pub {{ .name }}: {{ .data_type }}, {{- end }} } impl {{ .struct_name }} { pub const SUPER_TYPE: &'static str = "{{ .supertype }}"; } {{ end }} #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] pub enum ComponentType { {{- range $st := .auto_generated_structs }} {{ .struct_name }}, {{- end }} //DynamicGenerator, } // #[derive(Deserialize, Serialize, Default)] #[derive(Serialize, Default)] pub struct SystemData { pub time_series_in_memory: Option, pub time_series_compression_enabled: Option, //pub time_series_params: Option, {{- range $st := .auto_generated_structs }} pub {{ .struct_name_snake }}_vec: Vec<{{ .struct_name }}>, {{- end }} //pub dynamic_generator_vec: Vec, } pub fn push_component(data: &mut SystemData, kind: ComponentType, value: serde_json::Value) { match kind { {{- range $st := .auto_generated_structs }} ComponentType::{{ .struct_name }} => data.{{ .struct_name_snake }}_vec.push({{ .struct_name }}::deserialize(value).unwrap()), {{- end }} //ComponentType::DynamicGenerator => data.dynamic_generator_vec.push(DynamicGenerator::deserialize(value).unwrap()), } }"#; fn generate_structs() -> Result<(), String> { let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); d.push("descriptors/power_system_structs.json"); let mut dt_map: HashMap = HashMap::new(); dt_map.insert("Int".to_string(), "usize".to_string()); dt_map.insert("Float64".to_string(), "f64".to_string()); dt_map.insert("String".to_string(), "String".to_string()); dt_map.insert("Bool".to_string(), "bool".to_string()); dt_map.insert("Complex{Float64}".to_string(), "Cmplx64".to_string()); dt_map.insert( "VariableCost".to_string(), "VariableCost<(f64,f64)>".to_string(), ); dt_map.insert( "Dict{String, Any}".to_string(), "serde_json::Value".to_string(), ); dt_map.insert("Array{Float64,2}".to_string(), "[f64; 2]".to_string()); dt_map.insert("Vector{Float64}".to_string(), "Vec".to_string()); dt_map.insert("Vector{Float64}".to_string(), "Vec".to_string()); dt_map.insert( "Vector{Tuple{Float64,Float64}}".to_string(), "Vec<(f64,f64)>".to_string(), ); dt_map.insert("Vector{Symbol}".to_string(), "Vec".to_string()); dt_map.insert("Vector{Service}".to_string(), "Vec".to_string()); dt_map.insert( "Vector{StateTypes}".to_string(), "Vec".to_string(), ); dt_map.insert( "Union{Nothing, Min_Max}".to_string(), "Option>".to_string(), ); dt_map.insert( "Union{Nothing, Float64}".to_string(), "Option".to_string(), ); dt_map.insert( "Union{Nothing, Area}".to_string(), "Option".to_string(), ); dt_map.insert( "Union{Nothing, LoadZone}".to_string(), "Option".to_string(), ); dt_map.insert( "Union{Nothing, OperationalCost}".to_string(), "Option".to_string(), ); dt_map.insert( "Union{Nothing, DynamicInjection}".to_string(), "Option".to_string(), ); dt_map.insert( "Union{Nothing, IS.TimeSeriesKey}".to_string(), "Option".to_string(), ); dt_map.insert( "Union{Nothing, BusTypes}".to_string(), "Option".to_string(), ); dt_map.insert( "Union{Nothing, LoadModels}".to_string(), "Option".to_string(), ); dt_map.insert( "Union{Nothing, NamedTuple{(:min, :max), Tuple{Float64, Float64}}}".to_string(), "Option>".to_string(), ); dt_map.insert( "Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}".to_string(), "Option>".to_string(), ); dt_map.insert( "Union{Nothing, NamedTuple{(:startup, :shutdown), Tuple{Float64, Float64}}}".to_string(), "Option>".to_string(), ); dt_map.insert( "Union{Nothing, NamedTuple{(:hot, :warm, :cold), Tuple{Float64, Float64, Float64}}}" .to_string(), "Option>".to_string(), ); dt_map.insert( "Tuple{Float64, Float64}".to_string(), "(f64,f64)".to_string(), ); dt_map.insert( "NamedTuple{(:from, :to), Tuple{Float64, Float64}}".to_string(), "FromTo".to_string(), ); dt_map.insert( "NamedTuple{(:from_to, :to_from), Tuple{Float64, Float64}}".to_string(), "FlowLimit".to_string(), ); dt_map.insert( "NamedTuple{(:min, :max), Tuple{Float64, Float64}}".to_string(), "MinMax".to_string(), ); dt_map.insert( "NamedTuple{(:in, :out), Tuple{Float64, Float64}}".to_string(), "Efficiency".to_string(), ); dt_map.insert( "NamedTuple{(:up, :down), Tuple{Float64, Float64}}".to_string(), "UpDown".to_string(), ); dt_map.insert( "NamedTuple{(:l0, :l1), Tuple{Float64, Float64}}".to_string(), "L0L1".to_string(), ); dt_map.insert( "NamedTuple{(:hot, :warm, :cold), NTuple{3, Float64}}".to_string(), "HotWarmCold".to_string(), ); dt_map.insert( "InfrastructureSystems.TimeSeriesContainer".to_string(), "TimeSeriesContainer".to_string(), ); dt_map.insert("Bus".to_string(), "UUID".to_string()); dt_map.insert("Arc".to_string(), "UUID".to_string()); let file = fs::File::open(d).unwrap(); let mut st: PowerSystemStructs = serde_json::from_reader(file).expect("file should be JSON"); for ags in st.auto_generated_structs.iter_mut() { if let Some(docstring) = ags.docstring.as_ref() { ags.docstring = Some(docstring.replace("\n ", "\n/// ").replace("\n", "\n/// ")); } ags.struct_name_snake = Some(ags.struct_name.to_case(Case::Snake)); for fld in ags.fields.iter_mut() { let lower = fld.name.to_lowercase(); if fld.name != lower { fld.rename = Some(fld.name.to_string()); fld.name = fld.name.to_lowercase(); } if ags.struct_name == "HydroTurbineGov" && fld.name == "r" && fld.rename.is_some() { fld.name = "droop".to_string(); } if dt_map.contains_key(&fld.data_type.to_string()) { fld.data_type = dt_map.get(&fld.data_type).unwrap().to_string(); } if ags.struct_name == "GeneralGovModel" && fld.name == "rselect" && fld.rename.is_some() { fld.data_type = "isize".to_string(); } if let Some(comment) = fld.comment.as_ref() { fld.comment = Some(comment.replace("\n", "\n /// ")); } } } let output = gtmpl::template(TEMPLATE, st); let mut d = PathBuf::from(std::env::var("OUT_DIR").unwrap()); d.push("generated.rs"); let mut file = fs::File::create(d).unwrap(); if let Err(err) = file.write(output.unwrap().as_bytes()) { return Err(err.to_string()); } Ok(()) } fn main() { std::process::exit(match generate_structs() { Err(err) => { eprintln!("error: {:?}", err); 1 } Ok(_) => 0, }) }