Made the code execute the commands.
This commit is contained in:
		
							parent
							
								
									12a243796c
								
							
						
					
					
						commit
						e64ce1f459
					
				| 
						 | 
				
			
			@ -2,6 +2,10 @@ use std::collections::HashMap;
 | 
			
		|||
use std::fmt::Debug;
 | 
			
		||||
use std::fs::{canonicalize, read_to_string};
 | 
			
		||||
use std::path::{Path, PathBuf};
 | 
			
		||||
use std::process::Command;
 | 
			
		||||
use std::thread;
 | 
			
		||||
use std::thread::JoinHandle;
 | 
			
		||||
use std::time::Instant;
 | 
			
		||||
use clap::Args;
 | 
			
		||||
use glob::glob;
 | 
			
		||||
use globset::{Glob, GlobSetBuilder};
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +22,9 @@ pub struct Arguments {
 | 
			
		|||
pub fn run (arguments: &Arguments) -> Result<(), String> {
 | 
			
		||||
    let Arguments { entry, task_name } = arguments;
 | 
			
		||||
 | 
			
		||||
    // Start the timer
 | 
			
		||||
    let start_time = Instant::now();
 | 
			
		||||
 | 
			
		||||
    // Resolve the entry path
 | 
			
		||||
    let entry_config_path: PathBuf = resolve_config_path(entry)?;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -35,24 +42,143 @@ pub fn run (arguments: &Arguments) -> Result<(), String> {
 | 
			
		|||
    let config_structure: ConfigStructure = resolve_config_structure(&entry_config_path, configs)?;
 | 
			
		||||
 | 
			
		||||
    // Gather the tasks from the config
 | 
			
		||||
    let task_structure: TaskStructure = resolve_task_structure(config_structure, task_name);
 | 
			
		||||
    eprintln!("task_structure = {:?}", task_structure);
 | 
			
		||||
    let ordered_tasks: OrderedTasks = resolve_task_order(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);
 | 
			
		||||
    let task_exit: TaskExit = run_task_structure(&ordered_tasks)?;
 | 
			
		||||
 | 
			
		||||
    let task_amount = ordered_tasks.len();
 | 
			
		||||
    let execution_time = start_time.elapsed().as_secs_f32();
 | 
			
		||||
    let formatted_execution_time = (execution_time * 100.0).round() / 100.0;
 | 
			
		||||
 | 
			
		||||
    match task_exit {
 | 
			
		||||
        TaskExit::SUCCESS => println!("{}", format!("Successfully executed {} tasks within {} seconds", task_amount, formatted_execution_time)),
 | 
			
		||||
        TaskExit::FAILURE => println!("{}", format!("Failed after executing {} tasks within {} seconds", task_amount, formatted_execution_time)),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
struct TaskStructure {
 | 
			
		||||
fn run_task_structure(ordered_tasks: &OrderedTasks) -> Result<TaskExit, String> {
 | 
			
		||||
    let highest_order = find_highest_order(&ordered_tasks)?;
 | 
			
		||||
 | 
			
		||||
    for order in (0..=highest_order).rev() {
 | 
			
		||||
        match run_task_order(&ordered_tasks, order) {
 | 
			
		||||
            Ok(_) => {}
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                println!("{}", err);
 | 
			
		||||
                return Ok(TaskExit::FAILURE);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(TaskExit::SUCCESS)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn resolve_task_structure(config_structure: ConfigStructure, task_name: &String) -> TaskStructure {
 | 
			
		||||
    todo!()
 | 
			
		||||
fn run_task_order(ordered_tasks: &OrderedTasks, order: u64) -> Result<(), String> {
 | 
			
		||||
    let mut tasks: Vec<&Task> = vec![];
 | 
			
		||||
    for ordered_task in ordered_tasks {
 | 
			
		||||
        let OrderedTask { task, order: task_order } = ordered_task;
 | 
			
		||||
        if *task_order == order {
 | 
			
		||||
            tasks.push(task);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mut task_threads: Vec<JoinHandle<bool>> = vec![];
 | 
			
		||||
    for task in tasks {
 | 
			
		||||
        let task_thread = execute_task(task)?;
 | 
			
		||||
        task_threads.push(task_thread);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for task_thread in task_threads {
 | 
			
		||||
        if let Ok(success) = task_thread.join() {
 | 
			
		||||
            if !success {
 | 
			
		||||
                return Err("Command execution failed.".to_string());
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            return Err("Thread panicked.".to_string());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn execute_task(task: &Task) -> Result<JoinHandle<bool>, String> {
 | 
			
		||||
    let task_thread = thread::spawn({
 | 
			
		||||
        let task = task.clone();
 | 
			
		||||
        move || {
 | 
			
		||||
            let status = Command::new("sh")
 | 
			
		||||
                .arg("-c")
 | 
			
		||||
                .arg(&task.command)
 | 
			
		||||
                .current_dir(&task.directory)
 | 
			
		||||
                .status()
 | 
			
		||||
                .expect("Failed to execute command");
 | 
			
		||||
            status.success()
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Ok(task_thread)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn find_highest_order(ordered_tasks: &OrderedTasks) -> Result<u64, String> {
 | 
			
		||||
    let mut highest_order: u64 = 0u64;
 | 
			
		||||
 | 
			
		||||
    for ordered_task in ordered_tasks {
 | 
			
		||||
        if ordered_task.order > highest_order {
 | 
			
		||||
            highest_order = ordered_task.order;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(highest_order)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
enum TaskExit {
 | 
			
		||||
    SUCCESS,
 | 
			
		||||
    FAILURE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
struct Task {
 | 
			
		||||
    command: String,
 | 
			
		||||
    directory: PathBuf,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
struct OrderedTask {
 | 
			
		||||
    task: Task,
 | 
			
		||||
    order: u64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type OrderedTasks = Vec<OrderedTask>;
 | 
			
		||||
 | 
			
		||||
fn resolve_task_order(config_structure: ConfigStructure, task_name: &String) -> Result<OrderedTasks, String> {
 | 
			
		||||
    let mut ordered_tasks: OrderedTasks = vec![];
 | 
			
		||||
 | 
			
		||||
    order_tasks(&mut ordered_tasks, config_structure, task_name, 0);
 | 
			
		||||
 | 
			
		||||
    Ok(ordered_tasks)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn order_tasks(ordered_tasks: &mut OrderedTasks, config_structure: ConfigStructure, task_name: &String, index: u64) {
 | 
			
		||||
    let ConfigStructure { config, children } = config_structure;
 | 
			
		||||
    let Config { tasks, dir_path, .. } = config;
 | 
			
		||||
    match tasks.get(task_name) {
 | 
			
		||||
        None => {}
 | 
			
		||||
        Some(&ref config_task) => ordered_tasks.push(OrderedTask{
 | 
			
		||||
            task: Task {
 | 
			
		||||
                command: config_task.content.clone(),
 | 
			
		||||
                directory: dir_path,
 | 
			
		||||
            },
 | 
			
		||||
            order: index
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for child in children {
 | 
			
		||||
        order_tasks(ordered_tasks, child, task_name, index+1);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
| 
						 | 
				
			
			@ -65,7 +191,7 @@ fn resolve_config_structure(entry_config_path: &PathBuf, configs: Vec<Config>) -
 | 
			
		|||
    let mut path_map: HashMap<PathBuf, Config> = HashMap::new();
 | 
			
		||||
 | 
			
		||||
    for config in configs {
 | 
			
		||||
        path_map.insert(config.clone().path, config);
 | 
			
		||||
        path_map.insert(config.clone().file_path, config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let config_structure: ConfigStructure = construct_config_structure(entry_config_path, &path_map)?;
 | 
			
		||||
| 
						 | 
				
			
			@ -147,7 +273,8 @@ struct Config {
 | 
			
		|||
    name: String,
 | 
			
		||||
    task_engine: TaskEngine,
 | 
			
		||||
    tasks: HashMap<String, ConfigTask>,
 | 
			
		||||
    path: PathBuf,
 | 
			
		||||
    file_path: PathBuf,
 | 
			
		||||
    dir_path: PathBuf,
 | 
			
		||||
    directories: ConfigDirectories,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -163,17 +290,17 @@ fn parse_config_files(config_files: Vec<ConfigFile>) -> Result<Vec<Config>, Stri
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
fn parse_config_file(config_file: ConfigFile) -> Result<Config, String> {
 | 
			
		||||
    let ConfigFile { name, tasks: config_file_tasks, _file_path: file_path, _dir_path: dir_path, directories, task_engine } = config_file;
 | 
			
		||||
    let ConfigFile { name, tasks: config_file_tasks, __file_path: file_path, __dir_path: dir_path, directories, task_engine } = config_file;
 | 
			
		||||
 | 
			
		||||
    let tasks: ConfigTasks = match task_engine {
 | 
			
		||||
        TaskEngine::COMPOSER => parse_composer_tasks(dir_path)?,
 | 
			
		||||
        TaskEngine::NPM => parse_node_tasks(dir_path, "npm")?,
 | 
			
		||||
        TaskEngine::YARN => parse_node_tasks(dir_path, "yarn")?,
 | 
			
		||||
        TaskEngine::PNPM => parse_node_tasks(dir_path, "pnmp")?,
 | 
			
		||||
        TaskEngine::COMPOSER => parse_composer_tasks(&dir_path)?,
 | 
			
		||||
        TaskEngine::NPM => parse_node_tasks(&dir_path, "npm")?,
 | 
			
		||||
        TaskEngine::YARN => parse_node_tasks(&dir_path, "yarn")?,
 | 
			
		||||
        TaskEngine::PNPM => parse_node_tasks(&dir_path, "pnpm")?,
 | 
			
		||||
        TaskEngine::NONE => parse_config_tasks(config_file_tasks)?,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let config: Config = Config { name, tasks, path: file_path, directories, task_engine };
 | 
			
		||||
    let config: Config = Config { name, tasks, file_path, dir_path, directories, task_engine };
 | 
			
		||||
 | 
			
		||||
    Ok(config)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -184,7 +311,7 @@ struct PackageJsonFile {
 | 
			
		|||
    scripts: HashMap<String, String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_node_tasks(dir_path: PathBuf, prefix: &str) -> Result<ConfigTasks, String> {
 | 
			
		||||
fn parse_node_tasks(dir_path: &PathBuf, prefix: &str) -> Result<ConfigTasks, String> {
 | 
			
		||||
    let mut file_path = dir_path.clone();
 | 
			
		||||
    file_path.push("package.json");
 | 
			
		||||
    let content = read_file_content(file_path)?;
 | 
			
		||||
| 
						 | 
				
			
			@ -215,7 +342,7 @@ struct ComposerJsonFile {
 | 
			
		|||
    scripts: HashMap<String, ComposerJsonScriptValue>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_composer_tasks(dir_path: PathBuf) -> Result<ConfigTasks, String> {
 | 
			
		||||
fn parse_composer_tasks(dir_path: &PathBuf) -> Result<ConfigTasks, String> {
 | 
			
		||||
    let mut file_path = dir_path.clone();
 | 
			
		||||
    file_path.push("composer.json");
 | 
			
		||||
    let content = read_file_content(file_path)?;
 | 
			
		||||
| 
						 | 
				
			
			@ -265,7 +392,7 @@ fn discover_config_paths(path: &PathBuf) -> Result<Vec<PathBuf>, String> {
 | 
			
		|||
    // 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())?;
 | 
			
		||||
        let ConfigFile { directories, __file_path: _file_path, .. } = read_config_file(path_stack.pop().unwrap())?;
 | 
			
		||||
 | 
			
		||||
        // Extract directories
 | 
			
		||||
        let config_directory = _file_path.parent().ok_or("Failed to get parent directory")?;
 | 
			
		||||
| 
						 | 
				
			
			@ -318,10 +445,11 @@ struct ConfigFile {
 | 
			
		|||
    directories: ConfigDirectories,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    tasks: ConfigFileTasks,
 | 
			
		||||
    // The following fields are not part of the yaml file.
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    _file_path: PathBuf,
 | 
			
		||||
    __file_path: PathBuf,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    _dir_path: PathBuf,
 | 
			
		||||
    __dir_path: PathBuf,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn read_config_file(path: PathBuf) -> Result<ConfigFile, String> {
 | 
			
		||||
| 
						 | 
				
			
			@ -329,8 +457,8 @@ fn read_config_file(path: PathBuf) -> Result<ConfigFile, String> {
 | 
			
		|||
 | 
			
		||||
    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();
 | 
			
		||||
    config_file._dir_path = path.parent().unwrap().to_path_buf();
 | 
			
		||||
    config_file.__file_path = path.clone();
 | 
			
		||||
    config_file.__dir_path = path.parent().unwrap().to_path_buf();
 | 
			
		||||
 | 
			
		||||
    Ok(config_file)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue