rmcl 0.3.1

A fully featured Minecraft TUI launcher
// per-launch log files stored under .minecraft/logs/launches/
// each launch gets its own timestamped file so you can go back and see what
// crashed last tuesday at 3am

use std::path::{Path, PathBuf};

#[derive(Debug, Clone)]
pub struct LogFileEntry {
    pub name: String,
    pub path: PathBuf,
}

pub fn log_dir(instances_dir: &Path, instance_name: &str) -> PathBuf {
    instances_dir
        .join(instance_name)
        .join(".minecraft")
        .join("logs")
        .join("launches")
}

pub fn create_log_file(instances_dir: &Path, instance_name: &str) -> Option<PathBuf> {
    let dir = log_dir(instances_dir, instance_name);
    std::fs::create_dir_all(&dir).ok()?;
    let now = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S");
    Some(dir.join(format!("{now}.log")))
}

pub fn scan_log_files(instances_dir: &Path, instance_name: &str) -> Vec<LogFileEntry> {
    let dir = log_dir(instances_dir, instance_name);

    let read_dir = match std::fs::read_dir(&dir) {
        Ok(rd) => rd,
        Err(_) => return Vec::new(),
    };

    let mut entries: Vec<LogFileEntry> = read_dir
        .flatten()
        .filter_map(|entry| {
            let path = entry.path();
            let name = path.file_name()?.to_str()?.to_string();
            if name.ends_with(".log") {
                Some(LogFileEntry { name, path })
            } else {
                None
            }
        })
        .collect();

    entries.sort_by(|a, b| b.name.cmp(&a.name));
    entries
}

pub fn read_log_file(path: &Path) -> Vec<String> {
    match std::fs::read_to_string(path) {
        Ok(content) => content.lines().map(|l| l.to_string()).collect(),
        Err(_) => Vec::new(),
    }
}

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

    #[test]
    fn log_dir_builds_correct_path() {
        let p = log_dir(Path::new("/instances"), "my-world");
        assert_eq!(
            p,
            PathBuf::from("/instances/my-world/.minecraft/logs/launches")
        );
    }
}