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 |