Cleaned up config nesting, added config iterator and added simple validation.
This commit is contained in:
		
							parent
							
								
									c8649723f1
								
							
						
					
					
						commit
						6901f72977
					
				| 
						 | 
				
			
			@ -6,7 +6,6 @@ directories:
 | 
			
		|||
  - hello
 | 
			
		||||
  - project-*
 | 
			
		||||
  - nested
 | 
			
		||||
  - duplicate-*
 | 
			
		||||
 | 
			
		||||
tasks:
 | 
			
		||||
  dev: echo 'Hello from main!'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
use clap::Args;
 | 
			
		||||
use crate::utils::config_reader::parse_config;
 | 
			
		||||
use crate::utils::config::{parse_config, validate_config};
 | 
			
		||||
use crate::utils::file_resolvers::resolve_configuration_file;
 | 
			
		||||
 | 
			
		||||
#[derive(Args, Debug)]
 | 
			
		||||
| 
						 | 
				
			
			@ -17,6 +17,11 @@ pub fn run (arguments: &Arguments) -> Result<(), String> {
 | 
			
		|||
 | 
			
		||||
    let config = parse_config(&target)?;
 | 
			
		||||
 | 
			
		||||
    match validate_config(config.clone()) {
 | 
			
		||||
        Ok(_) => {}
 | 
			
		||||
        Err(err) => return Err(err)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    println!("{:?}", config);
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,118 @@
 | 
			
		|||
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>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Config {
 | 
			
		||||
    fn iter(self) -> ConfigIterator {
 | 
			
		||||
        return ConfigIterator::new(self.clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,81 +0,0 @@
 | 
			
		|||
use std::collections::HashMap;
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use std::fs::File;
 | 
			
		||||
use std::io::BufReader;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
use glob::glob;
 | 
			
		||||
 | 
			
		||||
#[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)]
 | 
			
		||||
pub struct ConfigTask {
 | 
			
		||||
    tag: String,
 | 
			
		||||
    command: String
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct SubConfig {
 | 
			
		||||
    config: Config,
 | 
			
		||||
    path: PathBuf,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct Config {
 | 
			
		||||
    name: String,
 | 
			
		||||
    tasks: Vec<ConfigTask>,
 | 
			
		||||
    sub_configs: Vec<SubConfig>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn parse_config(path_buf: &PathBuf) -> Result<Config, String> {
 | 
			
		||||
    let config_file = read_config_file(path_buf)?;
 | 
			
		||||
 | 
			
		||||
    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<SubConfig> = Vec::new();
 | 
			
		||||
 | 
			
		||||
    let parent_dir = path_buf.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(pattern.to_str().unwrap()).map_err(|e| format!("Failed to read glob pattern: {}", e))? {
 | 
			
		||||
            if let Ok(config_path) = entry {
 | 
			
		||||
                let sub_config = SubConfig {
 | 
			
		||||
                    config: parse_config(&config_path)?,
 | 
			
		||||
                    path: config_path,
 | 
			
		||||
                };
 | 
			
		||||
                sub_configs.push(sub_config);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let config = Config{name, tasks, sub_configs};
 | 
			
		||||
 | 
			
		||||
    Ok(config)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,2 +1,2 @@
 | 
			
		|||
pub mod file_resolvers;
 | 
			
		||||
pub mod config_reader;
 | 
			
		||||
pub mod config;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue