Exemple #1
0
    def parse_sections(self, docstring: str) -> List[Section]:  # noqa: D102
        if "signature" not in self.context:
            self.context["signature"] = getattr(self.context["obj"], "signature", None)
        if "annotation" not in self.context:
            self.context["annotation"] = getattr(self.context["obj"], "type", empty)
        if "attributes" not in self.context:
            self.context["attributes"] = {}

        sections = []
        current_section = []

        in_code_block = False

        lines = docstring.split("\n")
        i = 0

        while i < len(lines):
            line_lower = lines[i].lower()

            if in_code_block:
                if line_lower.lstrip(" ").startswith("```"):
                    in_code_block = False
                current_section.append(lines[i])

            elif line_lower in SECTIONS_TITLES:
                if current_section:
                    if any(current_section):
                        sections.append(Section(Section.Type.MARKDOWN, "\n".join(current_section)))
                    current_section = []
                section_reader = self.section_reader[SECTIONS_TITLES[line_lower]]
                section, i = section_reader(lines, i + 1)
                if section:
                    sections.append(section)

            elif line_lower.lstrip(" ").startswith("```"):
                in_code_block = True
                current_section.append(lines[i])

            else:
                if self.replace_admonitions and not in_code_block and i + 1 < len(lines):
                    match = RE_GOOGLE_STYLE_ADMONITION.match(lines[i])
                    if match:
                        groups = match.groupdict()
                        indent = groups["indent"]
                        if lines[i + 1].startswith(indent + " " * 4):
                            lines[i] = f"{indent}!!! {groups['type'].lower()}"
                            if groups["title"]:
                                lines[i] += f' "{groups["title"]}"'
                current_section.append(lines[i])

            i += 1

        if current_section:
            sections.append(Section(Section.Type.MARKDOWN, "\n".join(current_section)))

        return sections
Exemple #2
0
    def read_return_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
        """
        Parse an "returns" section.

        Arguments:
            lines: The return block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        text, i = self.read_block(lines, start_index)

        if self.context["signature"]:
            annotation = self.context["signature"].return_annotation
        else:
            annotation = self.context["annotation"]

        if annotation is empty:
            if not text:
                self.error("No return type annotation")
            else:
                try:
                    type_, text = text.split(":", 1)
                except ValueError:
                    self.error("No type in return description")
                else:
                    annotation = type_.lstrip()
                    text = text.lstrip()

        if annotation is empty and not text:
            self.error(f"Empty return section at line {start_index}")
            return None, i

        return Section(Section.Type.RETURN, AnnotatedObject(annotation, text)), i
Exemple #3
0
    def read_return_section(
        self,
        docstring_obj: Docstring,
    ) -> Optional[Section]:
        """
        Parse a "returns" section.

        Arguments:
            docstring_obj: Docstring object parsed by docstring_parser.

        Returns:
            A tuple containing a `Section` (or `None`).
        """
        return_obj = docstring_obj.returns if docstring_obj.returns else []
        text = return_obj.description if return_obj else ""

        if self.context["signature"]:
            annotation = self.context["signature"].return_annotation
        else:
            annotation = self.context["annotation"]

        if annotation is empty:
            if text:
                annotation = return_obj.type_name or empty
                text = return_obj.description
            elif return_obj and annotation is empty:
                self.error("No return type annotation")

        if return_obj and not text:
            self.error("Empty return description")
        if not return_obj or annotation is empty or not text:
            return None
        return Section(Section.Type.RETURN, AnnotatedObject(annotation, text))
Exemple #4
0
    def read_exceptions_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
        """
        Parse an "exceptions" section.

        Arguments:
            lines: The exceptions block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        exceptions = []
        block, i = self.read_block_items(lines, start_index)

        for exception_line in block:
            try:
                annotation, description = exception_line.split(": ", 1)
            except ValueError:
                self.error(f"Failed to get 'exception: description' pair from '{exception_line}'")
            else:
                exceptions.append(AnnotatedObject(annotation, description.lstrip(" ")))

        if exceptions:
            return Section(Section.Type.EXCEPTIONS, exceptions), i

        self.error(f"Empty exceptions section at line {start_index}")
        return None, i
Exemple #5
0
    def read_attributes_section(
        self,
        docstring: str,
        docstring_obj: Docstring,
    ) -> Optional[Section]:
        """
        Parse an "attributes" section.

        Arguments:
            docstring_obj: Docstring object parsed by docstring_parser.

        Returns:
            A tuple containing a `Section` (or `None`).
        """
        attributes = []
        docstring_attributes = [
            p for p in docstring_obj.params if p.args[0] == "attribute"
        ]

        for attr in docstring_attributes:
            attributes.append(
                Attribute(
                    name=attr.arg_name,
                    annotation=attr.type_name,
                    description=attr.description,
                ))

        if attributes:
            return Section(Section.Type.ATTRIBUTES, attributes)
        if re.search("Attributes\n", docstring):
            self.error("Empty attributes section")
        return None
Exemple #6
0
    def read_exceptions_section(
        self,
        docstring: str,
        docstring_obj: Docstring,
    ) -> Optional[Section]:
        """
        Parse an "exceptions" section.

        Arguments:
            docstring_obj: Docstring object parsed by docstring_parser.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        exceptions = []
        except_obj = docstring_obj.raises

        for exception in except_obj:
            exceptions.append(
                AnnotatedObject(exception.type_name, exception.description))

        if exceptions:
            return Section(Section.Type.EXCEPTIONS, exceptions)
        if re.search("Raises\n", docstring):
            self.error("Empty exceptions section")
        return None
Exemple #7
0
    def read_examples_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
        """
        Parse an "examples" section.

        Arguments:
            lines: The examples block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        text, i = self.read_block(lines, start_index)

        sub_sections = []
        in_code_example = False
        in_code_block = False
        current_text: List[str] = []
        current_example: List[str] = []

        for line in text.split("\n"):
            if is_empty_line(line):
                if in_code_example:
                    if current_example:
                        sub_sections.append((Section.Type.EXAMPLES, "\n".join(current_example)))
                        current_example = []
                    in_code_example = False
                else:
                    current_text.append(line)

            elif in_code_example:
                current_example.append(line)

            elif line.startswith("```"):
                in_code_block = not in_code_block
                current_text.append(line)

            elif in_code_block:
                current_text.append(line)

            elif line.startswith(">>>"):
                if current_text:
                    sub_sections.append((Section.Type.MARKDOWN, "\n".join(current_text)))
                    current_text = []
                in_code_example = True
                current_example.append(line)

            else:
                current_text.append(line)

        if current_text:
            sub_sections.append((Section.Type.MARKDOWN, "\n".join(current_text)))
        elif current_example:
            sub_sections.append((Section.Type.EXAMPLES, "\n".join(current_example)))

        if sub_sections:
            return Section(Section.Type.EXAMPLES, sub_sections), i

        self.error(f"Empty examples section at line {start_index}")
        return None, i
Exemple #8
0
    def read_parameters_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
        """
        Parse a "parameters" section.

        Arguments:
            lines: The parameters block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        parameters = []
        type_: Any
        block, i = self.read_block_items(lines, start_index)

        for param_line in block:
            try:
                name_with_type, description = param_line.split(":", 1)
            except ValueError:
                self.error(f"Failed to get 'name: description' pair from '{param_line}'")
                continue

            description = description.lstrip()

            if " " in name_with_type:
                name, type_ = name_with_type.split(" ", 1)
                type_ = type_.strip("()")
                if type_.endswith(", optional"):
                    type_ = type_[:-10]
            else:
                name = name_with_type
                type_ = empty

            default = empty
            annotation = type_
            kind = None

            try:
                signature_param = self.context["signature"].parameters[name.lstrip("*")]  # type: ignore
            except (AttributeError, KeyError):
                self.error(f"No type annotation for parameter '{name}'")
            else:
                if signature_param.annotation is not empty:
                    annotation = signature_param.annotation
                if signature_param.default is not empty:
                    default = signature_param.default
                kind = signature_param.kind

            parameters.append(
                Parameter(name=name, annotation=annotation, description=description, default=default, kind=kind)
            )

        if parameters:
            return Section(Section.Type.PARAMETERS, parameters), i

        self.error(f"Empty parameters section at line {start_index}")
        return None, i
 def _parsed_values_to_sections(self) -> List[Section]:
     markdown_text = "\n".join(
         _strip_blank_lines(self._parsed_values.description))
     result = [Section(Section.Type.MARKDOWN, markdown_text)]
     if self._parsed_values.parameters:
         param_values = list(self._parsed_values.parameters.values())
         result.append(Section(Section.Type.PARAMETERS, param_values))
     if self._parsed_values.attributes:
         attribute_values = list(self._parsed_values.attributes.values())
         result.append(Section(Section.Type.ATTRIBUTES, attribute_values))
     if self._parsed_values.return_value is not None:
         result.append(
             Section(Section.Type.RETURN, self._parsed_values.return_value))
     if self._parsed_values.exceptions:
         result.append(
             Section(Section.Type.EXCEPTIONS,
                     self._parsed_values.exceptions))
     return result
Exemple #10
0
    def read_parameters_section(
        self,
        docstring: str,
        docstring_obj: Docstring,
    ) -> Optional[Section]:
        """
        Parse a "parameters" section.

        Arguments:
            lines: The parameters block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        parameters = []

        docstring_params = [
            p for p in docstring_obj.params if p.args[0] == "param"
        ]

        for param in docstring_params:
            name = param.arg_name
            kind = None
            type_name = param.type_name
            default = param.default or empty
            try:
                signature_param = self.context["signature"].parameters[
                    name.lstrip("*")]  # type: ignore
            except (AttributeError, KeyError):
                self.error(f"No type annotation for parameter '{name}'")
            else:
                if signature_param.annotation is not empty:
                    type_name = signature_param.annotation
                if signature_param.default is not empty:
                    default = signature_param.default
                kind = signature_param.kind
            parameters.append(
                Parameter(
                    name=param.arg_name,
                    annotation=type_name,
                    description=param.description,
                    default=default,
                    kind=kind,
                ))

        if parameters:
            return Section(Section.Type.PARAMETERS, parameters)
        if re.search("Parameters\n", docstring):
            self.error("Empty parameter section")
        return None
Exemple #11
0
    def read_attributes_section(
            self, lines: List[str],
            start_index: int) -> Tuple[Optional[Section], int]:
        """
        Parse an "attributes" section.

        Arguments:
            lines: The parameters block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        attributes = []
        block, i = self.read_block_items(lines, start_index)

        for attr_line in block:
            try:
                name_with_type, description = attr_line.split(":", 1)
            except ValueError:
                self.error(
                    f"Failed to get 'name: description' pair from '{attr_line}'"
                )
                continue

            description = description.lstrip()

            if " " in name_with_type:
                name, annotation = name_with_type.split(" ", 1)
                annotation = annotation.strip("()")
                if annotation.endswith(", optional"):
                    annotation = annotation[:-10]
            else:
                name = name_with_type
                annotation = self.context["attributes"].get(name, {}).get(
                    "annotation", empty)

            attributes.append(
                Attribute(name=name,
                          annotation=annotation,
                          description=description))

        if attributes:
            return Section(Section.Type.ATTRIBUTES, attributes), i

        self.error(f"Empty attributes section at line {start_index}")
        return None, i
Exemple #12
0
    def read_keyword_arguments_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
        """
        Parse a "keyword arguments" section.

        Arguments:
            lines: The parameters block lines.
            start_index: The line number to start at.

        Returns:
            A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
        """
        parameters, i = self._parse_parameters_section(lines, start_index)

        if parameters:
            return Section(Section.Type.KEYWORD_ARGS, parameters), i

        self.error(f"Empty keyword arguments section at line {start_index}")
        return None, i
Exemple #13
0
    def parse_sections(self, docstring: str) -> List[Section]:  # noqa: D102
        if "signature" not in self.context:
            self.context["signature"] = getattr(self.context["obj"],
                                                "signature", None)
        if "annotation" not in self.context:
            self.context["annotation"] = getattr(self.context["obj"], "type",
                                                 empty)
        if "attributes" not in self.context:
            self.context["attributes"] = {}

        docstring_obj = parse(docstring)
        description_all = (
            none_str_cast(docstring_obj.short_description) + "\n\n" +
            none_str_cast(docstring_obj.long_description)).strip()
        sections = [Section(Section.Type.MARKDOWN, description_all)
                    ] if description_all else []
        sections_other = [
            reader(docstring_obj)  # type: ignore
            if sec == Section.Type.RETURN else reader(
                docstring, docstring_obj)  # type: ignore
            for (sec, reader) in self.section_reader.items()
        ]
        sections.extend([sec for sec in sections_other if sec])
        return sections
Exemple #14
0
    def read_examples_section(
        self,
        docstring: str,
        docstring_obj: Docstring,
    ) -> Optional[Section]:
        """
        Parse an "examples" section.

        Arguments:
            docstring_obj: Docstring object parsed by docstring_parser.

        Returns:
            A tuple containing a `Section` (or `None`).
        """
        text = next(
            (meta.description for meta in docstring_obj.meta
             if isinstance(meta, DocstringMeta) and meta.args[0] == "examples"
             ),
            "",
        )

        sub_sections = []
        in_code_example = False
        in_code_block = False
        current_text: List[str] = []
        current_example: List[str] = []

        if text:
            for line in text.split("\n"):
                if is_empty_line(line):
                    if in_code_example:
                        if current_example:
                            sub_sections.append((Section.Type.EXAMPLES,
                                                 "\n".join(current_example)))
                            current_example = []
                        in_code_example = False
                    else:
                        current_text.append(line)

                elif in_code_example:
                    current_example.append(line)

                elif line.startswith("```"):
                    in_code_block = not in_code_block
                    current_text.append(line)

                elif in_code_block:
                    current_text.append(line)

                elif line.startswith(">>>"):
                    if current_text:
                        sub_sections.append(
                            (Section.Type.MARKDOWN, "\n".join(current_text)))
                        current_text = []
                    in_code_example = True
                    current_example.append(line)
                else:
                    current_text.append(line)
        if current_text:
            sub_sections.append(
                (Section.Type.MARKDOWN, "\n".join(current_text)))
        elif current_example:
            sub_sections.append(
                (Section.Type.EXAMPLES, "\n".join(current_example)))
        if sub_sections:
            return Section(Section.Type.EXAMPLES, sub_sections)
        if re.search("Examples\n", docstring):
            self.error("Empty examples section")
        return None
Exemple #15
0
    def parse_sections(self, docstring: str) -> List[Section]:  # noqa: D102
        sections = []
        current_section = []

        in_code_block = False

        lines = docstring.split("\n")
        i = 0

        while i < len(lines):
            line_lower = lines[i].lower()

            if in_code_block:
                if line_lower.lstrip(" ").startswith("```"):
                    in_code_block = False
                current_section.append(lines[i])

            elif line_lower in TITLES_PARAMETERS:
                if current_section:
                    if any(current_section):
                        sections.append(
                            Section(Section.Type.MARKDOWN,
                                    "\n".join(current_section)))
                    current_section = []
                section, i = self.read_parameters_section(lines, i + 1)
                if section:
                    sections.append(section)

            elif line_lower in TITLES_EXCEPTIONS:
                if current_section:
                    if any(current_section):
                        sections.append(
                            Section(Section.Type.MARKDOWN,
                                    "\n".join(current_section)))
                    current_section = []
                section, i = self.read_exceptions_section(lines, i + 1)
                if section:
                    sections.append(section)

            elif line_lower in TITLES_RETURN:
                if current_section:
                    if any(current_section):
                        sections.append(
                            Section(Section.Type.MARKDOWN,
                                    "\n".join(current_section)))
                    current_section = []
                section, i = self.read_return_section(lines, i + 1)
                if section:
                    sections.append(section)

            elif line_lower.lstrip(" ").startswith("```"):
                in_code_block = True
                current_section.append(lines[i])

            else:
                if self.replace_admonitions and not in_code_block and i + 1 < len(
                        lines):
                    match = RE_GOOGLE_STYLE_ADMONITION.match(lines[i])
                    if match:
                        groups = match.groupdict()
                        indent = groups["indent"]
                        if lines[i + 1].startswith(indent + " " * 4):
                            lines[i] = f"{indent}!!! {groups['type'].lower()}"
                            if groups["title"]:
                                lines[i] += f' "{groups["title"]}"'
                current_section.append(lines[i])

            i += 1

        if current_section:
            sections.append(
                Section(Section.Type.MARKDOWN, "\n".join(current_section)))

        return sections