citrus_core/
lib.rs

1use serde::{Deserialize, Serialize};
2use std::fs::{self, File};
3use std::io::{Read, Write};
4use std::path::Path;
5use std::process::{Command, exit};
6use toml::Value;
7
8#[derive(Debug, Serialize, Deserialize)]
9pub struct Job {
10    name: String,
11    command: String,
12}
13
14#[derive(Debug, Serialize, Deserialize)]
15pub struct Task {
16    name: String,
17    jobs: Vec<Job>,
18}
19
20// Run Task
21pub fn run_task(name: &str) {
22    // Load the task from the JSON file
23    let output_directory = match get_config_file() {
24        Ok(dir) => dir,
25        Err(e) => {
26            eprintln!("Error getting config file: {}", e);
27            return;
28        }
29    };
30    let file_path = format!("{}/{}.json", output_directory, name);
31    let mut file = match File::open(&file_path) {
32        Ok(file) => file,
33        Err(err) => {
34            eprintln!("Error: Unable to open task file '{}'. Error: {}", file_path, err);
35            exit(1);
36        }
37    };
38    let mut contents = String::new();
39    file.read_to_string(&mut contents).expect("Unable to read task file");
40    let task: Task = serde_json::from_str(&contents).expect("Unable to parse task file");
41
42    for job in task.jobs {
43        println!("Executing job: {}", job.name);
44        let mut parts = job.command.split_whitespace();
45        let command = parts.next().expect("Invalid command");
46        let args: Vec<&str> = parts.collect();
47
48        let status = Command::new(command)
49            .args(&args)
50            .status()
51            .expect("Failed to execute command");
52
53        if !status.success() {
54            eprintln!("Error: Job '{}' failed with exit code {}", job.name, status);
55            exit(1);
56        }
57    }
58    println!("Task '{}' completed successfully.", task.name);
59}
60
61// Create Task
62pub fn create_task(name: &str, yaml_path: &str) {
63    let mut file = File::open(yaml_path).expect("Unable to open YAML file");
64    let mut contents = String::new();
65    file.read_to_string(&mut contents).expect("Unable to read YAML file");
66    let jobs: Vec<Job> = serde_yaml::from_str(&contents).expect("Unable to parse YAML file");
67
68    let task = Task {
69        name: name.to_string(),
70        jobs,
71    };
72
73    let output_directory = match get_config_file() {
74        Ok(dir) => dir,
75        Err(e) => {
76            eprintln!("Error getting config file: {}", e);
77            return;
78        }
79    };
80
81    let file_path = format!("{}/{}.json", output_directory, name);
82    save_task(&file_path, &task);
83
84    println!("Created task: {:?}", task);
85}
86
87// Save Task
88pub fn save_task(file_path: &str, task: &Task) {
89    let content = serde_json::to_string_pretty(task).expect("Unable to serialize task");
90    let mut file = File::create(file_path).expect("Unable to create file");
91    file.write_all(content.as_bytes()).expect("Unable to write file");
92}
93
94// Delete Task
95pub fn delete_task(name: &str) {
96    let output_directory = match get_config_file() {
97        Ok(dir) => dir,
98        Err(e) => {
99            eprintln!("Error getting config file: {}", e);
100            return;
101        }
102    };
103    let file_path = format!("{}/{}.json", output_directory, name);
104
105    if Path::new(&file_path).exists() {
106        match fs::remove_file(&file_path) {
107            Ok(_) => println!("Task '{}' deleted successfully.", name),
108            Err(err) => eprintln!("Error: Unable to delete task file '{}'. Error: {}", file_path, err),
109        }
110    } else {
111        eprintln!("Error: Task '{}' does not exist.", name);
112    }
113}
114
115// List Tasks
116pub fn list_tasks() {
117    let output_directory = match get_config_file() {
118        Ok(dir) => dir,
119        Err(e) => {
120            eprintln!("Error getting config file: {}", e);
121            return;
122        }
123    };
124    let paths = fs::read_dir(output_directory).expect("Unable to read directory");
125
126    let mut tasks = Vec::new();
127    for path in paths {
128        let path = path.expect("Unable to read path").path();
129        if path.extension().and_then(|s| s.to_str()) == Some("json") {
130            if let Some(task_name) = path.file_stem().and_then(|s| s.to_str()) {
131                tasks.push(task_name.to_string());
132            }
133        }
134    }
135
136    if tasks.is_empty() {
137        println!("No tasks found.");
138    } else {
139        println!("Tasks:");
140        for task in tasks {
141            println!("- {}", task);
142        }
143    }
144}
145
146// Update Task
147pub fn update_task(name: &str, yaml_path: &str) {
148    let output_directory = match get_config_file() {
149        Ok(dir) => dir,
150        Err(e) => {
151            eprintln!("Error getting config file: {}", e);
152            return;
153        }
154    };
155    let file_path = format!("{}/{}.json", output_directory, name);
156    if !Path::new(&file_path).exists() {
157        eprintln!("Error: Task '{}' does not exist.", name);
158        exit(1);
159    }
160
161    let mut file = File::open(yaml_path).expect("Unable to open YAML file");
162    let mut contents = String::new();
163    file.read_to_string(&mut contents).expect("Unable to read YAML file");
164    let jobs: Vec<Job> = serde_yaml::from_str(&contents).expect("Unable to parse YAML file");
165
166    let task = Task {
167        name: name.to_string(),
168        jobs,
169    };
170
171    save_task(&file_path, &task);
172
173    println!("Updated task: {:?}", task);
174}
175
176// Get Config File
177fn get_config_file() -> Result<String, &'static str> {
178    let mut config_file = File::open("citrus-config.toml").expect("Could not locate 'citrus-config.toml' in the current directory");
179    let mut contents = String::new();
180    config_file.read_to_string(&mut contents).expect("Could not read file!");
181    let value: Value = toml::from_str(&contents).expect("Could not parse TOML file!");
182    if let Some(task_directory) = value.get("config").and_then(|config| config.get("task_directory")).and_then(|v| v.as_str()) {
183        Ok(task_directory.to_string())
184    } else {
185        Err("Could not find 'task_directory' key in TOML file or it is not a string")
186    }
187}
188
189// Init
190pub fn init() {
191
192    let file_data = "[config]\ntask_directory = \".citrus\"";
193
194    let _config_file = match File::open("citrus-config.toml") {
195        Ok(_file) => {
196            println!("'citrus-config.toml' file found. Project already initialized.");
197            return;
198        }
199        Err(_) => {
200            let mut file = File::create("citrus-config.toml").expect("Cannot create 'citrus-config.toml'");
201            file.write_all(file_data.as_bytes()).expect("Cannot write to 'citrus-config.toml'");
202            let _dir = std::fs::create_dir(".citrus");
203            file
204        }
205    };
206
207    println!("Project successfully initialized!");
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213}