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