use std::str::FromStr; use knuffel::errors::DecodeError; use miette::miette; use regex::Regex; mod merge_with; pub use merge_with::*; #[derive(Debug, Clone, Copy, PartialEq)] pub struct Percent(pub f64); // MIN and MAX generics are only used during parsing to check the value. #[derive(Debug, Default, Clone, Copy, PartialEq)] pub struct FloatOrInt(pub f64); /// Flag, with an optional explicit value. /// /// Intended to be used as an `Option` field, as a tri-state: /// - (missing): unset, `None` /// - just `field`: set, `Some(true)` /// - explicitly `field true` or `field false`: set, `Some(true)` or `Some(false)` #[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq, Eq)] pub struct Flag(#[knuffel(argument, default = true)] pub bool); /// `Regex` that implements `PartialEq` by its string form. #[derive(Debug, Clone)] pub struct RegexEq(pub Regex); impl PartialEq for RegexEq { fn eq(&self, other: &Self) -> bool { self.0.as_str() == other.0.as_str() } } impl Eq for RegexEq {} impl FromStr for RegexEq { type Err = ::Err; fn from_str(s: &str) -> Result { Regex::from_str(s).map(Self) } } impl FromStr for Percent { type Err = miette::Error; fn from_str(s: &str) -> Result { let Some((value, empty)) = s.split_once('%') else { return Err(miette!("value must end with '%'")); }; if !empty.is_empty() { return Err(miette!("trailing characters after '%' are not allowed")); } let value: f64 = value.parse().map_err(|_| miette!("error parsing value"))?; Ok(Percent(value / 100.)) } } impl MergeWith> for f64 { fn merge_with(&mut self, part: &FloatOrInt) { *self = part.0; } } impl MergeWith for bool { fn merge_with(&mut self, part: &Flag) { *self = part.0; } } impl knuffel::DecodeScalar for FloatOrInt { fn type_check( type_name: &Option>, ctx: &mut knuffel::decode::Context, ) { if let Some(type_name) = &type_name { ctx.emit_error(DecodeError::unexpected( type_name, "type name", "no type name expected for this node", )); } } fn raw_decode( val: &knuffel::span::Spanned, ctx: &mut knuffel::decode::Context, ) -> Result> { match &**val { knuffel::ast::Literal::Int(ref value) => match value.try_into() { Ok(v) => { if (MIN..=MAX).contains(&v) { Ok(FloatOrInt(f64::from(v))) } else { ctx.emit_error(DecodeError::conversion( val, format!("value must be between {MIN} and {MAX}"), )); Ok(FloatOrInt::default()) } } Err(e) => { ctx.emit_error(DecodeError::conversion(val, e)); Ok(FloatOrInt::default()) } }, knuffel::ast::Literal::Decimal(ref value) => match value.try_into() { Ok(v) => { if (f64::from(MIN)..=f64::from(MAX)).contains(&v) { Ok(FloatOrInt(v)) } else { ctx.emit_error(DecodeError::conversion( val, format!("value must be between {MIN} and {MAX}"), )); Ok(FloatOrInt::default()) } } Err(e) => { ctx.emit_error(DecodeError::conversion(val, e)); Ok(FloatOrInt::default()) } }, _ => { ctx.emit_error(DecodeError::unsupported( val, "Unsupported value, only numbers are recognized", )); Ok(FloatOrInt::default()) } } } } pub fn expect_only_children( node: &knuffel::ast::SpannedNode, ctx: &mut knuffel::decode::Context, ) where S: knuffel::traits::ErrorSpan, { if let Some(type_name) = &node.type_name { ctx.emit_error(DecodeError::unexpected( type_name, "type name", "no type name expected for this node", )); } for val in node.arguments.iter() { ctx.emit_error(DecodeError::unexpected( &val.literal, "argument", "no arguments expected for this node", )) } for name in node.properties.keys() { ctx.emit_error(DecodeError::unexpected( name, "property", "no properties expected for this node", )) } } pub fn parse_arg_node>( name: &str, node: &knuffel::ast::SpannedNode, ctx: &mut knuffel::decode::Context, ) -> Result> { let mut iter_args = node.arguments.iter(); let val = iter_args.next().ok_or_else(|| { DecodeError::missing(node, format!("additional argument `{name}` is required")) })?; let value = knuffel::traits::DecodeScalar::decode(val, ctx)?; if let Some(val) = iter_args.next() { ctx.emit_error(DecodeError::unexpected( &val.literal, "argument", "unexpected argument", )); } for name in node.properties.keys() { ctx.emit_error(DecodeError::unexpected( name, "property", format!("unexpected property `{}`", name.escape_default()), )); } for child in node.children() { ctx.emit_error(DecodeError::unexpected( child, "node", format!("unexpected node `{}`", child.node_name.escape_default()), )); } Ok(value) }