From c6c830ab5d3a5aa0f06fe68d4a645a4ef45a4fb7 Mon Sep 17 00:00:00 2001 From: Jeff Dickey <216188+jdx@users.noreply.github.com> Date: Sun, 14 Jan 2024 02:37:13 -0600 Subject: [PATCH] wip --- cli/src/cli/generate/markdown.rs | 4 +-- examples/mise.usage.kdl | 11 ++++++++ src/lib.rs | 2 +- src/parse/cmd.rs | 47 ++++++++++++++++++-------------- src/parse/config.rs | 2 +- src/parse/helpers.rs | 30 +++++++++++++------- src/parse/spec.rs | 8 +++--- 7 files changed, 65 insertions(+), 39 deletions(-) diff --git a/cli/src/cli/generate/markdown.rs b/cli/src/cli/generate/markdown.rs index 2d25593..6dd908a 100644 --- a/cli/src/cli/generate/markdown.rs +++ b/cli/src/cli/generate/markdown.rs @@ -11,7 +11,7 @@ use thiserror::Error; use xx::file; use usage::parse::config::SpecConfig; -use usage::{SchemaCmd, Spec}; +use usage::{Spec, SpecCommand}; use crate::errors::UsageCLIError; @@ -450,7 +450,7 @@ impl MarkdownBuilder { } } -fn gather_subcommands(cmds: &[&SchemaCmd]) -> Vec { +fn gather_subcommands(cmds: &[&SpecCommand]) -> Vec { let mut subcommands = vec![]; for cmd in cmds { if cmd.hide { diff --git a/examples/mise.usage.kdl b/examples/mise.usage.kdl index bb0c48e..e8d0a15 100644 --- a/examples/mise.usage.kdl +++ b/examples/mise.usage.kdl @@ -15,4 +15,15 @@ config { cmd "zzz" \ help="Sleeps for a while." \ long_help="Sleeps for a while. The amount of time is determined by the --timeout option." { + + example r#" + mise zzz + mise zzz --timeout 2 + "# + + example r#" + $ mise zzz + Sleeping for 1.5 seconds... + Done. + "# } diff --git a/src/lib.rs b/src/lib.rs index 3618dd9..cb02bb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,6 @@ pub(crate) mod env; pub mod parse; pub use crate::parse::arg::Arg; -pub use crate::parse::cmd::SchemaCmd; +pub use crate::parse::cmd::SpecCommand; pub use crate::parse::flag::Flag; pub use crate::parse::spec::Spec; diff --git a/src/parse/cmd.rs b/src/parse/cmd.rs index 6dbff99..d0fe577 100644 --- a/src/parse/cmd.rs +++ b/src/parse/cmd.rs @@ -7,9 +7,9 @@ use kdl::{KdlDocument, KdlEntry, KdlNode}; use serde::Serialize; #[derive(Debug, Default, Serialize, Clone)] -pub struct SchemaCmd { +pub struct SpecCommand { pub full_cmd: Vec, - pub subcommands: IndexMap, + pub subcommands: IndexMap, pub args: Vec, pub flags: Vec, pub hide: bool, @@ -23,11 +23,12 @@ pub struct SchemaCmd { pub before_long_help: Option, pub after_help: Option, pub after_long_help: Option, + pub examples: Vec, } -impl SchemaCmd { +impl SpecCommand { pub(crate) fn parse(ctx: &ParsingContext, node: &NodeHelper) -> Result { - node.ensure_args_count(1, 1)?; + node.ensure_arg_len(1..=1)?; let mut cmd = Self { name: node.arg(0)?.ensure_string()?.to_string(), ..Default::default() @@ -44,37 +45,41 @@ impl SchemaCmd { } "subcommand_required" => cmd.subcommand_required = v.ensure_bool()?, "hide" => cmd.hide = v.ensure_bool()?, - k => bail_parse!(ctx, node.span(), "unsupported cmd key {k}"), + k => bail_parse!(ctx, node.span(), "unsupported cmd prop {k}"), } } for child in node.children() { - let child: NodeHelper = child.into(); match child.name() { "flag" => cmd.flags.push(Flag::parse(ctx, &child)?), "arg" => cmd.args.push(Arg::parse(ctx, &child)?), "cmd" => { - let node = SchemaCmd::parse(ctx, &child)?; + let node = SpecCommand::parse(ctx, &child)?; 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::>(); + .ensure_arg_len(1..)? + .args() + .map(|e| e.ensure_string()) + .collect::, _>>()?; let hide = child - .props() .get("hide") .map(|n| n.ensure_bool()) - .transpose()? - .unwrap_or(false); + .unwrap_or(Ok(false))?; if hide { cmd.hidden_aliases.extend(alias); } else { cmd.aliases.extend(alias); } } + "example" => { + let example = child + .ensure_arg_len(1..=1)? + .args() + .map(|e| e.ensure_string()) + .collect::, _>>()?; + cmd.examples.extend(example); + } k => bail_parse!(ctx, *child.node.span(), "unsupported cmd key {k}"), } } @@ -125,8 +130,8 @@ impl SchemaCmd { } } -impl From<&SchemaCmd> for KdlNode { - fn from(cmd: &SchemaCmd) -> Self { +impl From<&SpecCommand> for KdlNode { + fn from(cmd: &SpecCommand) -> Self { let mut node = Self::new("cmd"); node.entries_mut().push(cmd.name.clone().into()); if cmd.hide { @@ -194,7 +199,7 @@ impl From<&SchemaCmd> for KdlNode { } #[cfg(feature = "clap")] -impl From<&clap::Command> for SchemaCmd { +impl From<&clap::Command> for SpecCommand { fn from(cmd: &clap::Command) -> Self { let mut spec = Self { name: cmd.get_name().to_string(), @@ -225,7 +230,7 @@ impl From<&clap::Command> for SchemaCmd { } spec.subcommand_required = cmd.is_subcommand_required_set(); for subcmd in cmd.get_subcommands() { - let mut scmd: SchemaCmd = subcmd.into(); + let mut scmd: SpecCommand = subcmd.into(); scmd.name = subcmd.get_name().to_string(); spec.subcommands.insert(scmd.name.clone(), scmd); } @@ -234,8 +239,8 @@ impl From<&clap::Command> for SchemaCmd { } #[cfg(feature = "clap")] -impl From<&SchemaCmd> for clap::Command { - fn from(cmd: &SchemaCmd) -> Self { +impl From<&SpecCommand> for clap::Command { + fn from(cmd: &SpecCommand) -> Self { let mut app = Self::new(cmd.name.to_string()); if let Some(help) = &cmd.help { app = app.about(help); diff --git a/src/parse/config.rs b/src/parse/config.rs index d392d51..bd6cba9 100644 --- a/src/parse/config.rs +++ b/src/parse/config.rs @@ -18,7 +18,7 @@ impl SpecConfig { pub(crate) fn parse(ctx: &ParsingContext, node: &NodeHelper) -> Result { let mut config = Self::default(); for node in node.children() { - node.ensure_args_count(1, 1)?; + node.ensure_arg_len(1..=1)?; match node.name() { "prop" => { let key = node.arg(0)?; diff --git a/src/parse/helpers.rs b/src/parse/helpers.rs index 5bea665..639a7da 100644 --- a/src/parse/helpers.rs +++ b/src/parse/helpers.rs @@ -1,6 +1,8 @@ use indexmap::IndexMap; use kdl::{KdlEntry, KdlNode, KdlValue}; use miette::SourceSpan; +use std::fmt::Debug; +use std::ops::{Range, RangeBounds, RangeFrom}; use crate::error::UsageErr; use crate::parse::context::ParsingContext; @@ -22,19 +24,20 @@ impl<'a> NodeHelper<'a> { pub(crate) fn span(&self) -> SourceSpan { *self.node.span() } - pub(crate) fn ensure_args_count(&self, min: usize, max: usize) -> Result<(), UsageErr> { - let count = self - .node - .entries() - .iter() - .filter(|e| e.name().is_none()) - .count(); - if count < min || count > max { + pub(crate) fn ensure_arg_len(&self, range: R) -> Result<&Self, UsageErr> + where + R: RangeBounds + Debug, + { + let count = self.args().count(); + if !range.contains(&count) { let ctx = self.ctx; let span = self.span(); - bail_parse!(ctx, span, "expected {min} to {max} arguments, got {count}") + bail_parse!(ctx, span, "expected {range:?} arguments, got {count}",) } - Ok(()) + Ok(self) + } + pub(crate) fn get(&self, key: &str) -> Option { + self.node.get(key).map(|e| ParseEntry::new(self.ctx, e)) } pub(crate) fn arg(&self, i: usize) -> Result { if let Some(entry) = self.node.entries().get(i) { @@ -48,6 +51,13 @@ impl<'a> NodeHelper<'a> { } bail_parse!(self.ctx, self.span(), "missing argument") } + pub(crate) fn args(&self) -> impl Iterator + '_ { + self.node + .entries() + .iter() + .filter(|e| e.name().is_none()) + .map(|e| ParseEntry::new(self.ctx, e)) + } pub(crate) fn props(&self) -> IndexMap<&str, ParseEntry> { self.node .entries() diff --git a/src/parse/spec.rs b/src/parse/spec.rs index 1743f62..47c2a67 100644 --- a/src/parse/spec.rs +++ b/src/parse/spec.rs @@ -7,7 +7,7 @@ use serde::Serialize; use xx::file; use crate::error::UsageErr; -use crate::parse::cmd::SchemaCmd; +use crate::parse::cmd::SpecCommand; use crate::parse::config::SpecConfig; use crate::parse::context::ParsingContext; use crate::parse::helpers::NodeHelper; @@ -17,7 +17,7 @@ use crate::{Arg, Flag}; pub struct Spec { pub name: String, pub bin: String, - pub cmd: SchemaCmd, + pub cmd: SpecCommand, pub config: SpecConfig, pub version: Option, pub usage: String, @@ -69,7 +69,7 @@ impl Spec { "arg" => schema.cmd.args.push(Arg::parse(ctx, &node)?), "flag" => schema.cmd.flags.push(Flag::parse(ctx, &node)?), "cmd" => { - let node: SchemaCmd = SchemaCmd::parse(ctx, &node)?; + let node: SpecCommand = SpecCommand::parse(ctx, &node)?; schema.cmd.subcommands.insert(node.name.to_string(), node); } "config" => schema.config = SpecConfig::parse(ctx, &node)?, @@ -128,7 +128,7 @@ fn split_script(file: &Path) -> Result<(String, String), UsageErr> { Ok((schema, body)) } -fn set_subcommand_ancestors(cmd: &mut SchemaCmd, ancestors: &[String]) { +fn set_subcommand_ancestors(cmd: &mut SpecCommand, ancestors: &[String]) { let ancestors = ancestors.to_vec(); for subcmd in cmd.subcommands.values_mut() { subcmd.full_cmd = ancestors