Skip to content

Commit

Permalink
Perform interpolation on template parameters.
Browse files Browse the repository at this point in the history
  • Loading branch information
jimmycuadra committed Feb 25, 2016
1 parent 9df3887 commit 1c9a8a0
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 59 deletions.
49 changes: 49 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ version = "0.1.0"

[dependencies]
clap = "2.1.1"
lazy_static = "0.1.15"
regex = "0.1.54"
yaml-rust = "0.3.0"
2 changes: 1 addition & 1 deletion example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ parameters:
required: true
- name: "MONGODB_USER"
description: "Username for MongoDB user that will be used for accessing the database"
value: "password"
value: "username"
required: true
- name: "MONGODB_PASSWORD"
description: "Password for the MongoDB user"
Expand Down
18 changes: 11 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
extern crate clap;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate yaml_rust as yaml;

use std::collections::HashMap;
Expand All @@ -9,7 +12,7 @@ use std::process::exit;

use clap::{App, AppSettings, Arg};

use parameter::param_map;
use parameter::user_values;
use template::Template;

mod parameter;
Expand Down Expand Up @@ -46,18 +49,19 @@ fn real_main() -> Result<(), String> {
)
.get_matches();

let user_values = match matches.values_of("parameter") {
Some(parameters) => try!(user_values(parameters.map(|s| s.to_string()).collect())),
None => HashMap::new(),
};

let filename = matches.value_of("template").expect("template wasn't provided");
let mut file = try!(File::open(filename).map_err(|err| err.description().to_owned()));
let mut template_data = String::new();
try!(file.read_to_string(&mut template_data).map_err(|err| err.description().to_owned()));

let template = try!(Template::from_string(template_data));
let parameters = match matches.values_of("parameter") {
Some(parameters) => try!(param_map(parameters.map(|s| s.to_string()).collect())),
None => HashMap::new(),
};
let template = try!(Template::new(template_data, user_values));

match template.process(parameters) {
match template.process() {
Ok(manifests) => {
println!("{}", manifests);

Expand Down
43 changes: 25 additions & 18 deletions src/parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub struct Parameter {
pub name: String,
pub parameter_type: Option<ParameterType>,
pub required: bool,
pub value: Option<ParameterValue>,
pub value: Option<String>,
}

pub enum ParameterType {
Expand All @@ -18,16 +18,11 @@ pub enum ParameterType {
String,
}

pub enum ParameterValue {
Bool(bool),
Int(i64),
String(String),
}

pub type ParamMap = HashMap<String, String>;
pub type ParamMap = HashMap<String, Parameter>;
pub type UserValues = HashMap<String, String>;

impl Parameter {
pub fn from_yaml(yaml: &Yaml) -> Result<Self, String> {
pub fn new(yaml: &Yaml, user_values: &UserValues) -> Result<Self, String> {
let description = match yaml["description"] {
Yaml::String(ref description) => Some(description.clone()),
_ => None,
Expand All @@ -45,11 +40,23 @@ impl Parameter {
None => None,
};
let required = yaml["required"].as_bool().unwrap_or(false);
let value = match yaml["value"] {
Yaml::Boolean(ref value) => Some(ParameterValue::Bool(value.clone())),
Yaml::Integer(ref value) => Some(ParameterValue::Int(value.clone())),
Yaml::String(ref value) => Some(ParameterValue::String(value.clone())),
_ => return Err("Parameter values must be boolean, i64, or string.".to_owned()),
let value = match user_values.get(&name) {
Some(value) => Some(value.clone()),
None => match yaml["value"] {
Yaml::Boolean(ref value) => Some(format!("{}", value)),
Yaml::Integer(ref value) => Some(format!("{}", value)),
Yaml::String(ref value) => Some(value.clone()),
_ => if required {
return Err(
format!(
"Parameter {} required and must be boolean, i64, or string.",
display_name.unwrap_or(name),
)
)
} else {
None
},
}
};

Ok(Parameter {
Expand Down Expand Up @@ -77,18 +84,18 @@ impl FromStr for ParameterType {
}
}

pub fn param_map(parameters: Vec<String>) -> Result<ParamMap, String> {
let mut param_map = HashMap::new();
pub fn user_values(parameters: Vec<String>) -> Result<UserValues, String> {
let mut user_values = HashMap::new();

for parameter in parameters {
let mut parts: Vec<String> = parameter.split('=').map(|s| s.to_string()).collect();

if parts.len() == 2 {
param_map.insert(parts.remove(0), parts.remove(0));
user_values.insert(parts.remove(0), parts.remove(0));
} else {
return Err("Parameters must be supplied in the form KEY=VALUE.".to_string());
}
}

Ok(param_map)
Ok(user_values)
}
66 changes: 53 additions & 13 deletions src/processor.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,79 @@
use yaml::Yaml;
use yaml::yaml::{Array, Hash};
use regex::{Captures, Regex};

use parameter::ParamMap;

pub type ProcessorResult = Result<(), String>;
pub type ProcessorResult = Result<Option<Yaml>, String>;

pub fn process_yaml(yaml: &mut Yaml, parameters: &ParamMap) -> ProcessorResult {
match yaml {
&mut Yaml::Array(ref mut array) => try!(process_array(array, parameters)),
&mut Yaml::Hash(ref mut hash) => try!(process_hash(hash, parameters)),
&mut Yaml::String(ref mut string) => try!(process_string(string, parameters)),
_ => {},
&mut Yaml::Array(ref mut array) => process_array(array, parameters),
&mut Yaml::Hash(ref mut hash) => process_hash(hash, parameters),
&mut Yaml::String(ref mut string) => process_string(string, parameters),
_ => Ok(None),
}

Ok(())
}

fn process_array(array: &mut Array, parameters: &ParamMap) -> ProcessorResult {
for value in array {
try!(process_yaml(value, parameters));
match process_yaml(value, parameters) {
Ok(Some(new_value)) => *value = new_value,
Err(error) => return Err(error),
_ => {},
}
}

Ok(())
Ok(None)
}

fn process_hash(hash: &mut Hash, parameters: &ParamMap) -> ProcessorResult {
for (_, value) in hash {
try!(process_yaml(value, parameters));
match process_yaml(value, parameters) {
Ok(Some(new_value)) => *value = new_value,
Err(error) => return Err(error),
_ => {},
}
}

Ok(())
Ok(None)
}

fn process_string(string: &mut String, parameters: &ParamMap) -> ProcessorResult {
// TODO: Interpolate any parameters.
lazy_static! {
static ref LITERAL_INTERPOLATION: Regex = Regex::new(
r"\$\({2}(.*)\){2}"
).expect("Failed to compile regex.");
}

lazy_static! {
static ref STRING_INTERPOLATION: Regex = Regex::new(
r"\$\((.*)\)"
).expect("Failed to compile regex.");
}

let interpolate = |captures: &Captures| -> String {
let key = captures.at(1).expect("Failed to extract regex capture group.");

match parameters.get(key) {
Some(parameter) => parameter.value.clone().unwrap_or("~".to_owned()),
None => captures.at(0).expect("Failed to extract regex match.").to_owned(),
}
};

Ok(())
let replacement = LITERAL_INTERPOLATION.replace_all(string, &interpolate);

let contains_literal_replacement = &replacement != string;

let final_replacement = STRING_INTERPOLATION.replace_all(&replacement, &interpolate);

let contains_string_replacement = &final_replacement != &replacement;

if !contains_literal_replacement && !contains_string_replacement {
Ok(None)
} else if contains_literal_replacement && !contains_string_replacement {
Ok(Some(Yaml::from_str(&final_replacement)))
} else {
Ok(Some(Yaml::String(final_replacement)))
}
}
48 changes: 28 additions & 20 deletions src/template.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
use std::collections::HashMap;
use std::error::Error;

use yaml::{EmitError, Yaml, YamlEmitter, YamlLoader};

use parameter::Parameter;
use parameter::{ParamMap, Parameter, UserValues};
use processor::process_yaml;

pub struct Template {
pub objects: Vec<Yaml>,
pub parameters: Vec<Parameter>,
pub param_map: ParamMap,
}

impl Template {
pub fn from_string(string: String) -> Result<Self, String> {
pub fn new(string: String, user_values: UserValues) -> Result<Self, String> {
let docs = try!(YamlLoader::load_from_str(&string).map_err(|err| err.description().to_owned()));

if docs.len() != 1 {
Expand All @@ -31,25 +30,27 @@ impl Template {
template_objects.push(object.clone());
}

let mut template_parameters = vec![];
let parameters = match doc["parameters"].as_vec() {
Some(parameters) => parameters,
let mut param_map = ParamMap::new();
let parameter_specs = match doc["parameters"].as_vec() {
Some(parameter_specs) => parameter_specs,
None => return Err("Key \"parameters\" must be present and must be an array.".to_owned())
};

for parameter in parameters {
template_parameters.push(try!(Parameter::from_yaml(parameter)));
for parameter_spec in parameter_specs {
let parameter = try!(Parameter::new(parameter_spec, &user_values));

param_map.insert(parameter.name.clone(), parameter);
}

Ok(Template {
objects: template_objects,
parameters: template_parameters,
param_map: param_map,
})
}

pub fn process(mut self, parameters: HashMap<String, String>) -> Result<String, String> {
pub fn process(mut self) -> Result<String, String> {
for object in self.objects.iter_mut() {
try!(process_yaml(object, &parameters));
try!(process_yaml(object, &self.param_map));
}

dump(self.objects)
Expand All @@ -58,15 +59,22 @@ impl Template {

fn dump(objects: Vec<Yaml>) -> Result<String, String> {
let mut manifests = String::new();
let last = objects.len() - 1;

for (i, object) in objects.iter().enumerate() {
{
let mut emitter = YamlEmitter::new(&mut manifests);
try!(emitter.dump(&object).map_err(|error| {
match error {
EmitError::FmtError(error) => format!("{}", error),
EmitError::BadHashmapKey => "Bad hashmap key in YAML structure.".to_owned(),
}
}));
}

for object in objects.iter() {
let mut emitter = YamlEmitter::new(&mut manifests);
try!(emitter.dump(&object).map_err(|error| {
match error {
EmitError::FmtError(error) => format!("{}", error),
EmitError::BadHashmapKey => "Bad hashmap key in YAML structure.".to_owned(),
}
}));
if i != last {
manifests.push_str("\n");
}
}

Ok(manifests)
Expand Down

0 comments on commit 1c9a8a0

Please sign in to comment.