1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "gn/standard_out.h"
6
7 #include <stddef.h>
8
9 #include <string_view>
10 #include <vector>
11
12 #include "base/command_line.h"
13 #include "base/logging.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "gn/switches.h"
17 #include "util/build_config.h"
18
19 #if defined(OS_WIN)
20 #include <windows.h>
21 #else
22 #include <stdio.h>
23 #include <unistd.h>
24 #endif
25
26 namespace {
27
28 bool initialized = false;
29
30 #if defined(OS_WIN)
31 HANDLE hstdout;
32 WORD default_attributes;
33 #endif
34 bool is_console = false;
35
36 bool is_markdown = false;
37
38 // True while output is going into a markdown ```...``` code block.
39 bool in_body = false;
40
EnsureInitialized()41 void EnsureInitialized() {
42 if (initialized)
43 return;
44 initialized = true;
45
46 const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
47 if (cmdline->HasSwitch(switches::kMarkdown)) {
48 // Output help in Markdown's syntax, not color-highlighted.
49 is_markdown = true;
50 }
51
52 if (cmdline->HasSwitch(switches::kNoColor)) {
53 // Force color off.
54 is_console = false;
55 return;
56 }
57
58 #if defined(OS_WIN)
59 // On Windows, we can't force the color on. If the output handle isn't a
60 // console, there's nothing we can do about it.
61 hstdout = ::GetStdHandle(STD_OUTPUT_HANDLE);
62 CONSOLE_SCREEN_BUFFER_INFO info;
63 is_console = !!::GetConsoleScreenBufferInfo(hstdout, &info);
64 default_attributes = info.wAttributes;
65 #else
66 if (cmdline->HasSwitch(switches::kColor))
67 is_console = true;
68 else
69 is_console = isatty(fileno(stdout));
70 #endif
71 }
72
73 #if !defined(OS_WIN)
WriteToStdOut(const std::string & output)74 void WriteToStdOut(const std::string& output) {
75 size_t written_bytes = fwrite(output.data(), 1, output.size(), stdout);
76 DCHECK_EQ(output.size(), written_bytes);
77 }
78 #endif // !defined(OS_WIN)
79
OutputMarkdownDec(TextDecoration dec)80 void OutputMarkdownDec(TextDecoration dec) {
81 // The markdown rendering turns "dim" text to italics and any
82 // other colored text to bold.
83
84 #if defined(OS_WIN)
85 DWORD written = 0;
86 if (dec == DECORATION_DIM)
87 ::WriteFile(hstdout, "*", 1, &written, nullptr);
88 else if (dec != DECORATION_NONE)
89 ::WriteFile(hstdout, "**", 2, &written, nullptr);
90 #else
91 if (dec == DECORATION_DIM)
92 WriteToStdOut("*");
93 else if (dec != DECORATION_NONE)
94 WriteToStdOut("**");
95 #endif
96 }
97
98 } // namespace
99
100 #if defined(OS_WIN)
101
OutputString(const std::string & output,TextDecoration dec,HtmlEscaping escaping)102 void OutputString(const std::string& output,
103 TextDecoration dec,
104 HtmlEscaping escaping) {
105 EnsureInitialized();
106 DWORD written = 0;
107
108 if (is_markdown) {
109 OutputMarkdownDec(dec);
110 } else if (is_console) {
111 switch (dec) {
112 case DECORATION_NONE:
113 break;
114 case DECORATION_DIM:
115 ::SetConsoleTextAttribute(hstdout, FOREGROUND_INTENSITY);
116 break;
117 case DECORATION_RED:
118 ::SetConsoleTextAttribute(hstdout,
119 FOREGROUND_RED | FOREGROUND_INTENSITY);
120 break;
121 case DECORATION_GREEN:
122 // Keep green non-bold.
123 ::SetConsoleTextAttribute(hstdout, FOREGROUND_GREEN);
124 break;
125 case DECORATION_BLUE:
126 ::SetConsoleTextAttribute(hstdout,
127 FOREGROUND_BLUE | FOREGROUND_INTENSITY);
128 break;
129 case DECORATION_YELLOW:
130 ::SetConsoleTextAttribute(hstdout, FOREGROUND_RED | FOREGROUND_GREEN);
131 break;
132 }
133 }
134
135 std::string tmpstr = output;
136 if (is_markdown && dec == DECORATION_YELLOW) {
137 // https://code.google.com/p/gitiles/issues/detail?id=77
138 // Gitiles will replace "--" with an em dash in non-code text.
139 // Figuring out all instances of this might be difficult, but we can
140 // at least escape the instances where this shows up in a heading.
141 base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "--", "\\--");
142 }
143 if (is_markdown && !in_body && escaping == DEFAULT_ESCAPING) {
144 // Markdown auto-escapes < and > in code sections (and converts < to
145 // &tl; there), but not elsewhere.
146 base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "<", "<");
147 base::ReplaceSubstringsAfterOffset(&tmpstr, 0, ">", ">");
148 }
149 ::WriteFile(hstdout, tmpstr.c_str(), static_cast<DWORD>(tmpstr.size()),
150 &written, nullptr);
151
152 if (is_markdown) {
153 OutputMarkdownDec(dec);
154 } else if (is_console) {
155 ::SetConsoleTextAttribute(hstdout, default_attributes);
156 }
157 }
158
159 #else
160
OutputString(const std::string & output,TextDecoration dec,HtmlEscaping escaping)161 void OutputString(const std::string& output,
162 TextDecoration dec,
163 HtmlEscaping escaping) {
164 EnsureInitialized();
165 if (is_markdown) {
166 OutputMarkdownDec(dec);
167 } else if (is_console) {
168 switch (dec) {
169 case DECORATION_NONE:
170 break;
171 case DECORATION_DIM:
172 WriteToStdOut("\e[2m");
173 break;
174 case DECORATION_RED:
175 WriteToStdOut("\e[31m\e[1m");
176 break;
177 case DECORATION_GREEN:
178 WriteToStdOut("\e[32m");
179 break;
180 case DECORATION_BLUE:
181 WriteToStdOut("\e[34m\e[1m");
182 break;
183 case DECORATION_YELLOW:
184 WriteToStdOut("\e[33m");
185 break;
186 }
187 }
188
189 std::string tmpstr = output;
190 if (is_markdown && dec == DECORATION_YELLOW) {
191 // https://code.google.com/p/gitiles/issues/detail?id=77
192 // Gitiles will replace "--" with an em dash in non-code text.
193 // Figuring out all instances of this might be difficult, but we can
194 // at least escape the instances where this shows up in a heading.
195 base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "--", "\\--");
196 }
197 if (is_markdown && !in_body && escaping == DEFAULT_ESCAPING) {
198 // Markdown auto-escapes < and > in code sections (and converts < to
199 // &tl; there), but not elsewhere.
200 base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "<", "<");
201 base::ReplaceSubstringsAfterOffset(&tmpstr, 0, ">", ">");
202 }
203 WriteToStdOut(tmpstr.data());
204
205 if (is_markdown) {
206 OutputMarkdownDec(dec);
207 } else if (is_console && dec != DECORATION_NONE) {
208 WriteToStdOut("\e[0m");
209 }
210 }
211
212 #endif
213
PrintSectionHelp(const std::string & line,const std::string & topic,const std::string & tag)214 void PrintSectionHelp(const std::string& line,
215 const std::string& topic,
216 const std::string& tag) {
217 EnsureInitialized();
218
219 if (is_markdown) {
220 OutputString("* [" + line + "](#" + tag + ")\n");
221 } else if (topic.size()) {
222 OutputString("\n" + line + " (type \"gn help " + topic +
223 "\" for more help):\n");
224 } else {
225 OutputString("\n" + line + ":\n");
226 }
227 }
228
PrintShortHelp(const std::string & line,const std::string & link_tag)229 void PrintShortHelp(const std::string& line, const std::string& link_tag) {
230 EnsureInitialized();
231
232 if (is_markdown) {
233 if (link_tag.empty())
234 OutputString(" * " + line + "\n");
235 else
236 OutputString(" * [" + line + "](#" + link_tag + ")\n");
237 return;
238 }
239
240 size_t colon_offset = line.find(':');
241 size_t first_normal = 0;
242 if (colon_offset != std::string::npos) {
243 OutputString(" " + line.substr(0, colon_offset), DECORATION_YELLOW);
244 first_normal = colon_offset;
245 }
246
247 // See if the colon is followed by a " [" and if so, dim the contents of [ ].
248 if (first_normal > 0 && line.size() > first_normal + 2 &&
249 line[first_normal + 1] == ' ' && line[first_normal + 2] == '[') {
250 size_t begin_bracket = first_normal + 2;
251 OutputString(": ");
252 first_normal = line.find(']', begin_bracket);
253 if (first_normal == std::string::npos)
254 first_normal = line.size();
255 else
256 first_normal++;
257 OutputString(line.substr(begin_bracket, first_normal - begin_bracket),
258 DECORATION_DIM);
259 }
260
261 OutputString(line.substr(first_normal) + "\n");
262 }
263
PrintLongHelp(const std::string & text,const std::string & tag)264 void PrintLongHelp(const std::string& text, const std::string& tag) {
265 EnsureInitialized();
266
267 bool first_header = true;
268 in_body = false;
269 std::size_t empty_lines = 0;
270 for (const std::string& line : base::SplitString(
271 text, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) {
272 // Check for a heading line.
273 if (!line.empty() && line[0] != ' ') {
274 // New paragraph, just skip any trailing empty lines.
275 empty_lines = 0;
276
277 if (is_markdown) {
278 // GN's block-level formatting is converted to markdown as follows:
279 // * The first heading is treated as an H3.
280 // * Subsequent heading are treated as H4s.
281 // * Any other text is wrapped in a code block and displayed as-is.
282 //
283 // Span-level formatting (the decorations) is converted inside
284 // OutputString().
285 if (in_body) {
286 OutputString("```\n\n", DECORATION_NONE);
287 in_body = false;
288 }
289
290 if (first_header && !tag.empty()) {
291 OutputString("### <a name=\"" + tag + "\"></a>", DECORATION_NONE,
292 NO_ESCAPING);
293 first_header = false;
294 } else {
295 OutputString("#### ", DECORATION_NONE);
296 }
297 }
298
299 // Highlight up to the colon (if any).
300 size_t chars_to_highlight = line.find(':');
301 if (chars_to_highlight == std::string::npos)
302 chars_to_highlight = line.size();
303
304 OutputString(line.substr(0, chars_to_highlight), DECORATION_YELLOW);
305 OutputString(line.substr(chars_to_highlight));
306 OutputString("\n");
307 continue;
308 } else if (is_markdown && !line.empty() && !in_body) {
309 OutputString("```\n", DECORATION_NONE);
310 in_body = true;
311 }
312
313 // We buffer empty lines, so we can skip them if needed
314 // (i.e. new paragraph body, end of final paragraph body).
315 if (in_body && is_markdown) {
316 if (!line.empty() && empty_lines != 0) {
317 OutputString(std::string(empty_lines, '\n'));
318 empty_lines = 0;
319 } else if (line.empty()) {
320 ++empty_lines;
321 continue;
322 }
323 }
324
325 // Check for a comment.
326 TextDecoration dec = DECORATION_NONE;
327 for (const auto& elem : line) {
328 if (elem == '#' && !is_markdown) {
329 // Got a comment, draw dimmed.
330 dec = DECORATION_DIM;
331 break;
332 } else if (elem != ' ') {
333 break;
334 }
335 }
336
337 OutputString(line + "\n", dec);
338 }
339
340 if (is_markdown && in_body)
341 OutputString("```\n");
342 }
343