openstranded_map_tool/
cli.rs1use std::path::PathBuf;
20
21use clap::{Parser, Subcommand};
22
23use crate::convert::s2_to_osmap;
24use crate::parser::parse_s2_file;
25use crate::save_osmap_ron;
26use crate::types::OpenStrandedMap;
27
28#[derive(Parser)]
30#[command(name = "openstranded-map-tool", version, about)]
31pub struct Cli {
32 #[command(subcommand)]
33 pub command: Command,
34}
35
36#[derive(Subcommand)]
37pub enum Command {
38 Convert {
40 input: PathBuf,
42 output: Option<PathBuf>,
44 },
45 Info {
47 input: PathBuf,
49 },
50}
51
52pub fn run() -> Result<(), anyhow::Error> {
54 let cli = Cli::parse();
55 match cli.command {
56 Command::Convert { input, output } => cmd_convert(&input, output.as_deref()),
57 Command::Info { input } => cmd_info(&input),
58 }
59}
60
61fn cmd_convert(input: &PathBuf, output: Option<&std::path::Path>) -> Result<(), anyhow::Error> {
62 if !input.is_file() {
63 anyhow::bail!("input file not found: {}", input.display());
64 }
65
66 let ext = input
68 .extension()
69 .and_then(|e| e.to_str())
70 .unwrap_or("")
71 .to_lowercase();
72
73 match ext.as_str() {
74 "s2" | "map" => {
75 let s2 = parse_s2_file(input)?;
76 let source_name = input
77 .file_name()
78 .and_then(|s| s.to_str())
79 .unwrap_or("unknown");
80 let osmap = s2_to_osmap(s2, source_name);
81 let out_path = output
82 .map(|p| p.to_path_buf())
83 .unwrap_or_else(|| input.with_extension("osmap"));
84 save_osmap_ron(&osmap, &out_path)?;
85 eprintln!("converted {} -> {}", input.display(), out_path.display());
86 }
87 "osmap" => {
88 anyhow::bail!(
89 "input {} is already .osmap; use 'info' to inspect it",
90 input.display()
91 );
92 }
93 _ => {
94 anyhow::bail!("unsupported input format: .{ext} (expected .s2 or .map)");
95 }
96 }
97
98 Ok(())
99}
100
101fn cmd_info(input: &PathBuf) -> Result<(), anyhow::Error> {
102 if !input.is_file() {
103 anyhow::bail!("file not found: {}", input.display());
104 }
105
106 let ext = input
107 .extension()
108 .and_then(|e| e.to_str())
109 .unwrap_or("")
110 .to_lowercase();
111
112 match ext.as_str() {
113 "s2" | "map" => {
114 let s2 = parse_s2_file(input)?;
115 println!("=== .s2 Map Info ===");
116 println!("File: {}", input.display());
117 println!("Version: {}", s2.header.version);
118 println!("Date: {} {}", s2.header.date, s2.header.time);
119 println!("Author: {}", s2.header.author);
120 println!("Type: {}", s2.header.map_type);
121 println!("Terrain: {}×{}", s2.terrain_size, s2.terrain_size);
122 println!("Colormap: {}×{}", s2.colormap_dim, s2.colormap_dim);
123 println!("Objects: {}", s2.objects.len());
124 println!("Units: {}", s2.units.len());
125 println!("Items: {}", s2.items.len());
126 println!("Infos: {}", s2.infos.len());
127 println!("States: {}", s2.states.len());
128 println!("Exts: {}", s2.extensions.len());
129 if let Some(lt) = s2.extensions.iter().find(|e| e.key == "lasttime") {
130 println!("SaveTime: {}", lt.stuff);
131 }
132 if !s2.password.decoded.is_empty() {
133 println!("Password: {} (key={})", s2.password.decoded, s2.password.key);
134 }
135 }
136 "osmap" => {
137 let data = std::fs::read_to_string(input)?;
138 let osmap: OpenStrandedMap = ron::from_str(&data)
139 .map_err(|e| anyhow::anyhow!("failed to parse .osmap: {e}"))?;
140 println!("=== .osmap Info ===");
141 println!("File: {}", input.display());
142 println!("Name: {}", osmap.meta.name);
143 println!("Engine: {}", osmap.meta.engine_version);
144 println!("MapVer: {}", osmap.meta.map_version);
145 println!("Source: {}", osmap.meta.source_file);
146 if let Some(hdr) = &osmap.meta.s2_header {
147 println!("S2Ver: {}", hdr.version);
148 println!("S2Author: {}", hdr.author);
149 }
150 println!("Terrain: {}×{}", osmap.terrain.size, osmap.terrain.size);
151 println!("Entities: {}", osmap.entities.len());
152 let by_class = |class: &str| -> usize {
153 osmap.entities.iter().filter(|e| e.class == class).count()
154 };
155 println!(" objects: {}", by_class("object"));
156 println!(" units: {}", by_class("unit"));
157 println!(" items: {}", by_class("item"));
158 println!(" infos: {}", by_class("info"));
159 if let Some(cm) = &osmap.colormap {
160 println!("Colormap: {}×{}", cm.dim, cm.dim);
161 } else {
162 println!("Colormap: (none)");
163 }
164 if let Some(gr) = &osmap.grass {
165 let grass_count = gr.values.iter().filter(|&&v| v != 0).count();
166 println!("Grass: {}×{} ({} cells filled)", gr.dim, gr.dim, grass_count);
167 } else {
168 println!("Grass: (none)");
169 }
170 println!("Scripts: {}", osmap.scripts.len());
171 println!("Env Vars: {}", osmap.environment.len());
172 if osmap.environment.contains_key("password") {
173 println!("Password: {}", osmap.environment.get("password").unwrap());
174 }
175 }
176 _ => {
177 anyhow::bail!("unsupported format: .{ext} (expected .s2, .map, or .osmap)");
178 }
179 }
180
181 Ok(())
182}
183
184