Completed
Pull Request — master (#2615)
by Mischa
01:59
created

DocstyleDefinition.get_available_definitions()   A

Complexity

Conditions 4

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
c 1
b 0
f 0
dl 0
loc 17
rs 9.2
1
from collections import Iterable, namedtuple
2
from glob import iglob
3
import os.path
4
5
from coala_utils.decorators import (
6
    enforce_signature, generate_eq, generate_repr)
7
from coalib.parsing.ConfParser import ConfParser
8
9
10
@generate_repr()
11
@generate_eq("language", "docstyle", "markers")
12
class DocstyleDefinition:
13
    """
14
    The DocstyleDefinition class holds values that identify a certain type of
15
    documentation comment (for which language, documentation style/tool used
16
    etc.).
17
    """
18
    Metadata = namedtuple("Metadata", ("param_start", "param_end",
19
                                       "return_sep"))
20
21
    @enforce_signature
22
    def __init__(self, language: str, docstyle: str, markers: (Iterable, str),
23
                 metadata: Metadata):
24
        """
25
        Instantiates a new DocstyleDefinition.
26
27
        :param language: The case insensitive programming language of the
28
                         documentation comment, e.g. ``"CPP"`` for C++ or
29
                         ``"PYTHON3"``.
30
        :param docstyle: The case insensitive documentation style/tool used
31
                         to document code, e.g. ``"default"`` or ``"doxygen"``.
32
        :param markers:  An iterable of marker/delimiter string iterables
33
                         or a single marker/delimiter string iterable that
34
                         identify a documentation comment. See ``markers``
35
                         property for more details on markers.
36
        :param metadata: A namedtuple consisting of certain attributes that
37
                         form the layout of the certain documentation comment
38
                         e.g. ``param_start`` defining the start symbol of
39
                         the parameter fields and ``param_end`` defining the
40
                         end.
41
        """
42
        self._language = language.lower()
43
        self._docstyle = docstyle.lower()
44
45
        # Check and modify tuple if only one marker_set exists.
46
        markers = tuple(markers)
47
        if len(markers) == 3 and all(isinstance(x, str) for x in markers):
48
            markers = (markers,)
49
50
        self._markers = tuple(tuple(marker_set) for marker_set in markers)
51
52
        # Check marker set dimensions.
53
        for marker_set in self._markers:
54
            length = len(marker_set)
55
            if length != 3:
56
                raise ValueError("Length of a given marker set was not 3 (was "
57
                                 "actually {}).".format(length))
58
59
        self._metadata = metadata
60
61
    @property
62
    def language(self):
63
        """
64
        The programming language.
65
66
        :return: A lower-case string defining the programming language (i.e.
67
                 "cpp" or "python").
68
        """
69
        return self._language
70
71
    @property
72
    def docstyle(self):
73
        """
74
        The documentation style/tool used to document code.
75
76
        :return: A lower-case string defining the docstyle (i.e. "default" or
77
                 "doxygen").
78
        """
79
        return self._docstyle
80
81
    @property
82
    def markers(self):
83
        """
84
        A tuple of marker sets that identify a documentation comment.
85
86
        Marker sets consist of 3 entries where the first is the start-marker,
87
        the second one the each-line marker and the last one the end-marker.
88
        For example a marker tuple with a single marker set
89
        ``(("/**", "*", "*/"),)`` would match following documentation comment:
90
91
        ::
92
93
            /**
94
             * This is documentation.
95
             */
96
97
        It's also possible to supply an empty each-line marker
98
        (``("/**", "", "*/")``):
99
100
        ::
101
102
            /**
103
             This is more documentation.
104
             */
105
106
        Markers are matched "greedy", that means it will match as many
107
        each-line markers as possible. I.e. for ``("///", "///", "///")``):
108
109
        ::
110
111
            /// Brief documentation.
112
            ///
113
            /// Detailed documentation.
114
115
        :return: A tuple of marker/delimiter string tuples that identify a
116
                 documentation comment.
117
        """
118
        return self._markers
119
120
    @property
121
    def metadata(self):
122
        """
123
        A namedtuple of certain attributes present in the documentation.
124
125
        These attributes are used to define parts of the documentation.
126
        """
127
        return self._metadata
128
129
    @classmethod
130
    @enforce_signature
131
    def load(cls, language: str, docstyle: str, coalang_dir=None):
132
        """
133
        Loads a ``DocstyleDefinition`` from the coala docstyle definition files.
134
135
        This function considers all settings inside the according coalang-files
136
        as markers, except ``param_start``, ``param_end`` and ``return_sep``
137
        which are considered as special metadata markers.
138
139
        .. note::
140
141
            When placing new coala docstyle definition files, these must
142
            consist of only lowercase letters and end with ``.coalang``!
143
144
        :param language:           The case insensitive programming language of
145
                                   the documentation comment as a string.
146
        :param docstyle:           The case insensitive documentation
147
                                   style/tool used to document code, e.g.
148
                                   ``"default"`` or ``"doxygen"``.
149
        :param coalang_dir:        Path to directory with coalang docstyle
150
                                   definition files. This replaces the default
151
                                   path if given.
152
        :raises FileNotFoundError: Raised when the given docstyle was not
153
                                   found.
154
        :raises KeyError:          Raised when the given language is not
155
                                   defined for given docstyle.
156
        :return:                   The ``DocstyleDefinition`` for given language
157
                                   and docstyle.
158
        """
159
160
        docstyle = docstyle.lower()
161
162
        language_config_parser = ConfParser(remove_empty_iter_elements=False)
163
164
        coalang_file = os.path.join(
165
            coalang_dir or os.path.dirname(__file__), docstyle + ".coalang")
166
167
        try:
168
            docstyle_settings = language_config_parser.parse(coalang_file)
169
        except FileNotFoundError:
170
            raise FileNotFoundError("Docstyle definition " + repr(docstyle) +
171
                                    " not found.")
172
173
        language = language.lower()
174
175
        try:
176
            docstyle_settings = docstyle_settings[language]
177
        except KeyError:
178
            raise KeyError("Language {!r} is not defined for docstyle {!r}."
179
                           .format(language, docstyle))
180
181
        metadata_settings = ("param_start", "param_end", "return_sep")
182
183
        metadata = cls.Metadata(*(str(docstyle_settings.get(req_setting, ""))
184
                                  for req_setting in metadata_settings))
185
186
        marker_sets = (tuple(value)
187
                       for key, value in
188
                       docstyle_settings.contents.items()
189
                       if key not in metadata_settings and
190
                       not key.startswith("comment"))
191
192
        return cls(language, docstyle, marker_sets, metadata)
193
194
    @staticmethod
195
    def get_available_definitions():
196
        """
197
        Returns a sequence of pairs with ``(docstyle, language)`` which are
198
        available when using ``load()``.
199
200
        :return: A sequence of pairs with ``(docstyle, language)``.
201
        """
202
        language_config_parser = ConfParser(remove_empty_iter_elements=False)
203
        pattern = os.path.join(os.path.dirname(__file__), "*.coalang")
204
205
        for coalang_file in iglob(pattern):
206
            docstyle = os.path.splitext(os.path.basename(coalang_file))[0]
207
            # Ignore files that are not lowercase, as coalang files have to be.
208
            if docstyle.lower() == docstyle:
209
                for language in language_config_parser.parse(coalang_file):
210
                    yield docstyle, language.lower()
211