1use crate::color::spec_color;
2use crate::utils::{into_static_str, str_from_utf8};
3use lazy_static::lazy_static;
4use std::io::{Result, Write};
5use termcolor::{Buffer, Color, WriteColor};
6
7const HEADING_PREFIX: &str = "# ";
8const PADDED_BLOCK_PREFIX: &str = " ";
9const SHELL_PREFIX: &str = "$>";
10
11const CODE_CHAR: char = '`';
12const COMMENT_CHAR: char = '#';
13
14const PRIMARY_COLOR: Color = Color::Yellow;
15const SECONDARY_COLOR: Color = Color::Cyan;
16const CODE_COLOR: Color = Color::Green;
17
18lazy_static! {
19 static ref COLORED_HELP_ENABLED: bool = atty::is(atty::Stream::Stdout)
20 && std::env::args().any(|arg| arg == "-h" || arg == "--help");
21}
22
23pub fn highlight_static(text: &'static str) -> &'static str {
24 if *COLORED_HELP_ENABLED {
25 highlight_to_string(text).map_or(text, into_static_str)
26 } else {
27 text
28 }
29}
30
31fn highlight_to_string(text: &str) -> Result<String> {
32 let mut buffer = Buffer::ansi();
33 highlight(&mut buffer, text)?;
34 str_from_utf8(buffer.as_slice()).map(String::from)
35}
36
37pub fn highlight<O: Write + WriteColor>(output: &mut O, text: &str) -> Result<()> {
38 for line in text.lines() {
39 if let Some(header) = line.strip_prefix(HEADING_PREFIX) {
40 output.set_color(&spec_color(PRIMARY_COLOR))?;
41 write!(output, "{}", header)?;
42 } else if let Some(block) = line.strip_prefix(PADDED_BLOCK_PREFIX) {
43 write!(output, "{}", PADDED_BLOCK_PREFIX)?;
44
45 if let Some(command) = block.strip_prefix(SHELL_PREFIX) {
46 output.set_color(&spec_color(SECONDARY_COLOR))?;
47 write!(output, "{}", SHELL_PREFIX)?;
48 output.set_color(&spec_color(CODE_COLOR))?;
49
50 if let Some(comment_index) = command.rfind(COMMENT_CHAR) {
51 write!(output, "{}", &command[..comment_index])?;
52 output.set_color(&spec_color(SECONDARY_COLOR))?;
53 write!(output, "{}", &command[comment_index..])?;
54 } else {
55 write!(output, "{}", command)?;
56 }
57 } else {
58 output.set_color(&spec_color(SECONDARY_COLOR))?;
59 write!(output, "{}", block)?;
60 }
61 } else {
62 highlight_code(output, line)?;
63 }
64
65 output.reset()?;
66 writeln!(output)?;
67 }
68
69 Ok(())
70}
71
72fn highlight_code<O: Write + WriteColor>(output: &mut O, line: &str) -> Result<()> {
73 let mut in_code = false;
74 let mut last_index = 0;
75
76 for (index, char) in line.char_indices() {
77 if char == CODE_CHAR {
78 write!(output, "{}", &line[last_index..index])?;
79 if in_code {
80 output.reset()?;
81 } else {
82 output.set_color(&spec_color(CODE_COLOR))?;
83 }
84 last_index = index + 1;
85 in_code = !in_code;
86 }
87 }
88
89 write!(output, "{}", &line[last_index..])
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::testing::{ColoredOuput, OutputChunk};
96 use claim::*;
97 use indoc::indoc;
98
99 const SAMPLE_HELP: &str = indoc! {"
100 # Heading
101
102 Text.
103 Text with `code`.
104 Text with `code` and `more code`.
105
106 Padded block.
107 Padded block with `code`.
108
109 Text.
110
111 $> ls -la
112 $> ls -la # Shell comment
113 "};
114
115 #[test]
116 fn highlight_to_string() {
117 assert_gt!(super::highlight_to_string(SAMPLE_HELP).unwrap().len(), 0);
118 }
119
120 #[test]
121 fn highlight() {
122 let mut ouput = ColoredOuput::new();
123 super::highlight(&mut ouput, SAMPLE_HELP).unwrap();
124 assert_eq!(
125 ouput.chunks(),
126 &[
127 OutputChunk::color(Color::Yellow, "Heading"),
128 OutputChunk::plain("\n\nText.\nText with "),
129 OutputChunk::color(Color::Green, "code"),
130 OutputChunk::plain(".\nText with "),
131 OutputChunk::color(Color::Green, "code"),
132 OutputChunk::plain(" and "),
133 OutputChunk::color(Color::Green, "more code"),
134 OutputChunk::plain(".\n\n "),
135 OutputChunk::color(Color::Cyan, "Padded block."),
136 OutputChunk::plain("\n "),
137 OutputChunk::color(Color::Cyan, "Padded block with `code`."),
138 OutputChunk::plain("\n\nText.\n\n "),
139 OutputChunk::color(Color::Cyan, "$>"),
140 OutputChunk::color(Color::Green, " ls -la"),
141 OutputChunk::plain("\n "),
142 OutputChunk::color(Color::Cyan, "$>"),
143 OutputChunk::color(Color::Green, " ls -la "),
144 OutputChunk::color(Color::Cyan, "# Shell comment"),
145 OutputChunk::plain("\n")
146 ]
147 );
148 }
149}