Source code for flake8_ownership

# -*- coding: utf-8 -*-
"""
Flake8 extension for checking author, copyright, and license.

:author: Joe Joyce <joe@decafjoe.com>
:copyright: Copyright (c) Joe Joyce and contributors, 2016-2019.
:license: BSD
"""
import datetime
import re

#: Version of the extension.
#:
#: :type: :class:`str`
__version__ = '2.0.2'

#: Regex that matches the ``:author:`` line.
#:
#: :type: :func:`re <re.compile>`
author_re = re.compile(r'^:author: (?P<author>.+)$')

#: Regex that matches the ``:copyright:`` line.
#:
#: :type: :func:`re <re.compile>`
copyright_re = re.compile(r'^:copyright: (?P<copyright>.+)$')

#: Regex that matches the ``:license:`` line.
#:
#: :type: :func:`re <re.compile>`
license_re = re.compile(r'^:license: (?P<license>.+)$')


[docs]class Checker(object): """Flake8 checker class that checks for author, copyright, and license.""" #: Prefix for the error codes emitted from this class. #: #: :type: :class:`str` codes = 'O10' #: Name of the checker. #: #: :type: :class:`str` name = 'flake8_ownership' #: Version of the checker. #: #: :type: :class:`str` version = __version__ #: List of regexes of valid :author: values. #: #: :type: :class:`list` of :mod:`re` instances. author_re = None #: List of regexes of valid :copyright: values. #: #: :type: :class:`list` of :mod:`re` instances. copyright_re = None #: List of regexes of valid :license: values. #: #: :type: :class:`list` of :mod:`re` instances. license_re = None
[docs] @classmethod def add_options(cls, parser): """Add --author-re, --copyright-re, and --license-re options.""" parser.add_option( '--author-re', help='regular expression(s) for valid :author: lines', parse_from_config=True, ) parser.add_option( '--copyright-re', help='regular expression(s) for valid :copyright: lines', parse_from_config=True, ) parser.add_option( '--license-re', help='regular expression(s) for valid :license: lines', parse_from_config=True, )
@classmethod def _parse_option(cls, options, option): regexes = getattr(options, option, '') or '' regexes = regexes.split(',') if len(regexes) == 1 and regexes[0] == '': return [] rv = [] year = str(datetime.datetime.today().year) for r in regexes: regex = r.strip().replace('<COMMA>', ',').replace('<YEAR>', year) rv.append((regex, re.compile(regex))) return rv
[docs] @classmethod def parse_options(cls, options): """ Process the supplied configuration. This populates the :attr:`author_re`, :attr:`copyright_re`, and :attr:`license_re` attributes. For each configuration option, this substitutes ``<COMMA>`` and ``<YEAR>`` as appropriate, then compiles each regex. """ for option in ('author_re', 'copyright_re', 'license_re'): regexes = cls._parse_option(options, option) setattr(cls, option, [regex[1] for regex in regexes])
def __init__(self, tree, filename): """Initialize the checker; nothing special here.""" self.filename = filename self.tree = tree
[docs] def run(self): """Run the :class:`Checker` on a :attr:`filename`.""" tags = [] if self.author_re: tags.append(dict( name='author', error='0', re=author_re, expected=self.author_re, )) if self.copyright_re: tags.append(dict( name='copyright', error='1', re=copyright_re, expected=self.copyright_re, )) if self.license_re: tags.append(dict( name='license', error='2', re=license_re, expected=self.license_re, )) with open(self.filename) as f: i = 0 for line in f: i += 1 line = line[:-1] found_tag = None for tag in tags: match = tag['re'].search(line) if match: found_tag = tag value = match.groupdict()[tag['name']] for regex in tag['expected']: if regex.search(value): break else: msg = '%s%s unrecognized %s' % ( self.codes, tag['error'], tag['name'], ) yield i, 0, msg, type(self) break if found_tag: tags.remove(found_tag) if len(tags) == 0: break for tag in tags: msg = '%s%s missing %s' % (self.codes, tag['error'], tag['name']) yield 0, 0, msg, type(self)