• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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       case DECORATION_MAGENTA:
133         ::SetConsoleTextAttribute(
134             hstdout, FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
135         break;
136     }
137   }
138 
139   std::string tmpstr = output;
140   if (is_markdown && dec == DECORATION_YELLOW) {
141     // https://code.google.com/p/gitiles/issues/detail?id=77
142     // Gitiles will replace "--" with an em dash in non-code text.
143     // Figuring out all instances of this might be difficult, but we can
144     // at least escape the instances where this shows up in a heading.
145     base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "--", "\\--");
146   }
147   if (is_markdown && !in_body && escaping == DEFAULT_ESCAPING) {
148     // Markdown auto-escapes < and > in code sections (and converts &lt; to
149     // &amp;tl; there), but not elsewhere.
150     base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "<", "&lt;");
151     base::ReplaceSubstringsAfterOffset(&tmpstr, 0, ">", "&gt;");
152   }
153   ::WriteFile(hstdout, tmpstr.c_str(), static_cast<DWORD>(tmpstr.size()),
154               &written, nullptr);
155 
156   if (is_markdown) {
157     OutputMarkdownDec(dec);
158   } else if (is_console) {
159     ::SetConsoleTextAttribute(hstdout, default_attributes);
160   }
161 }
162 
163 #else
164 
OutputString(const std::string & output,TextDecoration dec,HtmlEscaping escaping)165 void OutputString(const std::string& output,
166                   TextDecoration dec,
167                   HtmlEscaping escaping) {
168   EnsureInitialized();
169   if (is_markdown) {
170     OutputMarkdownDec(dec);
171   } else if (is_console) {
172     switch (dec) {
173       case DECORATION_NONE:
174         break;
175       case DECORATION_DIM:
176         WriteToStdOut("\e[2m");
177         break;
178       case DECORATION_RED:
179         WriteToStdOut("\e[31m\e[1m");
180         break;
181       case DECORATION_GREEN:
182         WriteToStdOut("\e[32m");
183         break;
184       case DECORATION_BLUE:
185         WriteToStdOut("\e[34m\e[1m");
186         break;
187       case DECORATION_YELLOW:
188         WriteToStdOut("\e[33m");
189         break;
190       case DECORATION_MAGENTA:
191         WriteToStdOut("\e[35m\e[1m");
192         break;
193     }
194   }
195 
196   std::string tmpstr = output;
197   if (is_markdown && dec == DECORATION_YELLOW) {
198     // https://code.google.com/p/gitiles/issues/detail?id=77
199     // Gitiles will replace "--" with an em dash in non-code text.
200     // Figuring out all instances of this might be difficult, but we can
201     // at least escape the instances where this shows up in a heading.
202     base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "--", "\\--");
203   }
204   if (is_markdown && !in_body && escaping == DEFAULT_ESCAPING) {
205     // Markdown auto-escapes < and > in code sections (and converts &lt; to
206     // &amp;tl; there), but not elsewhere.
207     base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "<", "&lt;");
208     base::ReplaceSubstringsAfterOffset(&tmpstr, 0, ">", "&gt;");
209   }
210   WriteToStdOut(tmpstr.data());
211 
212   if (is_markdown) {
213     OutputMarkdownDec(dec);
214   } else if (is_console && dec != DECORATION_NONE) {
215     WriteToStdOut("\e[0m");
216   }
217 }
218 
219 #endif
220 
PrintSectionHelp(const std::string & line,const std::string & topic,const std::string & tag)221 void PrintSectionHelp(const std::string& line,
222                       const std::string& topic,
223                       const std::string& tag) {
224   EnsureInitialized();
225 
226   if (is_markdown) {
227     OutputString("*   [" + line + "](#" + tag + ")\n");
228   } else if (topic.size()) {
229     OutputString("\n" + line + " (type \"gn help " + topic +
230                  "\" for more help):\n");
231   } else {
232     OutputString("\n" + line + ":\n");
233   }
234 }
235 
PrintShortHelp(const std::string & line,const std::string & link_tag)236 void PrintShortHelp(const std::string& line, const std::string& link_tag) {
237   EnsureInitialized();
238 
239   if (is_markdown) {
240     if (link_tag.empty())
241       OutputString("    *   " + line + "\n");
242     else
243       OutputString("    *   [" + line + "](#" + link_tag + ")\n");
244     return;
245   }
246 
247   size_t colon_offset = line.find(':');
248   size_t first_normal = 0;
249   if (colon_offset != std::string::npos) {
250     OutputString("  " + line.substr(0, colon_offset), DECORATION_YELLOW);
251     first_normal = colon_offset;
252   }
253 
254   // See if the colon is followed by a " [" and if so, dim the contents of [ ].
255   if (first_normal > 0 && line.size() > first_normal + 2 &&
256       line[first_normal + 1] == ' ' && line[first_normal + 2] == '[') {
257     size_t begin_bracket = first_normal + 2;
258     OutputString(": ");
259     first_normal = line.find(']', begin_bracket);
260     if (first_normal == std::string::npos)
261       first_normal = line.size();
262     else
263       first_normal++;
264     OutputString(line.substr(begin_bracket, first_normal - begin_bracket),
265                  DECORATION_DIM);
266   }
267 
268   OutputString(line.substr(first_normal) + "\n");
269 }
270 
PrintLongHelp(const std::string & text,const std::string & tag)271 void PrintLongHelp(const std::string& text, const std::string& tag) {
272   EnsureInitialized();
273 
274   bool first_header = true;
275   in_body = false;
276   std::size_t empty_lines = 0;
277   for (const std::string& line : base::SplitString(
278            text, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) {
279     // Check for a heading line.
280     if (!line.empty() && line[0] != ' ') {
281       // New paragraph, just skip any trailing empty lines.
282       empty_lines = 0;
283 
284       if (is_markdown) {
285         // GN's block-level formatting is converted to markdown as follows:
286         // * The first heading is treated as an H3.
287         // * Subsequent heading are treated as H4s.
288         // * Any other text is wrapped in a code block and displayed as-is.
289         //
290         // Span-level formatting (the decorations) is converted inside
291         // OutputString().
292         if (in_body) {
293           OutputString("```\n\n", DECORATION_NONE);
294           in_body = false;
295         }
296 
297         if (first_header && !tag.empty()) {
298           OutputString("### <a name=\"" + tag + "\"></a>", DECORATION_NONE,
299                        NO_ESCAPING);
300           first_header = false;
301         } else {
302           OutputString("#### ", DECORATION_NONE);
303         }
304       }
305 
306       // Highlight up to the colon (if any).
307       size_t chars_to_highlight = line.find(':');
308       if (chars_to_highlight == std::string::npos)
309         chars_to_highlight = line.size();
310 
311       OutputString(line.substr(0, chars_to_highlight), DECORATION_YELLOW);
312       OutputString(line.substr(chars_to_highlight));
313       OutputString("\n");
314       continue;
315     } else if (is_markdown && !line.empty() && !in_body) {
316       OutputString("```\n", DECORATION_NONE);
317       in_body = true;
318     }
319 
320     // We buffer empty lines, so we can skip them if needed
321     // (i.e. new paragraph body, end of final paragraph body).
322     if (in_body && is_markdown) {
323       if (!line.empty() && empty_lines != 0) {
324         OutputString(std::string(empty_lines, '\n'));
325         empty_lines = 0;
326       } else if (line.empty()) {
327         ++empty_lines;
328         continue;
329       }
330     }
331 
332     // Check for a comment.
333     TextDecoration dec = DECORATION_NONE;
334     for (const auto& elem : line) {
335       if (elem == '#' && !is_markdown) {
336         // Got a comment, draw dimmed.
337         dec = DECORATION_DIM;
338         break;
339       } else if (elem != ' ') {
340         break;
341       }
342     }
343 
344     OutputString(line + "\n", dec);
345   }
346 
347   if (is_markdown && in_body)
348     OutputString("```\n");
349 }
350