diff --git a/crates/citrus-cli/Cargo.toml b/crates/citrus-cli/Cargo.toml index 9e9b884..2e81d52 100644 --- a/crates/citrus-cli/Cargo.toml +++ b/crates/citrus-cli/Cargo.toml @@ -1,15 +1,13 @@ [package] name = "citrus-cli" version = "0.1.4" -edition = "2021" -license = "MIT" +edition.workspace = true +rust-version.workspace = true +license.workspace = true description = "A simple task managing tool that can be configured with YAML files" repository = "https://github.com/davidkurilla/citrus" -readme = "../../README.md" +readme = "../../docs/README.md" [dependencies] clap = "3" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -serde_yaml = "0.9" -toml = "0.8.13" +citrus-core = "0.1.0" diff --git a/crates/citrus-cli/src/main.rs b/crates/citrus-cli/src/main.rs index 18ee678..afcbf51 100644 --- a/crates/citrus-cli/src/main.rs +++ b/crates/citrus-cli/src/main.rs @@ -1,22 +1,4 @@ use clap::{App, Arg, SubCommand}; -use serde::{Deserialize, Serialize}; -use std::fs::{self, File}; -use std::io::{Read, Write}; -use std::path::Path; -use std::process::{Command, exit}; -use toml::Value; - -#[derive(Debug, Serialize, Deserialize)] -struct Job { - name: String, - command: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct Task { - name: String, - jobs: Vec, -} fn main() { let matches = App::new("citrus") @@ -72,189 +54,27 @@ fn main() { match matches.subcommand() { Some(("run", sub_m)) => { let name = sub_m.value_of("name").unwrap(); - run_task(name); + citrus_core::run_task(name); } Some(("create", sub_m)) => { let name = sub_m.value_of("name").unwrap(); let yaml_path = sub_m.value_of("yaml").unwrap(); - create_task(name, yaml_path); + citrus_core::create_task(name, yaml_path); } Some(("list", _)) => { - list_tasks(); + citrus_core::list_tasks(); } Some(("delete", sub_m)) => { let name = sub_m.value_of("name").unwrap(); - delete_task(name); + citrus_core::delete_task(name); } Some(("update", sub_m)) => { let name = sub_m.value_of("name").unwrap(); let yaml_path = sub_m.value_of("yaml").unwrap(); - update_task(name, yaml_path); + citrus_core::update_task(name, yaml_path); } _ => { println!("Welcome to citrus!"); } } } - -fn run_task(name: &str) { - // Load the task from the JSON file - let output_directory = match get_config_file() { - Ok(dir) => dir, - Err(e) => { - eprintln!("Error getting config file: {}", e); - return; - } - }; - let file_path = format!("{}/{}.json", output_directory, name); - let mut file = match File::open(&file_path) { - Ok(file) => file, - Err(err) => { - eprintln!("Error: Unable to open task file '{}'. Error: {}", file_path, err); - exit(1); - } - }; - let mut contents = String::new(); - file.read_to_string(&mut contents).expect("Unable to read task file"); - let task: Task = serde_json::from_str(&contents).expect("Unable to parse task file"); - - for job in task.jobs { - println!("Executing job: {}", job.name); - let mut parts = job.command.split_whitespace(); - let command = parts.next().expect("Invalid command"); - let args: Vec<&str> = parts.collect(); - - let status = Command::new(command) - .args(&args) - .status() - .expect("Failed to execute command"); - - if !status.success() { - eprintln!("Error: Job '{}' failed with exit code {}", job.name, status); - exit(1); - } - } - println!("Task '{}' completed successfully.", task.name); -} - -fn create_task(name: &str, yaml_path: &str) { - let mut file = File::open(yaml_path).expect("Unable to open YAML file"); - let mut contents = String::new(); - file.read_to_string(&mut contents).expect("Unable to read YAML file"); - let jobs: Vec = serde_yaml::from_str(&contents).expect("Unable to parse YAML file"); - - let task = Task { - name: name.to_string(), - jobs, - }; - - let output_directory = match get_config_file() { - Ok(dir) => dir, - Err(e) => { - eprintln!("Error getting config file: {}", e); - return; - } - }; - - let file_path = format!("{}/{}.json", output_directory, name); - save_task(&file_path, &task); - - println!("Created task: {:?}", task); -} - -fn save_task(file_path: &str, task: &Task) { - let content = serde_json::to_string_pretty(task).expect("Unable to serialize task"); - let mut file = File::create(file_path).expect("Unable to create file"); - file.write_all(content.as_bytes()).expect("Unable to write file"); -} - -fn delete_task(name: &str) { - let output_directory = match get_config_file() { - Ok(dir) => dir, - Err(e) => { - eprintln!("Error getting config file: {}", e); - return; - } - }; - let file_path = format!("{}/{}.json", output_directory, name); - - if Path::new(&file_path).exists() { - match fs::remove_file(&file_path) { - Ok(_) => println!("Task '{}' deleted successfully.", name), - Err(err) => eprintln!("Error: Unable to delete task file '{}'. Error: {}", file_path, err), - } - } else { - eprintln!("Error: Task '{}' does not exist.", name); - } -} - -fn list_tasks() { - let output_directory = match get_config_file() { - Ok(dir) => dir, - Err(e) => { - eprintln!("Error getting config file: {}", e); - return; - } - }; - let paths = fs::read_dir(output_directory).expect("Unable to read directory"); - - let mut tasks = Vec::new(); - for path in paths { - let path = path.expect("Unable to read path").path(); - if path.extension().and_then(|s| s.to_str()) == Some("json") { - if let Some(task_name) = path.file_stem().and_then(|s| s.to_str()) { - tasks.push(task_name.to_string()); - } - } - } - - if tasks.is_empty() { - println!("No tasks found."); - } else { - println!("Tasks:"); - for task in tasks { - println!("- {}", task); - } - } -} - -fn update_task(name: &str, yaml_path: &str) { - let output_directory = match get_config_file() { - Ok(dir) => dir, - Err(e) => { - eprintln!("Error getting config file: {}", e); - return; - } - }; - let file_path = format!("{}/{}.json", output_directory, name); - if !Path::new(&file_path).exists() { - eprintln!("Error: Task '{}' does not exist.", name); - exit(1); - } - - let mut file = File::open(yaml_path).expect("Unable to open YAML file"); - let mut contents = String::new(); - file.read_to_string(&mut contents).expect("Unable to read YAML file"); - let jobs: Vec = serde_yaml::from_str(&contents).expect("Unable to parse YAML file"); - - let task = Task { - name: name.to_string(), - jobs, - }; - - save_task(&file_path, &task); - - println!("Updated task: {:?}", task); -} - -fn get_config_file() -> Result { - let mut config_file = File::open("citrus-config.toml").expect("Could not locate 'citrus-config.toml' in the current directory"); - let mut contents = String::new(); - config_file.read_to_string(&mut contents).expect("Could not read file!"); - let value: Value = toml::from_str(&contents).expect("Could not parse TOML file!"); - if let Some(task_directory) = value.get("config").and_then(|config| config.get("task_directory")).and_then(|v| v.as_str()) { - Ok(task_directory.to_string()) - } else { - Err("Could not find 'task_directory' key in TOML file or it is not a string") - } -} \ No newline at end of file diff --git a/crates/citrus-core/Cargo.toml b/crates/citrus-core/Cargo.toml new file mode 100644 index 0000000..c1b4083 --- /dev/null +++ b/crates/citrus-core/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "citrus-core" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +description = "A library of simple task managing functions." +repository = "https://github.com/davidkurilla/citrus" +readme = "../../docs/README.md" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_yaml = "0.9" +toml = "0.8.13" \ No newline at end of file diff --git a/crates/citrus-core/src/lib.rs b/crates/citrus-core/src/lib.rs new file mode 100644 index 0000000..42ae1bd --- /dev/null +++ b/crates/citrus-core/src/lib.rs @@ -0,0 +1,192 @@ +use serde::{Deserialize, Serialize}; +use std::fs::{self, File}; +use std::io::{Read, Write}; +use std::path::Path; +use std::process::{Command, exit}; +use toml::Value; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Job { + name: String, + command: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Task { + name: String, + jobs: Vec, +} + +// Run Task +pub fn run_task(name: &str) { + // Load the task from the JSON file + let output_directory = match get_config_file() { + Ok(dir) => dir, + Err(e) => { + eprintln!("Error getting config file: {}", e); + return; + } + }; + let file_path = format!("{}/{}.json", output_directory, name); + let mut file = match File::open(&file_path) { + Ok(file) => file, + Err(err) => { + eprintln!("Error: Unable to open task file '{}'. Error: {}", file_path, err); + exit(1); + } + }; + let mut contents = String::new(); + file.read_to_string(&mut contents).expect("Unable to read task file"); + let task: Task = serde_json::from_str(&contents).expect("Unable to parse task file"); + + for job in task.jobs { + println!("Executing job: {}", job.name); + let mut parts = job.command.split_whitespace(); + let command = parts.next().expect("Invalid command"); + let args: Vec<&str> = parts.collect(); + + let status = Command::new(command) + .args(&args) + .status() + .expect("Failed to execute command"); + + if !status.success() { + eprintln!("Error: Job '{}' failed with exit code {}", job.name, status); + exit(1); + } + } + println!("Task '{}' completed successfully.", task.name); +} + +// Create Task +pub fn create_task(name: &str, yaml_path: &str) { + let mut file = File::open(yaml_path).expect("Unable to open YAML file"); + let mut contents = String::new(); + file.read_to_string(&mut contents).expect("Unable to read YAML file"); + let jobs: Vec = serde_yaml::from_str(&contents).expect("Unable to parse YAML file"); + + let task = Task { + name: name.to_string(), + jobs, + }; + + let output_directory = match get_config_file() { + Ok(dir) => dir, + Err(e) => { + eprintln!("Error getting config file: {}", e); + return; + } + }; + + let file_path = format!("{}/{}.json", output_directory, name); + save_task(&file_path, &task); + + println!("Created task: {:?}", task); +} + +// Save Task +pub fn save_task(file_path: &str, task: &Task) { + let content = serde_json::to_string_pretty(task).expect("Unable to serialize task"); + let mut file = File::create(file_path).expect("Unable to create file"); + file.write_all(content.as_bytes()).expect("Unable to write file"); +} + +// Delete Task +pub fn delete_task(name: &str) { + let output_directory = match get_config_file() { + Ok(dir) => dir, + Err(e) => { + eprintln!("Error getting config file: {}", e); + return; + } + }; + let file_path = format!("{}/{}.json", output_directory, name); + + if Path::new(&file_path).exists() { + match fs::remove_file(&file_path) { + Ok(_) => println!("Task '{}' deleted successfully.", name), + Err(err) => eprintln!("Error: Unable to delete task file '{}'. Error: {}", file_path, err), + } + } else { + eprintln!("Error: Task '{}' does not exist.", name); + } +} + +// List Tasks +pub fn list_tasks() { + let output_directory = match get_config_file() { + Ok(dir) => dir, + Err(e) => { + eprintln!("Error getting config file: {}", e); + return; + } + }; + let paths = fs::read_dir(output_directory).expect("Unable to read directory"); + + let mut tasks = Vec::new(); + for path in paths { + let path = path.expect("Unable to read path").path(); + if path.extension().and_then(|s| s.to_str()) == Some("json") { + if let Some(task_name) = path.file_stem().and_then(|s| s.to_str()) { + tasks.push(task_name.to_string()); + } + } + } + + if tasks.is_empty() { + println!("No tasks found."); + } else { + println!("Tasks:"); + for task in tasks { + println!("- {}", task); + } + } +} + +// Update Task +pub fn update_task(name: &str, yaml_path: &str) { + let output_directory = match get_config_file() { + Ok(dir) => dir, + Err(e) => { + eprintln!("Error getting config file: {}", e); + return; + } + }; + let file_path = format!("{}/{}.json", output_directory, name); + if !Path::new(&file_path).exists() { + eprintln!("Error: Task '{}' does not exist.", name); + exit(1); + } + + let mut file = File::open(yaml_path).expect("Unable to open YAML file"); + let mut contents = String::new(); + file.read_to_string(&mut contents).expect("Unable to read YAML file"); + let jobs: Vec = serde_yaml::from_str(&contents).expect("Unable to parse YAML file"); + + let task = Task { + name: name.to_string(), + jobs, + }; + + save_task(&file_path, &task); + + println!("Updated task: {:?}", task); +} + +// Get Config File +fn get_config_file() -> Result { + let mut config_file = File::open("citrus-config.toml").expect("Could not locate 'citrus-config.toml' in the current directory"); + let mut contents = String::new(); + config_file.read_to_string(&mut contents).expect("Could not read file!"); + let value: Value = toml::from_str(&contents).expect("Could not parse TOML file!"); + if let Some(task_directory) = value.get("config").and_then(|config| config.get("task_directory")).and_then(|v| v.as_str()) { + Ok(task_directory.to_string()) + } else { + Err("Could not find 'task_directory' key in TOML file or it is not a string") + } +} + +#[cfg(test)] +mod tests { + use super::*; +}