| Index: tools/gn/command_format.cc
|
| diff --git a/tools/gn/command_format.cc b/tools/gn/command_format.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a9dceec43e875b667bb9181bbb3f5bd5196355f1
|
| --- /dev/null
|
| +++ b/tools/gn/command_format.cc
|
| @@ -0,0 +1,391 @@
|
| +// Copyright 2014 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 <sstream>
|
| +
|
| +#include "base/command_line.h"
|
| +#include "tools/gn/commands.h"
|
| +#include "tools/gn/input_file.h"
|
| +#include "tools/gn/parser.h"
|
| +#include "tools/gn/scheduler.h"
|
| +#include "tools/gn/setup.h"
|
| +#include "tools/gn/source_file.h"
|
| +#include "tools/gn/tokenizer.h"
|
| +
|
| +namespace commands {
|
| +
|
| +const char kSwitchDumpTree[] = "dump-tree";
|
| +
|
| +const char kFormat[] = "format";
|
| +const char kFormat_HelpShort[] =
|
| + "format: Format .gn file.";
|
| +const char kFormat_Help[] =
|
| + "gn format: Format .gn file. (ALPHA, WILL CURRENTLY DESTROY DATA!)\n"
|
| + "\n"
|
| + " gn format //some/BUILD.gn\n"
|
| + " gn format some\\BUILD.gn\n"
|
| + "\n"
|
| + " Formats .gn file to a standard format. THIS IS NOT FULLY IMPLEMENTED\n"
|
| + " YET! IT WILL EAT YOUR BEAUTIFUL .GN FILES. AND YOUR LAUNDRY.\n"
|
| + " At a minimum, make sure everything is `git commit`d so you can\n"
|
| + " `git checkout -f` to recover.\n";
|
| +
|
| +namespace {
|
| +
|
| +const int kIndentSize = 2;
|
| +
|
| +class Printer {
|
| + public:
|
| + Printer();
|
| + ~Printer();
|
| +
|
| + void Block(const ParseNode* file);
|
| +
|
| + std::string String() const { return output_; }
|
| +
|
| + private:
|
| + // Format a list of values using the given style.
|
| + enum SequenceStyle {
|
| + kSequenceStyleFunctionCall,
|
| + kSequenceStyleList,
|
| + kSequenceStyleBlock,
|
| + };
|
| +
|
| + enum ExprStyle {
|
| + kExprStyleRegular,
|
| + kExprStyleComment,
|
| + };
|
| +
|
| + // Add to output.
|
| + void Print(base::StringPiece str);
|
| +
|
| + // Add the current margin (as spaces) to the output.
|
| + void PrintMargin();
|
| +
|
| + void TrimAndPrintToken(const Token& token);
|
| +
|
| + // End the current line, flushing end of line comments.
|
| + void Newline();
|
| +
|
| + // Remove trailing spaces from the current line.
|
| + void Trim();
|
| +
|
| + // Get the 0-based x position on the current line.
|
| + int CurrentColumn();
|
| +
|
| + // Print the expression to the output buffer. Returns the type of element
|
| + // added to the output.
|
| + ExprStyle Expr(const ParseNode* root);
|
| +
|
| + template <class PARSENODE> // Just for const covariance.
|
| + void Sequence(SequenceStyle style, const std::vector<PARSENODE*>& list);
|
| +
|
| + std::string output_; // Output buffer.
|
| + std::vector<Token> comments_; // Pending end-of-line comments.
|
| + int margin_; // Left margin (number of spaces).
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(Printer);
|
| +};
|
| +
|
| +Printer::Printer() : margin_(0) {
|
| + output_.reserve(100 << 10);
|
| +}
|
| +
|
| +Printer::~Printer() {
|
| +}
|
| +
|
| +void Printer::Print(base::StringPiece str) {
|
| + str.AppendToString(&output_);
|
| +}
|
| +
|
| +void Printer::PrintMargin() {
|
| + output_ += std::string(margin_, ' ');
|
| +}
|
| +
|
| +void Printer::TrimAndPrintToken(const Token& token) {
|
| + std::string trimmed;
|
| + TrimWhitespaceASCII(token.value().as_string(), base::TRIM_ALL, &trimmed);
|
| + Print(trimmed);
|
| +}
|
| +
|
| +void Printer::Newline() {
|
| + if (!comments_.empty()) {
|
| + Print(" ");
|
| + int i = 0;
|
| + for (const auto& c : comments_) {
|
| + if (i > 0) {
|
| + Trim();
|
| + Print("\n");
|
| + PrintMargin();
|
| + }
|
| + TrimAndPrintToken(c);
|
| + }
|
| + comments_.clear();
|
| + }
|
| + Trim();
|
| + Print("\n");
|
| + PrintMargin();
|
| +}
|
| +
|
| +void Printer::Trim() {
|
| + size_t n = output_.size();
|
| + while (n > 0 && output_[n - 1] == ' ')
|
| + --n;
|
| + output_.resize(n);
|
| +}
|
| +
|
| +int Printer::CurrentColumn() {
|
| + int n = 0;
|
| + while (n < static_cast<int>(output_.size()) &&
|
| + output_[output_.size() - 1 - n] != '\n') {
|
| + ++n;
|
| + }
|
| + return n;
|
| +}
|
| +
|
| +void Printer::Block(const ParseNode* root) {
|
| + const BlockNode* block = root->AsBlock();
|
| +
|
| + if (block->comments()) {
|
| + for (const auto& c : block->comments()->before()) {
|
| + TrimAndPrintToken(c);
|
| + Newline();
|
| + }
|
| + }
|
| +
|
| + size_t i = 0;
|
| + for (const auto& stmt : block->statements()) {
|
| + Expr(stmt);
|
| + Newline();
|
| + if (stmt->comments()) {
|
| + // Why are before() not printed here too? before() are handled inside
|
| + // Expr(), as are suffix() which are queued to the next Newline().
|
| + // However, because it's a general expression handler, it doesn't insert
|
| + // the newline itself, which only happens between block statements. So,
|
| + // the after are handled explicitly here.
|
| + for (const auto& c : stmt->comments()->after()) {
|
| + TrimAndPrintToken(c);
|
| + Newline();
|
| + }
|
| + }
|
| + if (i < block->statements().size() - 1)
|
| + Newline();
|
| + ++i;
|
| + }
|
| +
|
| + if (block->comments()) {
|
| + for (const auto& c : block->comments()->after()) {
|
| + TrimAndPrintToken(c);
|
| + Newline();
|
| + }
|
| + }
|
| +}
|
| +
|
| +Printer::ExprStyle Printer::Expr(const ParseNode* root) {
|
| + ExprStyle result = kExprStyleRegular;
|
| + if (root->comments()) {
|
| + if (!root->comments()->before().empty()) {
|
| + Trim();
|
| + // If there's already other text on the line, start a new line.
|
| + if (CurrentColumn() > 0)
|
| + Print("\n");
|
| + // We're printing a line comment, so we need to be at the current margin.
|
| + PrintMargin();
|
| + for (const auto& c : root->comments()->before()) {
|
| + TrimAndPrintToken(c);
|
| + Newline();
|
| + }
|
| + }
|
| + }
|
| +
|
| + if (root->AsAccessor()) {
|
| + Print("TODO(scottmg): AccessorNode");
|
| + } else if (const BinaryOpNode* binop = root->AsBinaryOp()) {
|
| + // TODO(scottmg): Lots to do here for complex if expressions: reflowing,
|
| + // parenthesizing, etc.
|
| + Expr(binop->left());
|
| + Print(" ");
|
| + Print(binop->op().value());
|
| + Print(" ");
|
| + Expr(binop->right());
|
| + } else if (const BlockNode* block = root->AsBlock()) {
|
| + Sequence(kSequenceStyleBlock, block->statements());
|
| + } else if (const ConditionNode* condition = root->AsConditionNode()) {
|
| + Print("if (");
|
| + Expr(condition->condition());
|
| + Print(") {");
|
| + margin_ += kIndentSize;
|
| + Newline();
|
| + Block(condition->if_true());
|
| + margin_ -= kIndentSize;
|
| + Trim();
|
| + PrintMargin();
|
| + Print("}");
|
| + if (condition->if_false()) {
|
| + Print(" else ");
|
| + // If it's a block it's a bare 'else', otherwise it's an 'else if'. See
|
| + // ConditionNode::Execute.
|
| + bool is_else_if = condition->if_false()->AsBlock() == NULL;
|
| + if (is_else_if) {
|
| + Expr(condition->if_false());
|
| + } else {
|
| + Print("{");
|
| + margin_ += kIndentSize;
|
| + Newline();
|
| + Block(condition->if_false());
|
| + margin_ -= kIndentSize;
|
| + Trim();
|
| + PrintMargin();
|
| + Print("}");
|
| + }
|
| + }
|
| + } else if (const FunctionCallNode* func_call = root->AsFunctionCall()) {
|
| + Print(func_call->function().value());
|
| + Sequence(kSequenceStyleFunctionCall, func_call->args()->contents());
|
| + Print(" {");
|
| + margin_ += kIndentSize;
|
| + Newline();
|
| + Block(func_call->block());
|
| + margin_ -= kIndentSize;
|
| + Trim();
|
| + PrintMargin();
|
| + Print("}");
|
| + } else if (const IdentifierNode* identifier = root->AsIdentifier()) {
|
| + Print(identifier->value().value());
|
| + } else if (const ListNode* list = root->AsList()) {
|
| + Sequence(kSequenceStyleList, list->contents());
|
| + } else if (const LiteralNode* literal = root->AsLiteral()) {
|
| + // TODO(scottmg): Quoting?
|
| + Print(literal->value().value());
|
| + } else if (const UnaryOpNode* unaryop = root->AsUnaryOp()) {
|
| + Print(unaryop->op().value());
|
| + Expr(unaryop->operand());
|
| + } else if (const BlockCommentNode* block_comment = root->AsBlockComment()) {
|
| + Print(block_comment->comment().value());
|
| + result = kExprStyleComment;
|
| + } else {
|
| + CHECK(false) << "Unhandled case in Expr.";
|
| + }
|
| +
|
| + // Defer any end of line comment until we reach the newline.
|
| + if (root->comments() && !root->comments()->suffix().empty()) {
|
| + std::copy(root->comments()->suffix().begin(),
|
| + root->comments()->suffix().end(),
|
| + std::back_inserter(comments_));
|
| + }
|
| +
|
| + return result;
|
| +}
|
| +
|
| +template <class PARSENODE>
|
| +void Printer::Sequence(SequenceStyle style,
|
| + const std::vector<PARSENODE*>& list) {
|
| + bool force_multiline = false;
|
| + if (style == kSequenceStyleFunctionCall)
|
| + Print("(");
|
| + else if (style == kSequenceStyleList)
|
| + Print("[");
|
| +
|
| + if (style == kSequenceStyleBlock)
|
| + force_multiline = true;
|
| +
|
| + // If there's before line comments, make sure we have a place to put them.
|
| + for (const auto& i : list) {
|
| + if (i->comments() && !i->comments()->before().empty())
|
| + force_multiline = true;
|
| + }
|
| +
|
| + if (list.size() == 0 && !force_multiline) {
|
| + // No elements, and not forcing newlines, print nothing.
|
| + } else if (list.size() == 1 && !force_multiline) {
|
| + if (style != kSequenceStyleFunctionCall)
|
| + Print(" ");
|
| + Expr(list[0]);
|
| + CHECK(list[0]->comments()->after().empty());
|
| + if (style != kSequenceStyleFunctionCall)
|
| + Print(" ");
|
| + } else {
|
| + margin_ += kIndentSize;
|
| + size_t i = 0;
|
| + for (const auto& x : list) {
|
| + Newline();
|
| + ExprStyle expr_style = Expr(x);
|
| + CHECK(x->comments()->after().empty());
|
| + if (i < list.size() - 1 || style == kSequenceStyleList) {
|
| + if (expr_style == kExprStyleRegular)
|
| + Print(",");
|
| + else
|
| + Newline();
|
| + }
|
| + ++i;
|
| + }
|
| +
|
| + margin_ -= kIndentSize;
|
| + Newline();
|
| + }
|
| +
|
| + if (style == kSequenceStyleFunctionCall)
|
| + Print(")");
|
| + else if (style == kSequenceStyleList)
|
| + Print("]");
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +bool FormatFileToString(const std::string& input_filename,
|
| + bool dump_tree,
|
| + std::string* output) {
|
| + Setup setup;
|
| + Err err;
|
| + SourceFile input_file(input_filename);
|
| + const ParseNode* parse_node =
|
| + setup.scheduler().input_file_manager()->SyncLoadFile(
|
| + LocationRange(), &setup.build_settings(), input_file, &err);
|
| + if (err.has_error()) {
|
| + err.PrintToStdout();
|
| + return false;
|
| + }
|
| + if (dump_tree) {
|
| + std::ostringstream os;
|
| + parse_node->Print(os, 0);
|
| + printf("----------------------\n");
|
| + printf("-- PARSE TREE --------\n");
|
| + printf("----------------------\n");
|
| + printf("%s", os.str().c_str());
|
| + printf("----------------------\n");
|
| + }
|
| + Printer pr;
|
| + pr.Block(parse_node);
|
| + *output = pr.String();
|
| + return true;
|
| +}
|
| +
|
| +int RunFormat(const std::vector<std::string>& args) {
|
| + // TODO(scottmg): Eventually, this should be a list/spec of files, and they
|
| + // should all be done in parallel and in-place. For now, we don't want to
|
| + // overwrite good data with mistakenly reformatted stuff, so we just simply
|
| + // print the formatted output to stdout.
|
| + if (args.size() != 1) {
|
| + Err(Location(), "Expecting exactly one argument, see `gn help format`.\n")
|
| + .PrintToStdout();
|
| + return 1;
|
| + }
|
| +
|
| + bool dump_tree =
|
| + base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDumpTree);
|
| +
|
| + std::string input_name = args[0];
|
| + if (input_name[0] != '/') {
|
| + std::replace(input_name.begin(), input_name.end(), '\\', '/');
|
| + input_name = "//" + input_name;
|
| + }
|
| + std::string output_string;
|
| + if (FormatFileToString(input_name, dump_tree, &output_string)) {
|
| + printf("%s", output_string.c_str());
|
| + }
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +} // namespace commands
|
|
|