Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
320 lines (264 sloc) 12 KB
from collections import Iterable, namedtuple
from glob import iglob
import os.path
from coala_utils.decorators import (
enforce_signature, generate_eq, generate_repr)
from coalib.parsing.ConfParser import ConfParser
@generate_repr()
@generate_eq('language', 'docstyle', 'markers')
class DocstyleDefinition:
"""
The DocstyleDefinition class holds values that identify a certain type of
documentation comment (for which language, documentation style/tool used
etc.).
"""
Metadata = namedtuple('Metadata', ('param_start', 'param_end',
'exception_start', 'exception_end',
'return_sep'))
ClassPadding = namedtuple('ClassPadding',
('top_padding', 'bottom_padding'))
FunctionPadding = namedtuple('FunctionPadding',
('top_padding', 'bottom_padding'))
DocstringTypeRegex = namedtuple('DocstringTypeRegex',
('class_sign', 'func_sign'))
@enforce_signature
def __init__(self, language: str, docstyle: str, markers: (Iterable, str),
metadata: Metadata, class_padding: ClassPadding,
function_padding: FunctionPadding,
docstring_type_regex: DocstringTypeRegex,
docstring_position: str):
"""
Instantiates a new DocstyleDefinition.
:param language:
The case insensitive programming language of the
documentation comment, e.g. ``"CPP"`` for C++ or
``"PYTHON3"``.
:param docstyle:
The case insensitive documentation style/tool used
to document code, e.g. ``"default"`` or ``"doxygen"``.
:param markers:
An iterable of marker/delimiter string iterables
or a single marker/delimiter string iterable that
identify a documentation comment. See ``markers``
property for more details on markers.
:param metadata:
A namedtuple consisting of certain attributes that
form the layout of the certain documentation comment
e.g. ``param_start`` defining the start symbol of
the parameter fields and ``param_end`` defining the
end.
:param class_padding:
A namedtuple consisting of values about
blank lines before and after the documentation of
``docstring_type`` class.
:param function_padding:
A namedtuple consisting of values about
blank lines before and after the documentation of
``docstring_type`` function.
:param docstring_type_regex:
A namedtuple consisting of regex
about ``class`` and ``function`` of a language, which
is used to determine ``docstring_type`` of
DocumentationComment.
:param docstring_position:
Defines the position where the regex of
docstring type is present(i.e. ``top`` or ``bottom``).
"""
self._language = language.lower()
self._docstyle = docstyle.lower()
# Check and modify tuple if only one marker_set exists.
markers = tuple(markers)
if len(markers) == 3 and all(isinstance(x, str) for x in markers):
markers = (markers,)
self._markers = tuple(tuple(marker_set) for marker_set in markers)
# Check marker set dimensions.
for marker_set in self._markers:
length = len(marker_set)
if length != 3:
raise ValueError('Length of a given marker set was not 3 (was '
'actually {}).'.format(length))
self._metadata = metadata
self._class_padding = class_padding
self._function_padding = function_padding
self._docstring_type_regex = docstring_type_regex
self._docstring_position = docstring_position
@property
def language(self):
"""
The programming language.
:return: A lower-case string defining the programming language (i.e.
"cpp" or "python").
"""
return self._language
@property
def docstyle(self):
"""
The documentation style/tool used to document code.
:return: A lower-case string defining the docstyle (i.e. "default" or
"doxygen").
"""
return self._docstyle
@property
def markers(self):
"""
A tuple of marker sets that identify a documentation comment.
Marker sets consist of 3 entries where the first is the start-marker,
the second one the each-line marker and the last one the end-marker.
For example a marker tuple with a single marker set
``(("/**", "*", "*/"),)`` would match following documentation comment:
::
/**
* This is documentation.
*/
It's also possible to supply an empty each-line marker
(``("/**", "", "*/")``):
::
/**
This is more documentation.
*/
Markers are matched "greedy", that means it will match as many
each-line markers as possible. I.e. for ``("///", "///", "///")``):
::
/// Brief documentation.
///
/// Detailed documentation.
:return: A tuple of marker/delimiter string tuples that identify a
documentation comment.
"""
return self._markers
@property
def metadata(self):
"""
A namedtuple of certain attributes present in the documentation.
These attributes are used to define parts of the documentation.
"""
return self._metadata
@property
def class_padding(self):
"""
A namedtuple ``ClassPadding`` consisting of values about blank lines
before and after the documentation of ``docstring_type`` class.
These values are official standard of following blank lines before and
after the documentation of ``docstring_type`` class.
"""
return self._class_padding
@property
def function_padding(self):
"""
A namedtuple ``FunctionPadding`` consisting of values about blank
lines before and after the documentation of ``docstring_type``
function.
These values are official standard of following blank lines before and
after the documentation of ``docstring_type`` function.
"""
return self._function_padding
@property
def docstring_type_regex(self):
"""
A namedtuple ``DocstringTypeRegex`` consisting of regex about ``class``
and ``function`` of a language, which is used to determine
``docstring_type`` of DocumentationComment.
"""
return self._docstring_type_regex
@property
def docstring_position(self):
"""
Defines the position, where the regex of docstring type is present.
Depending on different languages the docstrings are present below or
above the defined class or function. This expicitly defines where the
class regex or function regex is present(i.e. ``top`` or ``bottom``).
"""
return self._docstring_position
@classmethod
@enforce_signature
def load(cls, language: str, docstyle: str, coalang_dir=None):
"""
Loads a ``DocstyleDefinition`` from the coala docstyle definition files.
This function considers all settings inside the according coalang-files
as markers, except ``param_start``, ``param_end`` and ``return_sep``
which are considered as special metadata markers.
.. note::
When placing new coala docstyle definition files, these must
consist of only lowercase letters and end with ``.coalang``!
:param language: The case insensitive programming language of
the documentation comment as a string.
:param docstyle: The case insensitive documentation
style/tool used to document code, e.g.
``"default"`` or ``"doxygen"``.
:param coalang_dir: Path to directory with coalang docstyle
definition files. This replaces the default
path if given.
:raises FileNotFoundError: Raised when the given docstyle was not
found.
:raises KeyError: Raised when the given language is not
defined for given docstyle.
:return: The ``DocstyleDefinition`` for given language
and docstyle.
"""
docstyle = docstyle.lower()
language_config_parser = ConfParser(remove_empty_iter_elements=False)
coalang_file = os.path.join(
coalang_dir or os.path.dirname(__file__), docstyle + '.coalang')
try:
docstyle_settings = language_config_parser.parse(coalang_file)
except FileNotFoundError:
raise FileNotFoundError('Docstyle definition ' + repr(docstyle) +
' not found.')
language = language.lower()
try:
docstyle_settings = docstyle_settings[language]
except KeyError:
raise KeyError('Language {!r} is not defined for docstyle {!r}.'
.format(language, docstyle))
metadata_settings = ('param_start', 'param_end',
'exception_start', 'exception_end',
'return_sep')
metadata = cls.Metadata(*(str(docstyle_settings.get(req_setting, ''))
for req_setting in metadata_settings))
try:
class_padding = cls.ClassPadding(
*(int(padding) for padding in tuple(
docstyle_settings['class_padding'])))
except IndexError:
class_padding = cls.ClassPadding('', '')
try:
function_padding = cls.FunctionPadding(
*(int(padding) for padding in tuple(
docstyle_settings['function_padding'])))
except IndexError:
function_padding = cls.FunctionPadding('', '')
try:
docstring_type_regex = cls.DocstringTypeRegex(
*(str(sign) for sign in tuple(
docstyle_settings['docstring_type_regex'])))
except IndexError:
docstring_type_regex = cls.DocstringTypeRegex('', '')
try:
docstring_position = docstyle_settings['docstring_position'].value
except IndexError:
docstring_position = ''
ignore_keys = (('class_padding', 'function_padding',
'docstring_type_regex', 'docstring_position') +
metadata_settings)
marker_sets = (tuple(value)
for key, value in
docstyle_settings.contents.items()
if key not in ignore_keys and
not key.startswith('comment'))
return cls(language, docstyle, marker_sets, metadata, class_padding,
function_padding, docstring_type_regex, docstring_position)
@staticmethod
def get_available_definitions():
"""
Returns a sequence of pairs with ``(docstyle, language)`` which are
available when using ``load()``.
:return: A sequence of pairs with ``(docstyle, language)``.
"""
pattern = os.path.join(os.path.dirname(__file__), '*.coalang')
for coalang_file in iglob(pattern):
docstyle = os.path.splitext(os.path.basename(coalang_file))[0]
# Ignore files that are not lowercase, as coalang files have to be.
if docstyle.lower() == docstyle:
parser = ConfParser(remove_empty_iter_elements=False)
for language in parser.parse(coalang_file):
yield docstyle, language.lower()