blob: b43ee121aa2521e66540ec634c958d33bc582c27 [file] [log] [blame]
// 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/input_conversion.h"
#include <utility>
#include "base/macros.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "tools/gn/build_settings.h"
#include "tools/gn/err.h"
#include "tools/gn/input_file.h"
#include "tools/gn/label.h"
#include "tools/gn/parse_tree.h"
#include "tools/gn/parser.h"
#include "tools/gn/scheduler.h"
#include "tools/gn/scope.h"
#include "tools/gn/settings.h"
#include "tools/gn/tokenizer.h"
#include "tools/gn/value.h"
namespace {
enum ValueOrScope {
PARSE_VALUE, // Treat the input as an expression.
PARSE_SCOPE, // Treat the input as code and return the resulting scope.
};
// Sets the origin of the value and any nested values with the given node.
Value ParseValueOrScope(const Settings* settings,
const std::string& input,
ValueOrScope what,
const ParseNode* origin,
Err* err) {
// The memory for these will be kept around by the input file manager
// so the origin parse nodes for the values will be preserved.
InputFile* input_file;
std::vector<Token>* tokens;
scoped_ptr<ParseNode>* parse_root_ptr;
g_scheduler->input_file_manager()->AddDynamicInput(
SourceFile(), &input_file, &tokens, &parse_root_ptr);
input_file->SetContents(input);
if (origin) {
// This description will be the blame for any error messages caused by
// script parsing or if a value is blamed. It will say
// "Error at <...>:line:char" so here we try to make a string for <...>
// that reads well in this context.
input_file->set_friendly_name(
"dynamically parsed input that " +
origin->GetRange().begin().Describe(true) +
" loaded ");
} else {
input_file->set_friendly_name("dynamic input");
}
*tokens = Tokenizer::Tokenize(input_file, err);
if (err->has_error())
return Value();
// Parse the file according to what we're looking for.
if (what == PARSE_VALUE)
*parse_root_ptr = Parser::ParseValue(*tokens, err);
else
*parse_root_ptr = Parser::Parse(*tokens, err); // Will return a Block.
if (err->has_error())
return Value();
ParseNode* parse_root = parse_root_ptr->get(); // For nicer syntax below.
// It's valid for the result to be a null pointer, this just means that the
// script returned nothing.
if (!parse_root)
return Value();
scoped_ptr<Scope> scope(new Scope(settings));
Value result = parse_root->Execute(scope.get(), err);
if (err->has_error())
return Value();
// When we want the result as a scope, the result is actually the scope
// we made, rather than the result of running the block (which will be empty).
if (what == PARSE_SCOPE) {
DCHECK(result.type() == Value::NONE);
result = Value(origin, std::move(scope));
}
return result;
}
Value ParseList(const std::string& input, const ParseNode* origin, Err* err) {
Value ret(origin, Value::LIST);
std::vector<std::string> as_lines = base::SplitString(
input, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
// Trim one empty line from the end since the last line might end in a
// newline. If the user wants more trimming, they'll specify "trim" in the
// input conversion options.
if (!as_lines.empty() && as_lines[as_lines.size() - 1].empty())
as_lines.resize(as_lines.size() - 1);
ret.list_value().reserve(as_lines.size());
for (const auto& line : as_lines)
ret.list_value().push_back(Value(origin, line));
return ret;
}
// Backend for ConvertInputToValue, this takes the extracted string for the
// input conversion so we can recursively call ourselves to handle the optional
// "trim" prefix. This original value is also kept for the purposes of throwing
// errors.
Value DoConvertInputToValue(const Settings* settings,
const std::string& input,
const ParseNode* origin,
const Value& original_input_conversion,
const std::string& input_conversion,
Err* err) {
if (input_conversion.empty())
return Value(); // Empty string means discard the result.
const char kTrimPrefix[] = "trim ";
if (base::StartsWith(input_conversion, kTrimPrefix,
base::CompareCase::SENSITIVE)) {
std::string trimmed;
base::TrimWhitespaceASCII(input, base::TRIM_ALL, &trimmed);
// Remove "trim" prefix from the input conversion and re-run.
return DoConvertInputToValue(
settings, trimmed, origin, original_input_conversion,
input_conversion.substr(arraysize(kTrimPrefix) - 1), err);
}
if (input_conversion == "value")
return ParseValueOrScope(settings, input, PARSE_VALUE, origin, err);
if (input_conversion == "string")
return Value(origin, input);
if (input_conversion == "list lines")
return ParseList(input, origin, err);
if (input_conversion == "scope")
return ParseValueOrScope(settings, input, PARSE_SCOPE, origin, err);
*err = Err(original_input_conversion, "Not a valid input_conversion.",
"Have you considered a career in retail?");
return Value();
}
} // namespace
extern const char kInputConversion_Help[] =
"input_conversion: Specifies how to transform input to a variable.\n"
"\n"
" input_conversion is an argument to read_file and exec_script that\n"
" specifies how the result of the read operation should be converted\n"
" into a variable.\n"
"\n"
" \"\" (the default)\n"
" Discard the result and return None.\n"
"\n"
" \"list lines\"\n"
" Return the file contents as a list, with a string for each line.\n"
" The newlines will not be present in the result. The last line may\n"
" or may not end in a newline.\n"
"\n"
" After splitting, each individual line will be trimmed of\n"
" whitespace on both ends.\n"
"\n"
" \"scope\"\n"
" Execute the block as GN code and return a scope with the\n"
" resulting values in it. If the input was:\n"
" a = [ \"hello.cc\", \"world.cc\" ]\n"
" b = 26\n"
" and you read the result into a variable named \"val\", then you\n"
" could access contents the \".\" operator on \"val\":\n"
" sources = val.a\n"
" some_count = val.b\n"
"\n"
" \"string\"\n"
" Return the file contents into a single string.\n"
"\n"
" \"value\"\n"
" Parse the input as if it was a literal rvalue in a buildfile.\n"
" Examples of typical program output using this mode:\n"
" [ \"foo\", \"bar\" ] (result will be a list)\n"
" or\n"
" \"foo bar\" (result will be a string)\n"
" or\n"
" 5 (result will be an integer)\n"
"\n"
" Note that if the input is empty, the result will be a null value\n"
" which will produce an error if assigned to a variable.\n"
"\n"
" \"trim ...\"\n"
" Prefixing any of the other transformations with the word \"trim\"\n"
" will result in whitespace being trimmed from the beginning and end\n"
" of the result before processing.\n"
"\n"
" Examples: \"trim string\" or \"trim list lines\"\n"
"\n"
" Note that \"trim value\" is useless because the value parser skips\n"
" whitespace anyway.\n";
Value ConvertInputToValue(const Settings* settings,
const std::string& input,
const ParseNode* origin,
const Value& input_conversion_value,
Err* err) {
if (input_conversion_value.type() == Value::NONE)
return Value(); // Allow null inputs to mean discard the result.
if (!input_conversion_value.VerifyTypeIs(Value::STRING, err))
return Value();
return DoConvertInputToValue(settings, input, origin, input_conversion_value,
input_conversion_value.string_value(), err);
}