//! Opening what a command needs: the design library and the equipment //! catalogue. //! //! Every command that scores anything wants both, and wants the same two //! error messages when it cannot have them. use std::path::{Path, PathBuf}; use crate::options::Opts; /// What every command that writes a file out of an install needs. pub(crate) struct FromInstall { pub(crate) megamek: PathBuf, pub(crate) out: PathBuf, /// The MegaMek release, read from the install unless the caller said. pub(crate) version: String, pub(crate) library: helm_unitfile::Library, } /// Open an install and settle where the output goes. /// /// `build` and `index` were asking the same five questions in the same order /// with the same two error messages, twelve hundred lines apart. pub(crate) fn from_install(opts: &Opts) -> Result { let megamek = opts .megamek .clone() .ok_or("--megamek is required: point it at a MegaMek install")?; let out = opts .out .clone() .ok_or("--out is required: nothing is written without somewhere to write it")?; let version = resolve_version(&megamek, opts.version.clone()); let library = read_library(&megamek)?; Ok(FromInstall { megamek, out, version, library, }) } /// Where the install and the bridge dump are. /// /// The pair every command that checks anything against MegaMek needs, and the /// two error messages that go with them. The reason a dump is wanted differs /// by command and is the caller's to give; that it is wanted at all is not. pub(crate) fn install_and_dump( opts: &Opts, why: &'static str, ) -> Result<(PathBuf, PathBuf), String> { let megamek = opts .megamek .clone() .ok_or("--megamek is required: point it at a MegaMek install")?; let bridge = opts.bridge_dir.clone().ok_or(why)?; Ok((megamek, bridge)) } /// The two things every command that scores anything needs. /// /// Four subcommands were opening the library and the catalogue with the same /// eight lines apiece, including the same two error messages. pub(crate) fn library_and_catalogue( opts: &Opts, ) -> Result<(helm_unitfile::Library, helm_core::Catalogue), String> { let (megamek, bridge) = install_and_dump(opts, "--bridge-dir is required: the catalogue lives there")?; let library = read_library(&megamek)?; let catalogue = helm_bridge::read_catalogue(&bridge.join("equipment.jsonl")).map_err(|e| e.to_string())?; Ok((library, catalogue)) } pub(crate) fn read_library(megamek: &Path) -> Result { let zip = megamek.join(helm_unitfile::LIBRARY_IN_INSTALL); let library = if zip.is_file() { eprintln!("reading {}", zip.display()); let file = std::fs::File::open(&zip).map_err(|e| format!("{}: {e}", zip.display()))?; helm_unitfile::read_zip(file).map_err(|e| e.to_string())? } else { let dir = megamek.join("data/mekfiles"); let dir = if dir.is_dir() { dir } else { megamek.to_path_buf() }; eprintln!("reading {}", dir.display()); helm_unitfile::read_dir(&dir).map_err(|e| e.to_string())? }; eprintln!("parsed {} units", library.units.len()); if !library.failures.is_empty() { eprintln!("{} files failed to parse:", library.failures.len()); for (path, reason) in library.failures.iter().take(10) { eprintln!(" {path}: {reason}"); } if library.failures.len() > 10 { eprintln!(" ... and {} more", library.failures.len() - 10); } } Ok(library) } /// The MegaMek version to record: what was asked for, else what the install /// says it is. A flag that can be typed wrong is a provenance hole, and the /// install can be asked. pub(crate) fn resolve_version(megamek: &Path, asked: Option) -> String { if let Some(v) = asked { return v; } match helm_unitfile::megamek_version(megamek) { Some(v) => { eprintln!("megamek {v} (read from the install)"); v } None => { eprintln!("could not read a version from {}", megamek.display()); "unknown".to_string() } } }