1use crate::{KeyValuePair, WasiNnGraph};
9use anyhow::{bail, Result};
10use clap::builder::{StringValueParser, TypedValueParser, ValueParserFactory};
11use clap::error::{Error, ErrorKind};
12use std::marker;
13use std::time::Duration;
14
15#[macro_export]
16macro_rules! wasmtime_option_group {
17 (
18 $(#[$attr:meta])*
19 pub struct $opts:ident {
20 $(
21 $(#[doc = $doc:tt])*
22 pub $opt:ident: $container:ident<$payload:ty>,
23 )+
24
25 $(
26 #[prefixed = $prefix:tt]
27 $(#[doc = $prefixed_doc:tt])*
28 pub $prefixed:ident: Vec<(String, Option<String>)>,
29 )?
30 }
31 enum $option:ident {
32 ...
33 }
34 ) => {
35 #[derive(Default, Debug)]
36 $(#[$attr])*
37 pub struct $opts {
38 $(
39 pub $opt: $container<$payload>,
40 )+
41 $(
42 pub $prefixed: Vec<(String, Option<String>)>,
43 )?
44 }
45
46 #[derive(Clone, Debug,PartialEq)]
47 #[allow(non_camel_case_types)]
48 enum $option {
49 $(
50 $opt($payload),
51 )+
52 $(
53 $prefixed(String, Option<String>),
54 )?
55 }
56
57 impl $crate::opt::WasmtimeOption for $option {
58 const OPTIONS: &'static [$crate::opt::OptionDesc<$option>] = &[
59 $(
60 $crate::opt::OptionDesc {
61 name: $crate::opt::OptName::Name(stringify!($opt)),
62 parse: |_, s| {
63 Ok($option::$opt(
64 $crate::opt::WasmtimeOptionValue::parse(s)?
65 ))
66 },
67 val_help: <$payload as $crate::opt::WasmtimeOptionValue>::VAL_HELP,
68 docs: concat!($($doc, "\n",)*),
69 },
70 )+
71 $(
72 $crate::opt::OptionDesc {
73 name: $crate::opt::OptName::Prefix($prefix),
74 parse: |name, val| {
75 Ok($option::$prefixed(
76 name.to_string(),
77 val.map(|v| v.to_string()),
78 ))
79 },
80 val_help: "[=val]",
81 docs: concat!($($prefixed_doc, "\n",)*),
82 },
83 )?
84 ];
85 }
86
87 impl $opts {
88 fn configure_with(&mut self, opts: &[$crate::opt::CommaSeparated<$option>]) {
89 for opt in opts.iter().flat_map(|o| o.0.iter()) {
90 match opt {
91 $(
92 $option::$opt(val) => {
93 let dst = &mut self.$opt;
94 wasmtime_option_group!(@push $container dst val);
95 }
96 )+
97 $(
98 $option::$prefixed(key, val) => self.$prefixed.push((key.clone(), val.clone())),
99 )?
100 }
101 }
102 }
103 }
104 };
105
106 (@push Option $dst:ident $val:ident) => (*$dst = Some($val.clone()));
107 (@push Vec $dst:ident $val:ident) => ($dst.push($val.clone()));
108}
109
110#[derive(Clone, Debug, PartialEq)]
112pub struct CommaSeparated<T>(pub Vec<T>);
113
114impl<T> ValueParserFactory for CommaSeparated<T>
115where
116 T: WasmtimeOption,
117{
118 type Parser = CommaSeparatedParser<T>;
119
120 fn value_parser() -> CommaSeparatedParser<T> {
121 CommaSeparatedParser(marker::PhantomData)
122 }
123}
124
125#[derive(Clone)]
126pub struct CommaSeparatedParser<T>(marker::PhantomData<T>);
127
128impl<T> TypedValueParser for CommaSeparatedParser<T>
129where
130 T: WasmtimeOption,
131{
132 type Value = CommaSeparated<T>;
133
134 fn parse_ref(
135 &self,
136 cmd: &clap::Command,
137 arg: Option<&clap::Arg>,
138 value: &std::ffi::OsStr,
139 ) -> Result<Self::Value, Error> {
140 let val = StringValueParser::new().parse_ref(cmd, arg, value)?;
141
142 let options = T::OPTIONS;
143 let arg = arg.expect("should always have an argument");
144 let arg_long = arg.get_long().expect("should have a long name specified");
145 let arg_short = arg.get_short().expect("should have a short name specified");
146
147 if val == "help" {
150 let mut max = 0;
151 for d in options {
152 max = max.max(d.name.display_string().len() + d.val_help.len());
153 }
154 println!("Available {arg_long} options:\n");
155 for d in options {
156 print!(
157 " -{arg_short} {:>1$}",
158 d.name.display_string(),
159 max - d.val_help.len()
160 );
161 print!("{}", d.val_help);
162 print!(" --");
163 if val == "help" {
164 for line in d.docs.lines().map(|s| s.trim()) {
165 if line.is_empty() {
166 break;
167 }
168 print!(" {line}");
169 }
170 println!();
171 } else {
172 println!();
173 for line in d.docs.lines().map(|s| s.trim()) {
174 let line = line.trim();
175 println!(" {line}");
176 }
177 }
178 }
179 println!("\npass `-{arg_short} help-long` to see longer-form explanations");
180 std::process::exit(0);
181 }
182 if val == "help-long" {
183 println!("Available {arg_long} options:\n");
184 for d in options {
185 println!(
186 " -{arg_short} {}{} --",
187 d.name.display_string(),
188 d.val_help
189 );
190 println!();
191 for line in d.docs.lines().map(|s| s.trim()) {
192 let line = line.trim();
193 println!(" {line}");
194 }
195 }
196 std::process::exit(0);
197 }
198
199 let mut result = Vec::new();
200 for val in val.split(',') {
201 let mut iter = val.splitn(2, '=');
203 let key = iter.next().unwrap();
204 let key_val = iter.next();
205
206 let option = options
208 .iter()
209 .filter_map(|d| match d.name {
210 OptName::Name(s) => {
211 let s = s.replace('_', "-");
212 if s == key {
213 Some((d, s))
214 } else {
215 None
216 }
217 }
218 OptName::Prefix(s) => {
219 let name = key.strip_prefix(s)?.strip_prefix("-")?;
220 Some((d, name.to_string()))
221 }
222 })
223 .next();
224
225 let (desc, key) = match option {
226 Some(pair) => pair,
227 None => {
228 let err = Error::raw(
229 ErrorKind::InvalidValue,
230 format!("unknown -{arg_short} / --{arg_long} option: {key}\n"),
231 );
232 return Err(err.with_cmd(cmd));
233 }
234 };
235
236 result.push((desc.parse)(&key, key_val).map_err(|e| {
237 Error::raw(
238 ErrorKind::InvalidValue,
239 format!("failed to parse -{arg_short} option `{val}`: {e:?}\n"),
240 )
241 .with_cmd(cmd)
242 })?)
243 }
244
245 Ok(CommaSeparated(result))
246 }
247}
248
249pub trait WasmtimeOption: Sized + Send + Sync + Clone + 'static {
252 const OPTIONS: &'static [OptionDesc<Self>];
253}
254
255pub struct OptionDesc<T> {
256 pub name: OptName,
257 pub docs: &'static str,
258 pub parse: fn(&str, Option<&str>) -> Result<T>,
259 pub val_help: &'static str,
260}
261
262pub enum OptName {
263 Name(&'static str),
266
267 Prefix(&'static str),
269}
270
271impl OptName {
272 fn display_string(&self) -> String {
273 match self {
274 OptName::Name(s) => s.replace('_', "-"),
275 OptName::Prefix(s) => format!("{s}-<KEY>"),
276 }
277 }
278}
279
280pub trait WasmtimeOptionValue: Sized {
283 const VAL_HELP: &'static str;
285
286 fn parse(val: Option<&str>) -> Result<Self>;
288}
289
290impl WasmtimeOptionValue for String {
291 const VAL_HELP: &'static str = "=val";
292 fn parse(val: Option<&str>) -> Result<Self> {
293 match val {
294 Some(val) => Ok(val.to_string()),
295 None => bail!("value must be specified with `key=val` syntax"),
296 }
297 }
298}
299
300impl WasmtimeOptionValue for u32 {
301 const VAL_HELP: &'static str = "=N";
302 fn parse(val: Option<&str>) -> Result<Self> {
303 let val = String::parse(val)?;
304 match val.strip_prefix("0x") {
305 Some(hex) => Ok(u32::from_str_radix(hex, 16)?),
306 None => Ok(val.parse()?),
307 }
308 }
309}
310
311impl WasmtimeOptionValue for u64 {
312 const VAL_HELP: &'static str = "=N";
313 fn parse(val: Option<&str>) -> Result<Self> {
314 let val = String::parse(val)?;
315 match val.strip_prefix("0x") {
316 Some(hex) => Ok(u64::from_str_radix(hex, 16)?),
317 None => Ok(val.parse()?),
318 }
319 }
320}
321
322impl WasmtimeOptionValue for usize {
323 const VAL_HELP: &'static str = "=N";
324 fn parse(val: Option<&str>) -> Result<Self> {
325 let val = String::parse(val)?;
326 match val.strip_prefix("0x") {
327 Some(hex) => Ok(usize::from_str_radix(hex, 16)?),
328 None => Ok(val.parse()?),
329 }
330 }
331}
332
333impl WasmtimeOptionValue for bool {
334 const VAL_HELP: &'static str = "[=y|n]";
335 fn parse(val: Option<&str>) -> Result<Self> {
336 match val {
337 None | Some("y") | Some("yes") | Some("true") => Ok(true),
338 Some("n") | Some("no") | Some("false") => Ok(false),
339 Some(s) => bail!("unknown boolean flag `{s}`, only yes,no,<nothing> accepted"),
340 }
341 }
342}
343
344impl WasmtimeOptionValue for Duration {
345 const VAL_HELP: &'static str = "=N|Ns|Nms|..";
346 fn parse(val: Option<&str>) -> Result<Duration> {
347 let s = String::parse(val)?;
348 if let Ok(val) = s.parse() {
350 return Ok(Duration::from_secs(val));
351 }
352 let dur = humantime::parse_duration(&s)?;
354 Ok(dur)
355 }
356}
357
358impl WasmtimeOptionValue for wasmtime::OptLevel {
359 const VAL_HELP: &'static str = "=0|1|2|s";
360 fn parse(val: Option<&str>) -> Result<Self> {
361 match String::parse(val)?.as_str() {
362 "0" => Ok(wasmtime::OptLevel::None),
363 "1" => Ok(wasmtime::OptLevel::Speed),
364 "2" => Ok(wasmtime::OptLevel::Speed),
365 "s" => Ok(wasmtime::OptLevel::SpeedAndSize),
366 other => bail!(
367 "unknown optimization level `{}`, only 0,1,2,s accepted",
368 other
369 ),
370 }
371 }
372}
373
374impl WasmtimeOptionValue for wasmtime::Strategy {
375 const VAL_HELP: &'static str = "=winch|cranelift";
376 fn parse(val: Option<&str>) -> Result<Self> {
377 match String::parse(val)?.as_str() {
378 "cranelift" => Ok(wasmtime::Strategy::Cranelift),
379 "winch" => Ok(wasmtime::Strategy::Winch),
380 other => bail!("unknown compiler `{other}` only `cranelift` and `winch` accepted",),
381 }
382 }
383}
384
385impl WasmtimeOptionValue for WasiNnGraph {
386 const VAL_HELP: &'static str = "=<format>::<dir>";
387 fn parse(val: Option<&str>) -> Result<Self> {
388 let val = String::parse(val)?;
389 let mut parts = val.splitn(2, "::");
390 Ok(WasiNnGraph {
391 format: parts.next().unwrap().to_string(),
392 dir: match parts.next() {
393 Some(part) => part.into(),
394 None => bail!("graph does not contain `::` separator for directory"),
395 },
396 })
397 }
398}
399
400impl WasmtimeOptionValue for KeyValuePair {
401 const VAL_HELP: &'static str = "=<name>=<val>";
402 fn parse(val: Option<&str>) -> Result<Self> {
403 let val = String::parse(val)?;
404 let mut parts = val.splitn(2, "=");
405 Ok(KeyValuePair {
406 key: parts.next().unwrap().to_string(),
407 value: match parts.next() {
408 Some(part) => part.into(),
409 None => "".to_string(),
410 },
411 })
412 }
413}