use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use std::collections::{HashMap, HashSet};
use std::env;
use std::fs;
use std::path::Path;
use std::process::{exit, Command};
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
#[derive(Debug, Clone)]
struct Task {
name: String,
command: String,
description: String,
runner: String,
depends_on: Vec<String>,
pre_hook: Option<String>,
post_hook: Option<String>,
}
#[derive(Debug)]
struct TaskResult {
name: String,
success: bool,
exit_code: Option<i32>,
}
fn main() {
let args: Vec<String> = env::args().collect();
println!("π Flux v0.5.0 - Task Runner (Watch Mode + Dependencies + Hooks)");
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
if args.len() < 2 || args[1] == "help" || args[1] == "--help" || args[1] == "-h" {
show_help();
exit(0);
}
let task_input = &args[1];
let task_args: Vec<String> = args.iter().skip(2).cloned().collect();
if task_input == "graph" {
show_dependency_graph();
exit(0);
}
if task_input == "watch" {
if task_args.is_empty() {
eprintln!("β KullanΔ±m: flux watch <task> [dirs...]");
eprintln!("Γrnek: flux watch build src/ tests/");
exit(1);
}
let task_to_watch = &task_args[0];
let dirs_to_watch: Vec<String> = task_args.iter().skip(1).cloned().collect();
watch_mode(task_to_watch, &dirs_to_watch);
exit(0);
}
let dry_run = task_args.contains(&"--dry-run".to_string());
let filtered_args: Vec<String> = task_args.into_iter().filter(|a| a != "--dry-run").collect();
if task_input.contains(',') {
let tasks: Vec<String> = task_input
.split(',')
.map(|s| s.trim().to_string())
.collect();
run_parallel_tasks(&tasks, &filtered_args, dry_run);
} else {
match task_input.as_str() {
"runners" => {
show_runners();
exit(0);
}
_ => {
let success = run_task_with_dependencies(
task_input,
&filtered_args,
dry_run,
&mut HashSet::new(),
);
exit(if success { 0 } else { 1 });
}
}
}
}
fn show_help() {
println!("KullanΔ±m: flux <task> [args...]");
println!(" flux <task1,task2,...> [args...] (Paralel)");
println!(" flux <task> --dry-run (SimΓΌlasyon)");
println!(" flux watch <task> [dirs...] (Watch Mode - v0.5.0)");
println!("");
println!("Komutlar:");
println!(" flux help - Bu yardΔ±m mesajΔ±");
println!(" flux runners - TΓΌm runner'larΔ± listele");
println!(" flux graph - Dependency graph gΓΆster");
println!(" flux watch <task> - Dosya deΔiΕikliklerini izle ve task Γ§alΔ±ΕtΔ±r (v0.5.0)");
println!(" flux <task> - Task'i Γ§alΔ±ΕtΔ±r (dependency'ler otomatik)");
println!(" flux <task> --dry-run - ΓalΔ±ΕtΔ±rmadan planΔ± gΓΆster");
println!("");
println!("Γrnekler:");
println!(" flux build");
println!(" flux build --release");
println!(" flux build,test # Paralel");
println!(" flux watch build src/ # Watch mode (v0.5.0)");
println!(" flux watch test . # TΓΌm dizini izle");
println!("");
println!("Config: flux.yaml");
}
fn show_runners() {
let config = match load_config("flux.yaml") {
Ok(c) => c,
Err(e) => {
eprintln!("β Config okunamadΔ±: {}", e);
exit(1);
}
};
let mut runners: HashMap<String, Vec<&Task>> = HashMap::new();
for task in config.values() {
runners.entry(task.runner.clone()).or_default().push(task);
}
println!("Desteklenen Runner'lar:");
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
for (runner, tasks) in &runners {
println!("\nπ§ {} ({} task)", runner, tasks.len());
for task in tasks {
let deps = if task.depends_on.is_empty() {
String::new()
} else {
format!(" [deps: {}]", task.depends_on.join(", "))
};
println!(" β’ {} - {}{}", task.name, task.description, deps);
}
}
println!("\nββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("Toplam: {} runner, {} task", runners.len(), config.len());
}
fn show_dependency_graph() {
let config = match load_config("flux.yaml") {
Ok(c) => c,
Err(e) => {
eprintln!("β Config okunamadΔ±: {}", e);
exit(1);
}
};
println!("π Dependency Graph (v0.4.0):");
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
for (name, task) in &config {
if !task.depends_on.is_empty() {
println!("\nπ¦ {}", name);
for dep in &task.depends_on {
println!(" ββ> {}", dep);
}
if let Some(ref pre) = task.pre_hook {
println!(" [pre: {}]", pre);
}
if let Some(ref post) = task.post_hook {
println!(" [post: {}]", post);
}
}
}
let independent: Vec<_> = config
.values()
.filter(|t| t.depends_on.is_empty())
.map(|t| t.name.clone())
.collect();
if !independent.is_empty() {
println!("\nπ― BaΔΔ±msΔ±z task'ler:");
for name in independent {
println!(" β’ {}", name);
}
}
println!("\nββββββββββββββββββββββββββββββββββββββββββββββββββ");
}
fn run_task_with_dependencies(
task_name: &str,
args: &[String],
dry_run: bool,
executed: &mut HashSet<String>,
) -> bool {
if executed.contains(task_name) {
return true;
}
let config = match load_config("flux.yaml") {
Ok(c) => c,
Err(e) => {
eprintln!("β Config okunamadΔ±: {}", e);
exit(1);
}
};
let task = match config.get(task_name) {
Some(t) => t.clone(),
None => {
eprintln!("β Hata: Bilinmeyen task '{}'", task_name);
eprintln!("");
eprintln!("Mevcut task'ler:");
for name in config.keys() {
println!(" β’ {}", name);
}
exit(1);
}
};
if !task.depends_on.is_empty() {
println!("π '{}' baΔΔ±mlΔ±lΔ±klarΔ±: {:?}", task_name, task.depends_on);
for dep in &task.depends_on {
let success = run_task_with_dependencies(dep, args, dry_run, executed);
if !success {
return false;
}
}
}
if !executed.contains(task_name) {
if dry_run {
println!("[DRY-RUN] {} Γ§alΔ±ΕtΔ±rΔ±lacak", task_name);
if let Some(ref pre) = task.pre_hook {
println!("[DRY-RUN] Pre-hook: {}", pre);
}
println!("[DRY-RUN] Command: {}", task.command);
if let Some(ref post) = task.post_hook {
println!("[DRY-RUN] Post-hook: {}", post);
}
executed.insert(task_name.to_string());
return true;
} else {
let success = run_single_task_internal(&task, args);
executed.insert(task_name.to_string());
return success;
}
}
true
}
fn run_single_task(task: &Task, args: &[String]) {
let success = run_single_task_internal(task, args);
if success {
exit(0);
} else {
exit(1);
}
}
fn run_single_task_internal(task: &Task, args: &[String]) -> bool {
println!("");
println!("π Task: {}", task.name);
println!("π§ Runner: {}", task.runner);
println!("π AΓ§Δ±klama: {}", task.description);
if !task.depends_on.is_empty() {
println!("π Dependencies: {}", task.depends_on.join(", "));
}
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
if let Some(ref pre_hook) = task.pre_hook {
println!("β‘ Pre-hook: {}", pre_hook);
let status = Command::new("sh").arg("-c").arg(pre_hook).status();
if let Ok(status) = status {
if !status.success() {
println!("β οΈ Pre-hook baΕarΔ±sΔ±z (devam ediliyor)");
}
}
println!("");
}
let full_command = if args.is_empty() {
task.command.clone()
} else {
format!("{} {}", task.command, args.join(" "))
};
println!("β Running: {}", full_command);
let output = Command::new("sh").arg("-c").arg(&full_command).status();
match output {
Ok(status) => {
if status.success() {
println!("");
println!("β
Task baΕarΔ±yla tamamlandΔ±: {}", task.name);
if let Some(ref post_hook) = task.post_hook {
println!("");
println!("β‘ Post-hook: {}", post_hook);
let post_status = Command::new("sh").arg("-c").arg(post_hook).status();
if let Ok(post_status) = post_status {
if !post_status.success() {
println!("β οΈ Post-hook baΕarΔ±sΔ±z");
}
}
}
true
} else {
println!("");
println!(
"β Task baΕarΔ±sΔ±z oldu: {} (exit code: {:?})",
task.name,
status.code()
);
false
}
}
Err(e) => {
eprintln!("β Komut Γ§alΔ±ΕtΔ±rma hatasΔ±: {}", e);
false
}
}
}
fn run_parallel_tasks(task_names: &[String], args: &[String], dry_run: bool) {
if dry_run {
println!("[DRY-RUN] Paralel Γ§alΔ±ΕtΔ±rma: {:?}", task_names);
for name in task_names {
println!("[DRY-RUN] β’ {}", name);
}
exit(0);
}
let config = match load_config("flux.yaml") {
Ok(c) => c,
Err(e) => {
eprintln!("β Config okunamadΔ±: {}", e);
exit(1);
}
};
println!("β‘ Paralel ΓalΔ±ΕtΔ±rma: {:?}", task_names);
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
let mut tasks_to_run: Vec<Task> = Vec::new();
for name in task_names {
match config.get(name) {
Some(task) => tasks_to_run.push(task.clone()),
None => {
eprintln!("β Bilinmeyen task: {}", name);
exit(1);
}
}
}
let results: Arc<Mutex<Vec<TaskResult>>> = Arc::new(Mutex::new(Vec::new()));
let mut handles = vec![];
for task in tasks_to_run {
let args_clone = args.to_vec();
let results_clone = Arc::clone(&results);
let handle = thread::spawn(move || {
println!("π [{}] BaΕlatΔ±lΔ±yor...", task.name);
let full_command = if args_clone.is_empty() {
task.command.clone()
} else {
format!("{} {}", task.command, args_clone.join(" "))
};
let output = Command::new("sh")
.arg("-c")
.arg(&full_command)
.output();
let result = match output {
Ok(output) => {
let success = output.status.success();
let exit_code = output.status.code();
if success {
println!("β
[{}] TamamlandΔ±", task.name);
} else {
println!("β [{}] BaΕarΔ±sΔ±z (exit: {:?})", task.name, exit_code);
}
TaskResult {
name: task.name.clone(),
success,
exit_code,
}
}
Err(e) => {
println!("β [{}] Hata: {}", task.name, e);
TaskResult {
name: task.name.clone(),
success: false,
exit_code: Some(1),
}
}
};
results_clone.lock().unwrap().push(result);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let final_results = results.lock().unwrap();
println!("");
println!("π Paralel ΓalΔ±ΕtΔ±rma SonuΓ§larΔ±:");
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
let mut all_success = true;
for result in final_results.iter() {
let status = if result.success { "β
" } else { "β" };
println!("{} {} (exit: {:?})", status, result.name, result.exit_code);
if !result.success {
all_success = false;
}
}
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
if all_success {
println!("π TΓΌm task'ler baΕarΔ±yla tamamlandΔ±!");
exit(0);
} else {
println!("β οΈ BazΔ± task'ler baΕarΔ±sΔ±z oldu");
exit(1);
}
}
fn load_config(path: &str) -> Result<HashMap<String, Task>, Box<dyn std::error::Error>> {
let content = fs::read_to_string(path)?;
let mut tasks = HashMap::new();
let mut current_task: Option<String> = None;
let mut current_command = String::new();
let mut current_description = String::new();
let mut current_runner = String::from("shell");
let mut current_depends_on: Vec<String> = Vec::new();
let mut current_pre_hook: Option<String> = None;
let mut current_post_hook: Option<String> = None;
for line in content.lines() {
let trimmed = line.trim();
if trimmed.ends_with(':')
&& !trimmed.starts_with(' ')
&& !trimmed.starts_with('\t')
&& !trimmed.starts_with("command:")
&& !trimmed.starts_with("description:")
&& !trimmed.starts_with("runner:")
&& !trimmed.starts_with("depends_on:")
&& !trimmed.starts_with("pre_hook:")
&& !trimmed.starts_with("post_hook:")
{
if let Some(name) = current_task.take() {
tasks.insert(
name.clone(),
Task {
name,
command: current_command.trim().to_string(),
description: current_description.trim().to_string(),
runner: current_runner.clone(),
depends_on: current_depends_on.clone(),
pre_hook: current_pre_hook.clone(),
post_hook: current_post_hook.clone(),
},
);
}
current_task = Some(trimmed.trim_end_matches(':').to_string());
current_command.clear();
current_description.clear();
current_runner = String::from("shell");
current_depends_on.clear();
current_pre_hook = None;
current_post_hook = None;
} else if let Some(ref _name) = current_task {
if trimmed.starts_with("command:") {
current_command = extract_value(trimmed, "command:");
} else if trimmed.starts_with("description:") {
current_description = extract_value(trimmed, "description:");
} else if trimmed.starts_with("runner:") {
current_runner = extract_value(trimmed, "runner:");
} else if trimmed.starts_with("depends_on:") {
let deps_str = extract_value(trimmed, "depends_on:");
let cleaned = deps_str.replace("[", "").replace("]", "").replace("\"", "");
current_depends_on = cleaned
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
} else if trimmed.starts_with("pre_hook:") {
current_pre_hook = Some(extract_value(trimmed, "pre_hook:"));
} else if trimmed.starts_with("post_hook:") {
current_post_hook = Some(extract_value(trimmed, "post_hook:"));
}
}
}
if let Some(name) = current_task {
tasks.insert(
name.clone(),
Task {
name,
command: current_command.trim().to_string(),
description: current_description.trim().to_string(),
runner: current_runner,
depends_on: current_depends_on,
pre_hook: current_pre_hook,
post_hook: current_post_hook,
},
);
}
Ok(tasks)
}
fn extract_value(line: &str, prefix: &str) -> String {
line.splitn(2, prefix)
.nth(1)
.unwrap_or("")
.trim()
.trim_matches('"')
.trim_matches('\'')
.to_string()
}
fn watch_mode(task_name: &str, dirs: &[String]) {
println!("ποΈ Watch Mode (v0.5.0)");
println!("π Δ°zlenecek task: {}", task_name);
println!("π Δ°zlenecek dizinler: {:?}", dirs);
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
let watch_dirs: Vec<&Path> = if dirs.is_empty() {
vec![Path::new(".")]
} else {
dirs.iter().map(|d| Path::new(d)).collect()
};
for dir in &watch_dirs {
if !dir.exists() {
eprintln!("β Dizin bulunamadΔ±: {:?}", dir);
exit(1);
}
}
println!("π Δ°lk Γ§alΔ±ΕtΔ±rma...");
let _ = run_task_with_dependencies(task_name, &[], false, &mut HashSet::new());
println!("");
println!("π Dosya deΔiΕiklikleri izleniyor... (Ctrl+C ile Γ§Δ±k)");
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
let (tx, rx) = channel::<Result<Event, notify::Error>>();
let mut watcher: RecommendedWatcher = Watcher::new(
tx,
Config::default().with_poll_interval(Duration::from_secs(1)),
)
.expect("Watcher oluΕturulamadΔ±");
for dir in &watch_dirs {
watcher
.watch(dir, RecursiveMode::Recursive)
.expect(&format!("Dizin izlenemedi: {:?}", dir));
}
let mut last_run = std::time::Instant::now();
let debounce_duration = Duration::from_secs(2);
loop {
match rx.recv() {
Ok(Ok(event)) => {
match event.kind {
notify::EventKind::Modify(_) | notify::EventKind::Create(_) => {
let paths: Vec<_> = event.paths.iter()
.filter_map(|p| p.file_name())
.filter_map(|n| n.to_str())
.collect();
if !paths.is_empty() {
if last_run.elapsed() >= debounce_duration {
println!("");
println!("π DeΔiΕiklik tespit edildi: {:?}", paths);
println!("β° {}", chrono::Local::now().format("%H:%M:%S"));
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
let _ = run_task_with_dependencies(
task_name,
&[],
false,
&mut HashSet::new(),
);
last_run = std::time::Instant::now();
println!("");
println!("π Δ°zlemeye devam ediliyor...");
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
}
}
}
_ => {}
}
}
Ok(Err(e)) => {
eprintln!("β οΈ Watch error: {:?}", e);
}
Err(e) => {
eprintln!("β Kanal hatasΔ±: {:?}", e);
break;
}
}
}
}