use std::path::PathBuf;
use clap::{Parser, Subcommand};
use crate::convert::s2_to_osmap;
use crate::parser::parse_s2_file;
use crate::save_osmap_ron;
use crate::types::OpenStrandedMap;
#[derive(Parser)]
#[command(name = "openstranded-map-tool", version, about)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand)]
pub enum Command {
Convert {
input: PathBuf,
output: Option<PathBuf>,
},
Info {
input: PathBuf,
},
}
pub fn run() -> Result<(), anyhow::Error> {
let cli = Cli::parse();
match cli.command {
Command::Convert { input, output } => cmd_convert(&input, output.as_deref()),
Command::Info { input } => cmd_info(&input),
}
}
fn cmd_convert(input: &PathBuf, output: Option<&std::path::Path>) -> Result<(), anyhow::Error> {
if !input.is_file() {
anyhow::bail!("input file not found: {}", input.display());
}
let ext = input
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_lowercase();
match ext.as_str() {
"s2" | "map" => {
let s2 = parse_s2_file(input)?;
let source_name = input
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("unknown");
let osmap = s2_to_osmap(s2, source_name);
let out_path = output
.map(|p| p.to_path_buf())
.unwrap_or_else(|| input.with_extension("osmap"));
save_osmap_ron(&osmap, &out_path)?;
eprintln!("converted {} -> {}", input.display(), out_path.display());
}
"osmap" => {
anyhow::bail!(
"input {} is already .osmap; use 'info' to inspect it",
input.display()
);
}
_ => {
anyhow::bail!("unsupported input format: .{ext} (expected .s2 or .map)");
}
}
Ok(())
}
fn cmd_info(input: &PathBuf) -> Result<(), anyhow::Error> {
if !input.is_file() {
anyhow::bail!("file not found: {}", input.display());
}
let ext = input
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_lowercase();
match ext.as_str() {
"s2" | "map" => {
let s2 = parse_s2_file(input)?;
println!("=== .s2 Map Info ===");
println!("File: {}", input.display());
println!("Version: {}", s2.header.version);
println!("Date: {} {}", s2.header.date, s2.header.time);
println!("Author: {}", s2.header.author);
println!("Type: {}", s2.header.map_type);
println!("Terrain: {}×{}", s2.terrain_size, s2.terrain_size);
println!("Colormap: {}×{}", s2.colormap_dim, s2.colormap_dim);
println!("Objects: {}", s2.objects.len());
println!("Units: {}", s2.units.len());
println!("Items: {}", s2.items.len());
println!("Infos: {}", s2.infos.len());
println!("States: {}", s2.states.len());
println!("Exts: {}", s2.extensions.len());
if let Some(lt) = s2.extensions.iter().find(|e| e.key == "lasttime") {
println!("SaveTime: {}", lt.stuff);
}
if !s2.password.decoded.is_empty() {
println!("Password: {} (key={})", s2.password.decoded, s2.password.key);
}
}
"osmap" => {
let data = std::fs::read_to_string(input)?;
let osmap: OpenStrandedMap = ron::from_str(&data)
.map_err(|e| anyhow::anyhow!("failed to parse .osmap: {e}"))?;
println!("=== .osmap Info ===");
println!("File: {}", input.display());
println!("Name: {}", osmap.meta.name);
println!("Engine: {}", osmap.meta.engine_version);
println!("MapVer: {}", osmap.meta.map_version);
println!("Source: {}", osmap.meta.source_file);
if let Some(hdr) = &osmap.meta.s2_header {
println!("S2Ver: {}", hdr.version);
println!("S2Author: {}", hdr.author);
}
println!("Terrain: {}×{}", osmap.terrain.size, osmap.terrain.size);
println!("Entities: {}", osmap.entities.len());
let by_class = |class: &str| -> usize {
osmap.entities.iter().filter(|e| e.class == class).count()
};
println!(" objects: {}", by_class("object"));
println!(" units: {}", by_class("unit"));
println!(" items: {}", by_class("item"));
println!(" infos: {}", by_class("info"));
if let Some(cm) = &osmap.colormap {
println!("Colormap: {}×{}", cm.dim, cm.dim);
} else {
println!("Colormap: (none)");
}
if let Some(gr) = &osmap.grass {
let grass_count = gr.values.iter().filter(|&&v| v != 0).count();
println!("Grass: {}×{} ({} cells filled)", gr.dim, gr.dim, grass_count);
} else {
println!("Grass: (none)");
}
println!("Scripts: {}", osmap.scripts.len());
println!("Env Vars: {}", osmap.environment.len());
if osmap.environment.contains_key("password") {
println!("Password: {}", osmap.environment.get("password").unwrap());
}
}
_ => {
anyhow::bail!("unsupported format: .{ext} (expected .s2, .map, or .osmap)");
}
}
Ok(())
}