1#[cfg(feature = "docgen")]
2use crate::{
3 info::Info,
4 meta_help::{HelpItem, HelpItems},
5};
6use crate::{
7 item::{Item, ShortLong},
8 Meta,
9};
10
11mod console;
12mod html;
13#[cfg(feature = "docgen")]
14mod manpage;
15mod splitter;
16
17pub(crate) use self::console::Color;
18use self::console::MAX_WIDTH;
19
20#[cfg(feature = "docgen")]
21pub use manpage::Section;
22
23impl From<&[(&str, Style)]> for Doc {
24 fn from(val: &[(&str, Style)]) -> Self {
25 let mut res = Doc::default();
26 for (text, style) in val {
27 res.write_str(text, *style);
28 }
29 res
30 }
31}
32
33impl<const N: usize> From<&'static [(&'static str, Style); N]> for Doc {
34 fn from(val: &'static [(&'static str, Style); N]) -> Self {
35 let mut res = Doc::default();
36 for (text, style) in val {
37 res.write_str(text, *style);
38 }
39 res
40 }
41}
42
43#[derive(Copy, Clone)]
50pub struct MetaInfo<'a>(pub(crate) &'a Meta);
51
52impl Doc {
53 #[inline]
54 pub fn text(&mut self, text: &str) {
58 self.write_str(text, Style::Text);
59 }
60
61 #[inline]
62 pub fn literal(&mut self, text: &str) {
66 self.write_str(text, Style::Literal);
67 }
68
69 #[inline]
70 pub fn emphasis(&mut self, text: &str) {
74 self.write_str(text, Style::Emphasis);
75 }
76
77 #[inline]
78 pub fn invalid(&mut self, text: &str) {
82 self.write_str(text, Style::Invalid);
83 }
84
85 pub fn meta(&mut self, meta: MetaInfo, for_usage: bool) {
89 self.write_meta(meta.0, for_usage);
90 }
91
92 pub fn doc(&mut self, buf: &Doc) {
96 self.tokens.push(Token::BlockStart(Block::InlineBlock));
97 self.tokens.extend(&buf.tokens);
98 self.payload.push_str(&buf.payload);
99 self.tokens.push(Token::BlockEnd(Block::InlineBlock));
100 }
101
102 pub fn em_doc(&mut self, buf: &Doc) {
105 self.tokens.push(Token::BlockStart(Block::InlineBlock));
106 if let Some(Token::Text {
107 bytes,
108 style: Style::Text,
109 }) = buf.tokens.first().copied()
110 {
111 let prefix = &buf.payload[0..bytes];
112 if let Some((a, b)) = prefix.split_once('\n') {
113 self.emphasis(a);
114 self.tokens.push(Token::BlockStart(Block::Section3));
115 self.text(b);
116
117 if buf.tokens.len() > 1 {
118 self.tokens.extend(&buf.tokens[1..]);
119 self.payload.push_str(&buf.payload[bytes..]);
120 }
121 self.tokens.push(Token::BlockEnd(Block::Section3));
122 } else {
123 self.emphasis(prefix);
124 }
125 } else {
126 self.tokens.extend(&buf.tokens);
127 self.payload.push_str(&buf.payload);
128 }
129
130 self.tokens.push(Token::BlockEnd(Block::InlineBlock));
131 }
132}
133
134impl Doc {
135 pub(crate) fn write_shortlong(&mut self, name: &ShortLong) {
136 match name {
137 ShortLong::Short(s) => {
138 self.write_char('-', Style::Literal);
139 self.write_char(*s, Style::Literal);
140 }
141 ShortLong::Long(l) | ShortLong::Both(_, l) => {
142 self.write_str("--", Style::Literal);
143 self.write_str(l, Style::Literal);
144 }
145 }
146 }
147
148 pub(crate) fn write_item(&mut self, item: &Item) {
149 match item {
150 Item::Positional { metavar, help: _ } => {
151 self.metavar(*metavar);
152 }
153 Item::Command {
154 name: _,
155 short: _,
156 help: _,
157 meta: _,
158 info: _,
159 } => {
160 self.write_str("COMMAND ...", Style::Metavar);
161 }
162 Item::Flag {
163 name,
164 shorts: _,
165 env: _,
166 help: _,
167 } => self.write_shortlong(name),
168 Item::Argument {
169 name,
170 shorts: _,
171 metavar,
172 env: _,
173 help: _,
174 } => {
175 self.write_shortlong(name);
176 self.write_char('=', Style::Text);
177 self.metavar(*metavar);
178 }
179 Item::Any {
180 metavar,
181 anywhere: _,
182 help: _,
183 } => {
184 self.doc(metavar);
185 }
186 }
187 }
188
189 pub(crate) fn write_meta(&mut self, meta: &Meta, for_usage: bool) {
190 fn go(meta: &Meta, f: &mut Doc) {
191 match meta {
192 Meta::And(xs) => {
193 for (ix, x) in xs.iter().enumerate() {
194 if ix != 0 {
195 f.write_str(" ", Style::Text);
196 }
197 go(x, f);
198 }
199 }
200 Meta::Or(xs) => {
201 for (ix, x) in xs.iter().enumerate() {
202 if ix != 0 {
203 f.write_str(" | ", Style::Text);
204 }
205 go(x, f);
206 }
207 }
208 Meta::Optional(m) => {
209 f.write_str("[", Style::Text);
210 go(m, f);
211 f.write_str("]", Style::Text);
212 }
213 Meta::Required(m) => {
214 f.write_str("(", Style::Text);
215 go(m, f);
216 f.write_str(")", Style::Text);
217 }
218 Meta::Item(i) => f.write_item(i),
219 Meta::Many(m) => {
220 go(m, f);
221 f.write_str("...", Style::Text);
222 }
223
224 Meta::Adjacent(m) | Meta::Subsection(m, _) | Meta::Suffix(m, _) => {
225 go(m, f);
226 }
227 Meta::Skip => {} Meta::CustomUsage(_, u) => {
229 f.doc(u);
230 }
231 Meta::Strict(m) => {
232 f.write_str("--", Style::Literal);
233 f.write_str(" ", Style::Text);
234 go(m, f);
235 }
236 }
237 }
238
239 let meta = meta.normalized(for_usage);
240 self.token(Token::BlockStart(Block::Mono));
241 go(&meta, self);
242 self.token(Token::BlockEnd(Block::Mono));
243 }
244}
245
246#[derive(Debug, Copy, Clone, Eq, PartialEq)]
248#[non_exhaustive]
249pub enum Style {
250 Text,
252
253 Emphasis,
255
256 Literal,
258
259 Metavar,
261
262 Invalid,
264}
265
266#[derive(Debug, Copy, Clone, Eq, PartialEq)]
267#[allow(dead_code, clippy::enum_variant_names)]
268pub(crate) enum Block {
269 Header,
271
272 Section2,
273
274 Section3,
277
278 Section4,
281
282 ItemTerm,
285
286 ItemBody,
289
290 DefinitionList,
293
294 Block,
297
298 InlineBlock,
301
302 TermRef,
306
307 Meta,
311
312 Mono,
314}
315
316#[derive(Debug, Copy, Clone)]
317pub(crate) enum Token {
318 Text { bytes: usize, style: Style },
319 BlockStart(Block),
320 BlockEnd(Block),
321}
322
323#[derive(Debug, Clone, Default)]
324pub struct Doc {
330 payload: String,
332
333 tokens: Vec<Token>,
335}
336
337impl std::fmt::Display for Doc {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 let width = f.width().unwrap_or(MAX_WIDTH);
340 f.write_str(&self.render_console(true, Color::Monochrome, width))
341 }
342}
343
344#[derive(Debug, Clone, Copy, Default)]
345struct Skip(usize);
346impl Skip {
347 fn enabled(self) -> bool {
348 self.0 > 0
349 }
350 fn enable(&mut self) {
351 self.0 = 1;
352 }
353 fn push(&mut self) {
354 if self.0 > 0 {
355 self.0 += 1;
356 }
357 }
358 fn pop(&mut self) {
359 self.0 = self.0.saturating_sub(1);
360 }
361}
362
363impl Doc {
364 pub(crate) fn is_empty(&self) -> bool {
365 self.tokens.is_empty()
366 }
367
368 pub(crate) fn first_line(&self) -> Option<Doc> {
369 if self.tokens.is_empty() {
370 return None;
371 }
372
373 let mut res = Doc::default();
374 let mut cur = 0;
375
376 for &t in &self.tokens {
377 match t {
378 Token::Text { bytes, style } => {
379 let payload = &self.payload[cur..cur + bytes];
380 if let Some((first, _)) = payload.split_once('\n') {
381 res.tokens.push(Token::Text {
382 bytes: first.len(),
383 style,
384 });
385 res.payload.push_str(first);
386 } else {
387 res.tokens.push(t);
388 res.payload.push_str(&self.payload[cur..cur + bytes]);
389 cur += bytes;
390 }
391 }
392 _ => break,
393 }
394 }
395 Some(res)
396 }
397
398 #[cfg(feature = "autocomplete")]
399 pub(crate) fn to_completion(&self) -> Option<String> {
400 let mut s = self.first_line()?.monochrome(false);
401 s.truncate(s.trim_end().len());
402 Some(s)
403 }
404}
405
406impl From<&str> for Doc {
407 fn from(value: &str) -> Self {
408 let mut buf = Doc::default();
409 buf.write_str(value, Style::Text);
410 buf
411 }
412}
413
414impl Doc {
415 #[inline(never)]
422 pub(crate) fn token(&mut self, token: Token) {
423 self.tokens.push(token);
424 }
425
426 pub(crate) fn write<T>(&mut self, input: T, style: Style)
427 where
428 T: std::fmt::Display,
429 {
430 use std::fmt::Write;
431 let old_len = self.payload.len();
432 let _ = write!(self.payload, "{}", input);
433 self.set_style(self.payload.len() - old_len, style);
434 }
435
436 #[inline(never)]
437 fn set_style(&mut self, len: usize, style: Style) {
438 match self.tokens.last_mut() {
441 Some(Token::Text {
442 bytes: prev_bytes,
443 style: prev_style,
444 }) if *prev_style == style => {
445 *prev_bytes += len;
446 }
447 _ => {
448 self.tokens.push(Token::Text { bytes: len, style });
449 }
450 }
451 }
452
453 #[inline(never)]
454 pub(crate) fn write_str(&mut self, input: &str, style: Style) {
455 self.payload.push_str(input);
456 self.set_style(input.len(), style);
457 }
458
459 #[inline(never)]
460 pub(crate) fn write_char(&mut self, c: char, style: Style) {
461 self.write_str(c.encode_utf8(&mut [0; 4]), style);
462 }
463}
464
465#[cfg(feature = "docgen")]
466#[derive(Debug, Clone)]
467struct DocSection<'a> {
468 path: Vec<String>,
470 info: &'a Info,
471 meta: &'a Meta,
472}
473
474#[cfg(feature = "docgen")]
475fn extract_sections<'a>(
476 meta: &'a Meta,
477 info: &'a Info,
478 path: &mut Vec<String>,
479 sections: &mut Vec<DocSection<'a>>,
480) {
481 sections.push(DocSection {
482 path: path.clone(),
483 info,
484 meta,
485 });
486 let mut hi = HelpItems::default();
487 hi.append_meta(meta);
488 for item in &hi.items {
489 if let HelpItem::Command {
490 name,
491 short: _,
492 help: _,
493 meta,
494 info,
495 } = item
496 {
497 path.push((*name).to_string());
498 extract_sections(meta, info, path, sections);
499 path.pop();
500 }
501 }
502}