config_disassembler/
cli.rs1use std::path::PathBuf;
15
16use crate::disassemble::{self, DisassembleOptions};
17use crate::error::{Error, Result};
18use crate::format::Format;
19use crate::reassemble::{self, ReassembleOptions};
20use crate::xml_cmd;
21
22pub async fn dispatch(args: Vec<String>) -> Result<()> {
24 let mut iter = args.into_iter();
25 let _program = iter.next();
26 let subcommand = match iter.next() {
27 Some(s) => s,
28 None => {
29 print_help();
30 return Ok(());
31 }
32 };
33 let rest: Vec<String> = iter.collect();
34
35 match subcommand.as_str() {
36 "help" | "-h" | "--help" => {
37 print_help();
38 Ok(())
39 }
40 "xml" => xml_cmd::run(rest).await,
41 "json" => run_format(Format::Json, rest),
42 "json5" => run_format(Format::Json5, rest),
43 "yaml" | "yml" => run_format(Format::Yaml, rest),
44 other => Err(Error::Usage(format!(
45 "unknown subcommand `{other}`. run `config-disassembler help` for usage."
46 ))),
47 }
48}
49
50fn run_format(default_format: Format, args: Vec<String>) -> Result<()> {
51 let mut iter = args.into_iter();
52 let action = iter.next().ok_or_else(|| {
53 Error::Usage(format!(
54 "{default_format} subcommand requires `disassemble` or `reassemble`"
55 ))
56 })?;
57 let rest: Vec<String> = iter.collect();
58
59 match action.as_str() {
60 "disassemble" => run_disassemble(default_format, rest),
61 "reassemble" => run_reassemble(default_format, rest),
62 "help" | "-h" | "--help" => {
63 print_format_help(default_format);
64 Ok(())
65 }
66 other => Err(Error::Usage(format!(
67 "unknown action `{other}` for `{default_format}`; expected `disassemble` or `reassemble`"
68 ))),
69 }
70}
71
72fn run_disassemble(default_format: Format, args: Vec<String>) -> Result<()> {
73 let mut input: Option<PathBuf> = None;
74 let mut output_dir: Option<PathBuf> = None;
75 let mut output_format: Option<Format> = None;
76 let mut input_format: Option<Format> = None;
77 let mut unique_id: Option<String> = None;
78 let mut pre_purge = false;
79 let mut post_purge = false;
80
81 let mut iter = args.into_iter();
82 while let Some(arg) = iter.next() {
83 match arg.as_str() {
84 "--output-dir" | "-o" => {
85 output_dir = Some(PathBuf::from(expect_value(&mut iter, "--output-dir")?));
86 }
87 "--output-format" => {
88 output_format = Some(expect_value(&mut iter, "--output-format")?.parse()?);
89 }
90 "--input-format" => {
91 input_format = Some(expect_value(&mut iter, "--input-format")?.parse()?);
92 }
93 "--unique-id" => {
94 unique_id = Some(expect_value(&mut iter, "--unique-id")?);
95 }
96 "--pre-purge" => pre_purge = true,
97 "--post-purge" => post_purge = true,
98 "-h" | "--help" => {
99 print_format_help(default_format);
100 return Ok(());
101 }
102 other if other.starts_with('-') => {
103 return Err(Error::Usage(format!("unknown option `{other}`")));
104 }
105 _ if input.is_none() => input = Some(PathBuf::from(arg)),
106 _ => {
107 return Err(Error::Usage(format!("unexpected argument `{arg}`")));
108 }
109 }
110 }
111
112 let input = input.ok_or_else(|| Error::Usage("missing <input> file path".into()))?;
113 let opts = DisassembleOptions {
114 input,
115 input_format: input_format.or(Some(default_format)),
116 output_dir,
117 output_format,
118 unique_id,
119 pre_purge,
120 post_purge,
121 };
122 let dir = disassemble::disassemble(opts)?;
123 println!("disassembled into {}", dir.display());
124 Ok(())
125}
126
127fn run_reassemble(default_format: Format, args: Vec<String>) -> Result<()> {
128 let mut input_dir: Option<PathBuf> = None;
129 let mut output: Option<PathBuf> = None;
130 let mut output_format: Option<Format> = None;
131 let mut post_purge = false;
132
133 let mut iter = args.into_iter();
134 while let Some(arg) = iter.next() {
135 match arg.as_str() {
136 "--output" | "-o" => {
137 output = Some(PathBuf::from(expect_value(&mut iter, "--output")?));
138 }
139 "--output-format" => {
140 output_format = Some(expect_value(&mut iter, "--output-format")?.parse()?);
141 }
142 "--post-purge" => post_purge = true,
143 "-h" | "--help" => {
144 print_format_help(default_format);
145 return Ok(());
146 }
147 other if other.starts_with('-') => {
148 return Err(Error::Usage(format!("unknown option `{other}`")));
149 }
150 _ if input_dir.is_none() => input_dir = Some(PathBuf::from(arg)),
151 _ => {
152 return Err(Error::Usage(format!("unexpected argument `{arg}`")));
153 }
154 }
155 }
156
157 let input_dir = input_dir.ok_or_else(|| Error::Usage("missing <input-dir> path".into()))?;
158 let opts = ReassembleOptions {
159 input_dir,
160 output,
161 output_format: output_format.or(Some(default_format)),
162 post_purge,
163 };
164 let path = reassemble::reassemble(opts)?;
165 println!("reassembled to {}", path.display());
166 Ok(())
167}
168
169fn expect_value<I: Iterator<Item = String>>(iter: &mut I, flag: &str) -> Result<String> {
170 iter.next()
171 .ok_or_else(|| Error::Usage(format!("`{flag}` expects a value")))
172}
173
174fn print_help() {
175 eprintln!(
176 "config-disassembler {ver}\n\
177\n\
178Disassemble configuration files (XML, JSON, JSON5, YAML) into smaller files\n\
179and reassemble the original on demand.\n\
180\n\
181USAGE:\n\
182 config-disassembler <subcommand> [args...]\n\
183\n\
184SUBCOMMANDS:\n\
185 xml Forward to the bundled xml-disassembler CLI.\n\
186 json Disassemble or reassemble a JSON file.\n\
187 json5 Disassemble or reassemble a JSON5 file.\n\
188 yaml Disassemble or reassemble a YAML file.\n\
189 help Show this help text.\n\
190\n\
191Run `config-disassembler <subcommand> --help` for subcommand details.\n",
192 ver = env!("CARGO_PKG_VERSION")
193 );
194}
195
196fn print_format_help(format: Format) {
197 eprintln!(
198 "config-disassembler {format} <action> [options]\n\
199\n\
200ACTIONS:\n\
201 disassemble <input> Split <input> into a directory of smaller files.\n\
202 reassemble <dir> Rebuild the original file from <dir>.\n\
203\n\
204DISASSEMBLE OPTIONS:\n\
205 -o, --output-dir <dir> Output directory (default: <input-stem> next to input).\n\
206 --input-format <fmt> Override the input format (default: inferred from extension or `{format}`).\n\
207 --output-format <fmt> Format used for the split files (default: same as input).\n\
208 --unique-id <field> For array roots, name files by this field on each element.\n\
209 --pre-purge Remove the output directory before writing.\n\
210 --post-purge Delete the input file after disassembly.\n\
211\n\
212REASSEMBLE OPTIONS:\n\
213 -o, --output <file> Output file (default: derived from metadata next to input dir).\n\
214 --output-format <fmt> Format to write the reassembled file in (default: original source format).\n\
215 --post-purge Remove the input directory after reassembly.\n\
216\n\
217<fmt> is one of: json, json5, yaml.\n"
218 );
219}