Major code clean up and overhaul
This commit is contained in:
		
							parent
							
								
									7d7db72a7b
								
							
						
					
					
						commit
						43dfbfd016
					
				|  | @ -1,41 +1,215 @@ | ||||||
|  | use std::collections::HashMap; | ||||||
|  | use std::fmt::Debug; | ||||||
|  | use std::fs::{canonicalize, read_to_string}; | ||||||
|  | use std::path::{Path, PathBuf}; | ||||||
| use clap::Args; | use clap::Args; | ||||||
| use crate::utils::config::{parse_config, validate_config}; | use glob::glob; | ||||||
| use crate::utils::file_resolvers::resolve_configuration_file; | use serde::Deserialize; | ||||||
| use crate::utils::tasks::run_task; |  | ||||||
| 
 | 
 | ||||||
| #[derive(Args, Debug)] | #[derive(Args, Debug)] | ||||||
| pub struct Arguments { | pub struct Arguments { | ||||||
|     #[arg()] |     #[arg(help = "Which task to run")] | ||||||
|     command: String, |     task_name: String, | ||||||
|     #[arg(long, default_value = ".")] |     #[arg(long, default_value = ".", help = "Which directory to use as entry, defaults to the current directory")] | ||||||
|     entry: String, |     entry: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn run (arguments: &Arguments) -> Result<(), String> { | pub fn run (arguments: &Arguments) -> Result<(), String> { | ||||||
|     let Arguments { entry, command } = arguments; |     let Arguments { entry, task_name: _task_name } = arguments; | ||||||
| 
 | 
 | ||||||
|     let target = resolve_configuration_file(entry)?; |     // Resolve the entry path
 | ||||||
|  |     let entry_config_path: PathBuf = resolve_config_path(entry)?; | ||||||
|  |     println!("entry_config_path: {:?}", entry_config_path); | ||||||
| 
 | 
 | ||||||
|     let config = parse_config(&target)?; |     // Discover all config paths
 | ||||||
|  |     let config_paths: Vec<PathBuf> = discover_config_paths(entry_config_path)?; | ||||||
|  |     println!("config_paths: {:?}", config_paths); | ||||||
| 
 | 
 | ||||||
|     match validate_config(config.clone()) { |     // Parse config file content
 | ||||||
|         Ok(_) => {} |     let config_files: Vec<ConfigFile> = read_config_files(config_paths)?; | ||||||
|         Err(err) => return Err(err) |     println!("config_files: {:?}", config_files); | ||||||
|  | 
 | ||||||
|  |     // Parse config files
 | ||||||
|  |     let configs: Vec<Config> = parse_config_files(config_files)?; | ||||||
|  |     println!("configs: {:?}", configs); | ||||||
|  | 
 | ||||||
|  |     // Resolve dependencies based on the directory structure
 | ||||||
|  |     // (In the future this will be configurable based on a dependency config field)
 | ||||||
|  |     // let config_structure: ConfigStructure = resolve_config_structure(configs);
 | ||||||
|  | 
 | ||||||
|  |     // Gather the tasks from the config
 | ||||||
|  |     // let task_structure: TaskStructure = resolve_task_structure(config_structure, task_name);
 | ||||||
|  | 
 | ||||||
|  |     // Run the commands, one by one
 | ||||||
|  |     // > In the future this is configurable on the rask level and maybe on the config file level
 | ||||||
|  |     // > Initially it fails the whole command if one task fails, but will also be configurable in the future
 | ||||||
|  |     // let task_exit: TaskExit = run_task_structure(task_structure);
 | ||||||
|  | 
 | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
|  | enum TaskType { | ||||||
|  |     SHELL | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
|  | #[allow(dead_code)] | ||||||
|  | struct ConfigTask { | ||||||
|  |     task_type: TaskType, | ||||||
|  |     content: String | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ConfigTasks = HashMap<String, ConfigTask>; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
|  | #[allow(dead_code)] | ||||||
|  | struct Config { | ||||||
|  |     name: String, | ||||||
|  |     tasks: HashMap<String, ConfigTask>, | ||||||
|  |     path: PathBuf, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn parse_config_files(config_files: Vec<ConfigFile>) -> Result<Vec<Config>, String> { | ||||||
|  |     let mut configs: Vec<Config> = vec![]; | ||||||
|  | 
 | ||||||
|  |     for config_file in config_files { | ||||||
|  |         let config = parse_config_file(config_file)?; | ||||||
|  |         configs.push(config); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for config in config.clone().iter() { |     Ok(configs) | ||||||
|         match config.get_task(command) { | } | ||||||
|             None => {} | 
 | ||||||
|             Some(task) => { | fn parse_config_file(config_file: ConfigFile) -> Result<Config, String> { | ||||||
|                 match run_task(&task) { |     let ConfigFile { name, tasks: config_file_tasks, _file_path: path, .. } = config_file; | ||||||
|                     Ok(_) => {} | 
 | ||||||
|                     Err(err) => return Err(err) |     let tasks = parse_config_tasks(config_file_tasks)?; | ||||||
|  | 
 | ||||||
|  |     let config: Config = Config {name, tasks, path}; | ||||||
|  | 
 | ||||||
|  |     Ok(config) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn parse_config_tasks(tasks: ConfigFileTasks) -> Result<ConfigTasks, String> { | ||||||
|  |     let mut config_tasks: ConfigTasks = HashMap::new(); | ||||||
|  | 
 | ||||||
|  |     for (key, value) in tasks { | ||||||
|  |         let config_task: ConfigTask = ConfigTask { | ||||||
|  |             task_type: TaskType::SHELL, | ||||||
|  |             content: value | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         config_tasks.insert(key, config_task); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Ok(config_tasks) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn read_config_files(paths: Vec<PathBuf>) -> Result<Vec<ConfigFile>, String> { | ||||||
|  |     let mut configs_files: Vec<ConfigFile> = vec![]; | ||||||
|  | 
 | ||||||
|  |     for path in paths { | ||||||
|  |         let config_file = read_config_file(path)?; | ||||||
|  |         configs_files.push(config_file); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Ok(configs_files) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn discover_config_paths(path: PathBuf) -> Result<Vec<PathBuf>, String> { | ||||||
|  |     let mut found_config_paths: Vec<PathBuf> = vec![path.clone()]; | ||||||
|  | 
 | ||||||
|  |     // Read config
 | ||||||
|  |     let mut path_stack: Vec<PathBuf> = vec![path.clone()]; | ||||||
|  |     while !path_stack.is_empty() { | ||||||
|  |         let ConfigFile { directories, _file_path, .. } = read_config_file(path_stack.pop().unwrap())?; | ||||||
|  | 
 | ||||||
|  |         // Extract directories
 | ||||||
|  |         let config_directory = _file_path.parent().ok_or("Failed to get parent directory")?; | ||||||
|  |         for directory in directories { | ||||||
|  |             let mut pattern: PathBuf = config_directory.to_path_buf(); | ||||||
|  |             pattern.push(&directory); | ||||||
|  |             if !pattern.ends_with(".yaml") { | ||||||
|  |                 pattern.push("rask.yaml"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Find config files based on the pattern in the directories value
 | ||||||
|  |             let pattern_string: &str = pattern.to_str().unwrap(); | ||||||
|  |             for pattern_results in glob(pattern_string).map_err(|e| format!("Failed to read glob pattern: {}", e))? { | ||||||
|  |                 if let Ok(found_config_path) = pattern_results { | ||||||
|  |                     // Only add if the path was not already processed, preventing loops.
 | ||||||
|  |                     if !found_config_paths.contains(&found_config_path) { | ||||||
|  |                         found_config_paths.push(found_config_path.clone()); | ||||||
|  |                         path_stack.push(found_config_path.clone()); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     println!("{:?}", config); |     Ok(found_config_paths) | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     Ok(()) | fn read_file_content (path: PathBuf) -> Result<String, String> { | ||||||
|  |     match read_to_string(path) { | ||||||
|  |         Ok(content) => Ok(content), | ||||||
|  |         Err(err) => Err(format!("Failed to read file: {}", err)), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ConfigFileTasks = HashMap<String, String>; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Deserialize, Default)] | ||||||
|  | struct ConfigFile { | ||||||
|  |     name: String, | ||||||
|  |     #[serde(default)] | ||||||
|  |     directories: Vec<String>, | ||||||
|  |     #[serde(default)] | ||||||
|  |     tasks: ConfigFileTasks, | ||||||
|  |     #[serde(default)] | ||||||
|  |     _file_path: PathBuf, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn read_config_file(path: PathBuf) -> Result<ConfigFile, String> { | ||||||
|  |     let content = read_file_content(path.clone())?; | ||||||
|  | 
 | ||||||
|  |     let mut config_file: ConfigFile = serde_yaml::from_str(&content).expect(format!("Failed to parse YAML from \"{:?}\"", path).as_str()); | ||||||
|  | 
 | ||||||
|  |     config_file._file_path = path.clone(); | ||||||
|  | 
 | ||||||
|  |     Ok(config_file) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn resolve_config_path<P: AsRef<Path> + Debug + Clone + Copy>(path: P) -> Result<PathBuf, String> { | ||||||
|  |     let full_path = match canonicalize(path) { | ||||||
|  |         Ok(full_path) => full_path, | ||||||
|  |         Err(_) => return Err(format!("Target does not exists: {:?}", path.clone())) | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     if full_path.is_dir() { | ||||||
|  |         let config_file = find_config_file(full_path)?; | ||||||
|  |         return Ok(config_file) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Ok(full_path) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const CONFIG_FILENAMES: [&str; 1] = ["rask.yaml"]; | ||||||
|  | 
 | ||||||
|  | fn find_config_file(directory_path: PathBuf) -> Result<PathBuf, String> { | ||||||
|  |     if !directory_path.is_dir() { | ||||||
|  |         return Err(format!("\"{:?}\" is not a directory", directory_path)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for filename in CONFIG_FILENAMES { | ||||||
|  |         let mut possible_config_file = directory_path.clone(); | ||||||
|  |         possible_config_file.push(filename); | ||||||
|  |         match possible_config_file.exists() { | ||||||
|  |             true => return Ok(possible_config_file), | ||||||
|  |             false => {} | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Err(format!("Unable to find a config file (\"{:?}\") in {:?}", CONFIG_FILENAMES, directory_path)) | ||||||
| } | } | ||||||
|  | @ -1,128 +0,0 @@ | ||||||
| use std::path::PathBuf; |  | ||||||
| use std::fs::File; |  | ||||||
| use std::io::BufReader; |  | ||||||
| use serde::Deserialize; |  | ||||||
| use std::collections::HashMap; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| pub fn validate_config(config: Config) -> Result<bool, String> { |  | ||||||
|     let mut names: Vec<String> = vec![]; |  | ||||||
| 
 |  | ||||||
|     for config in config.iter() { |  | ||||||
|         let Config { name, path, .. } = config; |  | ||||||
| 
 |  | ||||||
|         if names.contains(&name) { |  | ||||||
|             return Err(format!("Duplicate config name {} found: {:?}", name, path)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         names.push(name); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     Ok(true) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Deserialize, Default)] |  | ||||||
| struct ConfigFile { |  | ||||||
|     name: String, |  | ||||||
|     #[serde(default)] |  | ||||||
|     directories: Vec<String>, |  | ||||||
|     #[serde(default)] |  | ||||||
|     tasks: HashMap<String, String>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn read_config_file(path_buf: &PathBuf) -> Result<ConfigFile, String> { |  | ||||||
|     let file = File::open(path_buf).expect("Failed to read file"); |  | ||||||
| 
 |  | ||||||
|     let reader = BufReader::new(file); |  | ||||||
| 
 |  | ||||||
|     let config: ConfigFile = serde_yaml::from_reader(reader).expect("Failed to parse YAML"); |  | ||||||
| 
 |  | ||||||
|     Ok(config) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone)] |  | ||||||
| pub struct ConfigTask { |  | ||||||
|     tag: String, |  | ||||||
|     command: String |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone)] |  | ||||||
| pub struct Config { |  | ||||||
|     name: String, |  | ||||||
|     tasks: Vec<ConfigTask>, |  | ||||||
|     path: PathBuf, |  | ||||||
|     sub_configs: Vec<Config>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl Config { |  | ||||||
|     pub fn iter(self) -> ConfigIterator { |  | ||||||
|         return ConfigIterator::new(self.clone()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn get_task(&self, task_name: &String) -> Option<String> { |  | ||||||
|         for task in self.clone().tasks { |  | ||||||
|             if task.tag == *task_name { |  | ||||||
|                 return Some(task.command) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         None |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub struct ConfigIterator { |  | ||||||
|     stack: Vec<Config> |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl ConfigIterator { |  | ||||||
|     pub fn new(config: Config) -> ConfigIterator { |  | ||||||
|         let mut stack = vec![config]; |  | ||||||
|         ConfigIterator{ stack } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl Iterator for ConfigIterator { |  | ||||||
|     type Item = Config; |  | ||||||
| 
 |  | ||||||
|     fn next(&mut self) -> Option<Self::Item> { |  | ||||||
|         let next_config = self.stack.pop()?; |  | ||||||
| 
 |  | ||||||
|         self.stack.extend(next_config.sub_configs.iter().rev().map(|sub_config| sub_config.clone())); |  | ||||||
| 
 |  | ||||||
|         Some(next_config) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn parse_config(path: &PathBuf) -> Result<Config, String> { |  | ||||||
|     let config_file = read_config_file(path)?; |  | ||||||
| 
 |  | ||||||
|     let name = config_file.name; |  | ||||||
| 
 |  | ||||||
|     let tasks = config_file.tasks |  | ||||||
|         .iter() |  | ||||||
|         .map(|(tag, command)| ConfigTask{tag: tag.clone(), command: command.clone()}) |  | ||||||
|         .collect(); |  | ||||||
| 
 |  | ||||||
|     let mut sub_configs: Vec<Config> = Vec::new(); |  | ||||||
| 
 |  | ||||||
|     let parent_dir = path.parent().ok_or("Failed to get parent directory")?; |  | ||||||
| 
 |  | ||||||
|     for directory in config_file.directories { |  | ||||||
|         let mut pattern: PathBuf = parent_dir.to_path_buf(); |  | ||||||
|         pattern.push(&directory); |  | ||||||
|         if !pattern.ends_with(".yaml") { |  | ||||||
|             pattern.push("rask.yaml"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         for entry in glob::glob(pattern.to_str().unwrap()).map_err(|e| format!("Failed to read glob pattern: {}", e))? { |  | ||||||
|             if let Ok(config_path) = entry { |  | ||||||
|                 let sub_config = parse_config(&config_path)?; |  | ||||||
|                 sub_configs.push(sub_config); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let config = Config{name, tasks, sub_configs, path: path.clone()}; |  | ||||||
| 
 |  | ||||||
|     Ok(config) |  | ||||||
| } |  | ||||||
|  | @ -1,23 +0,0 @@ | ||||||
| use std::path::{Path, PathBuf}; |  | ||||||
| use std::fs::canonicalize; |  | ||||||
| 
 |  | ||||||
| const DEFAULT_FILENAME: &str = "rask.yaml"; |  | ||||||
| 
 |  | ||||||
| pub fn resolve_configuration_file(target: &String) -> Result<PathBuf, String> { |  | ||||||
|     let target_path = Path::new(target); |  | ||||||
| 
 |  | ||||||
|     let mut target = match canonicalize(target_path) { |  | ||||||
|         Ok(target) => target, |  | ||||||
|         Err(_) => return Err(format!("Target does not exists: {:?}", target)) |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     if target.is_dir() { |  | ||||||
|         target.push(DEFAULT_FILENAME); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if !target.exists() { |  | ||||||
|         return Err(format!("Target does not exists: {:?}", target)) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     Ok(target) |  | ||||||
| } |  | ||||||
|  | @ -1,9 +0,0 @@ | ||||||
| use std::process::Command; |  | ||||||
| 
 |  | ||||||
| pub fn run_task(task: &String) -> Result<(), String> { |  | ||||||
|     let mut command = Command::new(task); |  | ||||||
|     match command.spawn().unwrap().wait() { |  | ||||||
|         Ok(_) => Ok(()), |  | ||||||
|         Err(_) => Err(format!("Task failed: {}", task)) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
		Loading…
	
		Reference in New Issue