From 83c0969d26f773e27a09e4af3a8e34a0ae7d7344 Mon Sep 17 00:00:00 2001 From: Jeff Dickey <216188+jdx@users.noreply.github.com> Date: Sat, 13 Jan 2024 17:28:25 -0600 Subject: [PATCH] wip --- src/error.rs | 6 ++++- src/parse/arg.rs | 32 +++++++++++-------------- src/parse/cmd.rs | 57 ++++++++++++++++++++++---------------------- src/parse/config.rs | 1 + src/parse/flag.rs | 37 +++++++++++++--------------- src/parse/helpers.rs | 46 +++++++++++++++++++++++++++++++---- 6 files changed, 106 insertions(+), 73 deletions(-) diff --git a/src/error.rs b/src/error.rs index 4ce101c..988f61c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -31,7 +31,11 @@ impl UsageErr { #[macro_export] macro_rules! bail_parse { - ($span:expr, $fmt:literal$(,$arg:expr)*) => {{ + ($span:expr, $fmt:literal) => {{ + let msg = format!($fmt); + return std::result::Result::Err(UsageErr::new(msg, $span.span())); + }}; + ($span:expr, $fmt:literal, $($arg:tt)*) => {{ let msg = format!($fmt, $($arg)*); return std::result::Result::Err(UsageErr::new(msg, $span.span())); }}; diff --git a/src/parse/arg.rs b/src/parse/arg.rs index e8d51e1..84f1514 100644 --- a/src/parse/arg.rs +++ b/src/parse/arg.rs @@ -5,6 +5,7 @@ use itertools::Itertools; use kdl::{KdlEntry, KdlNode}; use crate::error::UsageErr; +use crate::parse::helpers::NodeHelper; #[derive(Debug, Default)] pub struct Arg { @@ -65,24 +66,19 @@ impl From<&Arg> for KdlNode { impl TryFrom<&KdlNode> for Arg { type Error = UsageErr; fn try_from(node: &KdlNode) -> Result { - let mut arg: Arg = node - .entries() - .first() - .and_then(|e| e.value().as_string()) - .map(|s| s.parse()) - .transpose()? - .unwrap_or_default(); - for entry in node.entries().iter().skip(1) { - match entry.name().unwrap().to_string().as_str() { - "help" => arg.help = entry.value().as_string().map(|s| s.to_string()), - "long_help" => arg.long_help = entry.value().as_string().map(|s| s.to_string()), - "required" => arg.required = entry.value().as_bool().unwrap(), - "var" => arg.var = entry.value().as_bool().unwrap(), - "hide" => arg.hide = entry.value().as_bool().unwrap(), - "var_min" => arg.var_min = entry.value().as_i64(), - "var_max" => arg.var_max = entry.value().as_i64(), - "default" => arg.default = entry.value().as_string().map(|s| s.to_string()), - _ => Err(UsageErr::new(entry.to_string(), entry.span()))?, + let hnode: NodeHelper = node.into(); + let mut arg: Arg = hnode.arg(0)?.ensure_string()?.parse()?; + for (k, v) in hnode.props() { + match k { + "help" => arg.help = Some(v.ensure_string()?), + "long_help" => arg.long_help = Some(v.ensure_string()?), + "required" => arg.required = v.ensure_bool()?, + "var" => arg.var = v.ensure_bool()?, + "hide" => arg.hide = v.ensure_bool()?, + "var_min" => arg.var_min = v.ensure_i64().map(Some)?, + "var_max" => arg.var_max = v.ensure_i64().map(Some)?, + "default" => arg.default = v.ensure_string().map(Some)?, + k => bail_parse!(v.entry, "unsupported key {k}"), } } Ok(arg) diff --git a/src/parse/cmd.rs b/src/parse/cmd.rs index 14a192e..74f588e 100644 --- a/src/parse/cmd.rs +++ b/src/parse/cmd.rs @@ -1,4 +1,5 @@ use crate::error::UsageErr; +use crate::parse::helpers::NodeHelper; use crate::{Arg, Flag}; use indexmap::IndexMap; use kdl::{KdlDocument, KdlEntry, KdlNode}; @@ -68,58 +69,56 @@ impl From<&SchemaCmd> for KdlNode { impl TryFrom<&KdlNode> for SchemaCmd { type Error = UsageErr; fn try_from(node: &KdlNode) -> Result { + let hnode: NodeHelper = node.into(); + hnode.ensure_args_count(1, 1)?; let mut cmd = Self { - name: node - .entries() - .first() - .expect("no name provided") - .value() - .as_string() - .unwrap() - .to_string(), + name: hnode.arg(0)?.ensure_string()?.to_string(), ..Default::default() }; - for entry in node.entries().iter().skip(1) { - match entry.name().unwrap().to_string().as_str() { - "help" => cmd.help = entry.value().as_string().map(|s| s.to_string()), - "long_help" => cmd.long_help = entry.value().as_string().map(|s| s.to_string()), - "before_help" => cmd.before_help = entry.value().as_string().map(|s| s.to_string()), - "before_long_help" => { - cmd.before_long_help = entry.value().as_string().map(|s| s.to_string()) - } - "after_help" => cmd.after_help = entry.value().as_string().map(|s| s.to_string()), + for (k, v) in hnode.props() { + match k { + "help" => cmd.help = Some(v.ensure_string()?), + "long_help" => cmd.long_help = Some(v.ensure_string()?), + "before_help" => cmd.before_help = Some(v.ensure_string()?), + "before_long_help" => cmd.before_long_help = Some(v.ensure_string()?), + "after_help" => cmd.after_help = Some(v.ensure_string()?), "after_long_help" => { - cmd.after_long_help = entry.value().as_string().map(|s| s.to_string()) + cmd.after_long_help = Some(v.ensure_string()?); } - "subcommand_required" => cmd.subcommand_required = entry.value().as_bool().unwrap(), - "hide" => cmd.hide = entry.value().as_bool().unwrap(), - _ => Err(UsageErr::new(entry.to_string(), entry.span()))?, + "subcommand_required" => cmd.subcommand_required = v.ensure_bool()?, + "hide" => cmd.hide = v.ensure_bool()?, + k => bail_parse!(node, "unsupported key {k}"), } } for child in node.children().map(|c| c.nodes()).unwrap_or_default() { - match child.name().to_string().as_str() { - "flag" => cmd.flags.push(child.try_into()?), - "arg" => cmd.args.push(child.try_into()?), + let child: NodeHelper = child.into(); + match child.name() { + "flag" => cmd.flags.push(child.node.try_into()?), + "arg" => cmd.args.push(child.node.try_into()?), "cmd" => { - let node: SchemaCmd = child.try_into()?; + let node: SchemaCmd = child.node.try_into()?; cmd.subcommands.insert(node.name.to_string(), node); } "alias" => { let alias = child + .node .entries() .iter() .filter_map(|e| e.value().as_string().map(|v| v.to_string())) .collect::>(); - if child + let hide = child + .props() .get("hide") - .is_some_and(|n| n.value().as_bool().unwrap()) - { + .map(|n| n.ensure_bool()) + .transpose()? + .unwrap_or(false); + if hide { cmd.hidden_aliases.extend(alias); } else { cmd.aliases.extend(alias); } } - _ => Err(UsageErr::new(child.to_string(), child.span()))?, + k => bail_parse!(child.node, "unsupported key {k}"), } } Ok(cmd) diff --git a/src/parse/config.rs b/src/parse/config.rs index f53b176..45a3a0f 100644 --- a/src/parse/config.rs +++ b/src/parse/config.rs @@ -52,6 +52,7 @@ impl TryFrom<&KdlNode> for SpecConfig { if let Some(children) = doc.children().map(|doc| doc.nodes()) { for node in children { let ph = NodeHelper::new(node); + ph.ensure_args_count(1, 1)?; match ph.name() { "prop" => { let key = ph.arg(0)?; diff --git a/src/parse/flag.rs b/src/parse/flag.rs index 6476ff1..f2d2c9d 100644 --- a/src/parse/flag.rs +++ b/src/parse/flag.rs @@ -5,6 +5,7 @@ use kdl::{KdlDocument, KdlEntry, KdlNode}; use crate::error::UsageErr; use crate::error::UsageErr::InvalidFlag; +use crate::parse::helpers::NodeHelper; use crate::{bail_parse, Arg}; #[derive(Debug, Default)] @@ -81,30 +82,26 @@ impl From<&Flag> for KdlNode { impl TryFrom<&KdlNode> for Flag { type Error = UsageErr; fn try_from(node: &KdlNode) -> Result { - let mut flag: Self = node - .entries() - .first() - .and_then(|e| e.value().as_string()) - .map(|s| s.parse()) - .transpose()? - .unwrap_or_default(); - for entry in node.entries().iter().skip(1) { - match entry.name().unwrap().to_string().as_str() { - "help" => flag.help = entry.value().as_string().map(|s| s.to_string()), - "long_help" => flag.long_help = entry.value().as_string().map(|s| s.to_string()), - "required" => flag.required = entry.value().as_bool().unwrap(), - "var" => flag.var = entry.value().as_bool().unwrap(), - "hide" => flag.hide = entry.value().as_bool().unwrap(), - "global" => flag.global = entry.value().as_bool().unwrap(), - "count" => flag.count = entry.value().as_bool().unwrap(), - k => bail_parse!(entry, "unsupported key {k}"), + let hnode: NodeHelper = node.into(); + let mut flag: Self = hnode.arg(0)?.ensure_string()?.parse()?; + for (k, v) in hnode.props() { + match k { + "help" => flag.help = Some(v.ensure_string()?), + "long_help" => flag.long_help = Some(v.ensure_string()?), + "required" => flag.required = v.ensure_bool()?, + "var" => flag.var = v.ensure_bool()?, + "hide" => flag.hide = v.ensure_bool()?, + "global" => flag.global = v.ensure_bool()?, + "count" => flag.count = v.ensure_bool()?, + k => bail_parse!(v.entry, "unsupported key {k}"), } } let children = node.children().map(|c| c.nodes()).unwrap_or_default(); for child in children { - match child.name().to_string().as_str() { - "arg" => flag.arg = Some(child.try_into()?), - k => bail_parse!(child, "unsupported key {k}"), + let child: NodeHelper = child.into(); + match child.name() { + "arg" => flag.arg = Some(child.node.try_into()?), + k => bail_parse!(child.node, "unsupported key {k}"), } } Ok(flag) diff --git a/src/parse/helpers.rs b/src/parse/helpers.rs index 3828270..b1adf73 100644 --- a/src/parse/helpers.rs +++ b/src/parse/helpers.rs @@ -1,7 +1,8 @@ -use crate::error::UsageErr; use indexmap::IndexMap; use kdl::{KdlEntry, KdlNode, KdlValue}; +use crate::error::UsageErr; + #[derive(Debug)] pub struct NodeHelper<'a> { pub(crate) node: &'a KdlNode, @@ -15,7 +16,24 @@ impl<'a> NodeHelper<'a> { pub(crate) fn name(&self) -> &str { self.node.name().value() } - + pub(crate) fn ensure_args_count(&self, min: usize, max: usize) -> Result<(), UsageErr> { + let count = self + .node + .entries() + .into_iter() + .filter(|e| e.name().is_none()) + .count(); + if count < min || count > max { + bail_parse!( + self.node, + "expected {} to {} arguments, got {}", + min, + max, + count + ) + } + Ok(()) + } pub(crate) fn arg(&self, i: usize) -> Result { if let Some(entry) = self.node.entries().get(i) { if entry.name().is_some() { @@ -25,7 +43,6 @@ impl<'a> NodeHelper<'a> { } bail_parse!(self.node, "missing argument") } - pub(crate) fn props(&self) -> IndexMap<&str, ParseEntry> { self.node .entries() @@ -60,9 +77,28 @@ impl<'a> From<&'a KdlEntry> for ParseEntry<'a> { } impl<'a> ParseEntry<'a> { - pub fn ensure_string(&self) -> Result<&str, UsageErr> { + pub fn ensure_i64(&self) -> Result { + match self.value.as_i64() { + Some(i) => Ok(i), + None => bail_parse!(self.entry, "expected integer"), + } + } + #[allow(dead_code)] + pub fn ensure_f64(&self) -> Result { + match self.value.as_f64() { + Some(f) => Ok(f), + None => bail_parse!(self.entry, "expected float"), + } + } + pub fn ensure_bool(&self) -> Result { + match self.value.as_bool() { + Some(b) => Ok(b), + None => bail_parse!(self.entry, "expected bool"), + } + } + pub fn ensure_string(&self) -> Result { match self.value.as_string() { - Some(s) => Ok(s), + Some(s) => Ok(s.to_string()), None => bail_parse!(self.entry, "expected string"), } }