1use std::collections::HashMap;
2use std::sync::OnceLock;
3
4use rich_rs::r#box::{
5 ASCII, ASCII2, ASCII_DOUBLE_HEAD, DOUBLE, DOUBLE_EDGE, HEAVY, HEAVY_EDGE, HEAVY_HEAD,
6 HORIZONTALS, MARKDOWN, MINIMAL, MINIMAL_DOUBLE_HEAD, MINIMAL_HEAVY_HEAD, ROUNDED, SIMPLE,
7 SIMPLE_HEAD, SIMPLE_HEAVY, SQUARE, SQUARE_DOUBLE_HEAD,
8};
9use rich_rs::{AlignMethod, ColorSystem, PaddingDimensions, Style};
10
11use crate::theme::{apply_theme, ThemeError};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ColorSystemMode {
15 Auto,
16 Standard,
17 EightBit,
18 TrueColor,
19 Windows,
20 None,
21}
22
23impl Default for ColorSystemMode {
24 fn default() -> Self {
25 Self::Auto
26 }
27}
28
29impl ColorSystemMode {
30 pub fn to_color_system(self, current: Option<ColorSystem>) -> Option<ColorSystem> {
31 match self {
32 ColorSystemMode::Auto => current,
33 ColorSystemMode::Standard => Some(ColorSystem::Standard),
34 ColorSystemMode::EightBit => Some(ColorSystem::EightBit),
35 ColorSystemMode::TrueColor => Some(ColorSystem::TrueColor),
36 ColorSystemMode::Windows => Some(ColorSystem::Windows),
37 ColorSystemMode::None => None,
38 }
39 }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum TextMarkup {
44 Ansi,
45 Rich,
46 Markdown,
47 None,
48}
49
50impl Default for TextMarkup {
51 fn default() -> Self {
52 TextMarkup::Ansi
53 }
54}
55
56#[derive(Debug, Clone)]
58pub struct PanelConfig {
59 pub box_type: rich_rs::r#box::Box,
60 pub border_style: Style,
61 pub title_style: Style,
62 pub panel_style: Style,
63 pub padding: PaddingDimensions,
64 pub align: AlignMethod,
65 pub expand: bool,
66}
67
68impl Default for PanelConfig {
69 fn default() -> Self {
70 Self {
71 box_type: ROUNDED,
72 border_style: parse_style("dim"),
73 title_style: Style::default(),
74 panel_style: Style::default(),
75 padding: PaddingDimensions::from((0, 1)),
76 align: AlignMethod::Left,
77 expand: true,
78 }
79 }
80}
81
82#[derive(Debug, Clone)]
84pub struct TableConfig {
85 pub show_lines: bool,
86 pub leading: usize,
87 pub pad_edge: bool,
88 pub padding: (usize, usize),
89 pub collapse_padding: bool,
90 pub expand: bool,
91 pub box_type: Option<rich_rs::r#box::Box>,
92 pub row_styles: Vec<Style>,
93 pub border_style: Style,
94}
95
96impl Default for TableConfig {
97 fn default() -> Self {
98 Self {
99 show_lines: false,
100 leading: 0,
101 pad_edge: false,
102 padding: (0, 1),
103 collapse_padding: false,
104 expand: false,
105 box_type: None,
106 row_styles: Vec::new(),
107 border_style: parse_style("dim"),
108 }
109 }
110}
111
112#[derive(Debug, Clone)]
114pub struct RichHelpConfig {
115 pub theme: Option<String>,
116 pub enable_theme_env_var: bool,
117
118 pub width: Option<usize>,
119 pub max_width: Option<usize>,
120 pub color_system: ColorSystemMode,
121 pub force_terminal: Option<bool>,
122
123 pub style_option: Style,
124 pub style_option_negative: Option<Style>,
125 pub style_argument: Style,
126 pub style_command: Style,
127 pub style_command_aliases: Style,
128 pub style_switch: Style,
129 pub style_switch_negative: Option<Style>,
130 pub style_metavar: Style,
131 pub style_metavar_append: Style,
132 pub style_metavar_separator: Style,
133 pub style_range_append: Option<Style>,
134 pub style_header_text: Style,
135 pub style_epilog_text: Style,
136 pub style_footer_text: Style,
137 pub style_usage: Style,
138 pub style_usage_command: Style,
139 pub style_usage_separator: Style,
140 pub style_deprecated: Style,
141 pub style_helptext_first_line: Style,
142 pub style_helptext: Style,
143 pub style_helptext_aliases: Option<Style>,
144 pub style_option_help: Style,
145 pub style_command_help: Style,
146 pub style_option_default: Style,
147 pub style_option_envvar: Style,
148 pub style_required_short: Style,
149 pub style_required_long: Style,
150 pub style_options_panel_border: Style,
151 pub style_options_panel_box: Option<rich_rs::r#box::Box>,
152 pub style_options_panel_help_style: Style,
153 pub style_options_panel_title_style: Style,
154 pub style_options_panel_padding: PaddingDimensions,
155 pub style_options_panel_style: Style,
156 pub align_options_panel: AlignMethod,
157 pub style_options_table_show_lines: bool,
158 pub style_options_table_leading: usize,
159 pub style_options_table_pad_edge: bool,
160 pub style_options_table_padding: PaddingDimensions,
161 pub style_options_table_collapse_padding: bool,
162 pub style_options_table_expand: bool,
163 pub style_options_table_box: Option<rich_rs::r#box::Box>,
164 pub style_options_table_row_styles: Vec<Style>,
165 pub style_options_table_border_style: Style,
166 pub style_commands_panel_border: Style,
167 pub panel_inline_help_in_title: bool,
168 pub panel_inline_help_delimiter: String,
169 pub style_commands_panel_box: Option<rich_rs::r#box::Box>,
170 pub style_commands_panel_help_style: Style,
171 pub style_commands_panel_title_style: Style,
172 pub style_commands_panel_padding: PaddingDimensions,
173 pub style_commands_panel_style: Style,
174 pub align_commands_panel: AlignMethod,
175 pub style_commands_table_show_lines: bool,
176 pub style_commands_table_leading: usize,
177 pub style_commands_table_pad_edge: bool,
178 pub style_commands_table_padding: PaddingDimensions,
179 pub style_commands_table_collapse_padding: bool,
180 pub style_commands_table_expand: bool,
181 pub style_commands_table_box: Option<rich_rs::r#box::Box>,
182 pub style_commands_table_row_styles: Vec<Style>,
183 pub style_commands_table_border_style: Style,
184 pub style_commands_table_column_width_ratio: Option<(Option<usize>, Option<usize>)>,
185 pub style_errors_panel_border: Style,
186 pub style_errors_panel_box: Option<rich_rs::r#box::Box>,
187 pub align_errors_panel: AlignMethod,
188 pub style_errors_suggestion: Option<Style>,
189 pub style_errors_suggestion_command: Option<Style>,
190 pub style_padding_errors: Style,
191 pub style_aborted: Style,
192 pub style_padding_usage: Style,
193 pub style_padding_helptext: Style,
194 pub style_padding_epilog: Style,
195
196 pub panel_title_padding: usize,
197
198 pub options_table_column_types: Vec<String>,
199 pub commands_table_column_types: Vec<String>,
200 pub options_table_help_sections: Vec<String>,
201 pub commands_table_help_sections: Vec<String>,
202
203 pub header_text: Option<String>,
204 pub footer_text: Option<String>,
205 pub panel_title_string: String,
206 pub deprecated_string: String,
207 pub deprecated_with_reason_string: String,
208 pub default_string: String,
209 pub envvar_string: String,
210 pub required_short_string: String,
211 pub required_long_string: String,
212 pub range_string: String,
213 pub append_metavars_help_string: String,
214 pub append_range_help_string: String,
215 pub helptext_aliases_string: String,
216 pub arguments_panel_title: String,
217 pub options_panel_title: String,
218 pub commands_panel_title: String,
219 pub errors_panel_title: String,
220 pub delimiter_comma: String,
221 pub delimiter_slash: String,
222 pub errors_suggestion: Option<String>,
223 pub errors_epilogue: Option<String>,
224 pub aborted_text: String,
225 pub errors_show_param_source: bool,
226 pub errors_param_source_format: String,
227
228 pub padding_header_text: PaddingDimensions,
229 pub padding_usage: PaddingDimensions,
230 pub padding_helptext: PaddingDimensions,
231 pub padding_helptext_deprecated: PaddingDimensions,
232 pub padding_helptext_first_line: PaddingDimensions,
233 pub padding_epilog: PaddingDimensions,
234 pub padding_footer_text: PaddingDimensions,
235 pub padding_errors_panel: PaddingDimensions,
236 pub padding_errors_suggestion: PaddingDimensions,
237 pub padding_errors_epilogue: PaddingDimensions,
238
239 pub show_arguments: Option<bool>,
240 pub show_commands: Option<bool>,
241 pub show_metavars_column: Option<bool>,
242 pub commands_before_options: bool,
243 pub default_panels_first: bool,
244 pub append_metavars_help: Option<bool>,
245 pub group_arguments_options: bool,
246 pub option_envvar_first: Option<bool>,
247 pub text_markup: TextMarkup,
248 pub text_emojis: Option<bool>,
249 pub text_paragraph_linebreaks: Option<String>,
250 pub use_click_short_help: bool,
251 pub helptext_show_aliases: bool,
252 pub show_prompt: bool,
253 pub prompt_string: String,
254 pub prompt_confirm_string: String,
255 pub prompt_hidden_string: String,
256 pub prompt_confirm_hidden_string: String,
257 pub command_aliases: HashMap<String, Vec<String>>,
258 pub option_groups: Vec<GroupConfig>,
259 pub command_groups: Vec<GroupConfig>,
260
261 pub panel_options: PanelConfig,
262 pub panel_commands: PanelConfig,
263 pub panel_arguments: PanelConfig,
264 pub table_options: TableConfig,
265 pub table_commands: TableConfig,
266 pub table_arguments: TableConfig,
267}
268
269#[derive(Debug, Clone)]
270pub struct GroupConfig {
271 pub name: String,
272 pub items: Vec<String>,
273 pub help: Option<String>,
274 pub inline_help_in_title: Option<bool>,
275 pub title_style: Option<Style>,
276 pub help_style: Option<Style>,
277}
278
279impl Default for RichHelpConfig {
280 fn default() -> Self {
281 let mut cfg = Self {
282 theme: None,
283 enable_theme_env_var: true,
284 width: None,
285 max_width: None,
286 color_system: ColorSystemMode::Auto,
287 force_terminal: None,
288 style_option: Style::default(),
289 style_option_negative: None,
290 style_argument: Style::default(),
291 style_command: Style::default(),
292 style_command_aliases: Style::default(),
293 style_switch: Style::default(),
294 style_switch_negative: None,
295 style_metavar: Style::default(),
296 style_metavar_append: Style::default(),
297 style_metavar_separator: Style::default(),
298 style_range_append: None,
299 style_header_text: Style::default(),
300 style_epilog_text: Style::default(),
301 style_footer_text: Style::default(),
302 style_usage: Style::default(),
303 style_usage_command: Style::default(),
304 style_usage_separator: Style::default(),
305 style_deprecated: Style::default(),
306 style_helptext_first_line: Style::default(),
307 style_helptext: Style::default(),
308 style_helptext_aliases: None,
309 style_option_help: Style::default(),
310 style_command_help: Style::default(),
311 style_option_default: Style::default(),
312 style_option_envvar: Style::default(),
313 style_required_short: Style::default(),
314 style_required_long: Style::default(),
315 style_options_panel_border: Style::default(),
316 style_options_panel_box: None,
317 style_options_panel_help_style: Style::default(),
318 style_options_panel_title_style: Style::default(),
319 style_options_panel_padding: PaddingDimensions::from((0, 1)),
320 style_options_panel_style: Style::default(),
321 align_options_panel: AlignMethod::Left,
322 style_options_table_show_lines: false,
323 style_options_table_leading: 0,
324 style_options_table_pad_edge: false,
325 style_options_table_padding: PaddingDimensions::from((0, 1)),
326 style_options_table_collapse_padding: false,
327 style_options_table_expand: true,
328 style_options_table_box: None,
329 style_options_table_row_styles: Vec::new(),
330 style_options_table_border_style: Style::default(),
331 style_commands_panel_border: Style::default(),
332 panel_inline_help_in_title: false,
333 panel_inline_help_delimiter: " - ".to_string(),
334 style_commands_panel_box: None,
335 style_commands_panel_help_style: Style::default(),
336 style_commands_panel_title_style: Style::default(),
337 style_commands_panel_padding: PaddingDimensions::from((0, 1)),
338 style_commands_panel_style: Style::default(),
339 align_commands_panel: AlignMethod::Left,
340 style_commands_table_show_lines: false,
341 style_commands_table_leading: 0,
342 style_commands_table_pad_edge: false,
343 style_commands_table_padding: PaddingDimensions::from((0, 1)),
344 style_commands_table_collapse_padding: false,
345 style_commands_table_expand: true,
346 style_commands_table_box: None,
347 style_commands_table_row_styles: Vec::new(),
348 style_commands_table_border_style: Style::default(),
349 style_commands_table_column_width_ratio: None,
350 style_errors_panel_border: Style::default(),
351 style_errors_panel_box: None,
352 align_errors_panel: AlignMethod::Left,
353 style_errors_suggestion: None,
354 style_errors_suggestion_command: None,
355 style_padding_errors: Style::default(),
356 style_aborted: parse_style("red"),
357 style_padding_usage: Style::default(),
358 style_padding_helptext: Style::default(),
359 style_padding_epilog: Style::default(),
360 panel_title_padding: 1,
361 options_table_column_types: vec![
362 "required".to_string(),
363 "opt_long".to_string(),
364 "opt_short".to_string(),
365 "metavar".to_string(),
366 "help".to_string(),
367 ],
368 commands_table_column_types: vec![
369 "name".to_string(),
370 "aliases".to_string(),
371 "help".to_string(),
372 ],
373 options_table_help_sections: vec![
374 "help".to_string(),
375 "deprecated".to_string(),
376 "envvar".to_string(),
377 "default".to_string(),
378 "required".to_string(),
379 ],
380 commands_table_help_sections: vec!["help".to_string(), "deprecated".to_string()],
381 header_text: None,
382 footer_text: None,
383 panel_title_string: "{}".to_string(),
384 deprecated_string: "[deprecated]".to_string(),
385 deprecated_with_reason_string: "[deprecated: {}]".to_string(),
386 default_string: "[default: {}]".to_string(),
387 envvar_string: "[env var: {}]".to_string(),
388 required_short_string: "*".to_string(),
389 required_long_string: "[required]".to_string(),
390 range_string: "[{}]".to_string(),
391 append_metavars_help_string: "({})".to_string(),
392 append_range_help_string: "[range: {}]".to_string(),
393 helptext_aliases_string: "Aliases: {}".to_string(),
394 arguments_panel_title: "Arguments".to_string(),
395 options_panel_title: "Options".to_string(),
396 commands_panel_title: "Commands".to_string(),
397 errors_panel_title: "Error".to_string(),
398 delimiter_comma: ",".to_string(),
399 delimiter_slash: "/".to_string(),
400 errors_suggestion: None,
401 errors_epilogue: None,
402 aborted_text: "Aborted.".to_string(),
403 errors_show_param_source: false,
404 errors_param_source_format: " (from {})".to_string(),
405 padding_header_text: PaddingDimensions::from((1, 1, 0, 1)),
406 padding_usage: PaddingDimensions::from(1),
407 padding_helptext: PaddingDimensions::from((0, 1, 1, 1)),
408 padding_helptext_deprecated: PaddingDimensions::from(0),
409 padding_helptext_first_line: PaddingDimensions::from(0),
410 padding_epilog: PaddingDimensions::from(1),
411 padding_footer_text: PaddingDimensions::from(1),
412 padding_errors_panel: PaddingDimensions::from((0, 0, 1, 0)),
413 padding_errors_suggestion: PaddingDimensions::from((0, 1, 0, 1)),
414 padding_errors_epilogue: PaddingDimensions::from((0, 1, 1, 1)),
415 show_arguments: None,
416 show_commands: None,
417 show_metavars_column: None,
418 commands_before_options: false,
419 default_panels_first: false,
420 append_metavars_help: None,
421 group_arguments_options: false,
422 option_envvar_first: None,
423 text_markup: TextMarkup::Ansi,
424 text_emojis: None,
425 text_paragraph_linebreaks: None,
426 use_click_short_help: false,
427 helptext_show_aliases: false,
428 show_prompt: false,
429 prompt_string: "[prompt]".to_string(),
430 prompt_confirm_string: "[prompt (confirm)]".to_string(),
431 prompt_hidden_string: "[prompt (hidden)]".to_string(),
432 prompt_confirm_hidden_string: "[prompt (confirm, hidden)]".to_string(),
433 command_aliases: HashMap::new(),
434 option_groups: Vec::new(),
435 command_groups: Vec::new(),
436 panel_options: PanelConfig::default(),
437 panel_commands: PanelConfig::default(),
438 panel_arguments: PanelConfig::default(),
439 table_options: TableConfig::default(),
440 table_commands: TableConfig::default(),
441 table_arguments: TableConfig::default(),
442 };
443
444 let _ = cfg.apply_theme_name("default-box");
445 cfg
446 }
447}
448
449impl RichHelpConfig {
450 pub fn global() -> &'static Self {
451 static GLOBAL: OnceLock<RichHelpConfig> = OnceLock::new();
452 GLOBAL.get_or_init(|| RichHelpConfig::load_from_env())
453 }
454
455 pub fn builder() -> RichHelpConfigBuilder {
456 RichHelpConfigBuilder {
457 config: RichHelpConfig::global().clone(),
458 }
459 }
460
461 pub fn load_from_env() -> Self {
462 let mut cfg = RichHelpConfig::default();
463 if !cfg.enable_theme_env_var {
464 return cfg;
465 }
466 apply_terminal_width_env(&mut cfg);
467 apply_force_terminal_env(&mut cfg);
468 let env = std::env::var("RICH_CLICK_THEME").ok();
469 if let Some(value) = env {
470 if value.trim_start().starts_with('{') {
471 if let Ok(json) = serde_json::from_str::<serde_json::Value>(&value) {
472 cfg.apply_json_overrides(&json);
473 }
474 } else {
475 cfg.theme = Some(value.trim().to_string());
476 let _ = cfg.apply_theme_name(value.trim());
477 }
478 }
479 cfg.sync_render_config();
480 cfg
481 }
482
483 pub fn apply_theme_name(&mut self, theme: &str) -> Result<(), ThemeError> {
484 let result = apply_theme(self, theme);
485 if result.is_ok() {
486 self.sync_render_config();
487 }
488 result
489 }
490
491 fn apply_json_overrides(&mut self, value: &serde_json::Value) {
492 if let Some(theme) = value.get("theme").and_then(|v| v.as_str()) {
493 self.theme = Some(theme.to_string());
494 let _ = self.apply_theme_name(theme);
495 }
496 if let Some(obj) = value.as_object() {
497 for (key, v) in obj {
498 if key == "theme" {
499 continue;
500 }
501 self.apply_override(key, v);
502 }
503 }
504 }
505
506 fn apply_override(&mut self, key: &str, value: &serde_json::Value) {
507 match key {
508 "width" => self.width = value.as_u64().map(|v| v as usize),
509 "max_width" => self.max_width = value.as_u64().map(|v| v as usize),
510 "color_system" => {
511 if let Some(mode) = parse_color_system_mode(value) {
512 self.color_system = mode;
513 }
514 }
515 "force_terminal" => self.force_terminal = value.as_bool(),
516 "panel_title_string" => {
517 if let Some(v) = value.as_str() {
518 self.panel_title_string = v.to_string();
519 }
520 }
521 "deprecated_string" => {
522 if let Some(v) = value.as_str() {
523 self.deprecated_string = v.to_string();
524 }
525 }
526 "deprecated_with_reason_string" => {
527 if let Some(v) = value.as_str() {
528 self.deprecated_with_reason_string = v.to_string();
529 }
530 }
531 "default_string" => {
532 if let Some(v) = value.as_str() {
533 self.default_string = v.to_string();
534 }
535 }
536 "envvar_string" => {
537 if let Some(v) = value.as_str() {
538 self.envvar_string = v.to_string();
539 }
540 }
541 "required_short_string" => {
542 if let Some(v) = value.as_str() {
543 self.required_short_string = v.to_string();
544 }
545 }
546 "required_long_string" => {
547 if let Some(v) = value.as_str() {
548 self.required_long_string = v.to_string();
549 }
550 }
551 "range_string" => {
552 if let Some(v) = value.as_str() {
553 self.range_string = v.to_string();
554 }
555 }
556 "append_metavars_help_string" => {
557 if let Some(v) = value.as_str() {
558 self.append_metavars_help_string = v.to_string();
559 }
560 }
561 "append_range_help_string" => {
562 if let Some(v) = value.as_str() {
563 self.append_range_help_string = v.to_string();
564 }
565 }
566 "delimiter_comma" => {
567 if let Some(v) = value.as_str() {
568 self.delimiter_comma = v.to_string();
569 }
570 }
571 "delimiter_slash" => {
572 if let Some(v) = value.as_str() {
573 self.delimiter_slash = v.to_string();
574 }
575 }
576 "options_panel_title" => {
577 if let Some(v) = value.as_str() {
578 self.options_panel_title = v.to_string();
579 }
580 }
581 "commands_panel_title" => {
582 if let Some(v) = value.as_str() {
583 self.commands_panel_title = v.to_string();
584 }
585 }
586 "arguments_panel_title" => {
587 if let Some(v) = value.as_str() {
588 self.arguments_panel_title = v.to_string();
589 }
590 }
591 "errors_panel_title" => {
592 if let Some(v) = value.as_str() {
593 self.errors_panel_title = v.to_string();
594 }
595 }
596 "aborted_text" => {
597 if let Some(v) = value.as_str() {
598 self.aborted_text = v.to_string();
599 }
600 }
601 "errors_show_param_source" => {
602 if let Some(v) = value.as_bool() {
603 self.errors_show_param_source = v;
604 }
605 }
606 "errors_param_source_format" => {
607 if let Some(v) = value.as_str() {
608 self.errors_param_source_format = v.to_string();
609 }
610 }
611 "show_prompt" => {
612 if let Some(v) = value.as_bool() {
613 self.show_prompt = v;
614 }
615 }
616 "prompt_string" => {
617 if let Some(v) = value.as_str() {
618 self.prompt_string = v.to_string();
619 }
620 }
621 "prompt_confirm_string" => {
622 if let Some(v) = value.as_str() {
623 self.prompt_confirm_string = v.to_string();
624 }
625 }
626 "prompt_hidden_string" => {
627 if let Some(v) = value.as_str() {
628 self.prompt_hidden_string = v.to_string();
629 }
630 }
631 "prompt_confirm_hidden_string" => {
632 if let Some(v) = value.as_str() {
633 self.prompt_confirm_hidden_string = v.to_string();
634 }
635 }
636 "style_option" => {
637 if let Some(v) = value.as_str() {
638 self.style_option = parse_style(v);
639 }
640 }
641 "style_option_negative" => self.style_option_negative = parse_optional_style(value),
642 "style_argument" => {
643 if let Some(v) = value.as_str() {
644 self.style_argument = parse_style(v);
645 }
646 }
647 "style_command" => {
648 if let Some(v) = value.as_str() {
649 self.style_command = parse_style(v);
650 }
651 }
652 "style_command_aliases" => {
653 if let Some(v) = value.as_str() {
654 self.style_command_aliases = parse_style(v);
655 }
656 }
657 "style_switch" => {
658 if let Some(v) = value.as_str() {
659 self.style_switch = parse_style(v);
660 }
661 }
662 "style_switch_negative" => self.style_switch_negative = parse_optional_style(value),
663 "style_metavar" => {
664 if let Some(v) = value.as_str() {
665 self.style_metavar = parse_style(v);
666 }
667 }
668 "style_metavar_append" => {
669 if let Some(v) = value.as_str() {
670 self.style_metavar_append = parse_style(v);
671 }
672 }
673 "style_metavar_separator" => {
674 if let Some(v) = value.as_str() {
675 self.style_metavar_separator = parse_style(v);
676 }
677 }
678 "style_range_append" => self.style_range_append = parse_optional_style(value),
679 "style_header_text" => {
680 if let Some(v) = value.as_str() {
681 self.style_header_text = parse_style(v);
682 }
683 }
684 "style_epilog_text" => {
685 if let Some(v) = value.as_str() {
686 self.style_epilog_text = parse_style(v);
687 }
688 }
689 "style_footer_text" => {
690 if let Some(v) = value.as_str() {
691 self.style_footer_text = parse_style(v);
692 }
693 }
694 "style_usage" => {
695 if let Some(v) = value.as_str() {
696 self.style_usage = parse_style(v);
697 }
698 }
699 "style_usage_command" => {
700 if let Some(v) = value.as_str() {
701 self.style_usage_command = parse_style(v);
702 }
703 }
704 "style_usage_separator" => {
705 if let Some(v) = value.as_str() {
706 self.style_usage_separator = parse_style(v);
707 }
708 }
709 "style_deprecated" => {
710 if let Some(v) = value.as_str() {
711 self.style_deprecated = parse_style(v);
712 }
713 }
714 "style_helptext_first_line" => {
715 if let Some(v) = value.as_str() {
716 self.style_helptext_first_line = parse_style(v);
717 }
718 }
719 "style_helptext" => {
720 if let Some(v) = value.as_str() {
721 self.style_helptext = parse_style(v);
722 }
723 }
724 "style_helptext_aliases" => self.style_helptext_aliases = parse_optional_style(value),
725 "style_option_help" => {
726 if let Some(v) = value.as_str() {
727 self.style_option_help = parse_style(v);
728 }
729 }
730 "style_command_help" => {
731 if let Some(v) = value.as_str() {
732 self.style_command_help = parse_style(v);
733 }
734 }
735 "style_option_default" => {
736 if let Some(v) = value.as_str() {
737 self.style_option_default = parse_style(v);
738 }
739 }
740 "style_option_envvar" => {
741 if let Some(v) = value.as_str() {
742 self.style_option_envvar = parse_style(v);
743 }
744 }
745 "style_required_short" => {
746 if let Some(v) = value.as_str() {
747 self.style_required_short = parse_style(v);
748 }
749 }
750 "style_required_long" => {
751 if let Some(v) = value.as_str() {
752 self.style_required_long = parse_style(v);
753 }
754 }
755 "style_options_panel_border" => {
756 if let Some(v) = value.as_str() {
757 self.style_options_panel_border = parse_style(v);
758 }
759 }
760 "style_commands_panel_border" => {
761 if let Some(v) = value.as_str() {
762 self.style_commands_panel_border = parse_style(v);
763 }
764 }
765 "style_errors_panel_border" => {
766 if let Some(v) = value.as_str() {
767 self.style_errors_panel_border = parse_style(v);
768 }
769 }
770 "style_options_panel_box" => self.style_options_panel_box = parse_box_value(value),
771 "style_commands_panel_box" => self.style_commands_panel_box = parse_box_value(value),
772 "style_errors_panel_box" => self.style_errors_panel_box = parse_box_value(value),
773 "style_options_panel_help_style" => {
774 if let Some(v) = value.as_str() {
775 self.style_options_panel_help_style = parse_style(v);
776 }
777 }
778 "style_commands_panel_help_style" => {
779 if let Some(v) = value.as_str() {
780 self.style_commands_panel_help_style = parse_style(v);
781 }
782 }
783 "style_options_panel_title_style" => {
784 if let Some(v) = value.as_str() {
785 self.style_options_panel_title_style = parse_style(v);
786 }
787 }
788 "style_commands_panel_title_style" => {
789 if let Some(v) = value.as_str() {
790 self.style_commands_panel_title_style = parse_style(v);
791 }
792 }
793 "style_options_panel_style" => {
794 if let Some(v) = value.as_str() {
795 self.style_options_panel_style = parse_style(v);
796 }
797 }
798 "style_commands_panel_style" => {
799 if let Some(v) = value.as_str() {
800 self.style_commands_panel_style = parse_style(v);
801 }
802 }
803 "style_padding_usage" => {
804 if let Some(v) = value.as_str() {
805 self.style_padding_usage = parse_style(v);
806 }
807 }
808 "style_padding_helptext" => {
809 if let Some(v) = value.as_str() {
810 self.style_padding_helptext = parse_style(v);
811 }
812 }
813 "style_padding_epilog" => {
814 if let Some(v) = value.as_str() {
815 self.style_padding_epilog = parse_style(v);
816 }
817 }
818 "style_padding_errors" => {
819 if let Some(v) = value.as_str() {
820 self.style_padding_errors = parse_style(v);
821 }
822 }
823 "style_aborted" => {
824 if let Some(v) = value.as_str() {
825 self.style_aborted = parse_style(v);
826 }
827 }
828 "style_options_table_border_style" => {
829 if let Some(v) = value.as_str() {
830 self.style_options_table_border_style = parse_style(v);
831 }
832 }
833 "style_commands_table_border_style" => {
834 if let Some(v) = value.as_str() {
835 self.style_commands_table_border_style = parse_style(v);
836 }
837 }
838 "style_commands_table_column_width_ratio" => {
839 if let Some(v) = parse_ratio_pair(value) {
840 self.style_commands_table_column_width_ratio = Some(v);
841 }
842 }
843 "style_options_table_show_lines" => {
844 if let Some(v) = value.as_bool() {
845 self.style_options_table_show_lines = v;
846 }
847 }
848 "style_commands_table_show_lines" => {
849 if let Some(v) = value.as_bool() {
850 self.style_commands_table_show_lines = v;
851 }
852 }
853 "style_options_table_leading" => {
854 if let Some(v) = value.as_u64() {
855 self.style_options_table_leading = v as usize;
856 }
857 }
858 "style_commands_table_leading" => {
859 if let Some(v) = value.as_u64() {
860 self.style_commands_table_leading = v as usize;
861 }
862 }
863 "style_options_table_pad_edge" => {
864 if let Some(v) = value.as_bool() {
865 self.style_options_table_pad_edge = v;
866 }
867 }
868 "style_commands_table_pad_edge" => {
869 if let Some(v) = value.as_bool() {
870 self.style_commands_table_pad_edge = v;
871 }
872 }
873 "style_options_table_collapse_padding" => {
874 if let Some(v) = value.as_bool() {
875 self.style_options_table_collapse_padding = v;
876 }
877 }
878 "style_commands_table_collapse_padding" => {
879 if let Some(v) = value.as_bool() {
880 self.style_commands_table_collapse_padding = v;
881 }
882 }
883 "style_options_table_expand" => {
884 if let Some(v) = value.as_bool() {
885 self.style_options_table_expand = v;
886 }
887 }
888 "style_commands_table_expand" => {
889 if let Some(v) = value.as_bool() {
890 self.style_commands_table_expand = v;
891 }
892 }
893 "style_options_table_box" => self.style_options_table_box = parse_box_value(value),
894 "style_commands_table_box" => self.style_commands_table_box = parse_box_value(value),
895 "style_options_table_row_styles" => {
896 if let Some(v) = parse_style_list(value) {
897 self.style_options_table_row_styles = v;
898 }
899 }
900 "style_commands_table_row_styles" => {
901 if let Some(v) = parse_style_list(value) {
902 self.style_commands_table_row_styles = v;
903 }
904 }
905 "style_options_panel_padding" => {
906 if let Some(v) = parse_padding_value(value) {
907 self.style_options_panel_padding = v;
908 }
909 }
910 "style_commands_panel_padding" => {
911 if let Some(v) = parse_padding_value(value) {
912 self.style_commands_panel_padding = v;
913 }
914 }
915 "style_options_table_padding" => {
916 if let Some(v) = parse_padding_value(value) {
917 self.style_options_table_padding = v;
918 }
919 }
920 "style_commands_table_padding" => {
921 if let Some(v) = parse_padding_value(value) {
922 self.style_commands_table_padding = v;
923 }
924 }
925 "panel_title_padding" => {
926 if let Some(v) = value.as_u64() {
927 self.panel_title_padding = v as usize;
928 }
929 }
930 "align_options_panel" => {
931 if let Some(v) = parse_align(value) {
932 self.align_options_panel = v;
933 }
934 }
935 "align_commands_panel" => {
936 if let Some(v) = parse_align(value) {
937 self.align_commands_panel = v;
938 }
939 }
940 "align_errors_panel" => {
941 if let Some(v) = parse_align(value) {
942 self.align_errors_panel = v;
943 }
944 }
945 "panel_inline_help_in_title" => {
946 if let Some(v) = value.as_bool() {
947 self.panel_inline_help_in_title = v;
948 }
949 }
950 "panel_inline_help_delimiter" => {
951 if let Some(v) = value.as_str() {
952 self.panel_inline_help_delimiter = v.to_string();
953 }
954 }
955 "options_table_column_types" => {
956 if let Some(v) = parse_string_list(value) {
957 self.options_table_column_types = v;
958 }
959 }
960 "commands_table_column_types" => {
961 if let Some(v) = parse_string_list(value) {
962 self.commands_table_column_types = v;
963 }
964 }
965 "options_table_help_sections" => {
966 if let Some(v) = parse_string_list(value) {
967 self.options_table_help_sections = v;
968 }
969 }
970 "commands_table_help_sections" => {
971 if let Some(v) = parse_string_list(value) {
972 self.commands_table_help_sections = v;
973 }
974 }
975 "show_arguments" => self.show_arguments = value.as_bool(),
976 "show_commands" => self.show_commands = value.as_bool(),
977 "show_metavars_column" => self.show_metavars_column = value.as_bool(),
978 "commands_before_options" => {
979 if let Some(v) = value.as_bool() {
980 self.commands_before_options = v;
981 }
982 }
983 "default_panels_first" => {
984 if let Some(v) = value.as_bool() {
985 self.default_panels_first = v;
986 }
987 }
988 "append_metavars_help" => self.append_metavars_help = value.as_bool(),
989 "group_arguments_options" => {
990 if let Some(v) = value.as_bool() {
991 self.group_arguments_options = v;
992 }
993 }
994 "option_envvar_first" => self.option_envvar_first = value.as_bool(),
995 "text_markup" => {
996 if let Some(v) = parse_text_markup(value) {
997 self.text_markup = v;
998 }
999 }
1000 "text_emojis" => self.text_emojis = value.as_bool(),
1001 "text_paragraph_linebreaks" => {
1002 if let Some(v) = value.as_str() {
1003 self.text_paragraph_linebreaks = Some(v.to_string());
1004 }
1005 }
1006 "use_click_short_help" => {
1007 if let Some(v) = value.as_bool() {
1008 self.use_click_short_help = v;
1009 }
1010 }
1011 "helptext_show_aliases" => {
1012 if let Some(v) = value.as_bool() {
1013 self.helptext_show_aliases = v;
1014 }
1015 }
1016 "command_aliases" => {
1017 if let Some(v) = parse_alias_map(value) {
1018 self.command_aliases = v;
1019 }
1020 }
1021 "option_groups" => {
1022 if let Some(v) = parse_group_list(value, "options") {
1023 self.option_groups = v;
1024 }
1025 }
1026 "command_groups" => {
1027 if let Some(v) = parse_group_list(value, "commands") {
1028 self.command_groups = v;
1029 }
1030 }
1031 "padding_header_text" => {
1032 if let Some(v) = parse_padding_value(value) {
1033 self.padding_header_text = v;
1034 }
1035 }
1036 "padding_usage" => {
1037 if let Some(v) = parse_padding_value(value) {
1038 self.padding_usage = v;
1039 }
1040 }
1041 "padding_helptext" => {
1042 if let Some(v) = parse_padding_value(value) {
1043 self.padding_helptext = v;
1044 }
1045 }
1046 "padding_helptext_deprecated" => {
1047 if let Some(v) = parse_padding_value(value) {
1048 self.padding_helptext_deprecated = v;
1049 }
1050 }
1051 "padding_helptext_first_line" => {
1052 if let Some(v) = parse_padding_value(value) {
1053 self.padding_helptext_first_line = v;
1054 }
1055 }
1056 "padding_epilog" => {
1057 if let Some(v) = parse_padding_value(value) {
1058 self.padding_epilog = v;
1059 }
1060 }
1061 "padding_footer_text" => {
1062 if let Some(v) = parse_padding_value(value) {
1063 self.padding_footer_text = v;
1064 }
1065 }
1066 "padding_errors_panel" => {
1067 if let Some(v) = parse_padding_value(value) {
1068 self.padding_errors_panel = v;
1069 }
1070 }
1071 "padding_errors_suggestion" => {
1072 if let Some(v) = parse_padding_value(value) {
1073 self.padding_errors_suggestion = v;
1074 }
1075 }
1076 "padding_errors_epilogue" => {
1077 if let Some(v) = parse_padding_value(value) {
1078 self.padding_errors_epilogue = v;
1079 }
1080 }
1081 _ => {}
1082 }
1083 }
1084
1085 fn sync_render_config(&mut self) {
1086 if self.text_emojis.is_none() {
1087 self.text_emojis = Some(matches!(
1088 self.text_markup,
1089 TextMarkup::Markdown | TextMarkup::Rich
1090 ));
1091 }
1092
1093 self.panel_options.box_type = self.style_options_panel_box.unwrap_or(ROUNDED);
1094 self.panel_options.border_style = self.style_options_panel_border;
1095 self.panel_options.title_style = self.style_options_panel_title_style;
1096 self.panel_options.panel_style = self.style_options_panel_style;
1097 self.panel_options.padding = self.style_options_panel_padding;
1098 self.panel_options.align = self.align_options_panel;
1099 self.panel_options.expand = true;
1100
1101 self.panel_commands.box_type = self.style_commands_panel_box.unwrap_or(ROUNDED);
1102 self.panel_commands.border_style = self.style_commands_panel_border;
1103 self.panel_commands.title_style = self.style_commands_panel_title_style;
1104 self.panel_commands.panel_style = self.style_commands_panel_style;
1105 self.panel_commands.padding = self.style_commands_panel_padding;
1106 self.panel_commands.align = self.align_commands_panel;
1107 self.panel_commands.expand = true;
1108
1109 self.panel_arguments = self.panel_options.clone();
1110
1111 let padding = self.style_options_table_padding.unpack();
1112 self.table_options.show_lines = self.style_options_table_show_lines;
1113 self.table_options.leading = self.style_options_table_leading;
1114 self.table_options.pad_edge = self.style_options_table_pad_edge;
1115 self.table_options.padding = (padding.3, padding.1);
1116 self.table_options.collapse_padding = self.style_options_table_collapse_padding;
1117 self.table_options.expand = self.style_options_table_expand;
1118 self.table_options.box_type = self.style_options_table_box;
1119 self.table_options.border_style = self.style_options_table_border_style;
1120
1121 let padding = self.style_commands_table_padding.unpack();
1122 self.table_commands.show_lines = self.style_commands_table_show_lines;
1123 self.table_commands.leading = self.style_commands_table_leading;
1124 self.table_commands.pad_edge = self.style_commands_table_pad_edge;
1125 self.table_commands.padding = (padding.3, padding.1);
1126 self.table_commands.collapse_padding = self.style_commands_table_collapse_padding;
1127 self.table_commands.expand = self.style_commands_table_expand;
1128 self.table_commands.box_type = self.style_commands_table_box;
1129 self.table_commands.border_style = self.style_commands_table_border_style;
1130
1131 self.table_arguments = self.table_options.clone();
1132 }
1133}
1134
1135fn apply_terminal_width_env(cfg: &mut RichHelpConfig) {
1136 let width = std::env::var("TERMINAL_WIDTH").ok();
1137 if let Some(value) = width {
1138 if let Ok(parsed) = value.trim().parse::<usize>() {
1139 if cfg.width.is_none() {
1140 cfg.width = Some(parsed);
1141 }
1142 if cfg.max_width.is_none() {
1143 cfg.max_width = Some(parsed);
1144 }
1145 }
1146 }
1147}
1148
1149fn apply_force_terminal_env(cfg: &mut RichHelpConfig) {
1150 if cfg.force_terminal.is_some() {
1151 return;
1152 }
1153 for key in ["FORCE_COLOR", "PY_COLORS", "GITHUB_ACTIONS"] {
1154 if let Ok(value) = std::env::var(key) {
1155 if let Some(parsed) = parse_truthy(&value) {
1156 cfg.force_terminal = Some(parsed);
1157 return;
1158 }
1159 }
1160 }
1161}
1162
1163fn parse_truthy(value: &str) -> Option<bool> {
1164 match value.trim().to_ascii_lowercase().as_str() {
1165 "1" | "true" | "yes" | "on" => Some(true),
1166 "0" | "false" | "no" | "off" => Some(false),
1167 _ => None,
1168 }
1169}
1170
1171pub struct RichHelpConfigBuilder {
1172 config: RichHelpConfig,
1173}
1174
1175impl RichHelpConfigBuilder {
1176 pub fn build(self) -> RichHelpConfig {
1177 let mut cfg = self.config;
1178 cfg.sync_render_config();
1179 cfg
1180 }
1181
1182 pub fn theme(mut self, theme: &str) -> Self {
1183 self.config.theme = Some(theme.to_string());
1184 let _ = self.config.apply_theme_name(theme);
1185 self
1186 }
1187
1188 pub fn width(mut self, width: usize) -> Self {
1189 self.config.width = Some(width);
1190 self
1191 }
1192
1193 pub fn max_width(mut self, width: usize) -> Self {
1194 self.config.max_width = Some(width);
1195 self
1196 }
1197
1198 pub fn color_system(mut self, mode: ColorSystemMode) -> Self {
1199 self.config.color_system = mode;
1200 self
1201 }
1202
1203 pub fn force_terminal(mut self, force: bool) -> Self {
1204 self.config.force_terminal = Some(force);
1205 self
1206 }
1207
1208 pub fn show_arguments(mut self, show: bool) -> Self {
1209 self.config.show_arguments = Some(show);
1210 self
1211 }
1212
1213 pub fn text_markup(mut self, markup: TextMarkup) -> Self {
1214 self.config.text_markup = markup;
1215 self
1216 }
1217
1218 pub fn text_emojis(mut self, enabled: bool) -> Self {
1219 self.config.text_emojis = Some(enabled);
1220 self
1221 }
1222
1223 pub fn text_paragraph_linebreaks(mut self, value: &str) -> Self {
1224 self.config.text_paragraph_linebreaks = Some(value.to_string());
1225 self
1226 }
1227
1228 pub fn commands_before_options(mut self, value: bool) -> Self {
1229 self.config.commands_before_options = value;
1230 self
1231 }
1232}
1233
1234fn parse_style(input: &str) -> Style {
1235 Style::parse(input).unwrap_or_default()
1236}
1237
1238fn parse_optional_style(value: &serde_json::Value) -> Option<Style> {
1239 if value.is_null() {
1240 return None;
1241 }
1242 value.as_str().map(parse_style)
1243}
1244
1245fn parse_style_list(value: &serde_json::Value) -> Option<Vec<Style>> {
1246 let list = value.as_array()?;
1247 let mut styles = Vec::with_capacity(list.len());
1248 for item in list {
1249 if let Some(s) = item.as_str() {
1250 styles.push(parse_style(s));
1251 }
1252 }
1253 Some(styles)
1254}
1255
1256fn parse_string_list(value: &serde_json::Value) -> Option<Vec<String>> {
1257 let list = value.as_array()?;
1258 let mut out = Vec::with_capacity(list.len());
1259 for item in list {
1260 if let Some(s) = item.as_str() {
1261 out.push(s.to_string());
1262 }
1263 }
1264 Some(out)
1265}
1266
1267fn parse_alias_map(value: &serde_json::Value) -> Option<HashMap<String, Vec<String>>> {
1268 let obj = value.as_object()?;
1269 let mut map = HashMap::new();
1270 for (key, val) in obj {
1271 if let Some(list) = parse_string_list(val) {
1272 map.insert(key.to_string(), list);
1273 }
1274 }
1275 Some(map)
1276}
1277
1278fn parse_group_list(value: &serde_json::Value, key: &str) -> Option<Vec<GroupConfig>> {
1279 let list = value.as_array()?;
1280 let mut groups = Vec::new();
1281 for item in list {
1282 let obj = item.as_object()?;
1283 let name = obj
1284 .get("name")
1285 .and_then(|v| v.as_str())
1286 .unwrap_or("Group")
1287 .to_string();
1288 let items = obj
1289 .get(key)
1290 .and_then(parse_string_list)
1291 .or_else(|| obj.get("items").and_then(parse_string_list))
1292 .unwrap_or_default();
1293 let help = obj
1294 .get("help")
1295 .and_then(|v| v.as_str())
1296 .map(|v| v.to_string());
1297 let inline_help_in_title = obj.get("inline_help_in_title").and_then(|v| v.as_bool());
1298 let title_style = obj
1299 .get("title_style")
1300 .and_then(|v| v.as_str())
1301 .map(parse_style);
1302 let help_style = obj
1303 .get("help_style")
1304 .and_then(|v| v.as_str())
1305 .map(parse_style);
1306 groups.push(GroupConfig {
1307 name,
1308 items,
1309 help,
1310 inline_help_in_title,
1311 title_style,
1312 help_style,
1313 });
1314 }
1315 Some(groups)
1316}
1317
1318fn parse_padding_value(value: &serde_json::Value) -> Option<PaddingDimensions> {
1319 if let Some(v) = value.as_u64() {
1320 return Some(PaddingDimensions::from(v as usize));
1321 }
1322 let list = value.as_array()?;
1323 match list.len() {
1324 1 => list[0]
1325 .as_u64()
1326 .map(|v| PaddingDimensions::from(v as usize)),
1327 2 => {
1328 let v0 = list[0].as_u64()?;
1329 let v1 = list[1].as_u64()?;
1330 Some(PaddingDimensions::from((v0 as usize, v1 as usize)))
1331 }
1332 4 => {
1333 let v0 = list[0].as_u64()?;
1334 let v1 = list[1].as_u64()?;
1335 let v2 = list[2].as_u64()?;
1336 let v3 = list[3].as_u64()?;
1337 Some(PaddingDimensions::from((
1338 v0 as usize,
1339 v1 as usize,
1340 v2 as usize,
1341 v3 as usize,
1342 )))
1343 }
1344 _ => None,
1345 }
1346}
1347
1348fn parse_ratio_pair(value: &serde_json::Value) -> Option<(Option<usize>, Option<usize>)> {
1349 let list = value.as_array()?;
1350 if list.len() != 2 {
1351 return None;
1352 }
1353 let left = list[0].as_u64().map(|v| v as usize);
1354 let right = list[1].as_u64().map(|v| v as usize);
1355 Some((left, right))
1356}
1357
1358fn parse_box_value(value: &serde_json::Value) -> Option<rich_rs::r#box::Box> {
1359 if value.is_null() {
1360 return None;
1361 }
1362 let name = value.as_str()?.to_ascii_uppercase();
1363 if name == "BLANK" || name == "NONE" {
1364 return None;
1365 }
1366 Some(match name.as_str() {
1367 "ASCII" => ASCII,
1368 "ASCII2" => ASCII2,
1369 "ASCII_DOUBLE_HEAD" => ASCII_DOUBLE_HEAD,
1370 "SQUARE" => SQUARE,
1371 "SQUARE_DOUBLE_HEAD" => SQUARE_DOUBLE_HEAD,
1372 "MINIMAL" => MINIMAL,
1373 "MINIMAL_HEAVY_HEAD" => MINIMAL_HEAVY_HEAD,
1374 "MINIMAL_DOUBLE_HEAD" => MINIMAL_DOUBLE_HEAD,
1375 "SIMPLE" => SIMPLE,
1376 "SIMPLE_HEAD" => SIMPLE_HEAD,
1377 "SIMPLE_HEAVY" => SIMPLE_HEAVY,
1378 "HORIZONTALS" => HORIZONTALS,
1379 "HORIZONTALS_DOUBLE_TOP" => HORIZONTALS,
1380 "ROUNDED" => ROUNDED,
1381 "HEAVY" => HEAVY,
1382 "HEAVY_EDGE" => HEAVY_EDGE,
1383 "HEAVY_HEAD" => HEAVY_HEAD,
1384 "DOUBLE" => DOUBLE,
1385 "DOUBLE_EDGE" => DOUBLE_EDGE,
1386 "MARKDOWN" => MARKDOWN,
1387 _ => return None,
1388 })
1389}
1390
1391fn parse_align(value: &serde_json::Value) -> Option<AlignMethod> {
1392 match value.as_str()? {
1393 "left" | "Left" | "LEFT" => Some(AlignMethod::Left),
1394 "center" | "Center" | "CENTER" => Some(AlignMethod::Center),
1395 "right" | "Right" | "RIGHT" => Some(AlignMethod::Right),
1396 _ => None,
1397 }
1398}
1399
1400fn parse_color_system_mode(value: &serde_json::Value) -> Option<ColorSystemMode> {
1401 match value.as_str()? {
1402 "auto" => Some(ColorSystemMode::Auto),
1403 "standard" => Some(ColorSystemMode::Standard),
1404 "256" => Some(ColorSystemMode::EightBit),
1405 "truecolor" => Some(ColorSystemMode::TrueColor),
1406 "windows" => Some(ColorSystemMode::Windows),
1407 "none" => Some(ColorSystemMode::None),
1408 _ => None,
1409 }
1410}
1411
1412fn parse_text_markup(value: &serde_json::Value) -> Option<TextMarkup> {
1413 match value.as_str()? {
1414 "ansi" => Some(TextMarkup::Ansi),
1415 "rich" => Some(TextMarkup::Rich),
1416 "markdown" => Some(TextMarkup::Markdown),
1417 "none" => Some(TextMarkup::None),
1418 _ => None,
1419 }
1420}