use std::collections::HashMap;
use std::fmt;
use std::fs;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::process::Command;
use glob::glob;
use regex::Regex;
use yaml_rust::Yaml;
use yaml_rust::YamlLoader;
#[derive(Clone, Hash, Eq, PartialEq, Debug)]
enum ParameterValue {
NoValue,
IntegerValue(i64),
StringValue(String),
}
impl fmt::Display for ParameterValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ParameterValue::NoValue => write!(f, "NOVALUE"),
ParameterValue::IntegerValue(i) => write!(f, "{}", i),
ParameterValue::StringValue(s) => write!(f, "\"{}\"", s),
}
}
}
struct ToolRun {
tool: String,
command: String,
output: String,
input: Vec<String>,
parameters: HashMap<String, ParameterValue>,
cmd_line: String,
}
impl ToolRun {
fn new(
tool: &str,
command: &str,
output: &str,
input: &Vec<String>,
parameters: &HashMap<String, ParameterValue>,
cmd_line: &str,
) -> ToolRun {
ToolRun {
tool: tool.to_string(),
command: command.to_string(),
output: output.to_string(),
input: input.clone(),
parameters: parameters.clone(),
cmd_line: cmd_line.to_string(),
}
}
fn run(&self, asset_builder: &AssetBuilder) -> Result<u32, &'static str> {
let tool = self.tool.as_str();
match tool {
"" => Ok(0),
"noop" => {
println!("NOOP -> Do nothing");
Ok(0)
},
"$asset" => {
println!("$asset command found");
asset_builder.tool_asset(&self)
},
_tool => asset_builder.tool_call_external(&self),
}
}
}
pub struct AssetBuilder {
content_directory: String,
data_directory: String,
_temp_directory: String,
_archive: String,
_paklist: String,
dry_run: bool,
}
impl AssetBuilder {
pub fn new(
content_directory: &str,
data_directory: &str,
temp_directory: &str,
archive: &str,
paklist: &str,
dry_run: &bool,
) -> AssetBuilder {
AssetBuilder {
content_directory: content_directory.to_string(),
data_directory: data_directory.to_string(),
_temp_directory: temp_directory.to_string(),
_archive: archive.to_string(),
_paklist: paklist.to_string(),
dry_run: *dry_run,
}
}
fn tool_asset(&self, tool_run: &ToolRun) -> Result<u32, &'static str> {
match tool_run.command.as_ref() {
"" => {
println!("NO command for asset tool");
Err("NO command for asset tool")
},
"dump" => {
println!("command. : {:?}", tool_run.command);
println!("output : {:?}", tool_run.output);
println!("input : {:?}", tool_run.input);
println!("parameters: {:?}", tool_run.parameters);
Ok(0)
},
"copy" => {
let mut number_of_assets_updated = 0;
for source in &tool_run.input {
let source_filename = Path::new(source).file_name().unwrap().to_str().unwrap(); let output = self.replace_placeholders(&tool_run, &source_filename);
println!("OUTPUT {:?}", output);
let dest = format!("{}/{}", self.data_directory, output);
if self.dry_run {
println!("🌵 Dry Run: Would copy from {:?} to {:?}", &source, &dest);
} else {
match fs::copy(&source, &dest) {
Ok(bytes) => {
println!(
"📁 🔧 ✅ Copied {:?} bytes from {:?} to {:?}",
bytes, &source, &dest
);
number_of_assets_updated += 1;
},
Err(_e) => {
println!(
"📁 🔧 ‼️ Error: Copying from {:?} to {:?}",
&source, &dest
);
return Err("Error while copying");
},
}
}
}
Ok(number_of_assets_updated)
},
cmd => {
println!("Unhandled asset tool command: {:?}", cmd);
Err("Unhandled asset tool command")
},
}
}
fn replace_placeholders(&self, tool_run: &ToolRun, input: &str) -> String {
let output = input.clone();
let re = Regex::new(r"\$\{(.*?)\}").unwrap();
let output = re.replace_all(
&output,
|c: ®ex::Captures| {
let placeholder_full = c.get(1).map_or("", |m| m.as_str());
let placeholder = placeholder_full.split(" ").next().map_or("", |m| m);
println!("Found {:?}", placeholder);
match placeholder {
"" => "".to_string(),
"tool" => tool_run.tool.clone(),
"command" => tool_run.command.clone(),
"output" => format!("{}", tool_run.output).clone(),
"input" => {
tool_run
.input
.iter()
.map(|s|
format!("{} ", s).clone())
.collect::<Vec<_>>()
.join(" ")
.to_string()
},
"input:basename" => {
let input = if tool_run.input.len() > 0 {
tool_run.input[0].clone()
} else {
"".to_string()
};
match Path::new(&input).file_name() {
Some(basename) => {
let remove_ext =
placeholder_full.split(" ").nth(1).map_or("", |m| m);
println!("remove: {}", &remove_ext);
let basename = basename
.to_os_string()
.into_string()
.unwrap_or("".to_string());
basename
.strip_suffix(remove_ext)
.map_or(basename.clone(), |m| m.to_string())
},
None => "".to_string(),
} },
"data_directory" => self.data_directory.clone(),
param => {
println!("{:?}", tool_run.parameters.get(param));
tool_run
.parameters
.get(param)
.unwrap_or(&ParameterValue::NoValue)
.to_string()
},
}
},
);
output.to_string()
}
fn tool_call_external(&self, tool_run: &ToolRun) -> Result<u32, &'static str> {
let cmd_line = self.replace_placeholders(&tool_run, &tool_run.cmd_line);
let cmd_line = self.replace_placeholders(&tool_run, &cmd_line);
println!("Calling\n{}\tfor {:#?}", cmd_line, tool_run.input);
if self.dry_run {
println!("🌵 Dry Run: >{}<", &cmd_line);
Ok(0)
} else {
dbg!(&cmd_line);
let mut cmd_line = cmd_line.split(' ');
let cmd = cmd_line.next().unwrap_or("");
let args: Vec<&str> = cmd_line.filter(|n| !n.trim().is_empty()).collect();
dbg!(&cmd);
dbg!(&args);
let output = Command::new(cmd).args(&args).output();
match output {
Err(e) => {
println!("Error running external program: {}", &e);
Err("Error running external command")
},
Ok(output) => {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("stdout:\n{}", stdout);
println!("stderr:\n{}", stderr);
println!("return code: {}", output.status.code().unwrap_or(-255));
let number_of_assets_updated = 1;
Ok(number_of_assets_updated)
},
}
}
}
pub fn build(&self) -> Result<u32, &'static str> {
let mut number_of_assets_updated = 0;
let config_glob = if Path::new(&self.content_directory).is_file() {
format!("{}", self.content_directory)
} else {
format!("{}/**/*.asset_config.yaml", self.content_directory)
};
let mut config_files = Vec::new();
for config_file in glob(&config_glob).expect("Failed glob pattern") {
match config_file {
Err(_e) => return Err("Error finding config"),
Ok(config_file) => {
config_files.push(config_file);
},
}
}
println!("Found {:?} config files", config_files.len());
for config_file in config_files {
println!("===\n{:?}", config_file);
let mut file = File::open(&config_file).expect("Failed opening file");
let mut config = String::new();
file.read_to_string(&mut config)
.expect("Failed reading file");
if config.len() == 0 {
return Err("Empty config");
}
let yaml = match YamlLoader::load_from_str(&config) {
Err(_e) => return Err("Broken config"),
Ok(yaml) => yaml,
};
println!("{:?}", yaml);
let config_file_path = Path::new(&config_file);
let asset_path = config_file_path.parent().unwrap_or(Path::new("."));
println!("Asset Path {:?}", asset_path);
for doc in yaml {
let tool = doc["tool"].as_str().unwrap_or("");
let command = doc["command"].as_str().unwrap_or("");
let output = doc["output"].as_str().unwrap_or("");
let cmd_line = doc["cmd_line"].as_str().unwrap_or("");
let mut input = Vec::new();
let combine_inputs = doc["combine-inputs"].as_bool().unwrap_or(false);
if doc["input"].is_array() {
match doc["input"].as_vec() {
None => {},
Some(i) => {
for i in i {
match i.as_str() {
None => {},
Some(s) => input.push(s.to_string()),
}
}
},
}
} else {
let i = doc["input"].as_str();
match i {
Some(i) => input.push(i.to_string()),
None => {},
};
}
let _input_original = input.iter().map(|i| format!("{}", i)).collect::<Vec<_>>();
let input = input
.iter()
.map(|i| format!("{}/{}", asset_path.display(), i))
.collect::<Vec<_>>();
let mut parameters = HashMap::new();
match doc["parameters"].as_hash() {
None => {},
Some(params) => {
for (name, value) in params {
let value = match value {
Yaml::Integer(v) => ParameterValue::IntegerValue(*v),
Yaml::String(v) => ParameterValue::StringValue(v.clone()),
x => {
println!("Unhandled parameter value {:?}", x);
ParameterValue::NoValue
},
};
let name = match name.as_str() {
Some(s) => s.to_string(),
x => {
println!("Unhandled name type {:?}", x);
"".to_string()
},
};
parameters.insert(name, value);
}
},
};
let mut expanded_input = Vec::new();
for i in input.iter() {
for exp in glob(&i).expect("Failed glob pattern") {
match exp {
Err(_e) => return Err("Error globing input"),
Ok(e) => {
match e.into_os_string().into_string() {
Err(_e) => return Err("Error globing input"),
Ok(e) => {
expanded_input.push(e);
},
}
},
}
}
}
if combine_inputs {
let tool_run = ToolRun::new(
&tool,
&command,
&output,
&expanded_input,
¶meters,
&cmd_line,
);
match tool_run.run(&self) {
Ok(n) => {
number_of_assets_updated += n;
},
Err(_e) => {},
}
} else {
for input in expanded_input.iter() {
let mut single_input = Vec::new();
single_input.push(input.clone());
let tool_run = ToolRun::new(
&tool,
&command,
&output,
&single_input,
¶meters,
&cmd_line,
);
match tool_run.run(&self) {
Ok(n) => {
number_of_assets_updated += n;
},
Err(_e) => {},
}
}
}
}
}
Ok(number_of_assets_updated)
}
}