Module: docstring_parser

Expand source code
# Copyright (C) 2023-present The Project Contributors
#
# 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.

import re
from typing import Dict
from typing import Pattern

# Regexp for docstring parsing
__parameters_re: Pattern = re.compile(r"Parameterss+----------")
__returns_re: Pattern = re.compile(r"Returnss+-------")


class MethodDocstringParameter:  # TODO: Move to a separate directory with other helper classes
    """Represents method argument docstring."""

    name: str = None
    """ Argument name. """

    type_: str = None
    """ Argument type. """

    optional: bool = None
    """ Argument is optional. """

    comment: str = None
    """ Argument comment. """

    meta: Dict[str, str] = None
    """ Argument meta information. """


class MethodDocstring:
    """Represents method docstring."""

    comment: str = None
    """ Method comment. """

    parameters: Dict[str, MethodDocstringParameter] = None
    """ Method parameters. """

    returns: MethodDocstringParameter = None
    """ Method returns. """

    def __init__(self):
        """Initialize an instance of MethodDocstring."""
        self.parameters = dict()


class EnumItemDocstring:
    """Represents enum item docstring."""

    comment: str = None
    """Item comment."""

    label: str = None
    """ Item label. """


def _parse_param_metadata(line: str) -> Dict[str, str] | None:
    """Parse param line in format 'param_name : param_type[, ..]'."""

    param_options = line.split(",")

    # Parse first item in format '[name : ]type'
    name_type = [x.strip() for x in param_options[0].split(" : ")]

    if len(name_type) > 2:
        return None

    result = dict()
    if len(name_type) == 2:
        result["_name"] = name_type[0]
        result["_type"] = name_type[1]
    else:
        result["_type"] = name_type[0]

    # Parse param options in format 'key[=val]'
    for opt in param_options[1:]:
        key_val = [x.strip() for x in opt.split("=")]

        if len(key_val) > 2:
            return None
        elif len(key_val) == 2:
            result[key_val[0]] = key_val[1]
        else:
            result[key_val[0]] = None

    return result


def _parse_method_docstring_param(param_line: str) -> MethodDocstringParameter:
    """Parse docstring parameter line."""

    param_meta = _parse_param_metadata(param_line)

    result = MethodDocstringParameter()
    result.name = param_meta.pop("_name", None)
    result.type_ = param_meta.pop("_type", None)
    result.meta = param_meta

    if "optional" in param_meta:
        param_opt = param_meta["optional"]
        if param_opt is None or param_opt == "True":
            result.optional = True

    return result


def _parse_method_docstring_returns(returns_block: str, result: MethodDocstring):
    """Extract return docstring."""

    returns_lines = [x for x in returns_block.splitlines() if len(x) != 0 and not x.isspace()]

    if len(returns_lines) == 0:
        return

    returns_doc = _parse_method_docstring_param(returns_lines[0])

    # Extract returns comment
    if len(returns_lines) > 1:
        returns_doc.comment = returns_lines[1].strip()

    result.returns = returns_doc


def _parse_method_docstring_parameters(parameters_block: str, result: MethodDocstring):
    """Extract parameters docstring."""

    parameters_lines = [x for x in parameters_block.splitlines() if len(x) != 0 and not x.isspace()]

    # Parse parameters, check i and i+1 lines
    i = 0
    while i < len(parameters_lines):
        # Find the line in format 'param_name : param_type[, ..]'
        param_doc = _parse_method_docstring_param(parameters_lines[i])

        if param_doc.name is not None:
            result.parameters[param_doc.name] = param_doc

            # Go to next line
            i += 1
            if i >= len(parameters_lines):
                break

            # Check if next line is not param comment
            param_type_next = parameters_lines[i].split(" : ")
            if len(param_type_next) >= 2:
                continue

            # Extract param comment
            param_comment = parameters_lines[i].strip()
            param_doc.comment = param_comment

        # Go to next line
        i += 1


def parse_method_docstring(method_doc: str | None) -> MethodDocstring:
    """Parse method docstring and extracts comments."""

    result = MethodDocstring()

    if method_doc is None:
        return result

    # Extract returns block
    returns_start = __returns_re.search(method_doc)
    if returns_start is not None:
        returns_block = method_doc[returns_start.end() :]
        method_doc = method_doc[: returns_start.start()]

        _parse_method_docstring_returns(returns_block, result)

    # Extract parameters block
    parameters_start = __parameters_re.search(method_doc)
    if parameters_start is not None:
        parameters_block = method_doc[parameters_start.end() :]
        method_doc = method_doc[: parameters_start.start()]

        _parse_method_docstring_parameters(parameters_block, result)

    # Extract comment
    comment = method_doc.strip()
    if len(comment) != 0:
        result.comment = comment

    return result


def parse_enum_items_definition(enum_item_docstring: str, enum_type_name: str, enum_item: str) -> EnumItemDocstring:
    """Parse enum label in format 'Item Label = label name.(not case sensitive)."""

    result = EnumItemDocstring()
    label_exist = re.compile(r"items*labels*=", flags=re.I).search(enum_item_docstring)

    if label_exist:
        result.comment = enum_item_docstring[: label_exist.start()].strip()
        correct_setup_label_structure = len(enum_item_docstring[label_exist.start() :].split("=")) == 2

        if not correct_setup_label_structure:
            raise RuntimeError(f"Invalid {enum_type_name} item label definition in the item {enum_item}.")

        result.label = enum_item_docstring[label_exist.start() :].split("=")[1].strip()
    else:
        result.comment = enum_item_docstring.strip()

    return result

Functions

def parse_enum_items_definition(enum_item_docstring: str, enum_type_name: str, enum_item: str) -> EnumItemDocstring

Parse enum label in format ‘Item Label = label name.(not case sensitive).

def parse_method_docstring(method_doc: str | None) -> MethodDocstring

Parse method docstring and extracts comments.

Classes

class EnumItemDocstring

Represents enum item docstring.

Expand source code
class EnumItemDocstring:
    """Represents enum item docstring."""

    comment: str = None
    """Item comment."""

    label: str = None
    """ Item label. """

Class variables

var comment

Item comment.

var label

Item label.

class MethodDocstring

Represents method docstring.

Initialize an instance of MethodDocstring.

Expand source code
class MethodDocstring:
    """Represents method docstring."""

    comment: str = None
    """ Method comment. """

    parameters: Dict[str, MethodDocstringParameter] = None
    """ Method parameters. """

    returns: MethodDocstringParameter = None
    """ Method returns. """

    def __init__(self):
        """Initialize an instance of MethodDocstring."""
        self.parameters = dict()

Class variables

var comment

Method comment.

var parameters

Method parameters.

var returns

Method returns.

class MethodDocstringParameter

Represents method argument docstring.

Expand source code
class MethodDocstringParameter:  # TODO: Move to a separate directory with other helper classes
    """Represents method argument docstring."""

    name: str = None
    """ Argument name. """

    type_: str = None
    """ Argument type. """

    optional: bool = None
    """ Argument is optional. """

    comment: str = None
    """ Argument comment. """

    meta: Dict[str, str] = None
    """ Argument meta information. """

Class variables

var comment

Argument comment.

var meta

Argument meta information.

var name

Argument name.

var optional

Argument is optional.

var type_

Argument type.