uxn-tal 0.2.7

A Rust library for assembling TAL (Tal Assembly Language) files into UXN ROM files
Documentation
/// Returns the path to drifloon.rom in the user's home directory, or just "drifloon.rom" if not found.
pub fn uxndis_repo_get_drifloon() -> PathBuf {
    let home_dir = dirs::home_dir();
    if let Some(home) = home_dir {
        let drifloon_path = home
            .join(".uxntal")
            .join(".uxndis")
            .join("src")
            .join("drifloon.rom");
        if drifloon_path.exists() {
            return drifloon_path;
        }
    }
    PathBuf::from("drifloon.rom")
}

/// Returns the path to uxndis.rom in the user's home directory, or just "uxndis.rom" if not found.
pub fn uxndis_repo_get_uxndis() -> PathBuf {
    let home_dir = dirs::home_dir();
    if let Some(home) = home_dir {
        let uxndis_path = home
            .join(".uxntal")
            .join(".uxndis")
            .join("src")
            .join("uxndis.rom");
        if uxndis_path.exists() {
            return uxndis_path;
        }
    }
    PathBuf::from("uxndis.rom")
}

/// Returns the path to uxndis-seed.rom in the user's home directory, or just "uxndis-seed.rom" if not found.
pub fn uxndis_repo_get_uxndis_seed() -> PathBuf {
    let home_dir = dirs::home_dir();
    if let Some(home) = home_dir {
        let uxndis_seed_path = home
            .join(".uxntal")
            .join(".uxndis")
            .join("bin")
            .join("uxndis-seed.rom");
        if uxndis_seed_path.exists() {
            return uxndis_seed_path;
        }
    }
    PathBuf::from("uxndis-seed.rom")
}
use std::{
    fs,
    path::{Path, PathBuf},
    process::Command,
};

use crate::{wsl::detect_wsl, Assembler, AssemblerError};

fn simple_err(path: &std::path::Path, msg: &str) -> AssemblerError {
    AssemblerError::SyntaxError {
        path: path.display().to_string(),
        line: 0,
        position: 0,
        message: msg.to_string(),
        source_line: String::new(),
    }
}

pub fn ensure_uxndis_repo() -> Result<Option<PathBuf>, AssemblerError> {
    struct DirGuard {
        original: PathBuf,
    }

    impl DirGuard {
        fn new(target: &Path) -> Option<Self> {
            let original = std::env::current_dir().ok()?;
            if std::env::set_current_dir(target).is_ok() {
                Some(DirGuard { original })
            } else {
                None
            }
        }
    }

    impl Drop for DirGuard {
        fn drop(&mut self) {
            let _ = std::env::set_current_dir(&self.original);
        }
    }
    let home_dir = dirs::home_dir().ok_or_else(|| {
        simple_err(
            Path::new("~/.uxntal/.uxndis"),
            "failed to get home directory",
        )
    })?;
    let uxntal_path = home_dir.join(".uxntal");
    let uxndis_path = uxntal_path.join(".uxndis");
    if !uxndis_path.exists() {
        let status = Command::new("git")
            .arg("clone")
            .arg("https://git.sr.ht/~rabbits/uxndis")
            .arg(&uxndis_path)
            .status()
            .map_err(|e| simple_err(&uxndis_path, &format!("failed to execute git clone: {e}")))?;
        if !status.success() {
            eprintln!("Failed to clone uxndis repository");
            return Err(simple_err(&uxndis_path, "failed to clone repository"));
        }
    } else {
        // If already exists, do a git pull
        let status = Command::new("git")
            .arg("-C")
            .arg(&uxndis_path)
            .arg("pull")
            .status()
            .ok();
        if let Some(status) = status {
            if !status.success() {
                eprintln!("Failed to pull uxndis repository");
            }
        } else {
            eprintln!("Failed to execute git pull for uxndis repository");
        }
    }
    if !uxndis_path.exists() {
        eprintln!("uxndis repository not found after clone/pull");
        return Err(simple_err(
            &uxndis_path,
            "uxndis repository not found after clone/pull",
        ));
    }
    let _guard = DirGuard::new(&uxndis_path);

    let uxndis_rom = uxndis_path.join("src").join("uxndis.rom");
    if !uxndis_rom.exists() {
        let mut asm = Assembler::new();

        let uxndis_tal = uxndis_path.join("src").join("uxndis.tal");
        if !uxndis_tal.exists() {
            eprintln!("uxndis.tal not found in uxndis repository");
            return Err(simple_err(&uxndis_tal, "uxndis.tal not found"));
        }
        eprintln!("Assembling uxndis.tal to uxndis.rom...");
        let uxndis_tal_contents = match fs::read_to_string(&uxndis_tal) {
            Ok(contents) => contents,
            Err(e) => {
                eprintln!("Failed to read uxndis.tal: {:?}", e);
                return Err(simple_err(&uxndis_tal, "failed to read uxndis.tal"));
            }
        };
        let ret = asm.assemble(&uxndis_tal_contents, Some(uxndis_rom.display().to_string()));
        match ret {
            Ok(rom) => {
                fs::write(&uxndis_rom, &rom)
                    .map_err(|e| simple_err(&uxndis_rom, &format!("failed to write rom: {e}")))
                    .ok();
                if uxndis_rom.exists() {
                    eprintln!("Successfully assembled uxndis.tal {}", uxndis_rom.display());
                } else {
                    eprintln!(
                        "Assembly succeeded but uxndis.rom not found at {}",
                        uxndis_rom.display()
                    );
                    return Err(simple_err(&uxndis_rom, "uxndis.rom not found"));
                }
            }
            Err(e) => {
                eprintln!("Failed to assemble uxndis.tal: {:?}", e);
                return Err(simple_err(&uxndis_tal, "failed to assemble uxndis.tal"));
            }
        }
    }

    Ok(Some(uxndis_path))
}

fn dis_err(path: &str, e: &str) -> AssemblerError {
    AssemblerError::Disassembly {
        message: format!("dis error on {}: {}", path, e),
    }
}

pub fn run_dis_file(rom_path: &str) -> Result<String, AssemblerError> {
    // let sym = format!("{rom_path}.sym");
    // if Path::new(&sym).exists() {
    //     let _ = fs::remove_file(&sym);
    // }
    let in_wsl = detect_wsl();
    let uxndis_path = crate::dis_uxndis::uxndis_repo_get_uxndis();
    let uxndis_path: String = if cfg!(windows) {
        wslpath::windows_to_wsl(&uxndis_path.to_string_lossy()).map_err(|e| {
            dis_err(
                rom_path,
                &format!("Could not convert uxndis path to WSL: {e}"),
            )
        })?
    } else {
        uxndis_path.to_string_lossy().to_string()
    };
    let rom_path = if cfg!(windows) {
        wslpath::windows_to_wsl(rom_path)
            .map_err(|e| dis_err(rom_path, &format!("Could not convert ROM path to WSL: {e}")))?
    } else {
        rom_path.to_string()
    };
    //     println!("Disassembly command: uxncli {} {}", uxndis_path, rom_path);
    // // println!("Disassembly written to {}", dis);
    // std::process::exit(0);
    let output = if cfg!(windows) && !in_wsl {
        if in_wsl {
            Command::new("uxncli")
                .arg(&uxndis_path)
                .arg(&rom_path)
                .output()
        } else {
            Command::new("wsl")
                .arg("uxncli")
                .arg(&uxndis_path)
                .arg(&rom_path)
                .output()
        }
        .map_err(|e| dis_err(&rom_path, &format!("uxndis failed: {e}")))?
    } else {
        Command::new("uxncli")
            .arg(&uxndis_path)
            .arg(&rom_path)
            .output()?
    };
    let rom_path = if cfg!(windows) {
        wslpath::wsl_to_windows(&rom_path).map_err(|e| {
            dis_err(
                &rom_path,
                &format!("Could not convert ROM path to Windows: {e}"),
            )
        })?
    } else {
        rom_path.to_string()
    };
    let dis = format!("{rom_path}.dis");
    // Write disassembly output to .dis file
    if let Err(e) = fs::write(&dis, &output.stdout) {
        eprintln!("Failed to write disassembly to {}: {}", dis, e);
    }

    Ok(String::from_utf8_lossy(&output.stdout).to_string())
}