[output_conversion] Add output_conversion options

Adding output_conversion as a variable on write_file. Implemented to
reverse input_conversion (on read_file/exec_script), this allows for
writing Values out as strings (format determined by the conversion
parameter).

Bug: gn:4
Change-Id: I54396dad48727c2c3882254c6af2060c8ef3bcb8
Reviewed-on: https://gn-review.googlesource.com/2520
Commit-Queue: Julie Hockett <juliehockett@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/build/gen.py b/build/gen.py
index 2dccbbb..7d437c9 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -483,6 +483,7 @@
         'tools/gn/ninja_utils.cc',
         'tools/gn/ninja_writer.cc',
         'tools/gn/operators.cc',
+        'tools/gn/output_conversion.cc',
         'tools/gn/output_file.cc',
         'tools/gn/parse_node_value_adapter.cc',
         'tools/gn/parser.cc',
@@ -578,6 +579,7 @@
         'tools/gn/ninja_target_writer_unittest.cc',
         'tools/gn/ninja_toolchain_writer_unittest.cc',
         'tools/gn/operators_unittest.cc',
+        'tools/gn/output_conversion_unittest.cc',
         'tools/gn/parse_tree_unittest.cc',
         'tools/gn/parser_unittest.cc',
         'tools/gn/path_output_unittest.cc',
diff --git a/docs/reference.md b/docs/reference.md
index 98a3fdd..48017d5 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -142,6 +142,7 @@
     *   [labels: About labels.](#labels)
     *   [ninja_rules: How Ninja build rules are named.](#ninja_rules)
     *   [nogncheck: Annotating includes for checking.](#nogncheck)
+    *   [output_conversion: Specifies how to transform a value to output.](#output_conversion)
     *   [runtime_deps: How runtime dependency computation works.](#runtime_deps)
     *   [source_expansion: Map sources to outputs for scripts.](#source_expansion)
     *   [switches: Show available command-line switches.](#switches)
@@ -3391,7 +3392,7 @@
 ### <a name="write_file"></a>**write_file**: Write a file to disk.
 
 ```
-  write_file(filename, data)
+  write_file(filename, data, output_conversion = "")
 
   If data is a list, the list will be written one-item-per-line with no quoting
   or brackets.
@@ -3403,9 +3404,6 @@
   One use for write_file is to write a list of inputs to an script that might
   be too long for the command line. However, it is preferable to use response
   files for this purpose. See "gn help response_file_contents".
-
-  TODO(brettw) we probably need an optional third argument to control list
-  formatting.
 ```
 
 #### **Arguments**
@@ -3416,6 +3414,9 @@
 
   data
       The list or string to write.
+
+  output_conversion
+    Controls how the output is written. See "gn help output_conversion".
 ```
 ## <a name="predefined_variables"></a>Built-in predefined variables
 
@@ -6027,51 +6028,87 @@
     myvalues.foo += 2
     empty_scope.new_thing = [ 1, 2, 3 ]
 ```
-### <a name="input_conversion"></a>**input_conversion**: Specifies how to transform input to a variable.
+### <a name="input_conversion"></a>**Input and output conversions are arguments to file and process functions**
+#### **that specify how to convert data to or from external formats. The possible**
+#### **values for parameters specifying conversions are**:
 
 ```
-  input_conversion is an argument to read_file and exec_script that specifies
-  how the result of the read operation should be converted into a variable.
-
   "" (the default)
-      Discard the result and return None.
+      input: Discard the result and return None.
+
+      output: If value is a list, then "list lines"; otherwise "value".
 
   "list lines"
-      Return the file contents as a list, with a string for each line. The
-      newlines will not be present in the result. The last line may or may not
-      end in a newline.
+      input:
+        Return the file contents as a list, with a string for each line. The
+        newlines will not be present in the result. The last line may or may not
+        end in a newline.
 
-      After splitting, each individual line will be trimmed of whitespace on
-      both ends.
+        After splitting, each individual line will be trimmed of whitespace on
+        both ends.
+
+      output:
+        Renders the value contents as a list, with a string for each line. The
+        newlines will not be present in the result. The last line will end in with
+        a newline.
 
   "scope"
-      Execute the block as GN code and return a scope with the resulting values
-      in it. If the input was:
-        a = [ "hello.cc", "world.cc" ]
-        b = 26
-      and you read the result into a variable named "val", then you could
-      access contents the "." operator on "val":
-        sources = val.a
-        some_count = val.b
+      input:
+        Execute the block as GN code and return a scope with the resulting values
+        in it. If the input was:
+          a = [ "hello.cc", "world.cc" ]
+          b = 26
+        and you read the result into a variable named "val", then you could
+        access contents the "." operator on "val":
+          sources = val.a
+          some_count = val.b
+
+      output:
+        Renders the value contents as a GN code block, reversing the input
+        result above.
 
   "string"
-      Return the file contents into a single string.
+      input: Return the file contents into a single string.
+
+      output:
+        Render the value contents into a single string. The output is:
+        a string renders with quotes, e.g. "str"
+        an integer renders as a stringified integer, e.g. "6"
+        a boolean renders as the associated string, e.g. "true"
+        a list renders as a representation of its contents, e.g. "[\"str\", 6]"
+        a scope renders as a GN code block of its values. If the Value was:
+            Value val;
+            val.a = [ "hello.cc", "world.cc" ];
+            val.b = 26
+          the resulting output would be:
+            "{
+                a = [ \"hello.cc\", \"world.cc\" ]
+                b = 26
+            }"
 
   "value"
-      Parse the input as if it was a literal rvalue in a buildfile. Examples of
-      typical program output using this mode:
-        [ "foo", "bar" ]     (result will be a list)
-      or
-        "foo bar"            (result will be a string)
-      or
-        5                    (result will be an integer)
+      input:
+        Parse the input as if it was a literal rvalue in a buildfile. Examples of
+        typical program output using this mode:
+          [ "foo", "bar" ]     (result will be a list)
+        or
+          "foo bar"            (result will be a string)
+        or
+          5                    (result will be an integer)
 
-      Note that if the input is empty, the result will be a null value which
-      will produce an error if assigned to a variable.
+        Note that if the input is empty, the result will be a null value which
+        will produce an error if assigned to a variable.
+
+      output:
+        Render the value contents as a literal rvalue. Strings render with escaped
+        quotes.
 
   "json"
-      Parse the input as a JSON and convert it to equivalent GN rvalue. The data
-      type mapping is:
+      input: Parse the input as a JSON and convert it to equivalent GN rvalue.
+
+      output: Convert the Value to equivalent JSON value.
+
+      The data type mapping is:
         a string in JSON maps to string in GN
         an integer in JSON maps to integer in GN
         a float in JSON is unsupported and will result in an error
@@ -6080,10 +6117,10 @@
         a boolean in JSON maps to boolean in GN
         a null in JSON is unsupported and will result in an error
 
-      Nota that the dictionary keys have to be valid GN identifiers otherwise
-      they will produce an error.
+      Nota that the input dictionary keys have to be valid GN identifiers
+      otherwise they will produce an error.
 
-  "trim ..."
+  "trim ..." (input only)
       Prefixing any of the other transformations with the word "trim" will
       result in whitespace being trimmed from the beginning and end of the
       result before processing.
@@ -6275,6 +6312,108 @@
   advice on fixing problems. Targets can also opt-out of checking, see
   "gn help check_includes".
 ```
+### <a name="output_conversion"></a>**Input and output conversions are arguments to file and process functions**
+#### **that specify how to convert data to or from external formats. The possible**
+#### **values for parameters specifying conversions are**:
+
+```
+  "" (the default)
+      input: Discard the result and return None.
+
+      output: If value is a list, then "list lines"; otherwise "value".
+
+  "list lines"
+      input:
+        Return the file contents as a list, with a string for each line. The
+        newlines will not be present in the result. The last line may or may not
+        end in a newline.
+
+        After splitting, each individual line will be trimmed of whitespace on
+        both ends.
+
+      output:
+        Renders the value contents as a list, with a string for each line. The
+        newlines will not be present in the result. The last line will end in with
+        a newline.
+
+  "scope"
+      input:
+        Execute the block as GN code and return a scope with the resulting values
+        in it. If the input was:
+          a = [ "hello.cc", "world.cc" ]
+          b = 26
+        and you read the result into a variable named "val", then you could
+        access contents the "." operator on "val":
+          sources = val.a
+          some_count = val.b
+
+      output:
+        Renders the value contents as a GN code block, reversing the input
+        result above.
+
+  "string"
+      input: Return the file contents into a single string.
+
+      output:
+        Render the value contents into a single string. The output is:
+        a string renders with quotes, e.g. "str"
+        an integer renders as a stringified integer, e.g. "6"
+        a boolean renders as the associated string, e.g. "true"
+        a list renders as a representation of its contents, e.g. "[\"str\", 6]"
+        a scope renders as a GN code block of its values. If the Value was:
+            Value val;
+            val.a = [ "hello.cc", "world.cc" ];
+            val.b = 26
+          the resulting output would be:
+            "{
+                a = [ \"hello.cc\", \"world.cc\" ]
+                b = 26
+            }"
+
+  "value"
+      input:
+        Parse the input as if it was a literal rvalue in a buildfile. Examples of
+        typical program output using this mode:
+          [ "foo", "bar" ]     (result will be a list)
+        or
+          "foo bar"            (result will be a string)
+        or
+          5                    (result will be an integer)
+
+        Note that if the input is empty, the result will be a null value which
+        will produce an error if assigned to a variable.
+
+      output:
+        Render the value contents as a literal rvalue. Strings render with escaped
+        quotes.
+
+  "json"
+      input: Parse the input as a JSON and convert it to equivalent GN rvalue.
+
+      output: Convert the Value to equivalent JSON value.
+
+      The data type mapping is:
+        a string in JSON maps to string in GN
+        an integer in JSON maps to integer in GN
+        a float in JSON is unsupported and will result in an error
+        an object in JSON maps to scope in GN
+        an array in JSON maps to list in GN
+        a boolean in JSON maps to boolean in GN
+        a null in JSON is unsupported and will result in an error
+
+      Nota that the input dictionary keys have to be valid GN identifiers
+      otherwise they will produce an error.
+
+  "trim ..." (input only)
+      Prefixing any of the other transformations with the word "trim" will
+      result in whitespace being trimmed from the beginning and end of the
+      result before processing.
+
+      Examples: "trim string" or "trim list lines"
+
+      Note that "trim value" is useless because the value parser skips
+      whitespace anyway.
+```
 ### <a name="runtime_deps"></a>**Runtime dependencies**
 
 ```
diff --git a/tools/gn/command_help.cc b/tools/gn/command_help.cc
index 9b9d428..cff2052 100644
--- a/tools/gn/command_help.cc
+++ b/tools/gn/command_help.cc
@@ -14,6 +14,7 @@
 #include "tools/gn/label.h"
 #include "tools/gn/label_pattern.h"
 #include "tools/gn/ninja_build_writer.h"
+#include "tools/gn/output_conversion.h"
 #include "tools/gn/parser.h"
 #include "tools/gn/runtime_deps.h"
 #include "tools/gn/setup.h"
@@ -71,6 +72,8 @@
   PrintShortHelp("labels: About labels.");
   PrintShortHelp("ninja_rules: How Ninja build rules are named.");
   PrintShortHelp("nogncheck: Annotating includes for checking.");
+  PrintShortHelp(
+      "output_conversion: Specifies how to transform a value to output.");
   PrintShortHelp("runtime_deps: How runtime dependency computation works.");
   PrintShortHelp("source_expansion: Map sources to outputs for scripts.");
   PrintShortHelp("switches: Show available command-line switches.");
@@ -158,11 +161,12 @@
   PrintLongHelp(kDotfile_Help, "dotfile");
   PrintLongHelp(kExecution_Help, "execution");
   PrintLongHelp(kGrammar_Help, "grammar");
-  PrintLongHelp(kInputConversion_Help, "input_conversion");
+  PrintLongHelp(kInputOutputConversion_Help, "input_conversion");
   PrintLongHelp(kLabelPattern_Help, "label_pattern");
   PrintLongHelp(kLabels_Help, "labels");
   PrintLongHelp(kNinjaRules_Help, "ninja_rules");
   PrintLongHelp(kNoGnCheck_Help, "nogncheck");
+  PrintLongHelp(kInputOutputConversion_Help, "output_conversion");
   PrintLongHelp(kRuntimeDeps_Help, "runtime_deps");
   PrintLongHelp(kSourceExpansion_Help, "source_expansion");
 
@@ -281,12 +285,15 @@
   random_topics["dotfile"] = []() { PrintLongHelp(kDotfile_Help); };
   random_topics["grammar"] = []() { PrintLongHelp(kGrammar_Help); };
   random_topics["input_conversion"] = []() {
-    PrintLongHelp(kInputConversion_Help);
+    PrintLongHelp(kInputOutputConversion_Help);
   };
   random_topics["label_pattern"] = []() { PrintLongHelp(kLabelPattern_Help); };
   random_topics["labels"] = []() { PrintLongHelp(kLabels_Help); };
   random_topics["ninja_rules"] = []() { PrintLongHelp(kNinjaRules_Help); };
   random_topics["nogncheck"] = []() { PrintLongHelp(kNoGnCheck_Help); };
+  random_topics["output_conversion"] = []() {
+    PrintLongHelp(kInputOutputConversion_Help);
+  };
   random_topics["runtime_deps"] = []() { PrintLongHelp(kRuntimeDeps_Help); };
   random_topics["source_expansion"] = []() {
     PrintLongHelp(kSourceExpansion_Help);
diff --git a/tools/gn/function_write_file.cc b/tools/gn/function_write_file.cc
index 172840c..44e6b16 100644
--- a/tools/gn/function_write_file.cc
+++ b/tools/gn/function_write_file.cc
@@ -13,6 +13,7 @@
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/functions.h"
 #include "tools/gn/input_file.h"
+#include "tools/gn/output_conversion.h"
 #include "tools/gn/parse_tree.h"
 #include "tools/gn/scheduler.h"
 #include "util/build_config.h"
@@ -24,7 +25,7 @@
 const char kWriteFile_Help[] =
     R"(write_file: Write a file to disk.
 
-  write_file(filename, data)
+  write_file(filename, data, output_conversion = "")
 
   If data is a list, the list will be written one-item-per-line with no quoting
   or brackets.
@@ -37,9 +38,6 @@
   be too long for the command line. However, it is preferable to use response
   files for this purpose. See "gn help response_file_contents".
 
-  TODO(brettw) we probably need an optional third argument to control list
-  formatting.
-
 Arguments
 
   filename
@@ -47,15 +45,18 @@
 
   data
       The list or string to write.
+
+  output_conversion
+    Controls how the output is written. See "gn help output_conversion".
 )";
 
 Value RunWriteFile(Scope* scope,
                    const FunctionCallNode* function,
                    const std::vector<Value>& args,
                    Err* err) {
-  if (args.size() != 2) {
+  if (args.size() != 3 && args.size() != 2) {
     *err = Err(function->function(), "Wrong number of arguments to write_file",
-               "I expected two arguments.");
+               "I expected two or three arguments.");
     return Value();
   }
 
@@ -79,15 +80,19 @@
   g_scheduler->AddGenDependency(
       scope->settings()->build_settings()->GetFullPath(source_file));
 
+  // Extract conversion value.
+  Value output_conversion;
+  if (args.size() != 3)
+    output_conversion = Value();
+  else
+    output_conversion = args[2];
+
   // Compute output.
   std::ostringstream contents;
-  if (args[1].type() == Value::LIST) {
-    const std::vector<Value>& list = args[1].list_value();
-    for (const auto& cur : list)
-      contents << cur.ToString(false) << std::endl;
-  } else {
-    contents << args[1].ToString(false);
-  }
+  ConvertValueToOutput(scope->settings(), args[1], output_conversion, contents,
+                       err);
+  if (err->has_error())
+    return Value();
 
   base::FilePath file_path =
       scope->settings()->build_settings()->GetFullPath(source_file);
diff --git a/tools/gn/input_conversion.cc b/tools/gn/input_conversion.cc
index f4a0f74..df58d9d 100644
--- a/tools/gn/input_conversion.cc
+++ b/tools/gn/input_conversion.cc
@@ -240,51 +240,87 @@
 
 }  // namespace
 
-const char kInputConversion_Help[] =
-    R"(input_conversion: Specifies how to transform input to a variable.
-
-  input_conversion is an argument to read_file and exec_script that specifies
-  how the result of the read operation should be converted into a variable.
+const char kInputOutputConversion_Help[] =
+    R"(Input and output conversions are arguments to file and process functions
+that specify how to convert data to or from external formats. The possible
+values for parameters specifying conversions are:
 
   "" (the default)
-      Discard the result and return None.
+      input: Discard the result and return None.
+
+      output: If value is a list, then "list lines"; otherwise "value".
 
   "list lines"
-      Return the file contents as a list, with a string for each line. The
-      newlines will not be present in the result. The last line may or may not
-      end in a newline.
+      input:
+        Return the file contents as a list, with a string for each line. The
+        newlines will not be present in the result. The last line may or may not
+        end in a newline.
 
-      After splitting, each individual line will be trimmed of whitespace on
-      both ends.
+        After splitting, each individual line will be trimmed of whitespace on
+        both ends.
+
+      output:
+        Renders the value contents as a list, with a string for each line. The
+        newlines will not be present in the result. The last line will end in with
+        a newline.
 
   "scope"
-      Execute the block as GN code and return a scope with the resulting values
-      in it. If the input was:
-        a = [ "hello.cc", "world.cc" ]
-        b = 26
-      and you read the result into a variable named "val", then you could
-      access contents the "." operator on "val":
-        sources = val.a
-        some_count = val.b
+      input:
+        Execute the block as GN code and return a scope with the resulting values
+        in it. If the input was:
+          a = [ "hello.cc", "world.cc" ]
+          b = 26
+        and you read the result into a variable named "val", then you could
+        access contents the "." operator on "val":
+          sources = val.a
+          some_count = val.b
+
+      output:
+        Renders the value contents as a GN code block, reversing the input
+        result above.
 
   "string"
-      Return the file contents into a single string.
+      input: Return the file contents into a single string.
+
+      output:
+        Render the value contents into a single string. The output is:
+        a string renders with quotes, e.g. "str"
+        an integer renders as a stringified integer, e.g. "6"
+        a boolean renders as the associated string, e.g. "true"
+        a list renders as a representation of its contents, e.g. "[\"str\", 6]"
+        a scope renders as a GN code block of its values. If the Value was:
+            Value val;
+            val.a = [ "hello.cc", "world.cc" ];
+            val.b = 26
+          the resulting output would be:
+            "{
+                a = [ \"hello.cc\", \"world.cc\" ]
+                b = 26
+            }"
 
   "value"
-      Parse the input as if it was a literal rvalue in a buildfile. Examples of
-      typical program output using this mode:
-        [ "foo", "bar" ]     (result will be a list)
-      or
-        "foo bar"            (result will be a string)
-      or
-        5                    (result will be an integer)
+      input:
+        Parse the input as if it was a literal rvalue in a buildfile. Examples of
+        typical program output using this mode:
+          [ "foo", "bar" ]     (result will be a list)
+        or
+          "foo bar"            (result will be a string)
+        or
+          5                    (result will be an integer)
 
-      Note that if the input is empty, the result will be a null value which
-      will produce an error if assigned to a variable.
+        Note that if the input is empty, the result will be a null value which
+        will produce an error if assigned to a variable.
+
+      output:
+        Render the value contents as a literal rvalue. Strings render with escaped
+        quotes.
 
   "json"
-      Parse the input as a JSON and convert it to equivalent GN rvalue. The data
-      type mapping is:
+      input: Parse the input as a JSON and convert it to equivalent GN rvalue.
+
+      output: Convert the Value to equivalent JSON value.
+
+      The data type mapping is:
         a string in JSON maps to string in GN
         an integer in JSON maps to integer in GN
         a float in JSON is unsupported and will result in an error
@@ -293,10 +329,10 @@
         a boolean in JSON maps to boolean in GN
         a null in JSON is unsupported and will result in an error
 
-      Nota that the dictionary keys have to be valid GN identifiers otherwise
-      they will produce an error.
+      Nota that the input dictionary keys have to be valid GN identifiers
+      otherwise they will produce an error.
 
-  "trim ..."
+  "trim ..." (input only)
       Prefixing any of the other transformations with the word "trim" will
       result in whitespace being trimmed from the beginning and end of the
       result before processing.
diff --git a/tools/gn/input_conversion.h b/tools/gn/input_conversion.h
index e6d5f6e..6e09eb4 100644
--- a/tools/gn/input_conversion.h
+++ b/tools/gn/input_conversion.h
@@ -12,7 +12,7 @@
 class Settings;
 class Value;
 
-extern const char kInputConversion_Help[];
+extern const char kInputOutputConversion_Help[];
 
 // Converts the given input string (is read from a file or output from a
 // script) to a Value. Conversions as specified in the input_conversion string
diff --git a/tools/gn/output_conversion.cc b/tools/gn/output_conversion.cc
new file mode 100644
index 0000000..70c21fa
--- /dev/null
+++ b/tools/gn/output_conversion.cc
@@ -0,0 +1,174 @@
+// Copyright 2018 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/output_conversion.h"
+
+#include "tools/gn/settings.h"
+#include "tools/gn/value.h"
+
+namespace {
+
+void ToString(const Value& output, std::ostream& out) {
+  out << output.ToString(false);
+}
+
+void ToStringQuoted(const Value& output, std::ostream& out) {
+  out << "\"" << output.ToString(false) << "\"";
+}
+
+void Indent(int indent, std::ostream& out) {
+  for (int i = 0; i < indent; ++i)
+    out << "  ";
+}
+
+// Forward declare so it can be used recursively.
+void RenderScopeToJSON(const Value& output, std::ostream& out, int indent);
+
+void RenderListToJSON(const Value& output, std::ostream& out, int indent) {
+  assert(indent > 0);
+  bool first = true;
+  out << "[\n";
+  for (const auto& value : output.list_value()) {
+    if (!first)
+      out << ",\n";
+    Indent(indent, out);
+    if (value.type() == Value::SCOPE)
+      RenderScopeToJSON(value, out, indent + 1);
+    else if (value.type() == Value::LIST)
+      RenderListToJSON(value, out, indent + 1);
+    else
+      out << value.ToString(true);
+    first = false;
+  }
+  out << "\n";
+  Indent(indent - 1, out);
+  out << "]";
+}
+
+void RenderScopeToJSON(const Value& output, std::ostream& out, int indent) {
+  assert(indent > 0);
+  Scope::KeyValueMap scope_values;
+  output.scope_value()->GetCurrentScopeValues(&scope_values);
+  bool first = true;
+  out << "{\n";
+  for (const auto& pair : scope_values) {
+    if (!first)
+      out << ",\n";
+    Indent(indent, out);
+    out << "\"" << pair.first.as_string() << "\": ";
+    if (pair.second.type() == Value::SCOPE)
+      RenderScopeToJSON(pair.second, out, indent + 1);
+    else if (pair.second.type() == Value::LIST)
+      RenderListToJSON(pair.second, out, indent + 1);
+    else
+      out << pair.second.ToString(true);
+    first = false;
+  }
+  out << "\n";
+  Indent(indent - 1, out);
+  out << "}";
+}
+
+void OutputListLines(const Value& output, std::ostream& out) {
+  assert(output.type() == Value::LIST);
+  const std::vector<Value>& list = output.list_value();
+  for (const auto& cur : list)
+    out << cur.ToString(false) << "\n";
+}
+
+void OutputString(const Value& output, std::ostream& out) {
+  if (output.type() == Value::NONE)
+    return;
+  if (output.type() == Value::STRING) {
+    ToString(output, out);
+    return;
+  }
+  ToStringQuoted(output, out);
+}
+
+void OutputValue(const Value& output, std::ostream& out) {
+  if (output.type() == Value::NONE)
+    return;
+  if (output.type() == Value::STRING) {
+    ToStringQuoted(output, out);
+    return;
+  }
+  ToString(output, out);
+}
+
+// The direct Value::ToString call wraps the scope in '{}', which we don't want
+// here for the top-level scope being output.
+void OutputScope(const Value& output, std::ostream& out) {
+  Scope::KeyValueMap scope_values;
+  output.scope_value()->GetCurrentScopeValues(&scope_values);
+  for (const auto& pair : scope_values) {
+    out << "  " << pair.first.as_string() << " = " << pair.second.ToString(true)
+        << "\n";
+  }
+}
+
+void OutputDefault(const Value& output, std::ostream& out) {
+  if (output.type() == Value::LIST)
+    OutputListLines(output, out);
+  else
+    ToString(output, out);
+}
+
+void OutputJSON(const Value& output, std::ostream& out) {
+  if (output.type() == Value::SCOPE) {
+    RenderScopeToJSON(output, out, /*indent=*/1);
+    return;
+  }
+  if (output.type() == Value::LIST) {
+    RenderListToJSON(output, out, /*indent=*/1);
+    return;
+  }
+  ToStringQuoted(output, out);
+}
+
+void DoConvertValueToOutput(const Value& output,
+                            const std::string& output_conversion,
+                            const Value& original_output_conversion,
+                            std::ostream& out,
+                            Err* err) {
+  if (output_conversion == "") {
+    OutputDefault(output, out);
+  } else if (output_conversion == "list lines") {
+    OutputListLines(output, out);
+  } else if (output_conversion == "string") {
+    OutputString(output, out);
+  } else if (output_conversion == "value") {
+    OutputValue(output, out);
+  } else if (output_conversion == "json") {
+    OutputJSON(output, out);
+  } else if (output_conversion == "scope") {
+    if (output.type() != Value::SCOPE) {
+      *err = Err(original_output_conversion, "Not a valid scope.");
+      return;
+    }
+    OutputScope(output, out);
+  } else {
+    // If we make it here, we didn't match any of the valid options.
+    *err = Err(original_output_conversion, "Not a valid output_conversion.",
+               "Run gn help output_conversion to see your options.");
+  }
+}
+
+}  // namespace
+
+void ConvertValueToOutput(const Settings* settings,
+                          const Value& output,
+                          const Value& output_conversion,
+                          std::ostream& out,
+                          Err* err) {
+  if (output_conversion.type() == Value::NONE) {
+    OutputDefault(output, out);
+    return;
+  }
+  if (!output_conversion.VerifyTypeIs(Value::STRING, err))
+    return;
+
+  DoConvertValueToOutput(output, output_conversion.string_value(),
+                         output_conversion, out, err);
+}
diff --git a/tools/gn/output_conversion.h b/tools/gn/output_conversion.h
new file mode 100644
index 0000000..127a6ca
--- /dev/null
+++ b/tools/gn/output_conversion.h
@@ -0,0 +1,26 @@
+// Copyright 2018 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.
+
+#ifndef TOOLS_GN_OUTPUT_CONVERSION_H_
+#define TOOLS_GN_OUTPUT_CONVERSION_H_
+
+#include <iostream>
+#include <string>
+
+class Err;
+class Settings;
+class Value;
+
+// Converts the given input Value to an output string (to be written to a file).
+// Conversions as specified in the output_conversion string will be performed.
+// The given ostream will be used for writing the resulting string.
+//
+// If the conversion string is invalid, the error will be set.
+void ConvertValueToOutput(const Settings* settings,
+                          const Value& output,
+                          const Value& output_conversion_value,
+                          std::ostream& out,
+                          Err* err);
+
+#endif  // TOOLS_GN_OUTPUT_CONVERSION_H_
diff --git a/tools/gn/output_conversion_unittest.cc b/tools/gn/output_conversion_unittest.cc
new file mode 100644
index 0000000..8115796
--- /dev/null
+++ b/tools/gn/output_conversion_unittest.cc
@@ -0,0 +1,351 @@
+// Copyright 2018 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/output_conversion.h"
+
+#include <sstream>
+
+#include "tools/gn/err.h"
+#include "tools/gn/input_conversion.h"
+#include "tools/gn/scope.h"
+#include "tools/gn/template.h"
+#include "tools/gn/test_with_scheduler.h"
+#include "tools/gn/test_with_scope.h"
+#include "tools/gn/value.h"
+#include "util/test/test.h"
+
+namespace {
+
+// InputConversion needs a global scheduler object.
+class OutputConversionTest : public TestWithScheduler {
+ public:
+  OutputConversionTest() = default;
+
+  const Settings* settings() { return setup_.settings(); }
+  Scope* scope() { return setup_.scope(); }
+
+ private:
+  TestWithScope setup_;
+};
+
+}  // namespace
+
+TEST_F(OutputConversionTest, ListLines) {
+  Err err;
+  Value output(nullptr, Value::LIST);
+  output.list_value().push_back(Value(nullptr, ""));
+  output.list_value().push_back(Value(nullptr, "foo"));
+  output.list_value().push_back(Value(nullptr, ""));
+  output.list_value().push_back(Value(nullptr, "bar"));
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "list lines"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ("\nfoo\n\nbar\n", result.str());
+}
+
+TEST_F(OutputConversionTest, String) {
+  Err err;
+  Value output(nullptr, "foo bar");
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "string"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "foo bar");
+}
+
+TEST_F(OutputConversionTest, StringInt) {
+  Err err;
+  Value output(nullptr, int64_t(6));
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "string"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "\"6\"");
+}
+
+TEST_F(OutputConversionTest, StringBool) {
+  Err err;
+  Value output(nullptr, true);
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "string"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "\"true\"");
+}
+
+TEST_F(OutputConversionTest, StringList) {
+  Err err;
+  Value output(nullptr, Value::LIST);
+  output.list_value().push_back(Value(nullptr, "foo"));
+  output.list_value().push_back(Value(nullptr, "bar"));
+  output.list_value().push_back(Value(nullptr, int64_t(6)));
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "string"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "\"[\"foo\", \"bar\", 6]\"");
+}
+
+TEST_F(OutputConversionTest, StringScope) {
+  Err err;
+
+  auto new_scope = std::make_unique<Scope>(settings());
+  // Add some values to the scope.
+  Value value(nullptr, "hello");
+  new_scope->SetValue("v", value, nullptr);
+  base::StringPiece private_var_name("_private");
+  new_scope->SetValue(private_var_name, value, nullptr);
+
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), Value(nullptr, std::move(new_scope)),
+                       Value(nullptr, "string"), result, &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "\"{\n  v = \"hello\"\n  _private = \"hello\"\n}\"");
+}
+
+TEST_F(OutputConversionTest, ValueString) {
+  Err err;
+  Value output(nullptr, "foo bar");
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "value"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "\"foo bar\"");
+}
+
+TEST_F(OutputConversionTest, ValueInt) {
+  Err err;
+  Value output(nullptr, int64_t(6));
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "value"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "6");
+}
+
+TEST_F(OutputConversionTest, ValueBool) {
+  Err err;
+  Value output(nullptr, true);
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "value"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "true");
+}
+
+TEST_F(OutputConversionTest, ValueList) {
+  Err err;
+  Value output(nullptr, Value::LIST);
+  output.list_value().push_back(Value(nullptr, "foo"));
+  output.list_value().push_back(Value(nullptr, "bar"));
+  output.list_value().push_back(Value(nullptr, int64_t(6)));
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "value"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "[\"foo\", \"bar\", 6]");
+}
+
+TEST_F(OutputConversionTest, ValueScope) {
+  Err err;
+
+  auto new_scope = std::make_unique<Scope>(settings());
+  // Add some values to the scope.
+  Value value(nullptr, "hello");
+  new_scope->SetValue("v", value, nullptr);
+  base::StringPiece private_var_name("_private");
+  new_scope->SetValue(private_var_name, value, nullptr);
+
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), Value(nullptr, std::move(new_scope)),
+                       Value(nullptr, "value"), result, &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "{\n  v = \"hello\"\n  _private = \"hello\"\n}");
+}
+
+TEST_F(OutputConversionTest, JSON) {
+  Err err;
+  auto new_scope = std::make_unique<Scope>(settings());
+  // Add some values to the scope.
+  Value a_value(nullptr, "foo");
+  new_scope->SetValue("a", a_value, nullptr);
+  Value b_value(nullptr, int64_t(6));
+  new_scope->SetValue("b", b_value, nullptr);
+
+  auto c_scope = std::make_unique<Scope>(settings());
+  Value e_value(nullptr, Value::LIST);
+  e_value.list_value().push_back(Value(nullptr, "bar"));
+
+  auto e_value_scope = std::make_unique<Scope>(settings());
+  Value f_value(nullptr, "baz");
+  e_value_scope->SetValue("f", f_value, nullptr);
+  e_value.list_value().push_back(Value(nullptr, std::move(e_value_scope)));
+
+  c_scope->SetValue("e", e_value, nullptr);
+
+  new_scope->SetValue("c", Value(nullptr, std::move(c_scope)), nullptr);
+
+  std::string expected(R"*({
+  "a": "foo",
+  "b": 6,
+  "c": {
+    "e": [
+      "bar",
+      {
+        "f": "baz"
+      }
+    ]
+  }
+})*");
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), Value(nullptr, std::move(new_scope)),
+                       Value(nullptr, "json"), result, &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), expected);
+}
+
+TEST_F(OutputConversionTest, ValueEmpty) {
+  Err err;
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), Value(), Value(nullptr, ""), result, &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "<void>");
+}
+
+TEST_F(OutputConversionTest, DefaultValue) {
+  Err err;
+  Value output(nullptr, "foo bar");
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, ""), result, &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "foo bar");
+}
+
+TEST_F(OutputConversionTest, DefaultListLines) {
+  Err err;
+  Value output(nullptr, Value::LIST);
+  output.list_value().push_back(Value(nullptr, ""));
+  output.list_value().push_back(Value(nullptr, "foo"));
+  output.list_value().push_back(Value(nullptr, ""));
+  output.list_value().push_back(Value(nullptr, "bar"));
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, ""), result, &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ("\nfoo\n\nbar\n", result.str());
+}
+
+TEST_F(OutputConversionTest, ReverseString) {
+  Err err;
+  std::string input("foo bar");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "string"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "string"), reverse,
+                       &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), input)
+      << "actual: " << reverse.str() << "expected:" << input;
+}
+
+TEST_F(OutputConversionTest, ReverseListLines) {
+  Err err;
+  std::string input("\nfoo\nbar\n\n");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "list lines"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "list lines"),
+                       reverse, &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), input)
+      << "actual: " << reverse.str() << "expected:" << input;
+}
+
+TEST_F(OutputConversionTest, ReverseValueString) {
+  Err err;
+  std::string input("\"str\"");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "value"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "value"), reverse,
+                       &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), input)
+      << "actual: " << reverse.str() << "expected:" << input;
+}
+
+TEST_F(OutputConversionTest, ReverseValueInt) {
+  Err err;
+  std::string input("6");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "value"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "value"), reverse,
+                       &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), input)
+      << "actual: " << reverse.str() << "expected:" << input;
+}
+
+TEST_F(OutputConversionTest, ReverseValueList) {
+  Err err;
+  std::string input("[\"a\", 5]");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "value"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "value"), reverse,
+                       &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), input)
+      << "actual: " << reverse.str() << "expected:" << input;
+}
+
+TEST_F(OutputConversionTest, ReverseValueDict) {
+  Err err;
+  std::string input("  a = 5\n  b = \"foo\"\n  c = 7\n");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "scope"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "scope"), reverse,
+                       &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), input)
+      << "actual: " << reverse.str() << "expected:" << input;
+}
+
+TEST_F(OutputConversionTest, ReverseValueEmpty) {
+  Err err;
+  Value result = ConvertInputToValue(settings(), "", nullptr,
+                                     Value(nullptr, "value"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "value"), reverse,
+                       &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), "");
+}