David Z. Chen | 55dafa9 | 2016-03-25 15:55:54 -0700 | [diff] [blame] | 1 | # Copyright 2016 The Bazel Authors. All rights reserved. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | """Common functions for skydoc.""" |
| 16 | |
| 17 | import re |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 18 | import textwrap |
David Z. Chen | 55dafa9 | 2016-03-25 15:55:54 -0700 | [diff] [blame] | 19 | from xml.sax.saxutils import escape |
| 20 | |
| 21 | |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 22 | ARGS_HEADING = "Args:" |
| 23 | EXAMPLES_HEADING = "Examples:" |
| 24 | EXAMPLE_HEADING = "Example:" |
David Chen | d42081e | 2016-04-21 21:17:05 +0000 | [diff] [blame] | 25 | OUTPUTS_HEADING = "Outputs:" |
| 26 | |
| 27 | |
David Z. Chen | 8861b7c | 2016-09-14 13:35:03 -0700 | [diff] [blame] | 28 | class InputError(Exception): |
| 29 | pass |
| 30 | |
| 31 | |
David Chen | d42081e | 2016-04-21 21:17:05 +0000 | [diff] [blame] | 32 | class ExtractedDocs(object): |
| 33 | """Simple class to contain the documentation extracted from a docstring.""" |
| 34 | |
| 35 | def __init__(self, doc, attr_docs, example_doc, output_docs): |
| 36 | self.doc = doc |
| 37 | self.attr_docs = attr_docs |
| 38 | self.example_doc = example_doc |
| 39 | self.output_docs = output_docs |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 40 | |
| 41 | |
David Z. Chen | 55dafa9 | 2016-03-25 15:55:54 -0700 | [diff] [blame] | 42 | def leading_whitespace(line): |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 43 | """Returns the number of leading whitespace in the line.""" |
David Z. Chen | 55dafa9 | 2016-03-25 15:55:54 -0700 | [diff] [blame] | 44 | return len(line) - len(line.lstrip()) |
| 45 | |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 46 | |
David Z. Chen | 8861b7c | 2016-09-14 13:35:03 -0700 | [diff] [blame] | 47 | def validate_strip_prefix(strip_prefix, bzl_files): |
| 48 | if not strip_prefix: |
| 49 | return strip_prefix |
| 50 | prefix = strip_prefix if strip_prefix.endswith('/') else strip_prefix + '/' |
| 51 | for path in bzl_files: |
| 52 | if not path.startswith(prefix): |
| 53 | raise InputError( |
| 54 | 'Input file %s does not have path prefix %s. Directory prefix set ' |
| 55 | 'with --strip_prefix must be common to all input files.' |
| 56 | % (path, strip_prefix)) |
| 57 | return prefix |
| 58 | |
David Chen | d42081e | 2016-04-21 21:17:05 +0000 | [diff] [blame] | 59 | def _parse_attribute_docs(attr_docs, lines, index): |
| 60 | """Extracts documentation in the form of name: description. |
| 61 | |
| 62 | This includes documentation for attributes and outputs. |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 63 | |
| 64 | Args: |
David Chen | d42081e | 2016-04-21 21:17:05 +0000 | [diff] [blame] | 65 | attr_docs: A dict used to store the extracted documentation. |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 66 | lines: List containing the input docstring split into lines. |
David Chen | d42081e | 2016-04-21 21:17:05 +0000 | [diff] [blame] | 67 | index: The index in lines containing the heading that begins the |
| 68 | documentation, such as "Args:" or "Outputs:". |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 69 | |
| 70 | Returns: |
David Chen | d42081e | 2016-04-21 21:17:05 +0000 | [diff] [blame] | 71 | Returns the next index after the documentation to resume processing |
| 72 | documentation in the caller. |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 73 | """ |
| 74 | attr = None # Current attribute name |
| 75 | desc = None # Description for current attribute |
| 76 | args_leading_ws = leading_whitespace(lines[index]) |
| 77 | i = index + 1 |
| 78 | while i < len(lines): |
| 79 | line = lines[i] |
| 80 | # If a blank line is encountered, we have finished parsing the "Args" |
| 81 | # section. |
| 82 | if line.strip() and leading_whitespace(line) == args_leading_ws: |
| 83 | break |
| 84 | # In practice, users sometimes add a "-" prefix, so we strip it even |
| 85 | # though it is not recommended by the style guide |
David Chen | d42081e | 2016-04-21 21:17:05 +0000 | [diff] [blame] | 86 | match = re.search(r"^\s*-?\s*([`\{\}\%\.\w]+):\s*(.*)", line) |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 87 | if match: # We have found a new attribute |
| 88 | if attr: |
David Chen | d42081e | 2016-04-21 21:17:05 +0000 | [diff] [blame] | 89 | attr_docs[attr] = escape(desc) |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 90 | attr, desc = match.group(1), match.group(2) |
| 91 | elif attr: |
| 92 | # Merge documentation when it is multiline |
| 93 | desc = desc + "\n" + line.strip() |
| 94 | i += + 1 |
| 95 | |
| 96 | if attr: |
David Chen | d42081e | 2016-04-21 21:17:05 +0000 | [diff] [blame] | 97 | attr_docs[attr] = escape(desc).strip() |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 98 | |
| 99 | return i |
| 100 | |
| 101 | |
| 102 | def _parse_example_docs(examples, lines, index): |
| 103 | """Extracts example documentation. |
| 104 | |
| 105 | Args: |
| 106 | examples: A list to contain the lines containing the example documentation. |
| 107 | lines: List containing the input docstring split into lines. |
| 108 | index: The index in lines containing "Example[s]:", which begins the |
| 109 | example documentation. |
| 110 | |
| 111 | Returns: |
| 112 | Returns the next index after the attribute documentation to resume |
| 113 | processing documentation in the caller. |
| 114 | """ |
| 115 | heading_leading_ws = leading_whitespace(lines[index]) |
| 116 | i = index + 1 |
| 117 | while i < len(lines): |
| 118 | line = lines[i] |
| 119 | if line.strip() and leading_whitespace(line) == heading_leading_ws: |
| 120 | break |
| 121 | examples.append(line) |
| 122 | i += 1 |
| 123 | |
| 124 | return i |
| 125 | |
| 126 | |
| 127 | def parse_docstring(doc): |
David Z. Chen | 55dafa9 | 2016-03-25 15:55:54 -0700 | [diff] [blame] | 128 | """Analyzes the documentation string for attributes. |
| 129 | |
| 130 | This looks for the "Args:" separator to fetch documentation for each |
| 131 | attribute. The "Args" section ends at the first blank line. |
| 132 | |
| 133 | Args: |
| 134 | doc: The documentation string |
| 135 | |
| 136 | Returns: |
| 137 | The new documentation string and a dictionary that maps each attribute to |
| 138 | its documentation |
| 139 | """ |
David Chen | d42081e | 2016-04-21 21:17:05 +0000 | [diff] [blame] | 140 | attr_docs = {} |
| 141 | output_docs = {} |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 142 | examples = [] |
David Z. Chen | 55dafa9 | 2016-03-25 15:55:54 -0700 | [diff] [blame] | 143 | lines = doc.split("\n") |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 144 | docs = [] |
| 145 | i = 0 |
| 146 | while i < len(lines): |
| 147 | line = lines[i] |
| 148 | if line.strip() == ARGS_HEADING: |
David Chen | d42081e | 2016-04-21 21:17:05 +0000 | [diff] [blame] | 149 | i = _parse_attribute_docs(attr_docs, lines, i) |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 150 | continue |
| 151 | elif line.strip() == EXAMPLES_HEADING or line.strip() == EXAMPLE_HEADING: |
| 152 | i = _parse_example_docs(examples, lines, i) |
| 153 | continue |
David Chen | d42081e | 2016-04-21 21:17:05 +0000 | [diff] [blame] | 154 | elif line.strip() == OUTPUTS_HEADING: |
| 155 | i = _parse_attribute_docs(output_docs, lines, i) |
| 156 | continue |
David Z. Chen | 55dafa9 | 2016-03-25 15:55:54 -0700 | [diff] [blame] | 157 | |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 158 | docs.append(line) |
| 159 | i += 1 |
David Z. Chen | 55dafa9 | 2016-03-25 15:55:54 -0700 | [diff] [blame] | 160 | |
David Chen | 709bba1 | 2016-04-13 09:18:46 +0000 | [diff] [blame] | 161 | doc = "\n".join(docs).strip() |
| 162 | examples_doc = textwrap.dedent("\n".join(examples)).strip() |
David Chen | d42081e | 2016-04-21 21:17:05 +0000 | [diff] [blame] | 163 | return ExtractedDocs(doc, attr_docs, examples_doc, output_docs) |