use std::{fs, path}; use std::io::ErrorKind; use toml; enum PathType { Exclude, DoNotScan, Okay, } pub struct Config { pub source: String, pub build_dir: String, pub do_not_scan: Vec, pub excludes: Vec, pub file_delim: String, pub command_delim: String, pub commands: Vec<(String, String)> } impl Config { pub fn new() -> Config { println!("Using built-in config"); Config { source: String::from("src"), build_dir: String::from("build"), do_not_scan: vec![], excludes: vec![], file_delim: String::from(":?"), command_delim: String::from(":!"), commands: vec![], } } pub fn import(config: &str) -> Config { println!("Using found config"); let config: toml::Value = toml::from_str(config).expect("Could not load config"); let config = config.get("config").expect("Could not find \"[config]\" field"); // Attempt to read commands file let commands: toml::Value = match config.get("commands_file") { Some(n) => { if n.is_str() { let file_string = n.as_str().unwrap(); let file_string = match fs::read_to_string(file_string) { Ok(n) => n, Err(_) => { println!("Could not load commands from file"); String::from("[commands]") }, }; toml::from_str(file_string.as_str()).expect("Invalid toml") } else { panic!() } }, None => {println!("Could not load commands from file"); toml::toml!{[commands]}}, }; let commands = commands.get("commands").expect("Could not find \"[commands]\" field"); let mut commands_vec = vec![]; let commands = commands.as_table().unwrap(); for x in commands.keys() { commands_vec.push((String::from(x), String::from(commands.get(x).unwrap().as_str().unwrap()))) } let mut do_not_scan = vec![]; for x in config["do_not_scan"].as_array().unwrap() { do_not_scan.push(String::from(x.as_str().unwrap())) } let mut exclude = vec![]; for x in config["exclude"].as_array().unwrap() { exclude.push(String::from(x.as_str().unwrap())) } Config { source: String::from(config["source"].as_str().unwrap()), build_dir: String::from(config["build_dir"].as_str().unwrap()), do_not_scan: do_not_scan, excludes: exclude, file_delim: String::from(":?"), command_delim: String::from(":!"), commands: commands_vec, } } } pub struct S3G { config: Config, } impl S3G { pub fn new(config: Config) -> S3G { S3G { config, } } pub fn run(&self) { let source = &self.config.source; let build = &self.config.build_dir; // Create build directory or panic S3G::create_dir(&path::PathBuf::from(build)).unwrap(); self.directory_scan(path::PathBuf::from(source)); } fn create_dir(path: &path::PathBuf) -> Result<(), ()> { match fs::create_dir(path) { Ok(_) => (), Err(n) => match n.kind() { ErrorKind::AlreadyExists => (), _ => return Err(()), } } return Ok(()) } fn remove_path_head(path: &path::PathBuf) -> path::PathBuf { let mut path = path.to_str().unwrap().split("/"); path.next(); let path: path::PathBuf = path.collect(); return path } fn append_path_head(path: &path::PathBuf, head: &str) -> path::PathBuf { let mut head = path::PathBuf::from(head); head.push(path); return head } fn replace_path_head(path: &path::PathBuf, head: &str) -> path::PathBuf { let path = S3G::remove_path_head(path); S3G::append_path_head(&path, head) } fn check_path(&self, path: &path::PathBuf) -> PathType { // Get Exclude and DNS lists let excludes: Vec = self.config.excludes.clone().into_iter().map(|x| path::PathBuf::from(x)).collect(); let dns: Vec = self.config.do_not_scan.clone().into_iter().map(|x| path::PathBuf::from(x)).collect(); // Convert path to str for comparisons let path = path.to_str().unwrap(); // Excludes for ex_path in excludes { let ex_path = ex_path.to_str().unwrap(); if &path.contains(&ex_path) == &true { return PathType::Exclude; } } // Do not scans (copy files) for dns_path in dns { let dns_path = dns_path.to_str().unwrap(); if &path.contains(&dns_path) == &true { return PathType::DoNotScan; } } return PathType::Okay; } fn copy_file(&self, path: &path::PathBuf) { let build_path = S3G::replace_path_head(&path, &self.config.build_dir); match fs::copy(path, build_path) { Ok(_) => (), Err(n) => { if n.kind() != ErrorKind::AlreadyExists { panic!() } }, } } fn directory_scan(&self, path: path::PathBuf) { // Check the path match self.check_path(&path) { PathType::Exclude => return, _ => (), }; // Create the dir in the build tree let build_path = S3G::replace_path_head(&path, &self.config.build_dir); S3G::create_dir(&build_path).unwrap(); // Iterate over path contents for x in fs::read_dir(path).unwrap() { let x = x.as_ref().unwrap().path(); if x.is_dir() { self.directory_scan(x); } else { match self.file_scan(&x) { None => (), Some(n) => self.file_write(&x, n).unwrap(), } } } } fn file_scan(&self, path: &path::PathBuf) -> Option { // Check path match self.check_path(&path) { PathType::Okay => (), PathType::DoNotScan => { self.copy_file(&path); return None }, PathType::Exclude => (), }; // File scanning let mut file_string = String::new(); let string = match fs::read_to_string(&path) { Ok(n) => n, Err(_) => { println!("Could not read file at: \"{}\", Continuing", &path.to_str().unwrap()); String::from("") } }; for line in string.lines() { if line.contains(&self.config.file_delim) || line.contains(&self.config.command_delim) { // Setup the line for parsing let mut v = line.trim().split(':'); // make sure to push the first string match v.next() { None => (), Some(n) => file_string.push_str(n), }; let v: Vec = v.map(|x| (String::from(':') + x)).collect(); // Parse lines for prefixes for n in v { let n = { let mut string = String::new(); // Commands for c in &self.config.commands { let delim = String::new() + &self.config.command_delim + &c.0; if n.contains(&delim) { let n = n.replace(&delim, &c.1); string.push_str(&n); } } // Files if n.contains(&self.config.file_delim) { let n: Vec<&str> = n.split(&self.config.file_delim).collect(); for s in n { let s = match s { "" => String::from(""), // Scan the file from the path n => self.file_scan(&path::PathBuf::from(n)).unwrap_or(String::from("")), }; string.push_str(&s); } } string }; // Push matching and scanned string file_string.push_str(&n); file_string.push('\n'); } } else { // Push unmatching line file_string.push_str(line); file_string.push('\n'); } } // Return the final file_string if file_string != "" { return Some(file_string); } else { return None; } } fn file_write(&self, path: &path::PathBuf, contents: String) -> Result<(),()> { match self.check_path(&path) { // PathType::Exclude => {println!("Excluding: \"{}\"", &path.to_str().unwrap()); return Ok(())}, PathType::Exclude => return Ok(()), _ => (), } let dest = S3G::replace_path_head(&path, &self.config.build_dir); match fs::write(dest, contents) { Ok(_) => Ok(()), Err(_) => Err(()), } } }