flux-core 0.5.2

Declarative task runner with dependency management, parallel execution, and watch mode
// flux-wasm v0.5.0
// WebAssembly target - Simplified task runner
// Native features (watch mode, parallel execution) not available in WASM

use std::collections::{HashMap, HashSet};
use std::process::exit;

#[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>,
}

fn main() {
    println!("🚀 Flux WASM v0.5.0 - Task Runner (WebAssembly)");
    println!("⚠️  Sadeleştirilmiş versiyon - Watch mode ve paralel çalıştırma yok");
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

    // WASM'da komut satırı argümanları farklı şekilde işlenir
    // WASI runtime tarafından sağlanır
    let args: Vec<String> = std::env::args().collect();

    if args.len() < 2 || args[1] == "help" || args[1] == "--help" || args[1] == "-h" {
        show_help();
        exit(0);
    }

    let task_input = &args[1];

    match task_input.as_str() {
        "runners" => {
            show_runners();
            exit(0);
        }
        "graph" => {
            show_dependency_graph();
            exit(0);
        }
        _ => {
            // Task çalıştır
            let success = run_task_with_dependencies(task_input, &[], &mut HashSet::new());
            exit(if success { 0 } else { 1 });
        }
    }
}

fn show_help() {
    println!("Kullanım: flux <task>");
    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 <task>    - Task'i çalıştır");
    println!("");
    println!("⚠️  WASM kısıtlamaları:");
    println!("  - Watch mode yok");
    println!("  - Paralel çalıştırma yok");
    println!("  - Dry-run modu yok");
    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 (WASM):");
    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 (WASM):");
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

    for (name, task) in &config {
        if !task.depends_on.is_empty() {
            println!("\n📦 {}", name);
            for dep in &task.depends_on {
                println!("   └─> {}", dep);
            }
        }
    }

    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],
    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);
            exit(1);
        }
    };

    // Önce bağımlılıkları çalıştır
    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, executed);
            if !success {
                return false;
            }
        }
    }

    // Şimdi asıl task'i çalıştır
    if !executed.contains(task_name) {
        let success = run_single_task_internal(&task);
        executed.insert(task_name.to_string());
        return success;
    }

    true
}

fn run_single_task_internal(task: &Task) -> 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!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

    // Pre-hook
    if let Some(ref pre_hook) = task.pre_hook {
        println!("⚡ Pre-hook: {}", pre_hook);
        // WASM'da gerçek komut çalıştırma yok - sadece bilgi
        println!("   (WASM: Pre-hook bilgi amaçlı gösterildi)");
    }

    println!("→ Command: {}", task.command);
    println!("   (WASM: Komut çalıştırma WASI runtime tarafından yapılmalı)");

    // Post-hook
    if let Some(ref post_hook) = task.post_hook {
        println!("⚡ Post-hook: {}", post_hook);
        println!("   (WASM: Post-hook bilgi amaçlı gösterildi)");
    }

    println!("✅ Task başarıyla tamamlandı: {}", task.name);
    true
}

fn load_config(path: &str) -> Result<HashMap<String, Task>, Box<dyn std::error::Error>> {
    let content = std::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()
}