use std::fs; use std::fs::File; use std::io::Write; use crate::{ArgumentJSON, ComponentJSON}; use std::path::PathBuf; fn stringify_argument((name, argument): (&String, &ArgumentJSON)) -> String { let mut response = format!("* `{}` - {}", name, argument.type_value.as_ref().unwrap_or(&"".to_string())); if let Some(description) = &argument.description { response.push_str(&format!(" - {}", description)); } response } fn doc(text: &Option, prefix: &str) -> String { match text { Some(text) => text.lines().map(|line| format!("{}// {}", prefix, line)) .collect::>().join("\n"), None => "".to_string() } } pub fn build_protobuf(components: &[ComponentJSON], output_path: PathBuf) { let proto_text_header = r#" // This file is automatically generated. Do not edit. Edit the component JSON instead. syntax = "proto3"; package whitenoise; import "value.proto"; message Component { ArgumentNodeIds arguments = 1; // if true, then don't include the evaluation for this component in the release bool omit = 2; // for interactive analyses uint32 submission = 3; oneof variant { "#.to_string(); let proto_text_variants = components.iter() .map(|component| format!(" {} {} = {};", component.id, component.name, component.proto_id + 100)) .collect::>().join("\n"); let proto_text_messages = components.iter() .map(|component| { println!("{:?}", component); // code gen for options let text_options = component.options.iter().enumerate().map(|(id, (name, spec))| { format!("{}\n {} {} = {};", doc(&spec.description, " "), spec.type_proto.clone().unwrap(), name, id + 1) }).collect::>().join("\n"); let mut component_description = format!("{} Component", component.id); if let Some(description) = component.description.clone() { component_description.push_str(&format!("\n\n{}", description)); } component_description.push_str(&format!("\n\nThis struct represents an abstract computation. Arguments are provided via the graph. Additional options are set via the fields on this struct. The return is the result of the {} on the arguments.", component.name)); let component_arguments = if component.arguments.is_empty() { "".to_string() } else { format!("\n\n# Arguments\n{}", component.arguments.iter() .map(stringify_argument) .collect::>().join("\n")) }; // options are already listed once under the struct fields // let component_options = match component.options.is_empty() { // true => "".to_string(), // false => format!("\n\n# Options\n{}", component.options.iter() // .map(stringify_argument) // .collect::>().join("\n")) // }; let component_returns = format!("\n\n# Returns\n{}", stringify_argument((&"Value".to_string(), &component.arg_return))); let text_component_header = doc(&Some(vec![component_description, component_arguments, component_returns].concat()), ""); format!("{}\nmessage {} {{\n{}\n}}", // code gen for the header text_component_header, component.id, text_options) }) .collect::>().join("\n\n"); let proto_text = format!("{}\n{}\n }}\n}}\n\n{}", proto_text_header, proto_text_variants, proto_text_messages); // overwrite/remove the components.proto file { fs::remove_file(output_path.clone()).ok(); let mut file = File::create(output_path).unwrap(); file.write_all(proto_text.as_bytes()) .expect("Unable to write components.proto file."); file.flush().unwrap(); } }