|  | // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "tools/gn/standard_out.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/command_line.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/strings/string_piece.h" | 
|  | #include "base/strings/string_split.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "build_config.h" | 
|  | #include "tools/gn/switches.h" | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  | #include <windows.h> | 
|  | #else | 
|  | #include <stdio.h> | 
|  | #include <unistd.h> | 
|  | #endif | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | bool initialized = false; | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  | HANDLE hstdout; | 
|  | WORD default_attributes; | 
|  | #endif | 
|  | bool is_console = false; | 
|  |  | 
|  | bool is_markdown = false; | 
|  |  | 
|  | void EnsureInitialized() { | 
|  | if (initialized) | 
|  | return; | 
|  | initialized = true; | 
|  |  | 
|  | const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); | 
|  | if (cmdline->HasSwitch(switches::kMarkdown)) { | 
|  | // Output help in Markdown's syntax, not color-highlighted. | 
|  | is_markdown = true; | 
|  | } | 
|  |  | 
|  | if (cmdline->HasSwitch(switches::kNoColor)) { | 
|  | // Force color off. | 
|  | is_console = false; | 
|  | return; | 
|  | } | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  | // On Windows, we can't force the color on. If the output handle isn't a | 
|  | // console, there's nothing we can do about it. | 
|  | hstdout = ::GetStdHandle(STD_OUTPUT_HANDLE); | 
|  | CONSOLE_SCREEN_BUFFER_INFO info; | 
|  | is_console = !!::GetConsoleScreenBufferInfo(hstdout, &info); | 
|  | default_attributes = info.wAttributes; | 
|  | #else | 
|  | if (cmdline->HasSwitch(switches::kColor)) | 
|  | is_console = true; | 
|  | else | 
|  | is_console = isatty(fileno(stdout)); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | #if !defined(OS_WIN) | 
|  | void WriteToStdOut(const std::string& output) { | 
|  | size_t written_bytes = fwrite(output.data(), 1, output.size(), stdout); | 
|  | DCHECK_EQ(output.size(), written_bytes); | 
|  | } | 
|  | #endif  // !defined(OS_WIN) | 
|  |  | 
|  | void OutputMarkdownDec(TextDecoration dec) { | 
|  | // The markdown rendering turns "dim" text to italics and any | 
|  | // other colored text to bold. | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  | DWORD written = 0; | 
|  | if (dec == DECORATION_DIM) | 
|  | ::WriteFile(hstdout, "*", 1, &written, nullptr); | 
|  | else if (dec != DECORATION_NONE) | 
|  | ::WriteFile(hstdout, "**", 2, &written, nullptr); | 
|  | #else | 
|  | if (dec == DECORATION_DIM) | 
|  | WriteToStdOut("*"); | 
|  | else if (dec != DECORATION_NONE) | 
|  | WriteToStdOut("**"); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  |  | 
|  | void OutputString(const std::string& output, TextDecoration dec) { | 
|  | EnsureInitialized(); | 
|  | DWORD written = 0; | 
|  |  | 
|  | if (is_markdown) { | 
|  | OutputMarkdownDec(dec); | 
|  | } else if (is_console) { | 
|  | switch (dec) { | 
|  | case DECORATION_NONE: | 
|  | break; | 
|  | case DECORATION_DIM: | 
|  | ::SetConsoleTextAttribute(hstdout, FOREGROUND_INTENSITY); | 
|  | break; | 
|  | case DECORATION_RED: | 
|  | ::SetConsoleTextAttribute(hstdout, | 
|  | FOREGROUND_RED | FOREGROUND_INTENSITY); | 
|  | break; | 
|  | case DECORATION_GREEN: | 
|  | // Keep green non-bold. | 
|  | ::SetConsoleTextAttribute(hstdout, FOREGROUND_GREEN); | 
|  | break; | 
|  | case DECORATION_BLUE: | 
|  | ::SetConsoleTextAttribute(hstdout, | 
|  | FOREGROUND_BLUE | FOREGROUND_INTENSITY); | 
|  | break; | 
|  | case DECORATION_YELLOW: | 
|  | ::SetConsoleTextAttribute(hstdout, | 
|  | FOREGROUND_RED | FOREGROUND_GREEN); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | std::string tmpstr = output; | 
|  | if (is_markdown && dec == DECORATION_YELLOW) { | 
|  | // https://code.google.com/p/gitiles/issues/detail?id=77 | 
|  | // Gitiles will replace "--" with an em dash in non-code text. | 
|  | // Figuring out all instances of this might be difficult, but we can | 
|  | // at least escape the instances where this shows up in a heading. | 
|  | base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "--", "\\--"); | 
|  | } | 
|  | ::WriteFile(hstdout, tmpstr.c_str(), static_cast<DWORD>(tmpstr.size()), | 
|  | &written, nullptr); | 
|  |  | 
|  | if (is_markdown) { | 
|  | OutputMarkdownDec(dec); | 
|  | } else if (is_console) { | 
|  | ::SetConsoleTextAttribute(hstdout, default_attributes); | 
|  | } | 
|  | } | 
|  |  | 
|  | #else | 
|  |  | 
|  | void OutputString(const std::string& output, TextDecoration dec) { | 
|  | EnsureInitialized(); | 
|  | if (is_markdown) { | 
|  | OutputMarkdownDec(dec); | 
|  | } else if (is_console) { | 
|  | switch (dec) { | 
|  | case DECORATION_NONE: | 
|  | break; | 
|  | case DECORATION_DIM: | 
|  | WriteToStdOut("\e[2m"); | 
|  | break; | 
|  | case DECORATION_RED: | 
|  | WriteToStdOut("\e[31m\e[1m"); | 
|  | break; | 
|  | case DECORATION_GREEN: | 
|  | WriteToStdOut("\e[32m"); | 
|  | break; | 
|  | case DECORATION_BLUE: | 
|  | WriteToStdOut("\e[34m\e[1m"); | 
|  | break; | 
|  | case DECORATION_YELLOW: | 
|  | WriteToStdOut("\e[33m\e[1m"); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | std::string tmpstr = output; | 
|  | if (is_markdown && dec == DECORATION_YELLOW) { | 
|  | // https://code.google.com/p/gitiles/issues/detail?id=77 | 
|  | // Gitiles will replace "--" with an em dash in non-code text. | 
|  | // Figuring out all instances of this might be difficult, but we can | 
|  | // at least escape the instances where this shows up in a heading. | 
|  | base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "--", "\\--"); | 
|  | } | 
|  | WriteToStdOut(tmpstr.data()); | 
|  |  | 
|  | if (is_markdown) { | 
|  | OutputMarkdownDec(dec); | 
|  | } else if (is_console && dec != DECORATION_NONE) { | 
|  | WriteToStdOut("\e[0m"); | 
|  | } | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | void PrintSectionHelp(const std::string& line, | 
|  | const std::string& topic, | 
|  | const std::string& tag) { | 
|  | EnsureInitialized(); | 
|  |  | 
|  | if (is_markdown) { | 
|  | OutputString("*   [" + line + "](#" + tag + ")\n"); | 
|  | } else if (topic.size()) { | 
|  | OutputString("\n" + line + " (type \"gn help " + topic + | 
|  | "\" for more help):\n"); | 
|  | } else { | 
|  | OutputString("\n" + line + ":\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PrintShortHelp(const std::string& line) { | 
|  | EnsureInitialized(); | 
|  |  | 
|  | size_t colon_offset = line.find(':'); | 
|  | size_t first_normal = 0; | 
|  | if (colon_offset != std::string::npos) { | 
|  | if (is_markdown) { | 
|  | OutputString("    *   [" + line + "](#" + line.substr(0, colon_offset) + | 
|  | ")\n"); | 
|  | } else { | 
|  | OutputString("  " + line.substr(0, colon_offset), DECORATION_YELLOW); | 
|  | first_normal = colon_offset; | 
|  | } | 
|  | } else if (is_markdown) { | 
|  | OutputString("    *   [" + line + "](" + line + ")\n"); | 
|  | } | 
|  |  | 
|  | if (is_markdown) | 
|  | return; | 
|  |  | 
|  | // See if the colon is followed by a " [" and if so, dim the contents of [ ]. | 
|  | if (first_normal > 0 && | 
|  | line.size() > first_normal + 2 && | 
|  | line[first_normal + 1] == ' ' && line[first_normal + 2] == '[') { | 
|  | size_t begin_bracket = first_normal + 2; | 
|  | OutputString(": "); | 
|  | first_normal = line.find(']', begin_bracket); | 
|  | if (first_normal == std::string::npos) | 
|  | first_normal = line.size(); | 
|  | else | 
|  | first_normal++; | 
|  | OutputString(line.substr(begin_bracket, first_normal - begin_bracket), | 
|  | DECORATION_DIM); | 
|  | } | 
|  |  | 
|  | OutputString(line.substr(first_normal) + "\n"); | 
|  | } | 
|  |  | 
|  | void PrintLongHelp(const std::string& text, const std::string& tag) { | 
|  | EnsureInitialized(); | 
|  |  | 
|  | bool first_header = true; | 
|  | bool in_body = false; | 
|  | std::size_t empty_lines = 0; | 
|  | for (const std::string& line : base::SplitString( | 
|  | text, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) { | 
|  | // Check for a heading line. | 
|  | if (!line.empty() && line[0] != ' ') { | 
|  | // New paragraph, just skip any trailing empty lines. | 
|  | empty_lines = 0; | 
|  |  | 
|  | if (is_markdown) { | 
|  | // GN's block-level formatting is converted to markdown as follows: | 
|  | // * The first heading is treated as an H3. | 
|  | // * Subsequent heading are treated as H4s. | 
|  | // * Any other text is wrapped in a code block and displayed as-is. | 
|  | // | 
|  | // Span-level formatting (the decorations) is converted inside | 
|  | // OutputString(). | 
|  | if (in_body) { | 
|  | OutputString("```\n\n", DECORATION_NONE); | 
|  | in_body = false; | 
|  | } | 
|  |  | 
|  | if (first_header) { | 
|  | std::string the_tag = tag; | 
|  | if (the_tag.size() == 0) { | 
|  | if (line.substr(0, 2) == "gn") { | 
|  | the_tag = line.substr(3, line.substr(3).find(' ')); | 
|  | } else { | 
|  | the_tag = line.substr(0, line.find(':')); | 
|  | } | 
|  | } | 
|  | OutputString("### <a name=\"" + the_tag + "\"></a>", DECORATION_NONE); | 
|  | first_header = false; | 
|  | } else { | 
|  | OutputString("#### ", DECORATION_NONE); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Highlight up to the colon (if any). | 
|  | size_t chars_to_highlight = line.find(':'); | 
|  | if (chars_to_highlight == std::string::npos) | 
|  | chars_to_highlight = line.size(); | 
|  |  | 
|  | OutputString(line.substr(0, chars_to_highlight), DECORATION_YELLOW); | 
|  | OutputString(line.substr(chars_to_highlight)); | 
|  | OutputString("\n"); | 
|  | continue; | 
|  | } else if (is_markdown && !line.empty() && !in_body) { | 
|  | OutputString("```\n", DECORATION_NONE); | 
|  | in_body = true; | 
|  | } | 
|  |  | 
|  | // We buffer empty lines, so we can skip them if needed | 
|  | // (i.e. new paragraph body, end of final paragraph body). | 
|  | if (in_body && is_markdown) { | 
|  | if (!line.empty() && empty_lines != 0) { | 
|  | OutputString(std::string(empty_lines, '\n')); | 
|  | empty_lines = 0; | 
|  | } else if (line.empty()) { | 
|  | ++empty_lines; | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Check for a comment. | 
|  | TextDecoration dec = DECORATION_NONE; | 
|  | for (const auto& elem : line) { | 
|  | if (elem == '#' && !is_markdown) { | 
|  | // Got a comment, draw dimmed. | 
|  | dec = DECORATION_DIM; | 
|  | break; | 
|  | } else if (elem != ' ') { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | OutputString(line + "\n", dec); | 
|  | } | 
|  |  | 
|  | if (is_markdown && in_body) | 
|  | OutputString("```\n"); | 
|  | } | 
|  |  |