diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 974504d..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 9690e68..8c74a35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1517,6 +1517,7 @@ dependencies = [ "serde", "shell-words", "strum", + "tera", "thiserror", "xx", ] diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 5d909e1..1dfe9ac 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -8,6 +8,7 @@ include = [ "/Cargo.lock", "/README.md", "/src/**/*.rs", + "/src/**/*.tera", ] description = "Library for working with usage specs" homepage = { workspace = true } @@ -21,7 +22,7 @@ name = "usage" [dependencies] clap = { version = "4", features = ["derive", "string"], optional = true } -heck = "0.5.0" +heck = "0.5" indexmap = { version = "2", features = ["serde"] } itertools = "0.13" kdl = "4" @@ -30,11 +31,13 @@ miette = "5" once_cell = "1" serde = { version = "1", features = ["derive"] } strum = { version = "0.26", features = ["derive"] } +tera = { version = "1", optional = true } thiserror = "1" xx = "1" [features] -default = ["clap"] +default = [] +docs = ["tera"] [dev-dependencies] ctor = "0.2" diff --git a/lib/src/docs/markdown/cmd_template.tera b/lib/src/docs/markdown/cmd_template.tera new file mode 100644 index 0000000..ccb7ee9 --- /dev/null +++ b/lib/src/docs/markdown/cmd_template.tera @@ -0,0 +1,76 @@ +{% set deprecated = "" %}{% if cmd.deprecated %}{% set deprecated = "~~" %}{% endif %} +{{ header }} {{deprecated}}`{{ bin }}{{ cmd.full_cmd | join(sep=" ") }}`{{deprecated}}{% if cmd.deprecated %} [deprecated]{% endif -%} + +{% if cmd.before_long_help %} + +{{ cmd.before_long_help | trim }} + +{% elif cmd.before_help %} +{{ cmd.before_help | trim }} +{% endif -%} + +{% if cmd.aliases %} + +{{ header }}# Aliases: `{{ cmd.aliases | join(sep="`, `") }}`{{""-}} +{% endif -%} + +{% if cmd.long_help %} + +{{ cmd.long_help | trim -}} +{% elif cmd.help %} + +{{ cmd.help | trim -}} +{% endif -%} + +{% for name, cmd in cmd.subcommands -%} +{% if loop.first %} +{{ header }}# Subcommands +{% endif %} +* `{{ cmd.usage }}` - {{ cmd.help -}} +{% endfor -%} + +{% if cmd.args -%} +{% for arg in cmd.args %} + +{{ header }}# Arg `{{ arg.usage }}` + +{% if arg.required %}(required) {% endif -%} +{{ arg.long_help | default(value=arg.help) -}} + +{% endfor -%} +{% endif -%} +{% if cmd.flags -%} +{% for flag in cmd.flags %} + +{% if flag.deprecated -%} +{{ header }}# Flag ~~`{{ flag.usage }}`~~ [deprecated] +{% else -%} +{{ header }}# Flag `{{ flag.usage }}` +{% endif %} +{{ flag.long_help | default(value=flag.help) -}} +{% endfor -%} +{% endif -%} + +{% for ex in cmd.examples -%} +{% if loop.first %} + +{{ header}}# Examples +{% endif %} +{% if ex.header -%} +{{ header }}# {{ ex.header }} +{% endif %} +```{{ ex.lang | default(value="") }} +{{ ex.code }} +``` +{% if ex.help %} +{{ ex.help -}} +{% endif -%} +{% endfor -%} + +{% if cmd.after_long_help %} + +{{ cmd.after_long_help | trim }} +{% elif cmd.after_help %} + +{{ cmd.after_help | trim }} +{% endif -%} \ No newline at end of file diff --git a/lib/src/docs/markdown/mod.rs b/lib/src/docs/markdown/mod.rs new file mode 100644 index 0000000..a93693d --- /dev/null +++ b/lib/src/docs/markdown/mod.rs @@ -0,0 +1,41 @@ +use crate::error::UsageErr; +use tera::{Context, Tera}; + +const SPEC_TEMPLATE: &str = include_str!("cmd_template.tera"); + +impl crate::spec::Spec { + pub fn render_markdown(&self) -> Result { + let mut ctx = Context::new(); + ctx.insert("header", "#"); + ctx.insert("bin", &self.bin); + ctx.insert("cmd", &self.cmd); + let out = Tera::one_off(SPEC_TEMPLATE, &ctx, false)?; + Ok(out) + } +} + +#[cfg(test)] +mod tests { + use crate::Spec; + use insta::assert_snapshot; + + #[test] + fn test_render_markdown() { + let spec: Spec = r#" + bin "mycli" + arg "arg1" help="arg1 description" + arg "arg2" help="arg2 description" default="default value" { + choices "choice1" "choice2" "choice3" + } + arg "arg3" help="arg3 description" required=true long_help="arg3 long description" + arg "argrest" var=true + + flag "--flag1" help="flag1 description" + flag "--flag2" help="flag2 description" long_help="flag2 long description" + flag "--flag3" help="flag3 description" negate="--no-flag3" + "# + .parse() + .unwrap(); + assert_snapshot!(spec.render_markdown().unwrap()); + } +} diff --git a/lib/src/docs/markdown/snapshots/usage__docs__markdown__tests__render_markdown.snap b/lib/src/docs/markdown/snapshots/usage__docs__markdown__tests__render_markdown.snap new file mode 100644 index 0000000..3934c84 --- /dev/null +++ b/lib/src/docs/markdown/snapshots/usage__docs__markdown__tests__render_markdown.snap @@ -0,0 +1,33 @@ +--- +source: lib/src/docs/markdown/mod.rs +expression: spec.render_markdown().unwrap() +--- +# `mycli` + +## Arg `` + +(required) arg1 description + +## Arg `` + +(required) arg2 description + +## Arg `` + +(required) arg3 long description + +## Arg `...` + +(required) + +## Flag `--flag1` + +flag1 description + +## Flag `--flag2` + +flag2 long description + +## Flag `--flag3` + +flag3 description diff --git a/lib/src/docs/mod.rs b/lib/src/docs/mod.rs new file mode 100644 index 0000000..577affa --- /dev/null +++ b/lib/src/docs/mod.rs @@ -0,0 +1 @@ +mod markdown; diff --git a/lib/src/error.rs b/lib/src/error.rs index c3b7533..d976529 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -26,6 +26,10 @@ pub enum UsageErr { #[error(transparent)] FromUtf8Error(#[from] std::string::FromUtf8Error), + #[cfg(feature = "tera")] + #[error(transparent)] + TeraError(#[from] tera::Error), + #[error(transparent)] #[diagnostic(transparent)] KdlError(#[from] kdl::KdlError), diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 112d1a7..e0dcd2c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -18,6 +18,8 @@ pub mod error; pub mod complete; pub mod spec; +#[cfg(feature = "docs")] +mod docs; pub mod parse; pub(crate) mod sh; #[cfg(test)]