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<AutoGeneratedStruct>,
}
#[derive(Clone, Deserialize, Serialize, Gtmpl)]
pub struct AutoGeneratedStruct {
pub struct_name: String,
pub docstring: Option<String>,
pub fields: Vec<Field>,
pub supertype: String,
pub struct_name_snake: Option<String>,
}
#[derive(Clone, Deserialize, Serialize, Gtmpl)]
pub struct Field {
pub name: String,
pub comment: Option<String>,
pub data_type: String,
pub rename: Option<String>,
}
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<bool>,
pub time_series_compression_enabled: Option<bool>,
//pub time_series_params: Option<TimeSeriesParameters>,
{{- range $st := .auto_generated_structs }}
pub {{ .struct_name_snake }}_vec: Vec<{{ .struct_name }}>,
{{- end }}
//pub dynamic_generator_vec: Vec<DynamicGenerator>,
}
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<String, String> = 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<f64>".to_string());
dt_map.insert("Vector{Float64}".to_string(), "Vec<f64>".to_string());
dt_map.insert(
"Vector{Tuple{Float64,Float64}}".to_string(),
"Vec<(f64,f64)>".to_string(),
);
dt_map.insert("Vector{Symbol}".to_string(), "Vec<String>".to_string());
dt_map.insert("Vector{Service}".to_string(), "Vec<Service>".to_string());
dt_map.insert(
"Vector{StateTypes}".to_string(),
"Vec<StateTypes>".to_string(),
);
dt_map.insert(
"Union{Nothing, Min_Max}".to_string(),
"Option<MinMax<f64>>".to_string(),
);
dt_map.insert(
"Union{Nothing, Float64}".to_string(),
"Option<f64>".to_string(),
);
dt_map.insert(
"Union{Nothing, Area}".to_string(),
"Option<UUID>".to_string(),
);
dt_map.insert(
"Union{Nothing, LoadZone}".to_string(),
"Option<UUID>".to_string(),
);
dt_map.insert(
"Union{Nothing, OperationalCost}".to_string(),
"Option<UUID>".to_string(),
);
dt_map.insert(
"Union{Nothing, DynamicInjection}".to_string(),
"Option<UUID>".to_string(),
);
dt_map.insert(
"Union{Nothing, IS.TimeSeriesKey}".to_string(),
"Option<TimeSeriesKey>".to_string(),
);
dt_map.insert(
"Union{Nothing, BusTypes}".to_string(),
"Option<BusTypes>".to_string(),
);
dt_map.insert(
"Union{Nothing, LoadModels}".to_string(),
"Option<LoadModels>".to_string(),
);
dt_map.insert(
"Union{Nothing, NamedTuple{(:min, :max), Tuple{Float64, Float64}}}".to_string(),
"Option<MinMax<f64>>".to_string(),
);
dt_map.insert(
"Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}".to_string(),
"Option<UpDown<f64>>".to_string(),
);
dt_map.insert(
"Union{Nothing, NamedTuple{(:startup, :shutdown), Tuple{Float64, Float64}}}".to_string(),
"Option<StartupShutdown<f64>>".to_string(),
);
dt_map.insert(
"Union{Nothing, NamedTuple{(:hot, :warm, :cold), Tuple{Float64, Float64, Float64}}}"
.to_string(),
"Option<HotWarmCold<f64>>".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<f64>".to_string(),
);
dt_map.insert(
"NamedTuple{(:from_to, :to_from), Tuple{Float64, Float64}}".to_string(),
"FlowLimit<f64>".to_string(),
);
dt_map.insert(
"NamedTuple{(:min, :max), Tuple{Float64, Float64}}".to_string(),
"MinMax<f64>".to_string(),
);
dt_map.insert(
"NamedTuple{(:in, :out), Tuple{Float64, Float64}}".to_string(),
"Efficiency<f64>".to_string(),
);
dt_map.insert(
"NamedTuple{(:up, :down), Tuple{Float64, Float64}}".to_string(),
"UpDown<f64>".to_string(),
);
dt_map.insert(
"NamedTuple{(:l0, :l1), Tuple{Float64, Float64}}".to_string(),
"L0L1<f64>".to_string(),
);
dt_map.insert(
"NamedTuple{(:hot, :warm, :cold), NTuple{3, Float64}}".to_string(),
"HotWarmCold<f64>".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,
})
}