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)]
268pub(crate) enum Block {
269 Header,
271
272 Section2,
273
274 Section3,
277
278 ItemTerm,
281
282 ItemBody,
285
286 DefinitionList,
289
290 Block,
293
294 InlineBlock,
297
298 TermRef,
302
303 Meta,
307
308 Mono,
310}
311
312#[derive(Debug, Copy, Clone)]
313pub(crate) enum Token {
314 Text { bytes: usize, style: Style },
315 BlockStart(Block),
316 BlockEnd(Block),
317}
318
319#[derive(Debug, Clone, Default)]
320pub struct Doc {
326 payload: String,
328
329 tokens: Vec<Token>,
331}
332
333impl std::fmt::Display for Doc {
334 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335 let width = f.width().unwrap_or(MAX_WIDTH);
336 f.write_str(&self.render_console(true, Color::Monochrome, width))
337 }
338}
339
340#[derive(Debug, Clone, Copy, Default)]
341struct Skip(usize);
342impl Skip {
343 fn enabled(self) -> bool {
344 self.0 > 0
345 }
346 fn enable(&mut self) {
347 self.0 = 1;
348 }
349 fn push(&mut self) {
350 if self.0 > 0 {
351 self.0 += 1;
352 }
353 }
354 fn pop(&mut self) {
355 self.0 = self.0.saturating_sub(1);
356 }
357}
358
359impl Doc {
360 pub(crate) fn is_empty(&self) -> bool {
361 self.tokens.is_empty()
362 }
363
364 pub(crate) fn first_line(&self) -> Option<Doc> {
365 if self.tokens.is_empty() {
366 return None;
367 }
368
369 let mut res = Doc::default();
370 let mut cur = 0;
371
372 for &t in &self.tokens {
373 match t {
374 Token::Text { bytes, style } => {
375 let payload = &self.payload[cur..cur + bytes];
376 if let Some((first, _)) = payload.split_once('\n') {
377 res.tokens.push(Token::Text {
378 bytes: first.len(),
379 style,
380 });
381 res.payload.push_str(first);
382 } else {
383 res.tokens.push(t);
384 res.payload.push_str(&self.payload[cur..cur + bytes]);
385 cur += bytes;
386 }
387 }
388 _ => break,
389 }
390 }
391 Some(res)
392 }
393
394 #[cfg(feature = "autocomplete")]
395 pub(crate) fn to_completion(&self) -> Option<String> {
396 let mut s = self.first_line()?.monochrome(false);
397 s.truncate(s.trim_end().len());
398 Some(s)
399 }
400}
401
402impl From<&str> for Doc {
403 fn from(value: &str) -> Self {
404 let mut buf = Doc::default();
405 buf.write_str(value, Style::Text);
406 buf
407 }
408}
409
410impl Doc {
411 #[inline(never)]
418 pub(crate) fn token(&mut self, token: Token) {
419 self.tokens.push(token);
420 }
421
422 pub(crate) fn write<T>(&mut self, input: T, style: Style)
423 where
424 T: std::fmt::Display,
425 {
426 use std::fmt::Write;
427 let old_len = self.payload.len();
428 let _ = write!(self.payload, "{}", input);
429 self.set_style(self.payload.len() - old_len, style);
430 }
431
432 #[inline(never)]
433 fn set_style(&mut self, len: usize, style: Style) {
434 match self.tokens.last_mut() {
437 Some(Token::Text {
438 bytes: prev_bytes,
439 style: prev_style,
440 }) if *prev_style == style => {
441 *prev_bytes += len;
442 }
443 _ => {
444 self.tokens.push(Token::Text { bytes: len, style });
445 }
446 }
447 }
448
449 #[inline(never)]
450 pub(crate) fn write_str(&mut self, input: &str, style: Style) {
451 self.payload.push_str(input);
452 self.set_style(input.len(), style);
453 }
454
455 #[inline(never)]
456 pub(crate) fn write_char(&mut self, c: char, style: Style) {
457 self.write_str(c.encode_utf8(&mut [0; 4]), style);
458 }
459}
460
461#[cfg(feature = "docgen")]
462#[derive(Debug, Clone)]
463struct DocSection<'a> {
464 path: Vec<String>,
466 info: &'a Info,
467 meta: &'a Meta,
468}
469
470#[cfg(feature = "docgen")]
471fn extract_sections<'a>(
472 meta: &'a Meta,
473 info: &'a Info,
474 path: &mut Vec<String>,
475 sections: &mut Vec<DocSection<'a>>,
476) {
477 sections.push(DocSection {
478 path: path.clone(),
479 info,
480 meta,
481 });
482 let mut hi = HelpItems::default();
483 hi.append_meta(meta);
484 for item in &hi.items {
485 if let HelpItem::Command {
486 name,
487 short: _,
488 help: _,
489 meta,
490 info,
491 } = item
492 {
493 path.push((*name).to_string());
494 extract_sections(meta, info, path, sections);
495 path.pop();
496 }
497 }
498}