chkc_help/
help_command.rs1use clap::{Args, Command};
4use termimad::crossterm::style::Stylize;
5
6use crate::{DocRegistry, HelpPage, HelpTheme};
7
8#[derive(Args, Debug, Clone)]
10pub struct HelpArgs {
11 pub topic: Vec<String>,
13}
14
15pub enum HelpTarget<'a> {
17 Command { path: String, cmd: &'a Command },
18 Guide { path: String },
19 Program { cmd: &'a Command },
20}
21
22pub fn resolve_help<'a>(root: &'a Command, topic: &[String]) -> anyhow::Result<HelpTarget<'a>> {
27 let mut cmd = root;
28 let mut path = Vec::new();
29
30 if topic.is_empty() {
31 return Ok(HelpTarget::Program { cmd });
32 }
33
34 for segment in topic {
35 if segment == "guide" {
36 return Ok(HelpTarget::Guide {
37 path: path.join("."),
38 });
39 }
40
41 cmd = cmd
42 .get_subcommands()
43 .find(|c| c.get_name() == segment)
44 .ok_or_else(|| anyhow::anyhow!("Unknown help topic"))?;
45
46 path.push(segment.clone());
47 }
48
49 Ok(HelpTarget::Command {
50 path: path.join("."),
51 cmd,
52 })
53}
54
55pub fn help_command(
57 app_name: &str,
58 app_version: Option<&str>,
59 root: &Command,
60 theme: &HelpTheme,
61 args: &HelpArgs,
62) -> anyhow::Result<()> {
63 run_help_topic(app_name, app_version, root, &DocRegistry::new(), theme, &args.topic)
64}
65
66pub fn help_command_docs(
68 app_name: &str,
69 app_version: Option<&str>,
70 root: &Command,
71 docs: &DocRegistry,
72 theme: &HelpTheme,
73 args: &HelpArgs,
74) -> anyhow::Result<()> {
75 run_help_topic(app_name, app_version, root, docs, theme, &args.topic)
76}
77
78pub fn help_command_program(
80 app_name: &str,
81 app_version: Option<&str>,
82 root: &Command,
83 theme: &HelpTheme,
84) -> anyhow::Result<()> {
85 run_help_topic(app_name, app_version, root, &DocRegistry::new(), theme, &Vec::new())
86}
87
88pub fn help_command_program_docs(
90 app_name: &str,
91 app_version: Option<&str>,
92 root: &Command,
93 docs: &DocRegistry,
94 theme: &HelpTheme,
95) -> anyhow::Result<()> {
96 run_help_topic(app_name, app_version, root, docs, theme, &Vec::new())
97}
98
99pub fn run_help_topic(
101 app_name: &str,
102 app_version: Option<&str>,
103 root: &Command,
104 docs: &DocRegistry,
105 theme: &HelpTheme,
106 topic: &[String],
107) -> anyhow::Result<()> {
108 let target = resolve_help(root, topic)?;
109
110 match target {
111 HelpTarget::Command { path, cmd } => {
112 let page = HelpPage::from_clap(
113 std::env::current_exe()
114 .expect("Failed to get executable path")
115 .file_name()
116 .expect("Failed to get executable name")
117 .to_str()
118 .unwrap(),
119 app_version,
120 &path,
121 cmd,
122 )
123 .with_docs(docs.command(&path));
124
125 crate::render_command_help(theme, &page);
126 }
127 HelpTarget::Guide { path } => {
128 let path = if path.is_empty() {
129 app_name.to_string()
130 } else {
131 path
132 };
133 if let Some(guide) = docs.guide(&path) {
134 let (_, rows) = termimad::crossterm::terminal::size().unwrap();
135 if guide.lines().count() > rows.into() {
136 crate::run_scrollable_help(theme, app_name, guide.to_string())?;
137 } else {
138 println!("{}", theme.skin.term_text(&guide));
139 }
140 } else {
141 println!(
142 "Guide for {} was {}.",
143 &path.with(theme.accent).bold(),
144 "not found".red().bold()
145 )
146 }
147 }
148 HelpTarget::Program { cmd } => {
149 let page = HelpPage::from_clap(app_name, app_version, "", cmd)
150 .with_docs(docs.command(""));
151
152 crate::render_command_help(theme, &page);
153 }
154 }
155
156 Ok(())
157}