From 254e35385ee2fa386971a2e9488af25f07fdc7ba Mon Sep 17 00:00:00 2001 From: David Kurilla Date: Sun, 2 Jun 2024 07:50:36 -0700 Subject: [PATCH 1/4] feat: create citrus-core crate --- crates/citrus-cli/Cargo.toml | 5 +- crates/citrus-core/Cargo.toml | 15 +++ crates/citrus-core/src/lib.rs | 192 ++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 crates/citrus-core/Cargo.toml create mode 100644 crates/citrus-core/src/lib.rs diff --git a/crates/citrus-cli/Cargo.toml b/crates/citrus-cli/Cargo.toml index 9e9b884..daa44a8 100644 --- a/crates/citrus-cli/Cargo.toml +++ b/crates/citrus-cli/Cargo.toml @@ -1,8 +1,9 @@ [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" diff --git a/crates/citrus-core/Cargo.toml b/crates/citrus-core/Cargo.toml new file mode 100644 index 0000000..b66fa91 --- /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 = "../../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..e931390 --- /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)] +struct Job { + name: String, + command: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Task { + name: String, + jobs: Vec, +} + +// Run Task +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 +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 +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 +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 +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 +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::*; +} From e29dc05d8da5daa070b1ba8847d645c0fe7d5f12 Mon Sep 17 00:00:00 2001 From: David Kurilla Date: Sun, 2 Jun 2024 07:53:59 -0700 Subject: [PATCH 2/4] feat: expose core functions --- crates/citrus-core/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/citrus-core/src/lib.rs b/crates/citrus-core/src/lib.rs index e931390..42ae1bd 100644 --- a/crates/citrus-core/src/lib.rs +++ b/crates/citrus-core/src/lib.rs @@ -6,19 +6,19 @@ use std::process::{Command, exit}; use toml::Value; #[derive(Debug, Serialize, Deserialize)] -struct Job { +pub struct Job { name: String, command: String, } #[derive(Debug, Serialize, Deserialize)] -struct Task { +pub struct Task { name: String, jobs: Vec, } // Run Task -fn run_task(name: &str) { +pub fn run_task(name: &str) { // Load the task from the JSON file let output_directory = match get_config_file() { Ok(dir) => dir, @@ -59,7 +59,7 @@ fn run_task(name: &str) { } // Create Task -fn create_task(name: &str, yaml_path: &str) { +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"); @@ -85,14 +85,14 @@ fn create_task(name: &str, yaml_path: &str) { } // Save Task -fn save_task(file_path: &str, task: &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 -fn delete_task(name: &str) { +pub fn delete_task(name: &str) { let output_directory = match get_config_file() { Ok(dir) => dir, Err(e) => { @@ -113,7 +113,7 @@ fn delete_task(name: &str) { } // List Tasks -fn list_tasks() { +pub fn list_tasks() { let output_directory = match get_config_file() { Ok(dir) => dir, Err(e) => { @@ -144,7 +144,7 @@ fn list_tasks() { } // Update Task -fn update_task(name: &str, yaml_path: &str) { +pub fn update_task(name: &str, yaml_path: &str) { let output_directory = match get_config_file() { Ok(dir) => dir, Err(e) => { From 4b40c6ad8f78930878c9feae9af236cf7c54cb30 Mon Sep 17 00:00:00 2001 From: David Kurilla Date: Sun, 2 Jun 2024 07:56:01 -0700 Subject: [PATCH 3/4] refactor: move README.md --- crates/citrus-core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/citrus-core/Cargo.toml b/crates/citrus-core/Cargo.toml index b66fa91..c1b4083 100644 --- a/crates/citrus-core/Cargo.toml +++ b/crates/citrus-core/Cargo.toml @@ -6,7 +6,7 @@ rust-version.workspace = true license.workspace = true description = "A library of simple task managing functions." repository = "https://github.com/davidkurilla/citrus" -readme = "../../README.md" +readme = "../../docs/README.md" [dependencies] serde = { version = "1.0", features = ["derive"] } From 98a0c8bc41181df9ca16d8e26f55dc26960367ba Mon Sep 17 00:00:00 2001 From: David Kurilla Date: Sun, 2 Jun 2024 08:00:41 -0700 Subject: [PATCH 4/4] refactor: use citrus-core lib --- crates/citrus-cli/Cargo.toml | 7 +- crates/citrus-cli/src/main.rs | 190 +--------------------------------- 2 files changed, 7 insertions(+), 190 deletions(-) diff --git a/crates/citrus-cli/Cargo.toml b/crates/citrus-cli/Cargo.toml index daa44a8..2e81d52 100644 --- a/crates/citrus-cli/Cargo.toml +++ b/crates/citrus-cli/Cargo.toml @@ -6,11 +6,8 @@ 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