Add new flag to Stardoc and allows Stardoc to output raw serialized proto.

Progress toward https://github.com/bazelbuild/skydoc/issues/189

PiperOrigin-RevId: 253128175
diff --git a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
index 325716d..900f613 100644
--- a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
+++ b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
@@ -66,6 +66,7 @@
 import com.google.devtools.build.lib.syntax.SkylarkImport;
 import com.google.devtools.build.lib.syntax.StarlarkSemantics;
 import com.google.devtools.build.lib.syntax.UserDefinedFunction;
+import com.google.devtools.build.skydoc.SkydocOptions.OutputFormat;
 import com.google.devtools.build.skydoc.fakebuildapi.FakeActionsInfoProvider;
 import com.google.devtools.build.skydoc.fakebuildapi.FakeBuildApiGlobals;
 import com.google.devtools.build.skydoc.fakebuildapi.FakeConfigApi;
@@ -110,12 +111,15 @@
 import com.google.devtools.build.skydoc.rendering.DocstringParseException;
 import com.google.devtools.build.skydoc.rendering.FunctionUtil;
 import com.google.devtools.build.skydoc.rendering.MarkdownRenderer;
+import com.google.devtools.build.skydoc.rendering.ProtoRenderer;
 import com.google.devtools.build.skydoc.rendering.ProviderInfoWrapper;
 import com.google.devtools.build.skydoc.rendering.RuleInfoWrapper;
 import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ProviderInfo;
 import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.RuleInfo;
 import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.UserDefinedFunctionInfo;
 import com.google.devtools.common.options.OptionsParser;
+import java.io.BufferedOutputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.nio.file.NoSuchFileException;
@@ -171,7 +175,8 @@
   }
 
   public static void main(String[] args)
-      throws IOException, InterruptedException, LabelSyntaxException, EvalException {
+      throws IOException, InterruptedException, LabelSyntaxException, EvalException,
+          DocstringParseException {
     OptionsParser parser =
         OptionsParser.newOptionsParser(StarlarkSemanticsOptions.class, SkydocOptions.class);
     parser.parseAndExitUponError(args);
@@ -226,8 +231,6 @@
       System.exit(1);
     }
 
-    MarkdownRenderer renderer = new MarkdownRenderer();
-
     Map<String, RuleInfo> filteredRuleInfos =
         ruleInfoMap.build().entrySet().stream()
             .filter(entry -> validSymbolName(symbolNames, entry.getKey()))
@@ -240,11 +243,23 @@
         userDefinedFunctions.build().entrySet().stream()
             .filter(entry -> validSymbolName(symbolNames, entry.getKey()))
             .collect(ImmutableMap.toImmutableMap(Entry::getKey, Entry::getValue));
-    try (PrintWriter printWriter = new PrintWriter(outputPath, "UTF-8")) {
-      printWriter.println(renderer.renderMarkdownHeader());
-      printRuleInfos(printWriter, renderer, filteredRuleInfos);
-      printProviderInfos(printWriter, renderer, filteredProviderInfos);
-      printUserDefinedFunctions(printWriter, renderer, filteredUserDefinedFunctions);
+
+    if (skydocOptions.outputFormat == OutputFormat.PROTO) {
+      try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outputPath))) {
+        new ProtoRenderer()
+            .appendRuleInfos(filteredRuleInfos.values())
+            .appendProviderInfos(filteredProviderInfos.values())
+            .appendUserDefinedFunctionInfos(filteredUserDefinedFunctions)
+            .writeModuleInfo(out);
+      }
+    } else if (skydocOptions.outputFormat == OutputFormat.MARKDOWN) {
+      MarkdownRenderer renderer = new MarkdownRenderer();
+      try (PrintWriter printWriter = new PrintWriter(outputPath, "UTF-8")) {
+        printWriter.println(renderer.renderMarkdownHeader());
+        printRuleInfos(printWriter, renderer, filteredRuleInfos);
+        printProviderInfos(printWriter, renderer, filteredProviderInfos);
+        printUserDefinedFunctions(printWriter, renderer, filteredUserDefinedFunctions);
+      }
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/skydoc/SkydocOptions.java b/src/main/java/com/google/devtools/build/skydoc/SkydocOptions.java
index d223e8c..8408bf8 100644
--- a/src/main/java/com/google/devtools/build/skydoc/SkydocOptions.java
+++ b/src/main/java/com/google/devtools/build/skydoc/SkydocOptions.java
@@ -14,6 +14,7 @@
 
 package com.google.devtools.build.skydoc;
 
+import com.google.devtools.common.options.EnumConverter;
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionDocumentationCategory;
 import com.google.devtools.common.options.OptionEffectTag;
@@ -48,6 +49,15 @@
   public String outputFilePath;
 
   @Option(
+      name = "output_format",
+      defaultValue = "markdown",
+      converter = OutputFormatConverter.class,
+      documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+      effectTags = OptionEffectTag.UNKNOWN,
+      help = "The format choice for the output file (\"markdown\" or \"proto\").")
+  public OutputFormat outputFormat;
+
+  @Option(
       name = "symbols",
       allowMultiple = true,
       defaultValue = "",
@@ -64,4 +74,21 @@
       effectTags = OptionEffectTag.UNKNOWN,
       help = "File path roots to search when resolving transitive bzl dependencies")
   public List<String> depRoots;
+
+  /** Converter for {@link OutputFormat} */
+  public static class OutputFormatConverter extends EnumConverter<OutputFormat> {
+
+    public OutputFormatConverter() {
+      super(OutputFormat.class, "output format");
+    }
+  }
+
+  /**
+   * The possible values for the --output_format flag, which controls the format of Stardoc's output
+   * file.
+   */
+  public enum OutputFormat {
+    MARKDOWN,
+    PROTO
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/skydoc/rendering/ProtoRenderer.java b/src/main/java/com/google/devtools/build/skydoc/rendering/ProtoRenderer.java
new file mode 100644
index 0000000..1d42833
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/skydoc/rendering/ProtoRenderer.java
@@ -0,0 +1,69 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.skydoc.rendering;
+
+import com.google.devtools.build.lib.syntax.UserDefinedFunction;
+import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ModuleInfo;
+import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ProviderInfo;
+import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.RuleInfo;
+import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.UserDefinedFunctionInfo;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/** Produces skydoc output in proto form. */
+public class ProtoRenderer {
+
+  private final ModuleInfo.Builder moduleInfo;
+
+  public ProtoRenderer() {
+    this.moduleInfo = ModuleInfo.newBuilder();
+  }
+
+  /** Appends {@link RuleInfo} protos to a {@link ModuleInfo.Builder}. */
+  public ProtoRenderer appendRuleInfos(Collection<RuleInfo> ruleInfos) {
+    for (RuleInfo ruleInfo : ruleInfos) {
+      moduleInfo.addRuleInfo(ruleInfo);
+    }
+    return this;
+  }
+
+  /** Appends {@link ProviderInfo} protos to a {@link ModuleInfo.Builder}. */
+  public ProtoRenderer appendProviderInfos(Collection<ProviderInfoWrapper> providerInfoWrappers) {
+    for (ProviderInfoWrapper providerInfoWrapper : providerInfoWrappers) {
+      ProviderInfo providerInfo = providerInfoWrapper.getProviderInfo();
+      moduleInfo.addProviderInfo(providerInfo);
+    }
+    return this;
+  }
+
+  /** Appends {@link UserDefinedFunctionInfo} protos to a {@link ModuleInfo.Builder}. */
+  public ProtoRenderer appendUserDefinedFunctionInfos(Map<String, UserDefinedFunction> funcInfosMap)
+      throws DocstringParseException {
+    for (Map.Entry<String, UserDefinedFunction> entry : funcInfosMap.entrySet()) {
+      UserDefinedFunctionInfo funcInfo =
+          FunctionUtil.fromNameAndFunction(entry.getKey(), entry.getValue());
+      moduleInfo.addFuncInfo(funcInfo);
+    }
+    return this;
+  }
+
+  /** Outputs the raw form of a {@link ModuleInfo} proto. */
+  public void writeModuleInfo(BufferedOutputStream outputStream) throws IOException {
+    ModuleInfo build = moduleInfo.build();
+    build.writeTo(outputStream);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/skydoc/rendering/proto/stardoc_output.proto b/src/main/java/com/google/devtools/build/skydoc/rendering/proto/stardoc_output.proto
index 539245a..b567484 100644
--- a/src/main/java/com/google/devtools/build/skydoc/rendering/proto/stardoc_output.proto
+++ b/src/main/java/com/google/devtools/build/skydoc/rendering/proto/stardoc_output.proto
@@ -23,6 +23,17 @@
 option java_package = "com.google.devtools.build.skydoc.rendering.proto";
 option java_outer_classname = "StardocOutputProtos";
 
+// The root output proto of Stardoc. A single invocation of Stardoc will output
+// exactly one instance of this proto, representing all documentation for
+// the input Starlark file.
+message ModuleInfo {
+  repeated RuleInfo rule_info = 1;
+
+  repeated ProviderInfo provider_info = 2;
+
+  repeated UserDefinedFunctionInfo func_info = 3;
+}
+
 // Representation of a Starlark rule attribute type. These generally
 // have a one-to-one correspondence with functions defined at
 // https://docs.bazel.build/versions/master/skylark/lib/attr.html.