GN: Add the ability to trim whitespace from script results.
Adds an optional "trim" mode that can trim whitespace from the beginnings and endings of script results when converting them to GN values.
This also adds default arguments for exec_script to simplify calling in cases where the arguments are not needed.
R=bbudge@chromium.org
Review URL: https://codereview.chromium.org/129043005
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 9753797060defb83611cd49937f2891086105792
diff --git a/tools/gn/function_exec_script.cc b/tools/gn/function_exec_script.cc
index 9cbb2dd..2109522 100644
--- a/tools/gn/function_exec_script.cc
+++ b/tools/gn/function_exec_script.cc
@@ -236,8 +236,10 @@
const char kExecScript_Help[] =
"exec_script: Synchronously run a script and return the output.\n"
"\n"
- " exec_script(filename, arguments, input_conversion,\n"
- " [file_dependencies])\n"
+ " exec_script(filename,\n"
+ " arguments = [],\n"
+ " input_conversion = \"\",\n"
+ " file_dependencies = [])\n"
"\n"
" Runs the given script, returning the stdout of the script. The build\n"
" generation will fail if the script does not exist or returns a nonzero\n"
@@ -256,11 +258,15 @@
"\n"
" arguments:\n"
" A list of strings to be passed to the script as arguments.\n"
+ " May be unspecified or the empty list which means no arguments.\n"
"\n"
" input_conversion:\n"
" Controls how the file is read and parsed.\n"
" See \"gn help input_conversion\".\n"
"\n"
+ " If unspecified, defaults to the empty string which causes the\n"
+ " script result to be discarded. exec script will return None.\n"
+ "\n"
" dependencies:\n"
" (Optional) A list of files that this script reads or otherwise\n"
" depends on. These dependencies will be added to the build result\n"
@@ -274,15 +280,19 @@
"\n"
" all_lines = exec_script(\"myscript.py\", [some_input], \"list lines\",\n"
" [ rebase_path(\"data_file.txt\", \".\","
- " root_build_dir) ])\n";
+ " root_build_dir) ])\n"
+ "\n"
+ " # This example just calls the script with no arguments and discards\n"
+ " # the result.\n"
+ " exec_script(\"//foo/bar/myscript.py\")\n";
Value RunExecScript(Scope* scope,
const FunctionCallNode* function,
const std::vector<Value>& args,
Err* err) {
- if (args.size() != 3 && args.size() != 4) {
+ if (args.size() < 1 || args.size() > 4) {
*err = Err(function->function(), "Wrong number of arguments to exec_script",
- "I expected three or four arguments.");
+ "I expected between one and four arguments.");
return Value();
}
@@ -327,13 +337,16 @@
CommandLine cmdline(python_path);
cmdline.AppendArgPath(script_path);
- const Value& script_args = args[1];
- if (!script_args.VerifyTypeIs(Value::LIST, err))
- return Value();
- for (size_t i = 0; i < script_args.list_value().size(); i++) {
- if (!script_args.list_value()[i].VerifyTypeIs(Value::STRING, err))
+ if (args.size() >= 2) {
+ // Optional command-line arguments to the script.
+ const Value& script_args = args[1];
+ if (!script_args.VerifyTypeIs(Value::LIST, err))
return Value();
- cmdline.AppendArg(script_args.list_value()[i].string_value());
+ for (size_t i = 0; i < script_args.list_value().size(); i++) {
+ if (!script_args.list_value()[i].VerifyTypeIs(Value::STRING, err))
+ return Value();
+ cmdline.AppendArg(script_args.list_value()[i].string_value());
+ }
}
// Log command line for debugging help.
@@ -394,7 +407,9 @@
return Value();
}
- return ConvertInputToValue(output, function, args[2], err);
+ // Default to None value for the input conversion if unspecified.
+ return ConvertInputToValue(output, function,
+ args.size() >= 3 ? args[2] : Value(), err);
}
} // namespace functions
diff --git a/tools/gn/function_read_file.cc b/tools/gn/function_read_file.cc
index 83973d3..17197f5 100644
--- a/tools/gn/function_read_file.cc
+++ b/tools/gn/function_read_file.cc
@@ -20,7 +20,7 @@
const char kReadFile_Help[] =
"read_file: Read a file into a variable.\n"
"\n"
- " read_file(filename, how_to_read)\n"
+ " read_file(filename, input_conversion)\n"
"\n"
" Whitespace will be trimmed from the end of the file. Throws an error\n"
" if the file can not be opened.\n"
diff --git a/tools/gn/input_conversion.cc b/tools/gn/input_conversion.cc
index 0f17676..c22789a 100644
--- a/tools/gn/input_conversion.cc
+++ b/tools/gn/input_conversion.cc
@@ -105,9 +105,10 @@
std::vector<std::string> as_lines;
base::SplitString(input, '\n', &as_lines);
- // Trim empty lines from the end.
- // Do we want to make this configurable?
- while (!as_lines.empty() && as_lines[as_lines.size() - 1].empty())
+ // 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());
@@ -116,6 +117,41 @@
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 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 (StartsWithASCII(input_conversion, kTrimPrefix, true)) {
+ std::string trimmed;
+ TrimWhitespaceASCII(input, TRIM_ALL, &trimmed);
+
+ // Remove "trim" prefix from the input conversion and re-run.
+ return DoConvertInputToValue(
+ trimmed, origin, original_input_conversion,
+ input_conversion.substr(arraysize(kTrimPrefix) - 1), err);
+ }
+
+ if (input_conversion == "value")
+ return ParseString(input, origin, err);
+ if (input_conversion == "string")
+ return Value(origin, input);
+ if (input_conversion == "list lines")
+ return ParseList(input, 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[] =
@@ -125,10 +161,16 @@
" 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. Empty newlines\n"
- " will be trimmed from the trailing end of the returned list.\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"
" \"value\"\n"
" Parse the input as if it was a literal rvalue in a buildfile.\n"
@@ -143,24 +185,26 @@
" which will produce an error if assigned to a variable.\n"
"\n"
" \"string\"\n"
- " Return the file contents into a single string.\n";
+ " Return the file contents into a single string.\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 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();
- const std::string& input_conversion = input_conversion_value.string_value();
-
- if (input_conversion == "value")
- return ParseString(input, origin, err);
- if (input_conversion == "string")
- return Value(origin, input);
- if (input_conversion == "list lines")
- return ParseList(input, origin, err);
-
- *err = Err(input_conversion_value, "Not a valid read file mode.",
- "Have you considered a career in retail?");
- return Value();
+ return DoConvertInputToValue(input, origin, input_conversion_value,
+ input_conversion_value.string_value(), err);
}
diff --git a/tools/gn/input_conversion_unittest.cc b/tools/gn/input_conversion_unittest.cc
index 59c0123..0e35575 100644
--- a/tools/gn/input_conversion_unittest.cc
+++ b/tools/gn/input_conversion_unittest.cc
@@ -14,19 +14,35 @@
EXPECT_FALSE(err.has_error());
EXPECT_EQ(Value::STRING, result.type());
EXPECT_EQ(input, result.string_value());
+
+ // Test with trimming.
+ result = ConvertInputToValue(input, NULL, Value(NULL, "trim string"), &err);
+ EXPECT_FALSE(err.has_error());
+ EXPECT_EQ(Value::STRING, result.type());
+ EXPECT_EQ("foo bar", result.string_value());
}
TEST(InputConversion, ListLines) {
Err err;
- std::string input("\nfoo\nbar \n");
+ std::string input("\nfoo\nbar \n\n");
Value result = ConvertInputToValue(input, NULL, Value(NULL, "list lines"),
&err);
EXPECT_FALSE(err.has_error());
EXPECT_EQ(Value::LIST, result.type());
- ASSERT_EQ(3u, result.list_value().size());
+ ASSERT_EQ(4u, result.list_value().size());
EXPECT_EQ("", result.list_value()[0].string_value());
EXPECT_EQ("foo", result.list_value()[1].string_value());
EXPECT_EQ("bar", result.list_value()[2].string_value());
+ EXPECT_EQ("", result.list_value()[3].string_value());
+
+ // Test with trimming.
+ result = ConvertInputToValue(input, NULL, Value(NULL, "trim list lines"),
+ &err);
+ EXPECT_FALSE(err.has_error());
+ EXPECT_EQ(Value::LIST, result.type());
+ ASSERT_EQ(2u, result.list_value().size());
+ EXPECT_EQ("foo", result.list_value()[0].string_value());
+ EXPECT_EQ("bar", result.list_value()[1].string_value());
}
TEST(InputConversion, ValueString) {
@@ -60,7 +76,9 @@
TEST(InputConversion, ValueEmpty) {
Err err;
- ConvertInputToValue("", NULL, Value(NULL, "value"), &err);
+ Value result = ConvertInputToValue("", NULL, Value(NULL, "value"), &err);
+ EXPECT_FALSE(err.has_error());
+ EXPECT_EQ(Value::NONE, result.type());
}
TEST(InputConversion, ValueError) {
@@ -79,3 +97,16 @@
result = ConvertInputToValue(input, NULL, Value(NULL, "value"), &err);
EXPECT_TRUE(err.has_error());
}
+
+// Passing none or the empty string for input conversion should ignore the
+// result.
+TEST(InputConversion, Ignore) {
+ Err err;
+ Value result = ConvertInputToValue("foo", NULL, Value(), &err);
+ EXPECT_FALSE(err.has_error());
+ EXPECT_EQ(Value::NONE, result.type());
+
+ result = ConvertInputToValue("foo", NULL, Value(NULL, ""), &err);
+ EXPECT_FALSE(err.has_error());
+ EXPECT_EQ(Value::NONE, result.type());
+}