Added init command
General code cleanup Fixed the Automatic task discovery, not discovering rask config tasks.
This commit is contained in:
		
							parent
							
								
									d8addbdb7f
								
							
						
					
					
						commit
						4fb6c7b497
					
				| 
						 | 
				
			
			@ -2,3 +2,4 @@ name: nested/folder
 | 
			
		|||
 | 
			
		||||
tasks:
 | 
			
		||||
  dev: echo 'Hello from nested/folder?'
 | 
			
		||||
  meme: echo 'Meme from nested/folder?'
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
*
 | 
			
		||||
!.gitignore
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
use clap::Args;
 | 
			
		||||
use crate::utils::file::{ConfigFile, parse_path_string, write_config_file};
 | 
			
		||||
 | 
			
		||||
#[derive(Args, Debug)]
 | 
			
		||||
pub struct Arguments {
 | 
			
		||||
    #[arg(long, default_value = ".", help = "Which directory to use as entry, defaults to the current directory")]
 | 
			
		||||
    entry: String,
 | 
			
		||||
    #[arg(help = "then name of the config file, defaults to the directory name", default_value = "")]
 | 
			
		||||
    name: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn execute(arguments: &Arguments) -> Result<(), String> {
 | 
			
		||||
    let Arguments { entry, name } = arguments;
 | 
			
		||||
 | 
			
		||||
    let mut path = parse_path_string(entry)?;
 | 
			
		||||
    if path.is_dir() {
 | 
			
		||||
        path.push("rask.yaml")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if path.exists() {
 | 
			
		||||
        return Err(format!("Rask already initialised at {:?}", path));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mut config_name = name.clone(); // Use clone to avoid modifying the original input
 | 
			
		||||
    if config_name.is_empty() {
 | 
			
		||||
        config_name = path.parent()
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .file_name()
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .to_str()
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .to_string();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let config_file: ConfigFile = ConfigFile {
 | 
			
		||||
        name: config_name,
 | 
			
		||||
        task_engine: Default::default(),
 | 
			
		||||
        directories: vec![],
 | 
			
		||||
        tasks: Default::default(),
 | 
			
		||||
        __file_path: Default::default(),
 | 
			
		||||
        __dir_path: Default::default(),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    write_config_file(path.clone(), config_file)?;
 | 
			
		||||
 | 
			
		||||
    // NOTE: We could improve the init command by adding a reverse search for a parent rake file
 | 
			
		||||
 | 
			
		||||
    println!("Rask initialised: {:?}", path);
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -40,8 +40,8 @@ fn get_config_tasks(configs: &Vec<Config>) -> Result<Vec<String>, String> {
 | 
			
		|||
    let mut tasks: Vec<String> = vec![];
 | 
			
		||||
 | 
			
		||||
    for config in configs {
 | 
			
		||||
        for configTask in &config.tasks {
 | 
			
		||||
            let ConfigTask { key, .. } = configTask;
 | 
			
		||||
        for config_task in &config.tasks {
 | 
			
		||||
            let ConfigTask { key, .. } = config_task;
 | 
			
		||||
            if !tasks.contains(&key) {
 | 
			
		||||
                tasks.push(key.clone());
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,2 +1,3 @@
 | 
			
		|||
pub mod run;
 | 
			
		||||
pub mod list;
 | 
			
		||||
pub mod init;
 | 
			
		||||
| 
						 | 
				
			
			@ -113,7 +113,7 @@ fn execute_task(task: &Task) -> Result<JoinHandle<bool>, String> {
 | 
			
		|||
                .arg(command)
 | 
			
		||||
                .current_dir(directory)
 | 
			
		||||
                .status()
 | 
			
		||||
                .expect("Failed to execute command");
 | 
			
		||||
                .map_err(|err| err.to_string());
 | 
			
		||||
            status.success()
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ use std::process::exit;
 | 
			
		|||
use clap::{Parser, Subcommand};
 | 
			
		||||
use commands::run;
 | 
			
		||||
use commands::list;
 | 
			
		||||
use commands::init;
 | 
			
		||||
 | 
			
		||||
mod commands;
 | 
			
		||||
mod utils;
 | 
			
		||||
| 
						 | 
				
			
			@ -10,6 +11,7 @@ mod utils;
 | 
			
		|||
enum Command {
 | 
			
		||||
    Run(run::Arguments),
 | 
			
		||||
    List(list::Arguments),
 | 
			
		||||
    Init(init::Arguments),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Parser, Debug)]
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +27,7 @@ fn main() {
 | 
			
		|||
    let result = match &arguments.command {
 | 
			
		||||
        Command::Run(arguments) => { run::execute(arguments) },
 | 
			
		||||
        Command::List(arguments) => { list::execute(arguments) },
 | 
			
		||||
        Command::Init(arguments) => { init::execute(arguments) },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    match result {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,5 @@
 | 
			
		|||
use std::path::{Path, PathBuf};
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
use std::fs::canonicalize;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use globset::{Glob, GlobSetBuilder};
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
| 
						 | 
				
			
			@ -172,7 +171,7 @@ fn parse_config_file(config_file: ConfigFile) -> Result<Config, String> {
 | 
			
		|||
        TaskEngine::NPM => parse_package_json_tasks(&dir_path, TaskType::NPM)?,
 | 
			
		||||
        TaskEngine::YARN => parse_package_json_tasks(&dir_path, TaskType::YARN)?,
 | 
			
		||||
        TaskEngine::NONE => parse_config_tasks(config_file_tasks)?,
 | 
			
		||||
        TaskEngine::AUTO => parse_discovered_tasks(&dir_path)?,
 | 
			
		||||
        TaskEngine::AUTO => parse_discovered_tasks(&dir_path, config_file_tasks)?,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let config: Config = Config { name, tasks, file_path, dir_path, directories };
 | 
			
		||||
| 
						 | 
				
			
			@ -181,12 +180,11 @@ fn parse_config_file(config_file: ConfigFile) -> Result<Config, String> {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
const PACKAGE_JSON_FILE: &str = "package.json";
 | 
			
		||||
const NPM_LOCK_FILE: &str = "package.lock";
 | 
			
		||||
const YARN_LOCK_FILE: &str = "yarn.lock";
 | 
			
		||||
const COMPOSER_JSON_FILE: &str = "composer.json";
 | 
			
		||||
 | 
			
		||||
fn parse_discovered_tasks(dir_path: &PathBuf) -> Result<ConfigTasks, String> {
 | 
			
		||||
    let mut config_tasks: ConfigTasks = vec![];
 | 
			
		||||
fn parse_discovered_tasks(dir_path: &PathBuf, config_file_tasks: ConfigFileTasks) -> Result<ConfigTasks, String> {
 | 
			
		||||
    let mut config_tasks: ConfigTasks = parse_config_tasks(config_file_tasks)?;
 | 
			
		||||
 | 
			
		||||
    // Gathering facts
 | 
			
		||||
    let has_composer_json = dir_path.join(COMPOSER_JSON_FILE).exists();
 | 
			
		||||
| 
						 | 
				
			
			@ -200,7 +198,7 @@ fn parse_discovered_tasks(dir_path: &PathBuf) -> Result<ConfigTasks, String> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    if has_package_json {
 | 
			
		||||
        let mut package_config_tasks: ConfigTasks;
 | 
			
		||||
        let package_config_tasks: ConfigTasks;
 | 
			
		||||
 | 
			
		||||
        if has_yarn_lock {
 | 
			
		||||
            package_config_tasks = parse_package_json_tasks(dir_path, TaskType::YARN)?;
 | 
			
		||||
| 
						 | 
				
			
			@ -331,10 +329,7 @@ fn get_config_glob_pattern(root_path: &Path, glob_pattern: &String) -> PathBuf {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
pub 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()))
 | 
			
		||||
    };
 | 
			
		||||
    let full_path = file::parse_path_string(path)?;
 | 
			
		||||
 | 
			
		||||
    if full_path.is_dir() {
 | 
			
		||||
        let config_file = find_config_file(full_path)?;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,8 @@
 | 
			
		|||
use std::path::PathBuf;
 | 
			
		||||
use std::fs::read_to_string;
 | 
			
		||||
use std::path::{Path, PathBuf};
 | 
			
		||||
use std::fs::{canonicalize, read_to_string, write};
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
 | 
			
		||||
pub fn read_file_content (path: PathBuf) -> Result<String, String> {
 | 
			
		||||
    match read_to_string(path) {
 | 
			
		||||
| 
						 | 
				
			
			@ -10,10 +11,16 @@ pub fn read_file_content (path: PathBuf) -> Result<String, String> {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn write_file_content(file_path: &PathBuf, content: &str) -> Result<(), String> {
 | 
			
		||||
    write(file_path, content).map_err(|err| format!("Failed to write to file: {}", err))?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn read_json_file<T: for<'a> Deserialize<'a>>(file_path: &PathBuf) -> Result<T, String> {
 | 
			
		||||
    let content = read_file_content(file_path.clone())?;
 | 
			
		||||
 | 
			
		||||
    let file_content: T = serde_json::from_str::<T>(&content).expect(format!("Failed to read the file: \"{:?}\"", file_path).as_str());
 | 
			
		||||
    let file_content: T = serde_json::from_str::<T>(&content).map_err(|err| err.to_string());
 | 
			
		||||
 | 
			
		||||
    Ok(file_content)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -21,44 +28,57 @@ pub fn read_json_file<T: for<'a> Deserialize<'a>>(file_path: &PathBuf) -> Result
 | 
			
		|||
fn read_yaml_file<T: for<'a> Deserialize<'a>>(file_path: &PathBuf) -> Result<T, String> {
 | 
			
		||||
    let content = read_file_content(file_path.clone())?;
 | 
			
		||||
 | 
			
		||||
    let file_content: T = serde_yaml::from_str::<T>(&content).expect(format!("Failed to read the file: \"{:?}\"", file_path).as_str());
 | 
			
		||||
    let file_content: T = serde_yaml::from_str::<T>(&content).map_err(|err| err.to_string())?;
 | 
			
		||||
 | 
			
		||||
    Ok(file_content)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Default, Deserialize)]
 | 
			
		||||
pub fn write_yaml_file<T: Serialize + Debug>(file_path: &PathBuf, data: &T) -> Result<(), String> {
 | 
			
		||||
    let yaml_content = serde_yaml::to_string(data).map_err(|err| err.to_string())?;
 | 
			
		||||
 | 
			
		||||
    write_file_content(file_path, &yaml_content)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
 | 
			
		||||
#[serde(rename_all = "lowercase")]
 | 
			
		||||
pub enum TaskEngine {
 | 
			
		||||
    #[serde(rename = "composer")]
 | 
			
		||||
    COMPOSER,
 | 
			
		||||
    #[serde(rename = "npm")]
 | 
			
		||||
    NPM,
 | 
			
		||||
    #[serde(rename = "yarn")]
 | 
			
		||||
    YARN,
 | 
			
		||||
    #[serde(rename = "none")]
 | 
			
		||||
    NONE,
 | 
			
		||||
    #[serde(rename = "auto")]
 | 
			
		||||
    #[default]
 | 
			
		||||
    AUTO,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub type ConfigFileTasks = HashMap<String, String>;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, Clone, Default)]
 | 
			
		||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
 | 
			
		||||
pub struct ConfigFile {
 | 
			
		||||
    pub(crate) name: String,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    #[serde(default, skip_serializing_if = "is_auto_task_engine")]
 | 
			
		||||
    pub(crate) task_engine: TaskEngine,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub(crate) directories: Vec<String>,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    #[serde(default, skip_serializing_if = "ConfigFileTasks::is_empty")]
 | 
			
		||||
    pub(crate) tasks: ConfigFileTasks,
 | 
			
		||||
    // The following fields are not part of the yaml file.
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    #[serde(default, skip_serializing_if = "skip_path")]
 | 
			
		||||
    pub(crate) __file_path: PathBuf,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    #[serde(default, skip_serializing_if = "skip_path")]
 | 
			
		||||
    pub(crate) __dir_path: PathBuf,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn is_auto_task_engine(value: &TaskEngine) -> bool {
 | 
			
		||||
    match value {
 | 
			
		||||
        TaskEngine::AUTO => true,
 | 
			
		||||
        _ => false,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn skip_path(path: &PathBuf) -> bool {
 | 
			
		||||
    path.to_str().map_or(true, |s| s.is_empty())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn read_config_file(config_file_path: PathBuf) -> Result<ConfigFile, String> {
 | 
			
		||||
    let mut config_file = read_yaml_file::<ConfigFile>(&config_file_path)?;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -68,3 +88,16 @@ pub fn read_config_file(config_file_path: PathBuf) -> Result<ConfigFile, String>
 | 
			
		|||
    Ok(config_file)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn write_config_file(config_file_path: PathBuf, config_file: ConfigFile) -> Result<(), String> {
 | 
			
		||||
    write_yaml_file::<ConfigFile>(&config_file_path, &config_file)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn parse_path_string<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()))
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Ok(full_path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue