xref: /wasmtime-44.0.1/crates/cli-flags/src/opt.rs (revision 133a0ef4)
18995750aSAlex Crichton //! Support for parsing Wasmtime's `-O`, `-W`, etc "option groups"
28995750aSAlex Crichton //!
38995750aSAlex Crichton //! This builds up a clap-derive-like system where there's ideally a single
48995750aSAlex Crichton //! macro `wasmtime_option_group!` which is invoked per-option which enables
58995750aSAlex Crichton //! specifying options in a struct-like syntax where all other boilerplate about
68995750aSAlex Crichton //! option parsing is contained exclusively within this module.
78995750aSAlex Crichton 
812fc764dSXinzhao Xu use crate::{KeyValuePair, WasiNnGraph};
98995750aSAlex Crichton use clap::builder::{StringValueParser, TypedValueParser, ValueParserFactory};
108995750aSAlex Crichton use clap::error::{Error, ErrorKind};
112a5c141cSLudvig Liljenberg use serde::de::{self, Visitor};
12*133a0ef4SChris Fallin use std::path::PathBuf;
13*133a0ef4SChris Fallin use std::str::FromStr;
148995750aSAlex Crichton use std::time::Duration;
152a5c141cSLudvig Liljenberg use std::{fmt, marker};
16b11f8675SNick Fitzgerald use wasmtime::{Result, bail};
178995750aSAlex Crichton 
1881f9efc0SVictor Adossi /// Characters which can be safely ignored while parsing numeric options to wasmtime
1981f9efc0SVictor Adossi const IGNORED_NUMBER_CHARS: [char; 1] = ['_'];
2081f9efc0SVictor Adossi 
218995750aSAlex Crichton #[macro_export]
228995750aSAlex Crichton macro_rules! wasmtime_option_group {
238995750aSAlex Crichton     (
24519808fcSAlex Crichton         $(#[$attr:meta])*
258995750aSAlex Crichton         pub struct $opts:ident {
268995750aSAlex Crichton             $(
278995750aSAlex Crichton                 $(#[doc = $doc:tt])*
283ecb338eSNick Fitzgerald                 $(#[doc($doc_attr:meta)])?
292a5c141cSLudvig Liljenberg                 $(#[serde($serde_attr:meta)])*
308995750aSAlex Crichton                 pub $opt:ident: $container:ident<$payload:ty>,
318995750aSAlex Crichton             )+
328995750aSAlex Crichton 
338995750aSAlex Crichton             $(
348995750aSAlex Crichton                 #[prefixed = $prefix:tt]
352a5c141cSLudvig Liljenberg                 $(#[serde($serde_attr2:meta)])*
368995750aSAlex Crichton                 $(#[doc = $prefixed_doc:tt])*
373ecb338eSNick Fitzgerald                 $(#[doc($prefixed_doc_attr:meta)])?
388995750aSAlex Crichton                 pub $prefixed:ident: Vec<(String, Option<String>)>,
398995750aSAlex Crichton             )?
408995750aSAlex Crichton         }
418995750aSAlex Crichton         enum $option:ident {
428995750aSAlex Crichton             ...
438995750aSAlex Crichton         }
448995750aSAlex Crichton     ) => {
458995750aSAlex Crichton         #[derive(Default, Debug)]
46519808fcSAlex Crichton         $(#[$attr])*
478995750aSAlex Crichton         pub struct $opts {
488995750aSAlex Crichton             $(
492a5c141cSLudvig Liljenberg                 $(#[serde($serde_attr)])*
503ecb338eSNick Fitzgerald                 $(#[doc($doc_attr)])?
518995750aSAlex Crichton                 pub $opt: $container<$payload>,
528995750aSAlex Crichton             )+
538995750aSAlex Crichton             $(
542a5c141cSLudvig Liljenberg                 $(#[serde($serde_attr2)])*
558995750aSAlex Crichton                 pub $prefixed: Vec<(String, Option<String>)>,
568995750aSAlex Crichton             )?
578995750aSAlex Crichton         }
588995750aSAlex Crichton 
59ba4e22bcSAlex Crichton         #[derive(Clone, PartialEq)]
6045b60bd6SAlex Crichton         #[expect(non_camel_case_types, reason = "macro-generated code")]
618995750aSAlex Crichton         enum $option {
628995750aSAlex Crichton             $(
638995750aSAlex Crichton                 $opt($payload),
648995750aSAlex Crichton             )+
658995750aSAlex Crichton             $(
668995750aSAlex Crichton                 $prefixed(String, Option<String>),
678995750aSAlex Crichton             )?
688995750aSAlex Crichton         }
698995750aSAlex Crichton 
708995750aSAlex Crichton         impl $crate::opt::WasmtimeOption for $option {
718995750aSAlex Crichton             const OPTIONS: &'static [$crate::opt::OptionDesc<$option>] = &[
728995750aSAlex Crichton                 $(
738995750aSAlex Crichton                     $crate::opt::OptionDesc {
748995750aSAlex Crichton                         name: $crate::opt::OptName::Name(stringify!($opt)),
758995750aSAlex Crichton                         parse: |_, s| {
768995750aSAlex Crichton                             Ok($option::$opt(
778995750aSAlex Crichton                                 $crate::opt::WasmtimeOptionValue::parse(s)?
788995750aSAlex Crichton                             ))
798995750aSAlex Crichton                         },
808995750aSAlex Crichton                         val_help: <$payload as $crate::opt::WasmtimeOptionValue>::VAL_HELP,
818995750aSAlex Crichton                         docs: concat!($($doc, "\n",)*),
828995750aSAlex Crichton                     },
838995750aSAlex Crichton                  )+
848995750aSAlex Crichton                 $(
858995750aSAlex Crichton                     $crate::opt::OptionDesc {
868995750aSAlex Crichton                         name: $crate::opt::OptName::Prefix($prefix),
878995750aSAlex Crichton                         parse: |name, val| {
888995750aSAlex Crichton                             Ok($option::$prefixed(
898995750aSAlex Crichton                                 name.to_string(),
908995750aSAlex Crichton                                 val.map(|v| v.to_string()),
918995750aSAlex Crichton                             ))
928995750aSAlex Crichton                         },
938995750aSAlex Crichton                         val_help: "[=val]",
948995750aSAlex Crichton                         docs: concat!($($prefixed_doc, "\n",)*),
958995750aSAlex Crichton                     },
968995750aSAlex Crichton                  )?
978995750aSAlex Crichton             ];
988995750aSAlex Crichton         }
998995750aSAlex Crichton 
100ba4e22bcSAlex Crichton         impl core::fmt::Display for $option {
101ba4e22bcSAlex Crichton             fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102ba4e22bcSAlex Crichton                 match self {
103ba4e22bcSAlex Crichton                     $(
104ba4e22bcSAlex Crichton                         $option::$opt(val) => {
105ba4e22bcSAlex Crichton                             write!(f, "{}=", stringify!($opt).replace('_', "-"))?;
106ba4e22bcSAlex Crichton                             $crate::opt::WasmtimeOptionValue::display(val, f)
107ba4e22bcSAlex Crichton                         }
108ba4e22bcSAlex Crichton                     )+
109ba4e22bcSAlex Crichton                     $(
110ba4e22bcSAlex Crichton                         $option::$prefixed(key, val) => {
111ba4e22bcSAlex Crichton                             write!(f, "{}-{key}", stringify!($prefixed))?;
112ba4e22bcSAlex Crichton                             if let Some(val) = val {
113ba4e22bcSAlex Crichton                                 write!(f, "={val}")?;
114ba4e22bcSAlex Crichton                             }
115ba4e22bcSAlex Crichton                             Ok(())
116ba4e22bcSAlex Crichton                         }
117ba4e22bcSAlex Crichton                     )?
118ba4e22bcSAlex Crichton                 }
119ba4e22bcSAlex Crichton             }
120ba4e22bcSAlex Crichton         }
121ba4e22bcSAlex Crichton 
1228995750aSAlex Crichton         impl $opts {
1238995750aSAlex Crichton             fn configure_with(&mut self, opts: &[$crate::opt::CommaSeparated<$option>]) {
1248995750aSAlex Crichton                 for opt in opts.iter().flat_map(|o| o.0.iter()) {
1258995750aSAlex Crichton                     match opt {
1268995750aSAlex Crichton                         $(
1278995750aSAlex Crichton                             $option::$opt(val) => {
128ba4e22bcSAlex Crichton                                 $crate::opt::OptionContainer::push(&mut self.$opt, val.clone());
1298995750aSAlex Crichton                             }
1308995750aSAlex Crichton                         )+
1318995750aSAlex Crichton                         $(
1328995750aSAlex Crichton                             $option::$prefixed(key, val) => self.$prefixed.push((key.clone(), val.clone())),
1338995750aSAlex Crichton                         )?
1348995750aSAlex Crichton                     }
1358995750aSAlex Crichton                 }
1368995750aSAlex Crichton             }
137ba4e22bcSAlex Crichton 
138ba4e22bcSAlex Crichton             fn to_options(&self) -> Vec<$option> {
139ba4e22bcSAlex Crichton                 let mut ret = Vec::new();
140ba4e22bcSAlex Crichton                 $(
141ba4e22bcSAlex Crichton                     for item in $crate::opt::OptionContainer::get(&self.$opt) {
142ba4e22bcSAlex Crichton                         ret.push($option::$opt(item.clone()));
143ba4e22bcSAlex Crichton                     }
144ba4e22bcSAlex Crichton                 )+
145ba4e22bcSAlex Crichton                 $(
146ba4e22bcSAlex Crichton                     for (key,val) in self.$prefixed.iter() {
147ba4e22bcSAlex Crichton                         ret.push($option::$prefixed(key.clone(), val.clone()));
148ba4e22bcSAlex Crichton                     }
149ba4e22bcSAlex Crichton                 )?
150ba4e22bcSAlex Crichton                 ret
151ba4e22bcSAlex Crichton             }
1528995750aSAlex Crichton         }
1538995750aSAlex Crichton     };
1548995750aSAlex Crichton }
1558995750aSAlex Crichton 
1568995750aSAlex Crichton /// Parser registered with clap which handles parsing the `...` in `-O ...`.
157519808fcSAlex Crichton #[derive(Clone, Debug, PartialEq)]
1588995750aSAlex Crichton pub struct CommaSeparated<T>(pub Vec<T>);
1598995750aSAlex Crichton 
1608995750aSAlex Crichton impl<T> ValueParserFactory for CommaSeparated<T>
1618995750aSAlex Crichton where
1628995750aSAlex Crichton     T: WasmtimeOption,
1638995750aSAlex Crichton {
1648995750aSAlex Crichton     type Parser = CommaSeparatedParser<T>;
1658995750aSAlex Crichton 
value_parser() -> CommaSeparatedParser<T>1668995750aSAlex Crichton     fn value_parser() -> CommaSeparatedParser<T> {
1678995750aSAlex Crichton         CommaSeparatedParser(marker::PhantomData)
1688995750aSAlex Crichton     }
1698995750aSAlex Crichton }
1708995750aSAlex Crichton 
1718995750aSAlex Crichton #[derive(Clone)]
1728995750aSAlex Crichton pub struct CommaSeparatedParser<T>(marker::PhantomData<T>);
1738995750aSAlex Crichton 
1748995750aSAlex Crichton impl<T> TypedValueParser for CommaSeparatedParser<T>
1758995750aSAlex Crichton where
1768995750aSAlex Crichton     T: WasmtimeOption,
1778995750aSAlex Crichton {
1788995750aSAlex Crichton     type Value = CommaSeparated<T>;
1798995750aSAlex Crichton 
parse_ref( &self, cmd: &clap::Command, arg: Option<&clap::Arg>, value: &std::ffi::OsStr, ) -> Result<Self::Value, Error>1808995750aSAlex Crichton     fn parse_ref(
1818995750aSAlex Crichton         &self,
1828995750aSAlex Crichton         cmd: &clap::Command,
1838995750aSAlex Crichton         arg: Option<&clap::Arg>,
1848995750aSAlex Crichton         value: &std::ffi::OsStr,
1858995750aSAlex Crichton     ) -> Result<Self::Value, Error> {
1868995750aSAlex Crichton         let val = StringValueParser::new().parse_ref(cmd, arg, value)?;
1878995750aSAlex Crichton 
1888995750aSAlex Crichton         let options = T::OPTIONS;
1898995750aSAlex Crichton         let arg = arg.expect("should always have an argument");
1908995750aSAlex Crichton         let arg_long = arg.get_long().expect("should have a long name specified");
1918995750aSAlex Crichton         let arg_short = arg.get_short().expect("should have a short name specified");
1928995750aSAlex Crichton 
1938995750aSAlex Crichton         // Handle `-O help` which dumps all the `-O` options, their messages,
1948995750aSAlex Crichton         // and then exits.
1958995750aSAlex Crichton         if val == "help" {
1968995750aSAlex Crichton             let mut max = 0;
1978995750aSAlex Crichton             for d in options {
1988995750aSAlex Crichton                 max = max.max(d.name.display_string().len() + d.val_help.len());
1998995750aSAlex Crichton             }
2008995750aSAlex Crichton             println!("Available {arg_long} options:\n");
2018995750aSAlex Crichton             for d in options {
2028995750aSAlex Crichton                 print!(
2038995750aSAlex Crichton                     "  -{arg_short} {:>1$}",
2048995750aSAlex Crichton                     d.name.display_string(),
2058995750aSAlex Crichton                     max - d.val_help.len()
2068995750aSAlex Crichton                 );
2078995750aSAlex Crichton                 print!("{}", d.val_help);
2088995750aSAlex Crichton                 print!(" --");
2098995750aSAlex Crichton                 if val == "help" {
2108995750aSAlex Crichton                     for line in d.docs.lines().map(|s| s.trim()) {
2118995750aSAlex Crichton                         if line.is_empty() {
2128995750aSAlex Crichton                             break;
2138995750aSAlex Crichton                         }
2148995750aSAlex Crichton                         print!(" {line}");
2158995750aSAlex Crichton                     }
2168995750aSAlex Crichton                     println!();
2178995750aSAlex Crichton                 } else {
2188995750aSAlex Crichton                     println!();
2198995750aSAlex Crichton                     for line in d.docs.lines().map(|s| s.trim()) {
2208995750aSAlex Crichton                         let line = line.trim();
2218995750aSAlex Crichton                         println!("        {line}");
2228995750aSAlex Crichton                     }
2238995750aSAlex Crichton                 }
2248995750aSAlex Crichton             }
2258995750aSAlex Crichton             println!("\npass `-{arg_short} help-long` to see longer-form explanations");
2268995750aSAlex Crichton             std::process::exit(0);
2278995750aSAlex Crichton         }
2288995750aSAlex Crichton         if val == "help-long" {
2298995750aSAlex Crichton             println!("Available {arg_long} options:\n");
2308995750aSAlex Crichton             for d in options {
2318995750aSAlex Crichton                 println!(
2328995750aSAlex Crichton                     "  -{arg_short} {}{} --",
2338995750aSAlex Crichton                     d.name.display_string(),
2348995750aSAlex Crichton                     d.val_help
2358995750aSAlex Crichton                 );
2368995750aSAlex Crichton                 println!();
2378995750aSAlex Crichton                 for line in d.docs.lines().map(|s| s.trim()) {
2388995750aSAlex Crichton                     let line = line.trim();
2398995750aSAlex Crichton                     println!("        {line}");
2408995750aSAlex Crichton                 }
2418995750aSAlex Crichton             }
2428995750aSAlex Crichton             std::process::exit(0);
2438995750aSAlex Crichton         }
2448995750aSAlex Crichton 
2458995750aSAlex Crichton         let mut result = Vec::new();
2468995750aSAlex Crichton         for val in val.split(',') {
2478995750aSAlex Crichton             // Split `k=v` into `k` and `v` where `v` is optional
2488995750aSAlex Crichton             let mut iter = val.splitn(2, '=');
2498995750aSAlex Crichton             let key = iter.next().unwrap();
2508995750aSAlex Crichton             let key_val = iter.next();
2518995750aSAlex Crichton 
2528995750aSAlex Crichton             // Find `key` within `T::OPTIONS`
2538995750aSAlex Crichton             let option = options
2548995750aSAlex Crichton                 .iter()
2558995750aSAlex Crichton                 .filter_map(|d| match d.name {
2568995750aSAlex Crichton                     OptName::Name(s) => {
2578995750aSAlex Crichton                         let s = s.replace('_', "-");
25890ac295eSAlex Crichton                         if s == key { Some((d, s)) } else { None }
2598995750aSAlex Crichton                     }
2608995750aSAlex Crichton                     OptName::Prefix(s) => {
2618995750aSAlex Crichton                         let name = key.strip_prefix(s)?.strip_prefix("-")?;
2628995750aSAlex Crichton                         Some((d, name.to_string()))
2638995750aSAlex Crichton                     }
2648995750aSAlex Crichton                 })
2658995750aSAlex Crichton                 .next();
2668995750aSAlex Crichton 
2678995750aSAlex Crichton             let (desc, key) = match option {
2688995750aSAlex Crichton                 Some(pair) => pair,
2698995750aSAlex Crichton                 None => {
2708995750aSAlex Crichton                     let err = Error::raw(
2718995750aSAlex Crichton                         ErrorKind::InvalidValue,
2728995750aSAlex Crichton                         format!("unknown -{arg_short} / --{arg_long} option: {key}\n"),
2738995750aSAlex Crichton                     );
2748995750aSAlex Crichton                     return Err(err.with_cmd(cmd));
2758995750aSAlex Crichton                 }
2768995750aSAlex Crichton             };
2778995750aSAlex Crichton 
2788995750aSAlex Crichton             result.push((desc.parse)(&key, key_val).map_err(|e| {
2798995750aSAlex Crichton                 Error::raw(
2808995750aSAlex Crichton                     ErrorKind::InvalidValue,
2818995750aSAlex Crichton                     format!("failed to parse -{arg_short} option `{val}`: {e:?}\n"),
2828995750aSAlex Crichton                 )
2838995750aSAlex Crichton                 .with_cmd(cmd)
2848995750aSAlex Crichton             })?)
2858995750aSAlex Crichton         }
2868995750aSAlex Crichton 
2878995750aSAlex Crichton         Ok(CommaSeparated(result))
2888995750aSAlex Crichton     }
2898995750aSAlex Crichton }
2908995750aSAlex Crichton 
2918995750aSAlex Crichton /// Helper trait used by `CommaSeparated` which contains a list of all options
2928995750aSAlex Crichton /// supported by the option group.
2938995750aSAlex Crichton pub trait WasmtimeOption: Sized + Send + Sync + Clone + 'static {
2948995750aSAlex Crichton     const OPTIONS: &'static [OptionDesc<Self>];
2958995750aSAlex Crichton }
2968995750aSAlex Crichton 
2978995750aSAlex Crichton pub struct OptionDesc<T> {
2988995750aSAlex Crichton     pub name: OptName,
2998995750aSAlex Crichton     pub docs: &'static str,
3008995750aSAlex Crichton     pub parse: fn(&str, Option<&str>) -> Result<T>,
3018995750aSAlex Crichton     pub val_help: &'static str,
3028995750aSAlex Crichton }
3038995750aSAlex Crichton 
3048995750aSAlex Crichton pub enum OptName {
3058995750aSAlex Crichton     /// A named option. Note that the `str` here uses `_` instead of `-` because
3068995750aSAlex Crichton     /// it's derived from Rust syntax.
3078995750aSAlex Crichton     Name(&'static str),
3088995750aSAlex Crichton 
3098995750aSAlex Crichton     /// A prefixed option which strips the specified `name`, then `-`.
3108995750aSAlex Crichton     Prefix(&'static str),
3118995750aSAlex Crichton }
3128995750aSAlex Crichton 
3138995750aSAlex Crichton impl OptName {
display_string(&self) -> String3148995750aSAlex Crichton     fn display_string(&self) -> String {
3158995750aSAlex Crichton         match self {
3168995750aSAlex Crichton             OptName::Name(s) => s.replace('_', "-"),
3178995750aSAlex Crichton             OptName::Prefix(s) => format!("{s}-<KEY>"),
3188995750aSAlex Crichton         }
3198995750aSAlex Crichton     }
3208995750aSAlex Crichton }
3218995750aSAlex Crichton 
3228995750aSAlex Crichton /// A helper trait for all types of options that can be parsed. This is what
3238995750aSAlex Crichton /// actually parses the `=val` in `key=val`
3248995750aSAlex Crichton pub trait WasmtimeOptionValue: Sized {
3258995750aSAlex Crichton     /// Help text for the value to be specified.
3268995750aSAlex Crichton     const VAL_HELP: &'static str;
3278995750aSAlex Crichton 
3288995750aSAlex Crichton     /// Parses the provided value, if given, returning an error on failure.
parse(val: Option<&str>) -> Result<Self>3298995750aSAlex Crichton     fn parse(val: Option<&str>) -> Result<Self>;
330ba4e22bcSAlex Crichton 
331ba4e22bcSAlex Crichton     /// Write the value to `f` that would parse to `self`.
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result332ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
3338995750aSAlex Crichton }
3348995750aSAlex Crichton 
3358995750aSAlex Crichton impl WasmtimeOptionValue for String {
3368995750aSAlex Crichton     const VAL_HELP: &'static str = "=val";
parse(val: Option<&str>) -> Result<Self>3378995750aSAlex Crichton     fn parse(val: Option<&str>) -> Result<Self> {
3388995750aSAlex Crichton         match val {
3398995750aSAlex Crichton             Some(val) => Ok(val.to_string()),
3408995750aSAlex Crichton             None => bail!("value must be specified with `key=val` syntax"),
3418995750aSAlex Crichton         }
3428995750aSAlex Crichton     }
343ba4e22bcSAlex Crichton 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result344ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345ba4e22bcSAlex Crichton         f.write_str(self)
346ba4e22bcSAlex Crichton     }
3478995750aSAlex Crichton }
3488995750aSAlex Crichton 
349*133a0ef4SChris Fallin impl WasmtimeOptionValue for PathBuf {
350*133a0ef4SChris Fallin     const VAL_HELP: &'static str = "=path";
parse(val: Option<&str>) -> Result<Self>351*133a0ef4SChris Fallin     fn parse(val: Option<&str>) -> Result<Self> {
352*133a0ef4SChris Fallin         match val {
353*133a0ef4SChris Fallin             Some(val) => Ok(PathBuf::from_str(val)?),
354*133a0ef4SChris Fallin             None => bail!("value must be specified with key=val syntax"),
355*133a0ef4SChris Fallin         }
356*133a0ef4SChris Fallin     }
357*133a0ef4SChris Fallin 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result358*133a0ef4SChris Fallin     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359*133a0ef4SChris Fallin         write!(f, "{self:?}")
360*133a0ef4SChris Fallin     }
361*133a0ef4SChris Fallin }
362*133a0ef4SChris Fallin 
3638995750aSAlex Crichton impl WasmtimeOptionValue for u32 {
3648995750aSAlex Crichton     const VAL_HELP: &'static str = "=N";
parse(val: Option<&str>) -> Result<Self>3658995750aSAlex Crichton     fn parse(val: Option<&str>) -> Result<Self> {
36681f9efc0SVictor Adossi         let val = String::parse(val)?.replace(IGNORED_NUMBER_CHARS, "");
3678c8725e5SAlex Crichton         match val.strip_prefix("0x") {
3688c8725e5SAlex Crichton             Some(hex) => Ok(u32::from_str_radix(hex, 16)?),
3698c8725e5SAlex Crichton             None => Ok(val.parse()?),
3708c8725e5SAlex Crichton         }
3718995750aSAlex Crichton     }
372ba4e22bcSAlex Crichton 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result373ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374ba4e22bcSAlex Crichton         write!(f, "{self}")
375ba4e22bcSAlex Crichton     }
3768995750aSAlex Crichton }
3778995750aSAlex Crichton 
3788995750aSAlex Crichton impl WasmtimeOptionValue for u64 {
3798995750aSAlex Crichton     const VAL_HELP: &'static str = "=N";
parse(val: Option<&str>) -> Result<Self>3808995750aSAlex Crichton     fn parse(val: Option<&str>) -> Result<Self> {
38181f9efc0SVictor Adossi         let val = String::parse(val)?.replace(IGNORED_NUMBER_CHARS, "");
3828c8725e5SAlex Crichton         match val.strip_prefix("0x") {
3838c8725e5SAlex Crichton             Some(hex) => Ok(u64::from_str_radix(hex, 16)?),
3848c8725e5SAlex Crichton             None => Ok(val.parse()?),
3858c8725e5SAlex Crichton         }
3868995750aSAlex Crichton     }
387ba4e22bcSAlex Crichton 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result388ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
389ba4e22bcSAlex Crichton         write!(f, "{self}")
390ba4e22bcSAlex Crichton     }
3918995750aSAlex Crichton }
3928995750aSAlex Crichton 
3938995750aSAlex Crichton impl WasmtimeOptionValue for usize {
3948995750aSAlex Crichton     const VAL_HELP: &'static str = "=N";
parse(val: Option<&str>) -> Result<Self>3958995750aSAlex Crichton     fn parse(val: Option<&str>) -> Result<Self> {
39681f9efc0SVictor Adossi         let val = String::parse(val)?.replace(IGNORED_NUMBER_CHARS, "");
3978c8725e5SAlex Crichton         match val.strip_prefix("0x") {
3988c8725e5SAlex Crichton             Some(hex) => Ok(usize::from_str_radix(hex, 16)?),
3998c8725e5SAlex Crichton             None => Ok(val.parse()?),
4008c8725e5SAlex Crichton         }
4018995750aSAlex Crichton     }
402ba4e22bcSAlex Crichton 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result403ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404ba4e22bcSAlex Crichton         write!(f, "{self}")
405ba4e22bcSAlex Crichton     }
4068995750aSAlex Crichton }
4078995750aSAlex Crichton 
4088995750aSAlex Crichton impl WasmtimeOptionValue for bool {
4098995750aSAlex Crichton     const VAL_HELP: &'static str = "[=y|n]";
parse(val: Option<&str>) -> Result<Self>4108995750aSAlex Crichton     fn parse(val: Option<&str>) -> Result<Self> {
4118995750aSAlex Crichton         match val {
4128995750aSAlex Crichton             None | Some("y") | Some("yes") | Some("true") => Ok(true),
4138995750aSAlex Crichton             Some("n") | Some("no") | Some("false") => Ok(false),
4148995750aSAlex Crichton             Some(s) => bail!("unknown boolean flag `{s}`, only yes,no,<nothing> accepted"),
4158995750aSAlex Crichton         }
4168995750aSAlex Crichton     }
417ba4e22bcSAlex Crichton 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result418ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419ba4e22bcSAlex Crichton         if *self {
420ba4e22bcSAlex Crichton             f.write_str("y")
421ba4e22bcSAlex Crichton         } else {
422ba4e22bcSAlex Crichton             f.write_str("n")
423ba4e22bcSAlex Crichton         }
424ba4e22bcSAlex Crichton     }
4258995750aSAlex Crichton }
4268995750aSAlex Crichton 
4278995750aSAlex Crichton impl WasmtimeOptionValue for Duration {
4288995750aSAlex Crichton     const VAL_HELP: &'static str = "=N|Ns|Nms|..";
parse(val: Option<&str>) -> Result<Duration>4298995750aSAlex Crichton     fn parse(val: Option<&str>) -> Result<Duration> {
4308995750aSAlex Crichton         let s = String::parse(val)?;
4318995750aSAlex Crichton         // assume an integer without a unit specified is a number of seconds ...
4328995750aSAlex Crichton         if let Ok(val) = s.parse() {
4338995750aSAlex Crichton             return Ok(Duration::from_secs(val));
4348995750aSAlex Crichton         }
435bda83128SAlex Crichton 
436bda83128SAlex Crichton         if let Some(num) = s.strip_suffix("s") {
437bda83128SAlex Crichton             if let Ok(val) = num.parse() {
438bda83128SAlex Crichton                 return Ok(Duration::from_secs(val));
439bda83128SAlex Crichton             }
440bda83128SAlex Crichton         }
441bda83128SAlex Crichton         if let Some(num) = s.strip_suffix("ms") {
442bda83128SAlex Crichton             if let Ok(val) = num.parse() {
443bda83128SAlex Crichton                 return Ok(Duration::from_millis(val));
444bda83128SAlex Crichton             }
445bda83128SAlex Crichton         }
446bda83128SAlex Crichton         if let Some(num) = s.strip_suffix("us").or(s.strip_suffix("μs")) {
447bda83128SAlex Crichton             if let Ok(val) = num.parse() {
448bda83128SAlex Crichton                 return Ok(Duration::from_micros(val));
449bda83128SAlex Crichton             }
450bda83128SAlex Crichton         }
451bda83128SAlex Crichton         if let Some(num) = s.strip_suffix("ns") {
452bda83128SAlex Crichton             if let Ok(val) = num.parse() {
453bda83128SAlex Crichton                 return Ok(Duration::from_nanos(val));
454bda83128SAlex Crichton             }
455bda83128SAlex Crichton         }
456bda83128SAlex Crichton 
457bda83128SAlex Crichton         bail!("failed to parse duration: {s}")
4588995750aSAlex Crichton     }
459ba4e22bcSAlex Crichton 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result460ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461bda83128SAlex Crichton         let subsec = self.subsec_nanos();
462bda83128SAlex Crichton         if subsec == 0 {
463bda83128SAlex Crichton             write!(f, "{}s", self.as_secs())
464bda83128SAlex Crichton         } else if subsec % 1_000 == 0 {
465bda83128SAlex Crichton             write!(f, "{}μs", self.as_micros())
466bda83128SAlex Crichton         } else if subsec % 1_000_000 == 0 {
467bda83128SAlex Crichton             write!(f, "{}ms", self.as_millis())
468bda83128SAlex Crichton         } else {
469bda83128SAlex Crichton             write!(f, "{}ns", self.as_nanos())
470bda83128SAlex Crichton         }
471ba4e22bcSAlex Crichton     }
4728995750aSAlex Crichton }
4738995750aSAlex Crichton 
4748995750aSAlex Crichton impl WasmtimeOptionValue for wasmtime::OptLevel {
4758995750aSAlex Crichton     const VAL_HELP: &'static str = "=0|1|2|s";
parse(val: Option<&str>) -> Result<Self>4768995750aSAlex Crichton     fn parse(val: Option<&str>) -> Result<Self> {
4778995750aSAlex Crichton         match String::parse(val)?.as_str() {
4788995750aSAlex Crichton             "0" => Ok(wasmtime::OptLevel::None),
4798995750aSAlex Crichton             "1" => Ok(wasmtime::OptLevel::Speed),
4808995750aSAlex Crichton             "2" => Ok(wasmtime::OptLevel::Speed),
4818995750aSAlex Crichton             "s" => Ok(wasmtime::OptLevel::SpeedAndSize),
482557cc2d6SAlex Crichton             other => bail!("unknown optimization level `{other}`, only 0,1,2,s accepted"),
4838995750aSAlex Crichton         }
4848995750aSAlex Crichton     }
485ba4e22bcSAlex Crichton 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result486ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487ba4e22bcSAlex Crichton         match *self {
488ba4e22bcSAlex Crichton             wasmtime::OptLevel::None => f.write_str("0"),
489ba4e22bcSAlex Crichton             wasmtime::OptLevel::Speed => f.write_str("2"),
490ba4e22bcSAlex Crichton             wasmtime::OptLevel::SpeedAndSize => f.write_str("s"),
491ba4e22bcSAlex Crichton             _ => unreachable!(),
492ba4e22bcSAlex Crichton         }
493ba4e22bcSAlex Crichton     }
4948995750aSAlex Crichton }
4958995750aSAlex Crichton 
4961e3e5fc1SChris Fallin impl WasmtimeOptionValue for wasmtime::RegallocAlgorithm {
4971e3e5fc1SChris Fallin     const VAL_HELP: &'static str = "=backtracking|single-pass";
parse(val: Option<&str>) -> Result<Self>4981e3e5fc1SChris Fallin     fn parse(val: Option<&str>) -> Result<Self> {
4991e3e5fc1SChris Fallin         match String::parse(val)?.as_str() {
5001e3e5fc1SChris Fallin             "backtracking" => Ok(wasmtime::RegallocAlgorithm::Backtracking),
50173de2ee9SChris Fallin             "single-pass" => Ok(wasmtime::RegallocAlgorithm::SinglePass),
502557cc2d6SAlex Crichton             other => {
503557cc2d6SAlex Crichton                 bail!("unknown regalloc algorithm`{other}`, only backtracking,single-pass accepted")
504557cc2d6SAlex Crichton             }
5051e3e5fc1SChris Fallin         }
5061e3e5fc1SChris Fallin     }
507ba4e22bcSAlex Crichton 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result508ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509ba4e22bcSAlex Crichton         match *self {
510ba4e22bcSAlex Crichton             wasmtime::RegallocAlgorithm::Backtracking => f.write_str("backtracking"),
51173de2ee9SChris Fallin             wasmtime::RegallocAlgorithm::SinglePass => f.write_str("single-pass"),
512ba4e22bcSAlex Crichton             _ => unreachable!(),
513ba4e22bcSAlex Crichton         }
514ba4e22bcSAlex Crichton     }
5151e3e5fc1SChris Fallin }
5161e3e5fc1SChris Fallin 
5178995750aSAlex Crichton impl WasmtimeOptionValue for wasmtime::Strategy {
5188995750aSAlex Crichton     const VAL_HELP: &'static str = "=winch|cranelift";
parse(val: Option<&str>) -> Result<Self>5198995750aSAlex Crichton     fn parse(val: Option<&str>) -> Result<Self> {
5208995750aSAlex Crichton         match String::parse(val)?.as_str() {
5218995750aSAlex Crichton             "cranelift" => Ok(wasmtime::Strategy::Cranelift),
5228995750aSAlex Crichton             "winch" => Ok(wasmtime::Strategy::Winch),
523519808fcSAlex Crichton             other => bail!("unknown compiler `{other}` only `cranelift` and `winch` accepted",),
5248995750aSAlex Crichton         }
5258995750aSAlex Crichton     }
526ba4e22bcSAlex Crichton 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result527ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528ba4e22bcSAlex Crichton         match *self {
529ba4e22bcSAlex Crichton             wasmtime::Strategy::Cranelift => f.write_str("cranelift"),
530ba4e22bcSAlex Crichton             wasmtime::Strategy::Winch => f.write_str("winch"),
531ba4e22bcSAlex Crichton             _ => unreachable!(),
532ba4e22bcSAlex Crichton         }
533ba4e22bcSAlex Crichton     }
5348995750aSAlex Crichton }
5358995750aSAlex Crichton 
53683bf774dSNick Fitzgerald impl WasmtimeOptionValue for wasmtime::Collector {
53783bf774dSNick Fitzgerald     const VAL_HELP: &'static str = "=drc|null";
parse(val: Option<&str>) -> Result<Self>53883bf774dSNick Fitzgerald     fn parse(val: Option<&str>) -> Result<Self> {
53983bf774dSNick Fitzgerald         match String::parse(val)?.as_str() {
54083bf774dSNick Fitzgerald             "drc" => Ok(wasmtime::Collector::DeferredReferenceCounting),
54183bf774dSNick Fitzgerald             "null" => Ok(wasmtime::Collector::Null),
54283bf774dSNick Fitzgerald             other => bail!("unknown collector `{other}` only `drc` and `null` accepted",),
54383bf774dSNick Fitzgerald         }
54483bf774dSNick Fitzgerald     }
545ba4e22bcSAlex Crichton 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result546ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
547ba4e22bcSAlex Crichton         match *self {
548ba4e22bcSAlex Crichton             wasmtime::Collector::DeferredReferenceCounting => f.write_str("drc"),
549ba4e22bcSAlex Crichton             wasmtime::Collector::Null => f.write_str("null"),
550ba4e22bcSAlex Crichton             _ => unreachable!(),
551ba4e22bcSAlex Crichton         }
552ba4e22bcSAlex Crichton     }
553ba4e22bcSAlex Crichton }
554ba4e22bcSAlex Crichton 
555698028ceSAlex Crichton impl WasmtimeOptionValue for wasmtime::Enabled {
556ba4e22bcSAlex Crichton     const VAL_HELP: &'static str = "[=y|n|auto]";
parse(val: Option<&str>) -> Result<Self>557ba4e22bcSAlex Crichton     fn parse(val: Option<&str>) -> Result<Self> {
558ba4e22bcSAlex Crichton         match val {
559698028ceSAlex Crichton             None | Some("y") | Some("yes") | Some("true") => Ok(wasmtime::Enabled::Yes),
560698028ceSAlex Crichton             Some("n") | Some("no") | Some("false") => Ok(wasmtime::Enabled::No),
561698028ceSAlex Crichton             Some("auto") => Ok(wasmtime::Enabled::Auto),
562698028ceSAlex Crichton             Some(s) => bail!("unknown flag `{s}`, only yes,no,auto,<nothing> accepted"),
563ba4e22bcSAlex Crichton         }
564ba4e22bcSAlex Crichton     }
565ba4e22bcSAlex Crichton 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result566ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
567ba4e22bcSAlex Crichton         match *self {
568698028ceSAlex Crichton             wasmtime::Enabled::Yes => f.write_str("y"),
569698028ceSAlex Crichton             wasmtime::Enabled::No => f.write_str("n"),
570698028ceSAlex Crichton             wasmtime::Enabled::Auto => f.write_str("auto"),
571ba4e22bcSAlex Crichton         }
572ba4e22bcSAlex Crichton     }
57383bf774dSNick Fitzgerald }
57483bf774dSNick Fitzgerald 
5758995750aSAlex Crichton impl WasmtimeOptionValue for WasiNnGraph {
5768995750aSAlex Crichton     const VAL_HELP: &'static str = "=<format>::<dir>";
parse(val: Option<&str>) -> Result<Self>5778995750aSAlex Crichton     fn parse(val: Option<&str>) -> Result<Self> {
5788995750aSAlex Crichton         let val = String::parse(val)?;
5798995750aSAlex Crichton         let mut parts = val.splitn(2, "::");
5808995750aSAlex Crichton         Ok(WasiNnGraph {
5818995750aSAlex Crichton             format: parts.next().unwrap().to_string(),
5828995750aSAlex Crichton             dir: match parts.next() {
5838995750aSAlex Crichton                 Some(part) => part.into(),
5848995750aSAlex Crichton                 None => bail!("graph does not contain `::` separator for directory"),
5858995750aSAlex Crichton             },
5868995750aSAlex Crichton         })
5878995750aSAlex Crichton     }
588ba4e22bcSAlex Crichton 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result589ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
590ba4e22bcSAlex Crichton         write!(f, "{}::{}", self.format, self.dir)
591ba4e22bcSAlex Crichton     }
5928995750aSAlex Crichton }
5934b842a05SXinzhao Xu 
59412fc764dSXinzhao Xu impl WasmtimeOptionValue for KeyValuePair {
5954b842a05SXinzhao Xu     const VAL_HELP: &'static str = "=<name>=<val>";
parse(val: Option<&str>) -> Result<Self>5964b842a05SXinzhao Xu     fn parse(val: Option<&str>) -> Result<Self> {
5974b842a05SXinzhao Xu         let val = String::parse(val)?;
5984b842a05SXinzhao Xu         let mut parts = val.splitn(2, "=");
59912fc764dSXinzhao Xu         Ok(KeyValuePair {
6004b842a05SXinzhao Xu             key: parts.next().unwrap().to_string(),
6014b842a05SXinzhao Xu             value: match parts.next() {
6024b842a05SXinzhao Xu                 Some(part) => part.into(),
6034b842a05SXinzhao Xu                 None => "".to_string(),
6044b842a05SXinzhao Xu             },
6054b842a05SXinzhao Xu         })
6064b842a05SXinzhao Xu     }
607ba4e22bcSAlex Crichton 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result608ba4e22bcSAlex Crichton     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609ba4e22bcSAlex Crichton         f.write_str(&self.key)?;
610ba4e22bcSAlex Crichton         if !self.value.is_empty() {
611ba4e22bcSAlex Crichton             f.write_str("=")?;
612ba4e22bcSAlex Crichton             f.write_str(&self.value)?;
613ba4e22bcSAlex Crichton         }
614ba4e22bcSAlex Crichton         Ok(())
615ba4e22bcSAlex Crichton     }
616ba4e22bcSAlex Crichton }
617ba4e22bcSAlex Crichton 
618ba4e22bcSAlex Crichton pub trait OptionContainer<T> {
push(&mut self, val: T)619ba4e22bcSAlex Crichton     fn push(&mut self, val: T);
get<'a>(&'a self) -> impl Iterator<Item = &'a T> where T: 'a620ba4e22bcSAlex Crichton     fn get<'a>(&'a self) -> impl Iterator<Item = &'a T>
621ba4e22bcSAlex Crichton     where
622ba4e22bcSAlex Crichton         T: 'a;
623ba4e22bcSAlex Crichton }
624ba4e22bcSAlex Crichton 
625ba4e22bcSAlex Crichton impl<T> OptionContainer<T> for Option<T> {
push(&mut self, val: T)626ba4e22bcSAlex Crichton     fn push(&mut self, val: T) {
627ba4e22bcSAlex Crichton         *self = Some(val);
628ba4e22bcSAlex Crichton     }
get<'a>(&'a self) -> impl Iterator<Item = &'a T> where T: 'a,629ba4e22bcSAlex Crichton     fn get<'a>(&'a self) -> impl Iterator<Item = &'a T>
630ba4e22bcSAlex Crichton     where
631ba4e22bcSAlex Crichton         T: 'a,
632ba4e22bcSAlex Crichton     {
633ba4e22bcSAlex Crichton         self.iter()
634ba4e22bcSAlex Crichton     }
635ba4e22bcSAlex Crichton }
636ba4e22bcSAlex Crichton 
637ba4e22bcSAlex Crichton impl<T> OptionContainer<T> for Vec<T> {
push(&mut self, val: T)638ba4e22bcSAlex Crichton     fn push(&mut self, val: T) {
639ba4e22bcSAlex Crichton         Vec::push(self, val);
640ba4e22bcSAlex Crichton     }
get<'a>(&'a self) -> impl Iterator<Item = &'a T> where T: 'a,641ba4e22bcSAlex Crichton     fn get<'a>(&'a self) -> impl Iterator<Item = &'a T>
642ba4e22bcSAlex Crichton     where
643ba4e22bcSAlex Crichton         T: 'a,
644ba4e22bcSAlex Crichton     {
645ba4e22bcSAlex Crichton         self.iter()
646ba4e22bcSAlex Crichton     }
6474b842a05SXinzhao Xu }
64881f9efc0SVictor Adossi 
6492a5c141cSLudvig Liljenberg // Used to parse toml values into string so that we can reuse the `WasmtimeOptionValue::parse`
6502a5c141cSLudvig Liljenberg // for parsing toml values the same way we parse command line values.
6512a5c141cSLudvig Liljenberg //
6522a5c141cSLudvig Liljenberg // Used for wasmtime::Strategy, wasmtime::Collector, wasmtime::OptLevel, wasmtime::RegallocAlgorithm
6532a5c141cSLudvig Liljenberg struct ToStringVisitor {}
6542a5c141cSLudvig Liljenberg 
6552a5c141cSLudvig Liljenberg impl<'de> Visitor<'de> for ToStringVisitor {
6562a5c141cSLudvig Liljenberg     type Value = String;
6572a5c141cSLudvig Liljenberg 
expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result6582a5c141cSLudvig Liljenberg     fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
6592a5c141cSLudvig Liljenberg         write!(formatter, "&str, u64, or i64")
6602a5c141cSLudvig Liljenberg     }
6612a5c141cSLudvig Liljenberg 
visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: de::Error,6622a5c141cSLudvig Liljenberg     fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
6632a5c141cSLudvig Liljenberg     where
6642a5c141cSLudvig Liljenberg         E: de::Error,
6652a5c141cSLudvig Liljenberg     {
6662a5c141cSLudvig Liljenberg         Ok(s.to_owned())
6672a5c141cSLudvig Liljenberg     }
6682a5c141cSLudvig Liljenberg 
visit_u64<E>(self, v: u64) -> Result<Self::Value, E> where E: de::Error,6692a5c141cSLudvig Liljenberg     fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
6702a5c141cSLudvig Liljenberg     where
6712a5c141cSLudvig Liljenberg         E: de::Error,
6722a5c141cSLudvig Liljenberg     {
6732a5c141cSLudvig Liljenberg         Ok(v.to_string())
6742a5c141cSLudvig Liljenberg     }
6752a5c141cSLudvig Liljenberg 
visit_i64<E>(self, v: i64) -> Result<Self::Value, E> where E: de::Error,6762a5c141cSLudvig Liljenberg     fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
6772a5c141cSLudvig Liljenberg     where
6782a5c141cSLudvig Liljenberg         E: de::Error,
6792a5c141cSLudvig Liljenberg     {
6802a5c141cSLudvig Liljenberg         Ok(v.to_string())
6812a5c141cSLudvig Liljenberg     }
6822a5c141cSLudvig Liljenberg }
6832a5c141cSLudvig Liljenberg 
6842a5c141cSLudvig Liljenberg // Deserializer that uses the `WasmtimeOptionValue::parse` to parse toml values
cli_parse_wrapper<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error> where T: WasmtimeOptionValue, D: serde::Deserializer<'de>,6852a5c141cSLudvig Liljenberg pub(crate) fn cli_parse_wrapper<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
6862a5c141cSLudvig Liljenberg where
6872a5c141cSLudvig Liljenberg     T: WasmtimeOptionValue,
6882a5c141cSLudvig Liljenberg     D: serde::Deserializer<'de>,
6892a5c141cSLudvig Liljenberg {
6902a5c141cSLudvig Liljenberg     let to_string_visitor = ToStringVisitor {};
6912a5c141cSLudvig Liljenberg     let str = deserializer.deserialize_any(to_string_visitor)?;
6922a5c141cSLudvig Liljenberg 
6932a5c141cSLudvig Liljenberg     T::parse(Some(&str))
6942a5c141cSLudvig Liljenberg         .map(Some)
6952a5c141cSLudvig Liljenberg         .map_err(serde::de::Error::custom)
6962a5c141cSLudvig Liljenberg }
6972a5c141cSLudvig Liljenberg 
69881f9efc0SVictor Adossi #[cfg(test)]
69981f9efc0SVictor Adossi mod tests {
70081f9efc0SVictor Adossi     use super::WasmtimeOptionValue;
70181f9efc0SVictor Adossi 
70281f9efc0SVictor Adossi     #[test]
numbers_with_underscores()70381f9efc0SVictor Adossi     fn numbers_with_underscores() {
70481f9efc0SVictor Adossi         assert!(<u32 as WasmtimeOptionValue>::parse(Some("123")).is_ok_and(|v| v == 123));
70581f9efc0SVictor Adossi         assert!(<u32 as WasmtimeOptionValue>::parse(Some("1_2_3")).is_ok_and(|v| v == 123));
70681f9efc0SVictor Adossi     }
70781f9efc0SVictor Adossi }
708