1use std::io::{self, Write};
2
3use crate::Color;
4
5#[derive(Debug, Clone)]
9pub struct Help<'a>(pub &'a [HelpSection<'a>]);
10
11impl Help<'_> {
12 pub fn write(&self, buf: &mut impl Write, long: bool, colored: bool) -> io::Result<()> {
13 for section in self.0 {
14 section.write(buf, long, colored, 0, false)?;
15 }
16 Ok(())
17 }
18}
19
20#[derive(Debug, Clone)]
23#[non_exhaustive]
24pub enum HelpSection<'a> {
25 Short(&'a HelpSection<'a>),
26 Long(&'a HelpSection<'a>),
27
28 Text(&'a [Segment<'a>]),
29 Name(&'a str, &'a [HelpSection<'a>]),
30 Table(TableMode, &'a [(&'a str, &'a [HelpSection<'a>])]),
31}
32
33impl HelpSection<'_> {
34 fn write(
35 &self,
36 buf: &mut impl Write,
37 long: bool,
38 colored: bool,
39 indent: usize,
40 same_line: bool,
41 ) -> io::Result<bool> {
42 match *self {
43 HelpSection::Short(section) => {
44 if !long {
45 return section.write(buf, long, colored, indent, same_line);
46 }
47 }
48 HelpSection::Long(section) => {
49 if long {
50 return section.write(buf, long, colored, indent, same_line);
51 }
52 }
53 HelpSection::Text(segments) => {
54 if !same_line {
55 buf.write_all(
56 &b" "[..indent],
57 )?;
58 }
59 for segment in segments {
60 segment.write(&mut *buf, colored, indent)?;
61 }
62 buf.write_all(b"\n")?;
63 return Ok(true);
64 }
65 HelpSection::Name(name, sections) => {
66 buf.write_all(
67 &b"\n "[..indent + 1],
68 )?;
69
70 if colored {
71 buf.write_all(Color::ANSI_Y.as_bytes())?;
72 buf.write_all(name.as_bytes())?;
73 buf.write_all(Color::ANSI_RESET.as_bytes())?;
74 } else {
75 buf.write_all(name.as_bytes())?;
76 }
77 let new_indent = indent + 4;
78 buf.write_all(b":\n")?;
79
80 let mut line_written = false;
81 for section in sections {
82 if line_written {
83 buf.write_all(
84 &b" "[..new_indent],
85 )?;
86 }
87 line_written |= section.write(buf, long, colored, new_indent, false)?;
88 }
89 return Ok(line_written);
90 }
91 HelpSection::Table(style, rows) => {
92 let mut is_small = match style {
93 TableMode::Compact => true,
94 TableMode::Auto => !long,
95 };
96 let col_width = if is_small {
97 rows.iter().map(|&(col1, _)| col1.len()).max().unwrap_or(0) + 2
99 } else {
100 0
101 };
102 if col_width + indent > 50 {
103 is_small = false;
104 }
105 let new_indent = if is_small { indent + col_width } else { indent + 8 };
106
107 for (i, &(key, value)) in rows.iter().enumerate() {
108 if !long && value.iter().all(|section| matches!(section, HelpSection::Long(_)))
109 {
110 continue;
111 }
112
113 buf.write_all(
114 &b" "[..indent],
115 )?;
116
117 if colored {
118 buf.write_all(Color::ANSI_G.as_bytes())?;
119 buf.write_all(key.as_bytes())?;
120 buf.write_all(Color::ANSI_RESET.as_bytes())?;
121 } else {
122 buf.write_all(key.as_bytes())?;
123 }
124
125 if is_small {
126 buf.write_all(
127 &b" "
128 [..col_width - key.len()],
129 )?;
130 } else {
131 buf.write_all(b"\n")?;
132 }
133
134 let mut line_written = false;
135 for section in value {
136 line_written |= section.write(
137 buf,
138 long,
139 colored,
140 new_indent,
141 is_small && !line_written,
142 )?;
143 }
144
145 if !is_small && i + 1 < rows.len() {
146 buf.write_all(b"\n")?;
147 }
148 }
149 return Ok(true);
150 }
151 }
152 Ok(false)
153 }
154}
155
156#[derive(Debug, Copy, Clone)]
157#[non_exhaustive]
158pub enum TableMode {
159 Compact,
160 Auto,
161}
162
163#[derive(Debug, Clone)]
164pub struct Segment<'a> {
165 pub style: Option<Color>,
166 pub text: &'a str,
167 pub ticks: bool,
168}
169
170impl<'a> Segment<'a> {
171 pub const fn new(text: &'a str) -> Self {
172 Segment { style: None, text, ticks: false }
173 }
174
175 pub fn write(&self, buf: &mut impl Write, colored: bool, indent: usize) -> io::Result<()> {
176 if let Some(color) = &self.style {
177 if colored {
178 buf.write_all(color.ansi_code().as_bytes())?;
179 } else if self.ticks {
180 buf.write_all(b"`")?;
181 }
182 }
183
184 let mut is_first_line = true;
185 for line in self.text.split('\n') {
186 if !is_first_line {
187 buf.write_all(
188 &b"\n "[..indent + 1],
189 )?;
190 }
191 buf.write_all(line.as_bytes())?;
192 is_first_line = false;
193 }
194
195 if self.style.is_some() {
196 if colored {
197 buf.write_all(Color::ANSI_RESET.as_bytes())?;
198 } else if self.ticks {
199 buf.write_all(b"`")?;
200 }
201 }
202
203 Ok(())
204 }
205}