From 484e9404bd832e50b094da66a8df048313d0c24c Mon Sep 17 00:00:00 2001 From: Sean Myers Date: Sat, 2 Oct 2021 14:29:42 -0700 Subject: [PATCH] Add full builder (#11) * Add full builder First bit for all resource output, imports, and fix mappings in the output. --- Cargo.toml | 3 ++- src/main.rs | 19 ++++----------- src/parser/condition.rs | 10 ++------ src/parser/lookup_table.rs | 43 +++++++++++++++++--------------- src/semantic/importer.rs | 50 ++++++++++++++++++++++++++++++++++++++ src/semantic/mod.rs | 43 ++++++++++++++++++++++++++++---- 6 files changed, 121 insertions(+), 47 deletions(-) create mode 100644 src/semantic/importer.rs diff --git a/Cargo.toml b/Cargo.toml index 41cf9120..6ca78d20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,5 @@ edition = "2018" clap = "3.0.0-beta.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -nom = "7.0.0" \ No newline at end of file +nom = "7.0.0" +voca_rs = "1.14.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8a6d2386..581bd723 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,11 @@ use clap::{App, Arg}; +use noctilucent::semantic::importer::Importer; use noctilucent::semantic::reference::ReferenceTable; use noctilucent::semantic::to_string; use noctilucent::CloudformationParseTree; use serde_json::Value; use std::fs; +use voca_rs::case::camel_case; fn main() { let matches = App::new("Transmutes cfn templates to cdk") @@ -25,22 +27,11 @@ fn main() { let cfn_tree = CloudformationParseTree::build(&value).unwrap(); let reference_table = ReferenceTable::new(&cfn_tree); - println!("Amount of parameters: {}", cfn_tree.parameters.params.len()); - println!("Amount of mappings: {}", cfn_tree.mappings.mappings.len()); + let import = Importer::new(&cfn_tree); - println!( - "Amount of conditions: {}", - cfn_tree.conditions.conditions.len() - ); - println!( - "Amount of resources: {}", - cfn_tree.resources.resources.len() - ); - - println!("===================================="); + println!("{}", import.synthesize().join("\n")); println!("{}", cfn_tree.mappings.synthesize()); - println!("===================================="); for (_, cond) in cfn_tree.conditions.conditions.iter() { println!("{}", cond.synthesize()); } @@ -58,7 +49,7 @@ fn main() { match to_string(prop, &reference_table) { None => {} Some(x) => { - println!("\t{}:{},", name, x); + println!("\t{}:{},", camel_case(name), x); } } } diff --git a/src/parser/condition.rs b/src/parser/condition.rs index d303256b..f6bade72 100644 --- a/src/parser/condition.rs +++ b/src/parser/condition.rs @@ -45,10 +45,7 @@ impl ConditionParseTree { fn synthesize_condition_recursive(val: &ConditionValue) -> String { match val { ConditionValue::And(x) => { - let a: Vec = x - .iter() - .map(|x| synthesize_condition_recursive(x)) - .collect(); + let a: Vec = x.iter().map(synthesize_condition_recursive).collect(); let inner = a.join(" && "); format!("({})", inner) @@ -68,10 +65,7 @@ fn synthesize_condition_recursive(val: &ConditionValue) -> String { } } ConditionValue::Or(x) => { - let a: Vec = x - .iter() - .map(|x| synthesize_condition_recursive(x)) - .collect(); + let a: Vec = x.iter().map(synthesize_condition_recursive).collect(); let inner = a.join(" || "); format!("({})", inner) diff --git a/src/parser/lookup_table.rs b/src/parser/lookup_table.rs index da58619f..b3d5c811 100644 --- a/src/parser/lookup_table.rs +++ b/src/parser/lookup_table.rs @@ -28,9 +28,14 @@ impl MappingsParseTree { pub fn synthesize(&self) -> String { let mut mappings_ts_str = String::new(); for (mapping_name, mapping) in self.mappings.iter() { + let record_type = match mapping.find_first_type() { + MappingInnerValue::String(_) => "Record>", + MappingInnerValue::List(_) => "Record>", + }; mappings_ts_str.push_str(&format!( - "const {} = {}", + "const {}: {} = {}", mapping_name, + record_type, mapping.synthesize() )); } @@ -59,40 +64,40 @@ impl MappingParseTree { } fn synthesize(&self) -> String { - let mut mapping_parse_tree_ts = String::from("new Map(\n"); + let mut mapping_parse_tree_ts = String::from("{\n"); + let mut outer_records = Vec::new(); for (outer_mapping_key, inner_mapping) in self.mappings.iter() { - mapping_parse_tree_ts.push_str(&format!( - "\t[{}\t],\n", - synthesize_outer_mapping(outer_mapping_key, inner_mapping) + outer_records.push(format!( + "\t\"{}\": {}", + outer_mapping_key, + synthesize_inner_mapping(inner_mapping) )); } - mapping_parse_tree_ts.push_str(")\n"); + + let outer = outer_records.join(",\n"); + mapping_parse_tree_ts.push_str(&outer); + mapping_parse_tree_ts.push_str("\n};\n"); mapping_parse_tree_ts } -} -fn synthesize_outer_mapping( - outer_mapping_entry: &str, - inner_mapping: &HashMap, -) -> String { - format!( - "\"{}\", {}", - outer_mapping_entry, - synthesize_inner_mapping(inner_mapping) - ) + fn find_first_type(&self) -> &MappingInnerValue { + let value = self.mappings.values().next().unwrap(); + let inner_value = value.values().next().unwrap(); + inner_value + } } fn synthesize_inner_mapping(inner_mapping: &HashMap) -> String { - let mut inner_mapping_ts_str = String::from("new Map(\n"); + let mut inner_mapping_ts_str = String::from("{\n"); let mut inner_mapping_entries = Vec::new(); for (inner_mapping_key, inner_mapping_value) in inner_mapping { inner_mapping_entries.push(format!( - "\t\t[\"{}\", {}]", + "\t\t\"{}\": {}", inner_mapping_key, inner_mapping_value )); } inner_mapping_ts_str.push_str(&inner_mapping_entries.join(",\n")); - inner_mapping_ts_str.push_str(")\n"); + inner_mapping_ts_str.push_str("\n\t}"); inner_mapping_ts_str } diff --git a/src/semantic/importer.rs b/src/semantic/importer.rs new file mode 100644 index 00000000..b2856dc5 --- /dev/null +++ b/src/semantic/importer.rs @@ -0,0 +1,50 @@ +use crate::CloudformationParseTree; +use std::collections::HashSet; + +pub struct Importer { + type_names: HashSet, +} + +impl Importer { + pub fn new(parse_tree: &CloudformationParseTree) -> Importer { + let mut type_names = HashSet::new(); + for resource in parse_tree.resources.resources.iter() { + let type_name = TypeName::new(&resource.resource_type); + type_names.insert(type_name); + } + + Importer { type_names } + } + + pub fn synthesize(&self) -> Vec { + self.type_names.iter().map(|x| x.synthesize()).collect() + } +} + +#[derive(PartialEq, PartialOrd, Clone, Debug, Eq, Hash)] +struct TypeName { + organization: String, + service: String, +} + +impl TypeName { + // In CloudFormation, typenames are always of the form `:::: + fn new(name: &str) -> TypeName { + let mut split_ref = name.split("::"); + + // These must always exist. + let organization = split_ref.next().unwrap().to_ascii_lowercase(); + let service = split_ref.next().unwrap().to_ascii_lowercase(); + + TypeName { + organization, + service, + } + } + fn synthesize(&self) -> String { + return format!( + "import * as {} from '@aws-cdk/{}-{}';", + self.service, self.organization, self.service + ); + } +} diff --git a/src/semantic/mod.rs b/src/semantic/mod.rs index 25aea680..3bb1e36f 100644 --- a/src/semantic/mod.rs +++ b/src/semantic/mod.rs @@ -1,7 +1,9 @@ use crate::parser::resource::ResourceValue; use crate::parser::sub::{sub_parse_tree, SubValue}; use crate::semantic::reference::ReferenceTable; +use std::collections::HashMap; +pub mod importer; pub mod reference; pub fn to_string(resource_value: &ResourceValue, ref_table: &ReferenceTable) -> Option { @@ -45,6 +47,26 @@ pub fn to_string(resource_value: &ResourceValue, ref_table: &ReferenceTable) -> // just resolve the objects. let val = to_string(&arr[0], ref_table).unwrap(); + let mut excess_map = HashMap::new(); + if arr.len() > 1 { + let mut iter = arr.iter(); + iter.next(); + + for obj in iter { + match obj { + ResourceValue::Object(obj) => { + for (key, val) in obj.iter() { + let val_str = to_string(val, ref_table).unwrap(); + excess_map.insert(key.to_string(), val_str); + } + } + _ => { + // these aren't possible, so panic + panic!("Isn't possible condition") + } + } + } + } let vars = sub_parse_tree(val.as_str()).unwrap(); let r: Vec = vars .iter() @@ -54,17 +76,28 @@ pub fn to_string(resource_value: &ResourceValue, ref_table: &ReferenceTable) -> "AWS::Region" => String::from("${this.region}"), "AWS::Partition" => String::from("${this.partition}"), "AWS::AccountId" => String::from("${this.account}"), - x => format!("${{props.{}}}", x), + x => match excess_map.get(x) { + None => { + format!("${{props.{}}}", x) + } + Some(x) => { + format!("${{{}}}", x) + } + }, }, }) .collect(); Option::Some(format!("`{}`", r.join(""))) } - ResourceValue::FindInMap(mapper, first, _second) => { - let mapper_str = to_string(mapper, ref_table).unwrap(); + ResourceValue::FindInMap(mapper, first, second) => { + let a: &ResourceValue = mapper.as_ref(); + let mapper_str = match a { + ResourceValue::String(x) => x.to_string(), + &_ => to_string(mapper, ref_table).unwrap(), + }; let first_str = to_string(first, ref_table).unwrap(); - let second_str = to_string(first, ref_table).unwrap(); + let second_str = to_string(second, ref_table).unwrap(); Option::Some(format!("{}[{}][{}]", mapper_str, first_str, second_str)) } @@ -78,7 +111,7 @@ pub fn to_string(resource_value: &ResourceValue, ref_table: &ReferenceTable) -> .unwrap(); let true_expr = to_string(true_expr, ref_table).unwrap(); let false_expr = match to_string(false_expr, ref_table) { - None => String::from("NOPE_WTF"), + None => String::from("{}"), Some(x) => x, };