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