asimov_cli/commands/
help.rs1use clientele::SubcommandsProvider;
4use rayon::prelude::*;
5use std::process::Stdio;
6
7use crate::Result;
8
9pub struct CommandDescription {
10 pub name: String,
11 pub description: String,
12 pub usage: Option<String>,
13}
14
15pub struct Help;
17
18impl Help {
19 pub fn execute(&self) -> Vec<CommandDescription> {
20 let output = self.collect_output();
21
22 let mut result = vec![];
23 for (name, description) in output {
24 let lines = description.lines().collect::<Vec<_>>();
25 let usage = lines.iter().find(|line| line.starts_with("Usage:"));
26
27 let description = lines
29 .iter()
30 .map_while(|line| {
31 if !line.trim().is_empty() {
32 Some(line.to_string())
33 } else {
34 None
35 }
36 })
37 .collect::<Vec<_>>()
38 .join("\n");
39
40 result.push(CommandDescription {
41 name,
42 description,
43 usage: usage.map(|usage| usage.to_string()),
44 });
45 }
46
47 result
48 }
49
50 fn is_child_running(&self, child: &mut std::process::Child) -> Result<bool, std::io::Error> {
51 Ok(child.try_wait()?.is_none())
52 }
53
54 fn collect_output(&self) -> Vec<(String, String)> {
55 const MAX_WAIT_TIME: std::time::Duration = std::time::Duration::from_secs(1);
56
57 let provider = SubcommandsProvider::collect("asimov-", 1);
58 let commands = provider.get_commands();
59
60 let start_time = std::time::Instant::now();
61
62 commands
63 .par_iter()
64 .filter_map(|cmd| {
65 let Ok(mut child) = std::process::Command::new(&cmd.path)
66 .args(["--help"])
67 .stdin(Stdio::inherit())
68 .stdout(Stdio::piped())
69 .stderr(Stdio::piped())
70 .spawn()
71 else {
72 return None;
73 };
74
75 loop {
76 let Ok(is_running) = self.is_child_running(&mut child) else {
77 return None;
78 };
79
80 if !is_running {
81 break;
82 }
83
84 let now = std::time::Instant::now();
85 if now.duration_since(start_time) > MAX_WAIT_TIME {
86 child.kill().ok();
87 drop(child);
88 return None;
89 }
90
91 rayon::yield_local();
92 }
93
94 let output = child.wait_with_output().unwrap();
95 if !output.status.success() {
96 return None;
97 }
98
99 let stdout = String::from_utf8_lossy(&output.stdout);
100 Some((cmd.name.clone(), stdout.to_string()))
101 })
102 .collect()
103 }
104}