nativ 0.3.0

Nativ CLI — compile .nativ DSL to real SwiftUI and Jetpack Compose
use clap::Args;
use std::path::Path;

#[derive(Args)]
pub struct CheckArgs {
    /// Project directory (default: current directory)
    #[arg(short, long, default_value = ".")]
    pub dir: String,
}

pub fn run(args: CheckArgs, verbose: bool) -> Result<(), Box<dyn std::error::Error>> {
    let project_dir = Path::new(&args.dir);
    let config_path = project_dir.join("nativ.toml");

    let _config = nativ_config::NativConfig::load(&config_path)?;

    let result = nativ_pipeline::check(project_dir)?;

    if result.errors.is_empty() {
        if verbose {
            println!("{} files checked", result.files_checked);
        }
        println!("No errors found");
        Ok(())
    } else {
        for err in &result.errors {
            eprintln!("  {err}");
        }
        Err(format!("{} errors found", result.errors.len()).into())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn args_for(dir: &Path) -> CheckArgs {
        CheckArgs {
            dir: dir.display().to_string(),
        }
    }

    #[test]
    fn fails_when_config_is_missing() {
        let tmp = tempfile::tempdir().unwrap();

        let err = run(args_for(tmp.path()), false).unwrap_err();
        assert!(err.to_string().contains("nativ.toml"), "{err}");
    }

    #[test]
    fn succeeds_on_valid_project() {
        let tmp = tempfile::tempdir().unwrap();
        std::fs::write(tmp.path().join("nativ.toml"), "[app]\nname = \"t\"\n").unwrap();
        std::fs::create_dir_all(tmp.path().join("src")).unwrap();
        std::fs::write(
            tmp.path().join("src").join("home.nativ"),
            "screen Home:\n  text \"Hello\"\n",
        )
        .unwrap();

        assert!(run(args_for(tmp.path()), true).is_ok());
    }

    #[test]
    fn fails_on_parse_error() {
        let tmp = tempfile::tempdir().unwrap();
        std::fs::write(tmp.path().join("nativ.toml"), "[app]\nname = \"t\"\n").unwrap();
        std::fs::create_dir_all(tmp.path().join("src")).unwrap();
        // Tab indentation is always rejected by the preprocessor.
        std::fs::write(
            tmp.path().join("src").join("bad.nativ"),
            "screen Bad:\n\ttext \"tabs\"\n",
        )
        .unwrap();

        assert!(run(args_for(tmp.path()), false).is_err());
    }
}