# -*- 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.1'
#: 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)