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 <map>
6
7#include "base/strings/string_number_conversions.h"
8#include "base/strings/string_util.h"
9#include "tools/gn/commands.h"
10#include "tools/gn/input_file.h"
11#include "tools/gn/parse_tree.h"
12#include "tools/gn/setup.h"
13#include "tools/gn/standard_out.h"
14#include "tools/gn/tokenizer.h"
15
16namespace commands {
17
18namespace {
19
20bool DoesLineBeginWithComment(const base::StringPiece& line) {
21  // Skip whitespace.
22  size_t i = 0;
23  while (i < line.size() && IsAsciiWhitespace(line[i]))
24    i++;
25
26  return i < line.size() && line[i] == '#';
27}
28
29// Returns the offset of the beginning of the line identified by |offset|.
30size_t BackUpToLineBegin(const std::string& data, size_t offset) {
31  // Degenerate case of an empty line. Below we'll try to return the
32  // character after the newline, but that will be incorrect in this case.
33  if (offset == 0 || Tokenizer::IsNewline(data, offset))
34    return offset;
35
36  size_t cur = offset;
37  do {
38    cur --;
39    if (Tokenizer::IsNewline(data, cur))
40      return cur + 1;  // Want the first character *after* the newline.
41  } while (cur > 0);
42  return 0;
43}
44
45// Assumes DoesLineBeginWithComment().
46std::string StripCommentFromLine(const base::StringPiece& line) {
47  std::string ret = line.as_string();
48  for (size_t i = 0; i < ret.size(); i++) {
49    if (ret[i] == '#') {
50      ret[i] = ' ';
51      break;
52    }
53  }
54  return ret;
55}
56
57// Tries to find the comment before the setting of the given value.
58void GetContextForValue(const Value& value,
59                        std::string* location_str,
60                        std::string* comment) {
61  Location location = value.origin()->GetRange().begin();
62  const InputFile* file = location.file();
63  if (!file)
64    return;
65
66  *location_str = file->name().value() + ":" +
67      base::IntToString(location.line_number());
68
69  const std::string& data = file->contents();
70  size_t line_off =
71      Tokenizer::ByteOffsetOfNthLine(data, location.line_number());
72
73  while (line_off > 1) {
74    line_off -= 2;  // Back up to end of previous line.
75    size_t previous_line_offset = BackUpToLineBegin(data, line_off);
76
77    base::StringPiece line(&data[previous_line_offset],
78                           line_off - previous_line_offset + 1);
79    if (!DoesLineBeginWithComment(line))
80      break;
81
82    comment->insert(0, StripCommentFromLine(line) + "\n");
83    line_off = previous_line_offset;
84  }
85}
86
87void PrintArgHelp(const base::StringPiece& name, const Value& value) {
88  OutputString(name.as_string(), DECORATION_YELLOW);
89  OutputString("  Default = " + value.ToString(true) + "\n");
90
91  if (value.origin()) {
92    std::string location, comment;
93    GetContextForValue(value, &location, &comment);
94    OutputString("    " + location + "\n" + comment);
95  } else {
96    OutputString("    (Internally set)\n");
97  }
98}
99
100}  // namespace
101
102extern const char kArgs[] = "args";
103extern const char kArgs_HelpShort[] =
104    "args: Display configurable arguments declared by the build.";
105extern const char kArgs_Help[] =
106    "gn args [arg name]\n"
107    "  Displays all arguments declared by buildfiles along with their\n"
108    "  description. Build arguments are anything in a declare_args() block\n"
109    "  in any buildfile. The comment preceeding the declaration will be\n"
110    "  displayed here (so comment well!).\n"
111    "\n"
112    "  These arguments can be overriden on the command-line:\n"
113    "    --args=\"doom_melon_setting=5 component_build=1\"\n"
114    "  or in a toolchain definition (see \"gn help buildargs\" for more on\n"
115    "  how this all works).\n"
116    "\n"
117    "  If \"arg name\" is specified, only the information for that argument\n"
118    "  will be displayed. Otherwise all arguments will be displayed.\n";
119
120int RunArgs(const std::vector<std::string>& args) {
121  Setup* setup = new Setup;
122  setup->set_check_for_bad_items(false);
123  if (!setup->DoSetup() || !setup->Run())
124    return 1;
125
126  const Scope::KeyValueMap& build_args =
127      setup->build_settings().build_args().declared_arguments();
128
129  if (args.size() == 1) {
130    // Get help on a specific command.
131    Scope::KeyValueMap::const_iterator found_arg = build_args.find(args[0]);
132    if (found_arg == build_args.end()) {
133      Err(Location(), "Unknown build arg.",
134          "You asked for \"" + args[0] + "\" which I didn't find in any "
135          "buildfile\nassociated with this build.");
136      return 1;
137    }
138    PrintArgHelp(args[0], found_arg->second);
139    return 0;
140  } else if (args.size() > 1) {
141    // Too many arguments.
142    Err(Location(), "You're holding it wrong.",
143        "Usage: \"gn args [arg name]\"").PrintToStdout();
144    return 1;
145  }
146
147  // List all arguments. First put them in a regular map so they're sorted.
148  std::map<base::StringPiece, Value> sorted_args;
149  for (Scope::KeyValueMap::const_iterator i = build_args.begin();
150       i != build_args.end(); ++i)
151    sorted_args.insert(*i);
152
153  for (std::map<base::StringPiece, Value>::iterator i = sorted_args.begin();
154       i != sorted_args.end(); ++i) {
155    PrintArgHelp(i->first, i->second);
156    OutputString("\n");
157  }
158
159  return 0;
160}
161
162}  // namespace commands
163