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)]