diff --git a/README.md b/README.md index 5ef897d..317add6 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ sass_repositories() git_repository( name = "io_bazel_skydoc", remote = "https://github.com/bazelbuild/skydoc.git", - tag = "0.0.2", + tag = "0.0.3", ) load("@io_bazel_skydoc//skylark:skylark.bzl", "skydoc_repositories") skydoc_repositories() diff --git a/skydoc/build.proto b/skydoc/build.proto index 099a148..0a7fe44 100644 --- a/skydoc/build.proto +++ b/skydoc/build.proto @@ -58,9 +58,16 @@ message AllowedRuleClassInfo { repeated string allowed_rule_class = 2; } +// Information about an output target for a rule. +message OutputTarget { + // The template name of the output target (e.g. %{name}.jar. + optional string template = 1; + // The documentation for the output target. + optional string documentation = 2; +} + // This message represents a single attribute of a single rule. message AttributeDefinition { - // Rule name, e.g. "cc_library" required string name = 1; required Attribute.Discriminator type = 2; @@ -81,6 +88,7 @@ message RuleDefinition { optional string example_documentation = 4; + repeated OutputTarget output = 5; } message BuildLanguage { diff --git a/skydoc/common.py b/skydoc/common.py index 517648d..0c5770e 100644 --- a/skydoc/common.py +++ b/skydoc/common.py @@ -22,6 +22,17 @@ ARGS_HEADING = "Args:" EXAMPLES_HEADING = "Examples:" EXAMPLE_HEADING = "Example:" +OUTPUTS_HEADING = "Outputs:" + + +class ExtractedDocs(object): + """Simple class to contain the documentation extracted from a docstring.""" + + def __init__(self, doc, attr_docs, example_doc, output_docs): + self.doc = doc + self.attr_docs = attr_docs + self.example_doc = example_doc + self.output_docs = output_docs def leading_whitespace(line): @@ -29,18 +40,20 @@ def leading_whitespace(line): return len(line) - len(line.lstrip()) -def _parse_attribute_docs(attr_doc, lines, index): - """Extracts attribute documentation. +def _parse_attribute_docs(attr_docs, lines, index): + """Extracts documentation in the form of name: description. + + This includes documentation for attributes and outputs. Args: - attr_doc: A dict used to store the extracted attribute documentation. + attr_docs: A dict used to store the extracted documentation. lines: List containing the input docstring split into lines. - index: The index in lines containing "Args:", which begins the argument - documentation. + index: The index in lines containing the heading that begins the + documentation, such as "Args:" or "Outputs:". Returns: - Returns the next index after the attribute documentation to resume - processing documentation in the caller. + Returns the next index after the documentation to resume processing + documentation in the caller. """ attr = None # Current attribute name desc = None # Description for current attribute @@ -54,10 +67,10 @@ def _parse_attribute_docs(attr_doc, lines, index): break # In practice, users sometimes add a "-" prefix, so we strip it even # though it is not recommended by the style guide - match = re.search(r"^\s*-?\s*(\w+):\s*(.*)", line) + match = re.search(r"^\s*-?\s*([`\{\}\%\.\w]+):\s*(.*)", line) if match: # We have found a new attribute if attr: - attr_doc[attr] = escape(desc) + attr_docs[attr] = escape(desc) attr, desc = match.group(1), match.group(2) elif attr: # Merge documentation when it is multiline @@ -65,7 +78,7 @@ def _parse_attribute_docs(attr_doc, lines, index): i += + 1 if attr: - attr_doc[attr] = escape(desc).strip() + attr_docs[attr] = escape(desc).strip() return i @@ -108,7 +121,8 @@ def parse_docstring(doc): The new documentation string and a dictionary that maps each attribute to its documentation """ - attr_doc = {} + attr_docs = {} + output_docs = {} examples = [] lines = doc.split("\n") docs = [] @@ -116,15 +130,18 @@ def parse_docstring(doc): while i < len(lines): line = lines[i] if line.strip() == ARGS_HEADING: - i = _parse_attribute_docs(attr_doc, lines, i) + i = _parse_attribute_docs(attr_docs, lines, i) continue elif line.strip() == EXAMPLES_HEADING or line.strip() == EXAMPLE_HEADING: i = _parse_example_docs(examples, lines, i) continue + elif line.strip() == OUTPUTS_HEADING: + i = _parse_attribute_docs(output_docs, lines, i) + continue docs.append(line) i += 1 doc = "\n".join(docs).strip() examples_doc = textwrap.dedent("\n".join(examples)).strip() - return doc, attr_doc, examples_doc + return ExtractedDocs(doc, attr_docs, examples_doc, output_docs) diff --git a/skydoc/common_test.py b/skydoc/common_test.py index 7c36331..8e8e25d 100644 --- a/skydoc/common_test.py +++ b/skydoc/common_test.py @@ -22,10 +22,11 @@ class CommonTest(unittest.TestCase): def test_rule_doc_only(self): docstring = 'Rule documentation only docstring.' - doc, attr_doc, example_doc = common.parse_docstring(docstring) - self.assertEqual('Rule documentation only docstring.', doc) - self.assertDictEqual({}, attr_doc) - self.assertEqual('', example_doc) + extracted_docs = common.parse_docstring(docstring) + self.assertEqual('Rule documentation only docstring.', extracted_docs.doc) + self.assertDictEqual({}, extracted_docs.attr_docs) + self.assertEqual('', extracted_docs.example_doc) + self.assertDictEqual({}, extracted_docs.output_docs) def test_rule_and_attribute_doc(self): docstring = ( @@ -39,10 +40,11 @@ def test_rule_and_attribute_doc(self): 'visibility': 'The visibility of this rule.' } - doc, attr_doc, example_doc = common.parse_docstring(docstring) - self.assertEqual('Rule and attribute documentation.', doc) - self.assertDictEqual(expected_attrs, attr_doc) - self.assertEqual('', example_doc) + extracted_docs = common.parse_docstring(docstring) + self.assertEqual('Rule and attribute documentation.', extracted_docs.doc) + self.assertDictEqual(expected_attrs, extracted_docs.attr_docs) + self.assertEqual('', extracted_docs.example_doc) + self.assertDictEqual({}, extracted_docs.output_docs) def test_multi_line_doc(self): docstring = ( @@ -68,10 +70,11 @@ def test_multi_line_doc(self): 'Documentation for visibility continued here.') } - doc, attr_doc, example_doc = common.parse_docstring(docstring) - self.assertEqual(expected_doc, doc) - self.assertDictEqual(expected_attrs, attr_doc) - self.assertEqual('', example_doc) + extracted_docs = common.parse_docstring(docstring) + self.assertEqual(expected_doc, extracted_docs.doc) + self.assertDictEqual(expected_attrs, extracted_docs.attr_docs) + self.assertEqual('', extracted_docs.example_doc) + self.assertDictEqual({}, extracted_docs.output_docs) def test_invalid_args(self): docstring = ( @@ -81,10 +84,11 @@ def test_invalid_args(self): ' name: A unique name for this rule.\n' ' visibility: The visibility of this rule.\n') - doc, attr_doc, example_doc = common.parse_docstring(docstring) - self.assertEqual(docstring.strip(), doc) - self.assertDictEqual({}, attr_doc) - self.assertEqual('', example_doc) + extracted_docs = common.parse_docstring(docstring) + self.assertEqual(docstring.strip(), extracted_docs.doc) + self.assertDictEqual({}, extracted_docs.attr_docs) + self.assertEqual('', extracted_docs.example_doc) + self.assertDictEqual({}, extracted_docs.output_docs) def test_example(self): docstring = ( @@ -111,10 +115,11 @@ def test_example(self): '\n' 'Note about this example.') - doc, attr_doc, example_doc = common.parse_docstring(docstring) - self.assertEqual('Documentation with example', doc) - self.assertDictEqual(expected_attrs, attr_doc) - self.assertEqual(expected_example_doc, example_doc) + extracted_docs = common.parse_docstring(docstring) + self.assertEqual('Documentation with example', extracted_docs.doc) + self.assertDictEqual(expected_attrs, extracted_docs.attr_docs) + self.assertEqual(expected_example_doc, extracted_docs.example_doc) + self.assertDictEqual({}, extracted_docs.output_docs) def test_example_after_attrs(self): docstring = ( @@ -141,10 +146,41 @@ def test_example_after_attrs(self): '\n' 'Note about this example.') - doc, attr_doc, example_doc = common.parse_docstring(docstring) - self.assertEqual('Documentation with example', doc) - self.assertDictEqual(expected_attrs, attr_doc) - self.assertEqual(expected_example_doc, example_doc) + extracted_docs = common.parse_docstring(docstring) + self.assertEqual('Documentation with example', extracted_docs.doc) + self.assertDictEqual(expected_attrs, extracted_docs.attr_docs) + self.assertEqual(expected_example_doc, extracted_docs.example_doc) + self.assertDictEqual({}, extracted_docs.output_docs) + + def test_outputs(self): + docstring = ( + 'Documentation with outputs\n' + '\n' + 'Args:\n' + ' name: A unique name for this rule.\n' + ' visibility: The visibility of this rule.\n' + '\n' + 'Outputs:\n' + ' %{name}.jar: A Java archive.\n' + ' %{name}_deploy.jar: A Java archive suitable for deployment.\n' + '\n' + ' Only built if explicitly requested.\n') + expected_attrs = { + 'name': 'A unique name for this rule.', + 'visibility': 'The visibility of this rule.' + } + expected_outputs = { + '%{name}.jar': 'A Java archive.', + '%{name}_deploy.jar': ('A Java archive suitable for deployment.\n\n' + 'Only built if explicitly requested.'), + } + + extracted_docs = common.parse_docstring(docstring) + self.assertEqual('Documentation with outputs', extracted_docs.doc) + self.assertDictEqual(expected_attrs, extracted_docs.attr_docs) + self.assertEqual('', extracted_docs.example_doc) + self.assertDictEqual(expected_outputs, extracted_docs.output_docs) + if __name__ == '__main__': unittest.main() diff --git a/skydoc/macro_extractor.py b/skydoc/macro_extractor.py index 8d65fea..4269d42 100644 --- a/skydoc/macro_extractor.py +++ b/skydoc/macro_extractor.py @@ -72,20 +72,21 @@ def _add_macro_doc(self, stmt): doc = ast.get_docstring(stmt) if doc: - doc, attr_doc, example_doc = common.parse_docstring(doc) - rule.documentation = doc - rule.example_documentation = example_doc + extracted_docs = common.parse_docstring(doc) + rule.documentation = extracted_docs.doc + if extracted_docs.example_doc: + rule.example_documentation = extracted_docs.example_doc else: - doc = "" - attr_doc = {} + extracted_docs = common.ExtractedDocs( + doc="", attr_docs={}, example_doc="", output_docs={}) for i in range(len(stmt.args.args)): attr = rule.attribute.add() attr_name = stmt.args.args[i].id attr.name = attr_name - if attr_name in attr_doc: - attr.documentation = attr_doc[attr_name] + if attr_name in extracted_docs.attr_docs: + attr.documentation = extracted_docs.attr_docs[attr_name] if i < shift: # The first arguments are mandatory attr.mandatory = True @@ -94,6 +95,11 @@ def _add_macro_doc(self, stmt): attr.mandatory = False attr.type = get_type(stmt.args.defaults[i - shift]) + for template, doc in extracted_docs.output_docs.iteritems(): + output = rule.output.add() + output.template = template + output.documentation = doc + def parse_bzl(self, bzl_file): """Extracts documentation for all public macros from the given .bzl file. diff --git a/skydoc/macro_extractor_test.py b/skydoc/macro_extractor_test.py index 852441f..79b3d95 100644 --- a/skydoc/macro_extractor_test.py +++ b/skydoc/macro_extractor_test.py @@ -232,6 +232,114 @@ def example_macro(name, foo, visibility=None): self.check_protos(src, expected) + def test_macro_with_example_doc(self): + src = textwrap.dedent("""\ + def macro_with_example(name, foo, visibility=None): + \"\"\"Macro with examples. + + Args: + name: A unique name for this rule. + foo: A test argument. + visibility: The visibility of this rule. + + Example: + Here is an example of how to use this rule. + \"\"\" + native.genrule( + name = name, + out = ["foo"], + cmd = "touch $@", + visibility = visibility, + ) + """) + + expected = textwrap.dedent("""\ + rule { + name: "macro_with_example" + documentation: "Macro with examples." + example_documentation: "Here is an example of how to use this rule." + attribute { + name: "name" + type: UNKNOWN + mandatory: true + documentation: "A unique name for this rule." + } + attribute { + name: "foo" + type: UNKNOWN + mandatory: true + documentation: "A test argument." + } + attribute { + name: "visibility" + type: UNKNOWN + mandatory: false + documentation: "The visibility of this rule." + } + } + """) + + self.check_protos(src, expected) + + def test_macro_with_output_doc(self): + src = textwrap.dedent("""\ + def macro_with_outputs(name, foo, visibility=None): + \"\"\"Macro with output documentation. + + Outputs: + %{name}.jar: A Java archive. + %{name}_deploy.jar: A Java archive suitable for deployment. + + Only built if explicitly requested. + + Args: + name: A unique name for this rule. + foo: A test argument. + visibility: The visibility of this rule. + \"\"\" + native.genrule( + name = name, + out = ["foo"], + cmd = "touch $@", + visibility = visibility, + ) + """) + + expected = textwrap.dedent("""\ + rule { + name: "macro_with_outputs" + documentation: "Macro with output documentation." + attribute { + name: "name" + type: UNKNOWN + mandatory: true + documentation: "A unique name for this rule." + } + attribute { + name: "foo" + type: UNKNOWN + mandatory: true + documentation: "A test argument." + } + attribute { + name: "visibility" + type: UNKNOWN + mandatory: false + documentation: "The visibility of this rule." + } + output { + template: "%{name}_deploy.jar" + documentation: "A Java archive suitable for deployment.\\n\\nOnly built if explicitly requested." + } + output { + template: "%{name}.jar" + documentation: "A Java archive." + } + } + """) + + self.check_protos(src, expected) + def test_file_doc_title_only(self): src = textwrap.dedent("""\ \"\"\"Example rules\"\"\" diff --git a/skydoc/rule.py b/skydoc/rule.py index 034749b..5ac2114 100644 --- a/skydoc/rule.py +++ b/skydoc/rule.py @@ -17,6 +17,7 @@ import mistune from skydoc import build_pb2 + class Attribute(object): """Representation of an attribute used to render documentation templates.""" @@ -82,6 +83,15 @@ def _get_type_str(self, proto): type_str += '; Required' if proto.mandatory else '; Optional' return type_str + +class Output(object): + """Representation of an output used to render documentation templates.""" + + def __init__(self, proto): + self.__proto = proto + self.template = mistune.markdown(proto.template) + self.documentation = mistune.markdown(proto.documentation) + class Rule(object): """Representation of a rule used to render documentation templates.""" @@ -94,6 +104,9 @@ def __init__(self, proto): self.attributes = [] for attribute in proto.attribute: self.attributes.append(Attribute(attribute)) + self.outputs = [] + for output in proto.output: + self.outputs.append(Output(output)) def _get_signature(self, proto): """Returns the rule signature for this rule.""" @@ -107,6 +120,7 @@ def _get_signature(self, proto): signature += ')' return signature + class RuleSet(object): """Representation of a rule set used to render documentation templates.""" diff --git a/skydoc/rule_extractor.py b/skydoc/rule_extractor.py index 4df5c30..65c6600 100644 --- a/skydoc/rule_extractor.py +++ b/skydoc/rule_extractor.py @@ -77,14 +77,22 @@ def _add_rule_doc(self, name, doc): name: The name of the rule. doc: The docstring extracted for the rule. """ - doc, attr_doc, example_doc = common.parse_docstring(doc) + extracted_docs = common.parse_docstring(doc) if name in self.__extracted_rules: rule = self.__extracted_rules[name] - rule.doc = doc - rule.example_doc = example_doc - for attr_name, attr_doc in attr_doc.iteritems(): + rule.doc = extracted_docs.doc + rule.example_doc = extracted_docs.example_doc + for attr_name, desc in extracted_docs.attr_docs.iteritems(): if attr_name in rule.attrs: - rule.attrs[attr_name].doc = attr_doc + rule.attrs[attr_name].doc = desc + + # Match the output name from the docstring with the corresponding output + # template name extracted from rule() and store a mapping of output + # template name to documentation. + for output_name, desc in extracted_docs.output_docs.iteritems(): + if output_name in rule.outputs: + output_template = rule.outputs[output_name] + rule.output_docs[output_template] = desc def _extract_docstrings(self, bzl_file): """Extracts the docstrings for all public rules in the .bzl file. @@ -129,8 +137,10 @@ def _assemble_protos(self): for rule_desc in rules: rule = self.__language.rule.add() rule.name = rule_desc.name - rule.documentation = rule_desc.doc - rule.example_documentation = rule_desc.example_doc + if rule_desc.doc: + rule.documentation = rule_desc.doc + if rule_desc.example_doc: + rule.example_documentation = rule_desc.example_doc attrs = sorted(rule_desc.attrs.values(), cmp=attr.attr_compare) for attr_desc in attrs: @@ -138,13 +148,19 @@ def _assemble_protos(self): continue attr_proto = rule.attribute.add() attr_proto.name = attr_desc.name - attr_proto.documentation = attr_desc.doc + if attr_desc.doc: + attr_proto.documentation = attr_desc.doc attr_proto.type = attr_desc.type attr_proto.mandatory = attr_desc.mandatory # TODO(dzc): Save the default value of the attribute. This will require # adding a proto field to the AttributeDefinition proto, perhaps as a # oneof. + for template, doc in rule_desc.output_docs.iteritems(): + output = rule.output.add() + output.template = template + output.documentation = doc + def parse_bzl(self, bzl_file): """Extracts the documentation for all public rules from the given .bzl file. diff --git a/skydoc/rule_extractor_test.py b/skydoc/rule_extractor_test.py index 4dea64f..6264509 100644 --- a/skydoc/rule_extractor_test.py +++ b/skydoc/rule_extractor_test.py @@ -182,24 +182,20 @@ def _impl(ctx): expected = textwrap.dedent("""\ rule { name: "undocumented" - documentation: "" attribute { name: "name" type: UNKNOWN mandatory: true - documentation: "" } attribute { name: "arg_label" type: LABEL mandatory: false - documentation: "" } attribute { name: "arg_string" type: STRING mandatory: false - documentation: "" } } """) @@ -389,5 +385,123 @@ def example_macro(name, foo, visibility=None): self.check_protos(src, expected) + def test_rule_with_example_doc(self): + src = textwrap.dedent("""\ + def _impl(ctx): + return struct() + + rule_with_example = rule( + implementation = _impl, + attrs = { + "arg_label": attr.label(), + "arg_string": attr.string(), + }, + ) + \"\"\"Rule with example. + + Example: + Here is an example of how to use this rule. + + Args: + name: A unique name for this rule. + arg_label: A label argument. + arg_string: A string argument. + \"\"\" + """) + + expected = textwrap.dedent("""\ + rule { + name: "rule_with_example" + documentation: "Rule with example." + example_documentation: "Here is an example of how to use this rule." + attribute { + name: "name" + type: UNKNOWN + mandatory: true + documentation: "A unique name for this rule." + } + attribute { + name: "arg_label" + type: LABEL + mandatory: false + documentation: "A label argument." + } + attribute { + name: "arg_string" + type: STRING + mandatory: false + documentation: "A string argument." + } + } + """) + + self.check_protos(src, expected) + + def test_rule_with_output_doc(self): + src = textwrap.dedent("""\ + def _impl(ctx): + return struct() + + rule_with_outputs = rule( + implementation = _impl, + attrs = { + "arg_label": attr.label(), + "arg_string": attr.string(), + }, + outputs = { + "jar": "%{name}.jar", + "deploy_jar": "%{name}_deploy.jar", + }, + ) + \"\"\"Rule with output documentation. + + Outputs: + jar: A Java archive. + deploy_jar: A Java archive suitable for deployment. + + Only built if explicitly requested. + + Args: + name: A unique name for this rule. + arg_label: A label argument. + arg_string: A string argument. + \"\"\" + """) + + expected = textwrap.dedent("""\ + rule { + name: "rule_with_outputs" + documentation: "Rule with output documentation." + attribute { + name: "name" + type: UNKNOWN + mandatory: true + documentation: "A unique name for this rule." + } + attribute { + name: "arg_label" + type: LABEL + mandatory: false + documentation: "A label argument." + } + attribute { + name: "arg_string" + type: STRING + mandatory: false + documentation: "A string argument." + } + output { + template: "%{name}_deploy.jar" + documentation: "A Java archive suitable for deployment.\\n\\nOnly built if explicitly requested." + } + output { + template: "%{name}.jar" + documentation: "A Java archive." + } + } + """) + + self.check_protos(src, expected) + if __name__ == '__main__': unittest.main() diff --git a/skydoc/stubs/skylark_globals.py b/skydoc/stubs/skylark_globals.py index e3cf113..51d25d5 100644 --- a/skydoc/stubs/skylark_globals.py +++ b/skydoc/stubs/skylark_globals.py @@ -41,10 +41,9 @@ def __init__(self, label_string): self.label_string = label_string class RuleDescriptor(object): - def __init__(self, implementation, test=False, attrs={}, outputs=None, + def __init__(self, implementation, test=False, attrs={}, outputs={}, executable=False, output_to_genfiles=False, fragments=[], - host_fragments=[], local=False, doc='', example_doc='', - type='rule'): + host_fragments=[], local=False, doc='', type='rule'): """Constructor for RuleDescriptor Args: @@ -52,7 +51,9 @@ def __init__(self, implementation, test=False, attrs={}, outputs=None, implementation: The implementation function for the rule (not used). test: Whether this is a test rule attrs: Dictionary mapping attribute name to attribute descriptor - outputs: List of outputs (not used). + outputs: Outputs for this rule as a mapping from string to template name. + The dictionary keys are used to refer to the output in the docstring + documentation. executable: Whether this rule produces an executable. output_to_genfiles: Whether the rule generates files in the genfiles directory rather than the bin directory (not used). @@ -80,7 +81,9 @@ def __init__(self, implementation, test=False, attrs={}, outputs=None, self.host_fragments = host_fragments self.local = local self.doc = doc - self.example_doc = example_doc + self.example_doc = '' + self.outputs = outputs + self.output_docs = {} self.type = type for name, attr in self.attrs.iteritems(): attr.name = name diff --git a/skydoc/templates/BUILD b/skydoc/templates/BUILD index ba76e9f..72a2b6f 100644 --- a/skydoc/templates/BUILD +++ b/skydoc/templates/BUILD @@ -7,6 +7,7 @@ filegroup( "html.jinja", "markdown.jinja", "nav.jinja", + "outputs.jinja", "overview.jinja", "toc.jinja", ], diff --git a/skydoc/templates/html.jinja b/skydoc/templates/html.jinja index 557c359..c96bfaf 100644 --- a/skydoc/templates/html.jinja +++ b/skydoc/templates/html.jinja @@ -62,6 +62,13 @@ Documentation generated by Skydoc {{ rule.documentation }} +% if rule.outputs[0] is defined: +

+ Outputs +

+% include "outputs.jinja" +% endif + % if rule.attributes[0] is defined:

Attributes

% include "attributes.jinja" diff --git a/skydoc/templates/markdown.jinja b/skydoc/templates/markdown.jinja index 54bb213..e91cf14 100644 --- a/skydoc/templates/markdown.jinja +++ b/skydoc/templates/markdown.jinja @@ -31,6 +31,13 @@ Documentation generated by Skydoc {{ rule.documentation }} +% if rule.outputs[0] is defined: + +### Outputs + +% include "outputs.jinja" +% endif + % if rule.attributes[0] is defined: ### Attributes diff --git a/skydoc/templates/outputs.jinja b/skydoc/templates/outputs.jinja new file mode 100644 index 0000000..a620c0d --- /dev/null +++ b/skydoc/templates/outputs.jinja @@ -0,0 +1,31 @@ +{# +Copyright 2016 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. +#} + + + + + + +% for output in rule.outputs: + + + + +% endfor + +
{{ output.template }} + {{ output.documentation }} +
diff --git a/skylark/skylark.bzl b/skylark/skylark.bzl index f986ae8..92225f6 100644 --- a/skylark/skylark.bzl +++ b/skylark/skylark.bzl @@ -173,6 +173,9 @@ Args: format: The type of output to generate. Possible values are `"markdown"` and `"html"`. +Outputs: + skylark_doc_zip: A zip file containing the generated documentation. + Example: Suppose you have a project containing Skylark rules you want to document: