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
20pub fn run_task(name: &str) {
22 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
61pub 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
87pub 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
94pub 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
115pub 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
146pub 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
176fn 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
189pub 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}