/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2019, Google Inc. * * geometry.cpp - Geometry classes tests */ #include #include #include "test.h" using namespace std; using namespace libcamera; class GeometryTest : public Test { protected: template bool compare(const T &lhs, const T &rhs, bool (*op)(const T &lhs, const T &rhs), const char *opName, bool expect) { bool result = op(lhs, rhs); if (result != expect) { cout << lhs << opName << " " << rhs << "test failed" << std::endl; return false; } return true; } int run() { /* * Point tests */ /* Equality */ if (!compare(Point(50, 100), Point(50, 100), &operator==, "==", true)) return TestFail; if (!compare(Point(-50, 100), Point(-50, 100), &operator==, "==", true)) return TestFail; if (!compare(Point(50, -100), Point(50, -100), &operator==, "==", true)) return TestFail; if (!compare(Point(-50, -100), Point(-50, -100), &operator==, "==", true)) return TestFail; /* Inequality */ if (!compare(Point(50, 100), Point(50, 100), &operator!=, "!=", false)) return TestFail; if (!compare(Point(-50, 100), Point(-50, 100), &operator!=, "!=", false)) return TestFail; if (!compare(Point(50, -100), Point(50, -100), &operator!=, "!=", false)) return TestFail; if (!compare(Point(-50, -100), Point(-50, -100), &operator!=, "!=", false)) return TestFail; if (!compare(Point(-50, 100), Point(50, 100), &operator!=, "!=", true)) return TestFail; if (!compare(Point(50, -100), Point(50, 100), &operator!=, "!=", true)) return TestFail; if (!compare(Point(-50, -100), Point(50, 100), &operator!=, "!=", true)) return TestFail; /* Negation */ if (Point(50, 100) != -Point(-50, -100) || Point(50, 100) == -Point(50, -100) || Point(50, 100) == -Point(-50, 100)) { cout << "Point negation test failed" << endl; return TestFail; } /* Default constructor */ if (Point() != Point(0, 0)) { cout << "Default constructor test failed" << endl; return TestFail; } /* * Size tests */ if (!Size().isNull() || !Size(0, 0).isNull()) { cout << "Null size incorrectly reported as not null" << endl; return TestFail; } if (Size(0, 100).isNull() || Size(100, 0).isNull() || Size(100, 100).isNull()) { cout << "Non-null size incorrectly reported as null" << endl; return TestFail; } /* * Test alignDownTo(), alignUpTo(), boundTo(), expandTo(), * growBy() and shrinkBy() */ Size s(50, 50); s.alignDownTo(16, 16); if (s != Size(48, 48)) { cout << "Size::alignDownTo() test failed" << endl; return TestFail; } s.alignUpTo(32, 32); if (s != Size(64, 64)) { cout << "Size::alignUpTo() test failed" << endl; return TestFail; } s.boundTo({ 40, 40 }); if (s != Size(40, 40)) { cout << "Size::boundTo() test failed" << endl; return TestFail; } s.expandTo({ 50, 50 }); if (s != Size(50, 50)) { cout << "Size::expandTo() test failed" << endl; return TestFail; } s.growBy({ 10, 20 }); if (s != Size(60, 70)) { cout << "Size::growBy() test failed" << endl; return TestFail; } s.shrinkBy({ 20, 10 }); if (s != Size(40, 60)) { cout << "Size::shrinkBy() test failed" << endl; return TestFail; } s.shrinkBy({ 100, 100 }); if (s != Size(0, 0)) { cout << "Size::shrinkBy() clamp test failed" << endl; return TestFail; } s = Size(50,50).alignDownTo(16, 16).alignUpTo(32, 32) .boundTo({ 40, 80 }).expandTo({ 16, 80 }) .growBy({ 4, 4 }).shrinkBy({ 10, 20 }); if (s != Size(34, 64)) { cout << "Size chained in-place modifiers test failed" << endl; return TestFail; } /* * Test alignedDownTo(), alignedUpTo(), boundedTo(), * expandedTo(), grownBy() and shrunkBy() */ if (Size(0, 0).alignedDownTo(16, 8) != Size(0, 0) || Size(1, 1).alignedDownTo(16, 8) != Size(0, 0) || Size(16, 8).alignedDownTo(16, 8) != Size(16, 8)) { cout << "Size::alignedDownTo() test failed" << endl; return TestFail; } if (Size(0, 0).alignedUpTo(16, 8) != Size(0, 0) || Size(1, 1).alignedUpTo(16, 8) != Size(16, 8) || Size(16, 8).alignedUpTo(16, 8) != Size(16, 8)) { cout << "Size::alignedUpTo() test failed" << endl; return TestFail; } if (Size(0, 0).boundedTo({ 100, 100 }) != Size(0, 0) || Size(200, 50).boundedTo({ 100, 100 }) != Size(100, 50) || Size(50, 200).boundedTo({ 100, 100 }) != Size(50, 100)) { cout << "Size::boundedTo() test failed" << endl; return TestFail; } if (Size(0, 0).expandedTo({ 100, 100 }) != Size(100, 100) || Size(200, 50).expandedTo({ 100, 100 }) != Size(200, 100) || Size(50, 200).expandedTo({ 100, 100 }) != Size(100, 200)) { cout << "Size::expandedTo() test failed" << endl; return TestFail; } if (Size(0, 0).grownBy({ 10, 20 }) != Size(10, 20) || Size(200, 50).grownBy({ 10, 20 }) != Size(210, 70)) { cout << "Size::grownBy() test failed" << endl; return TestFail; } if (Size(200, 50).shrunkBy({ 10, 20 }) != Size(190, 30) || Size(200, 50).shrunkBy({ 10, 100 }) != Size(190, 0) || Size(200, 50).shrunkBy({ 300, 20 }) != Size(0, 30)) { cout << "Size::shrunkBy() test failed" << endl; return TestFail; } /* Aspect ratio tests */ if (Size(0, 0).boundedToAspectRatio(Size(4, 3)) != Size(0, 0) || Size(1920, 1440).boundedToAspectRatio(Size(16, 9)) != Size(1920, 1080) || Size(1920, 1440).boundedToAspectRatio(Size(65536, 36864)) != Size(1920, 1080) || Size(1440, 1920).boundedToAspectRatio(Size(9, 16)) != Size(1080, 1920) || Size(1920, 1080).boundedToAspectRatio(Size(4, 3)) != Size(1440, 1080) || Size(1920, 1080).boundedToAspectRatio(Size(65536, 49152)) != Size(1440, 1080) || Size(1024, 1024).boundedToAspectRatio(Size(1, 1)) != Size(1024, 1024) || Size(1920, 1080).boundedToAspectRatio(Size(16, 9)) != Size(1920, 1080) || Size(200, 100).boundedToAspectRatio(Size(16, 9)) != Size(177, 100) || Size(300, 200).boundedToAspectRatio(Size(16, 9)) != Size(300, 168)) { cout << "Size::boundedToAspectRatio() test failed" << endl; return TestFail; } if (Size(0, 0).expandedToAspectRatio(Size(4, 3)) != Size(0, 0) || Size(1920, 1440).expandedToAspectRatio(Size(16, 9)) != Size(2560, 1440) || Size(1920, 1440).expandedToAspectRatio(Size(65536, 36864)) != Size(2560, 1440) || Size(1440, 1920).expandedToAspectRatio(Size(9, 16)) != Size(1440, 2560) || Size(1920, 1080).expandedToAspectRatio(Size(4, 3)) != Size(1920, 1440) || Size(1920, 1080).expandedToAspectRatio(Size(65536, 49152)) != Size(1920, 1440) || Size(1024, 1024).expandedToAspectRatio(Size(1, 1)) != Size(1024, 1024) || Size(1920, 1080).expandedToAspectRatio(Size(16, 9)) != Size(1920, 1080) || Size(200, 100).expandedToAspectRatio(Size(16, 9)) != Size(200, 112) || Size(300, 200).expandedToAspectRatio(Size(16, 9)) != Size(355, 200)) { cout << "Size::expandedToAspectRatio() test failed" << endl; return TestFail; } /* Size::centeredTo() tests */ if (Size(0, 0).centeredTo(Point(50, 100)) != Rectangle(50, 100, 0, 0) || Size(0, 0).centeredTo(Point(-50, -100)) != Rectangle(-50, -100, 0, 0) || Size(100, 200).centeredTo(Point(50, 100)) != Rectangle(0, 0, 100, 200) || Size(100, 200).centeredTo(Point(-50, -100)) != Rectangle(-100, -200, 100, 200) || Size(101, 201).centeredTo(Point(-50, -100)) != Rectangle(-100, -200, 101, 201) || Size(101, 201).centeredTo(Point(-51, -101)) != Rectangle(-101, -201, 101, 201)) { cout << "Size::centeredTo() test failed" << endl; return TestFail; } /* Scale a size by a float */ if (Size(1000, 2000) * 2.0 != Size(2000, 4000) || Size(300, 100) * 0.5 != Size(150, 50) || Size(1, 2) * 1.6 != Size(1, 3)) { cout << "Size::operator*() failed" << endl; return TestFail; } if (Size(1000, 2000) / 2.0 != Size(500, 1000) || Size(300, 100) / 0.5 != Size(600, 200) || Size(1000, 2000) / 3.0 != Size(333, 666)) { cout << "Size::operator*() failed" << endl; return TestFail; } s = Size(300, 100); s *= 0.3333; if (s != Size(99, 33)) { cout << "Size::operator*() test failed" << endl; return TestFail; } s = Size(300, 100); s /= 3; if (s != Size(100, 33)) { cout << "Size::operator*() test failed" << endl; return TestFail; } /* Test Size equality and inequality. */ if (!compare(Size(100, 100), Size(100, 100), &operator==, "==", true)) return TestFail; if (!compare(Size(100, 100), Size(100, 100), &operator!=, "!=", false)) return TestFail; if (!compare(Size(100, 100), Size(200, 100), &operator==, "==", false)) return TestFail; if (!compare(Size(100, 100), Size(200, 100), &operator!=, "!=", true)) return TestFail; if (!compare(Size(100, 100), Size(100, 200), &operator==, "==", false)) return TestFail; if (!compare(Size(100, 100), Size(100, 200), &operator!=, "!=", true)) return TestFail; /* Test Size ordering based on combined with and height. */ if (!compare(Size(100, 100), Size(200, 200), &operator<, "<", true)) return TestFail; if (!compare(Size(100, 100), Size(200, 200), &operator<=, "<=", true)) return TestFail; if (!compare(Size(100, 100), Size(200, 200), &operator>, ">", false)) return TestFail; if (!compare(Size(100, 100), Size(200, 200), &operator>=, ">=", false)) return TestFail; if (!compare(Size(200, 200), Size(100, 100), &operator<, "<", false)) return TestFail; if (!compare(Size(200, 200), Size(100, 100), &operator<=, "<=", false)) return TestFail; if (!compare(Size(200, 200), Size(100, 100), &operator>, ">", true)) return TestFail; if (!compare(Size(200, 200), Size(100, 100), &operator>=, ">=", true)) return TestFail; /* Test Size ordering based on area (with overlapping sizes). */ if (!compare(Size(200, 100), Size(100, 400), &operator<, "<", true)) return TestFail; if (!compare(Size(200, 100), Size(100, 400), &operator<=, "<=", true)) return TestFail; if (!compare(Size(200, 100), Size(100, 400), &operator>, ">", false)) return TestFail; if (!compare(Size(200, 100), Size(100, 400), &operator>=, ">=", false)) return TestFail; if (!compare(Size(100, 400), Size(200, 100), &operator<, "<", false)) return TestFail; if (!compare(Size(100, 400), Size(200, 100), &operator<=, "<=", false)) return TestFail; if (!compare(Size(100, 400), Size(200, 100), &operator>, ">", true)) return TestFail; if (!compare(Size(100, 400), Size(200, 100), &operator>=, ">=", true)) return TestFail; /* Test Size ordering based on width (with identical areas). */ if (!compare(Size(100, 200), Size(200, 100), &operator<, "<", true)) return TestFail; if (!compare(Size(100, 200), Size(200, 100), &operator<=, "<=", true)) return TestFail; if (!compare(Size(100, 200), Size(200, 100), &operator>, ">", false)) return TestFail; if (!compare(Size(100, 200), Size(200, 100), &operator>=, ">=", false)) return TestFail; if (!compare(Size(200, 100), Size(100, 200), &operator<, "<", false)) return TestFail; if (!compare(Size(200, 100), Size(100, 200), &operator<=, "<=", false)) return TestFail; if (!compare(Size(200, 100), Size(100, 200), &operator>, ">", true)) return TestFail; if (!compare(Size(200, 100), Size(100, 200), &operator>=, ">=", true)) return TestFail; /* * Rectangle tests */ /* Test Rectangle::isNull(). */ if (!Rectangle(0, 0, 0, 0).isNull() || !Rectangle(1, 1, 0, 0).isNull()) { cout << "Null rectangle incorrectly reported as not null" << endl; return TestFail; } if (Rectangle(0, 0, 0, 1).isNull() || Rectangle(0, 0, 1, 0).isNull() || Rectangle(0, 0, 1, 1).isNull()) { cout << "Non-null rectangle incorrectly reported as null" << endl; return TestFail; } /* Rectangle::size(), Rectangle::topLeft() and Rectangle::center() tests */ if (Rectangle(-1, -2, 3, 4).size() != Size(3, 4) || Rectangle(0, 0, 100000, 200000).size() != Size(100000, 200000)) { cout << "Rectangle::size() test failed" << endl; return TestFail; } if (Rectangle(1, 2, 3, 4).topLeft() != Point(1, 2) || Rectangle(-1, -2, 3, 4).topLeft() != Point(-1, -2)) { cout << "Rectangle::topLeft() test failed" << endl; return TestFail; } if (Rectangle(0, 0, 300, 400).center() != Point(150, 200) || Rectangle(-1000, -2000, 300, 400).center() != Point(-850, -1800) || Rectangle(10, 20, 301, 401).center() != Point(160, 220) || Rectangle(11, 21, 301, 401).center() != Point(161, 221) || Rectangle(-1011, -2021, 301, 401).center() != Point(-861, -1821)) { cout << "Rectangle::center() test failed" << endl; return TestFail; } /* Rectangle::boundedTo() (intersection function) */ if (Rectangle(0, 0, 1000, 2000).boundedTo(Rectangle(0, 0, 1000, 2000)) != Rectangle(0, 0, 1000, 2000) || Rectangle(-500, -1000, 1000, 2000).boundedTo(Rectangle(0, 0, 1000, 2000)) != Rectangle(0, 0, 500, 1000) || Rectangle(500, 1000, 1000, 2000).boundedTo(Rectangle(0, 0, 1000, 2000)) != Rectangle(500, 1000, 500, 1000) || Rectangle(300, 400, 50, 100).boundedTo(Rectangle(0, 0, 1000, 2000)) != Rectangle(300, 400, 50, 100) || Rectangle(0, 0, 1000, 2000).boundedTo(Rectangle(300, 400, 50, 100)) != Rectangle(300, 400, 50, 100) || Rectangle(0, 0, 100, 100).boundedTo(Rectangle(50, 100, 100, 100)) != Rectangle(50, 100, 50, 0) || Rectangle(0, 0, 100, 100).boundedTo(Rectangle(100, 50, 100, 100)) != Rectangle(100, 50, 0, 50) || Rectangle(-10, -20, 10, 20).boundedTo(Rectangle(10, 20, 100, 100)) != Rectangle(10, 20, 0, 0)) { cout << "Rectangle::boundedTo() test failed" << endl; return TestFail; } /* Rectangle::enclosedIn() tests */ if (Rectangle(10, 20, 300, 400).enclosedIn(Rectangle(-10, -20, 1300, 1400)) != Rectangle(10, 20, 300, 400) || Rectangle(-100, -200, 3000, 4000).enclosedIn(Rectangle(-10, -20, 1300, 1400)) != Rectangle(-10, -20, 1300, 1400) || Rectangle(-100, -200, 300, 400).enclosedIn(Rectangle(-10, -20, 1300, 1400)) != Rectangle(-10, -20, 300, 400) || Rectangle(5100, 6200, 300, 400).enclosedIn(Rectangle(-10, -20, 1300, 1400)) != Rectangle(990, 980, 300, 400) || Rectangle(100, -300, 150, 200).enclosedIn(Rectangle(50, 0, 200, 300)) != Rectangle(100, 0, 150, 200) || Rectangle(100, -300, 150, 1200).enclosedIn(Rectangle(50, 0, 200, 300)) != Rectangle(100, 0, 150, 300) || Rectangle(-300, 100, 200, 150).enclosedIn(Rectangle(0, 50, 300, 200)) != Rectangle(0, 100, 200, 150) || Rectangle(-300, 100, 1200, 150).enclosedIn(Rectangle(0, 50, 300, 200)) != Rectangle(0, 100, 300, 150)) { cout << "Rectangle::enclosedIn() test failed" << endl; return TestFail; } /* Rectange::scaledBy() tests */ if (Rectangle(10, 20, 300, 400).scaledBy(Size(0, 0), Size(1, 1)) != Rectangle(0, 0, 0, 0) || Rectangle(10, -20, 300, 400).scaledBy(Size(32768, 65536), Size(32768, 32768)) != Rectangle(10, -40, 300, 800) || Rectangle(-30000, 10000, 20000, 20000).scaledBy(Size(7, 7), Size(7, 7)) != Rectangle(-30000, 10000, 20000, 20000) || Rectangle(-20, -30, 320, 240).scaledBy(Size(1280, 960), Size(640, 480)) != Rectangle(-40, -60, 640, 480) || Rectangle(1, 1, 2026, 1510).scaledBy(Size(4056, 3024), Size(2028, 1512)) != Rectangle(2, 2, 4052, 3020)) { cout << "Rectangle::scaledBy() test failed" << endl; return TestFail; } /* Rectangle::translatedBy() tests */ if (Rectangle(10, -20, 300, 400).translatedBy(Point(-30, 40)) != Rectangle(-20, 20, 300, 400) || Rectangle(-10, 20, 400, 300).translatedBy(Point(50, -60)) != Rectangle(40, -40, 400, 300)) { cout << "Rectangle::translatedBy() test failed" << endl; return TestFail; } /* Rectangle::scaleBy() tests */ Rectangle r(-20, -30, 320, 240); r.scaleBy(Size(1280, 960), Size(640, 480)); if (r != Rectangle(-40, -60, 640, 480)) { cout << "Rectangle::scaleBy() test failed" << endl; return TestFail; } r = Rectangle(1, 1, 2026, 1510); r.scaleBy(Size(4056, 3024), Size(2028, 1512)); if (r != Rectangle(2, 2, 4052, 3020)) { cout << "Rectangle::scaleBy() test failed" << endl; return TestFail; } /* Rectangle::translateBy() tests */ r = Rectangle(10, -20, 300, 400); r.translateBy(Point(-30, 40)); if (r != Rectangle(-20, 20, 300, 400)) { cout << "Rectangle::translateBy() test failed" << endl; return TestFail; } r = Rectangle(-10, 20, 400, 300); r.translateBy(Point(50, -60)); if (r != Rectangle(40, -40, 400, 300)) { cout << "Rectangle::translateBy() test failed" << endl; return TestFail; } return TestPass; } }; TEST_REGISTER(GeometryTest) 6'>406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611
#!/usr/bin/python3
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2018, Google Inc.
#
# Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
#
# checkstyle.py - A patch style checker script based on astyle or clang-format
#
# TODO:
#
# - Support other formatting tools and checkers (cppcheck, cpplint, kwstyle, ...)
# - Split large hunks to minimize context noise
# - Improve style issues counting
#

import argparse
import difflib
import fnmatch
import os.path
import re
import shutil
import subprocess
import sys

astyle_options = (
    '-n',
    '--style=linux',
    '--indent=force-tab=8',
    '--attach-namespaces',
    '--attach-extern-c',
    '--pad-oper',
    '--align-pointer=name',
    '--align-reference=name',
    '--keep-one-line-blocks',
    '--max-code-length=120'
)

dependencies = {
    'astyle': False,
    'clang-format': False,
    'git': True,
}

# ------------------------------------------------------------------------------
# Colour terminal handling
#

class Colours:
    Default = 0
    Black = 0
    Red = 31
    Green = 32
    Yellow = 33
    Blue = 34
    Magenta = 35
    Cyan = 36
    LightGrey = 37
    DarkGrey = 90
    LightRed = 91
    LightGreen = 92
    Lightyellow = 93
    LightBlue = 94
    LightMagenta = 95
    LightCyan = 96
    White = 97

    @staticmethod
    def fg(colour):
        if sys.stdout.isatty():
            return '\033[%um' % colour
        else:
            return ''

    @staticmethod
    def bg(colour):
        if sys.stdout.isatty():
            return '\033[%um' % (colour + 10)
        else:
            return ''

    @staticmethod
    def reset():
        if sys.stdout.isatty():
            return '\033[0m'
        else:
            return ''


# ------------------------------------------------------------------------------
# Diff parsing, handling and printing
#

class DiffHunkSide(object):
    """A side of a diff hunk, recording line numbers"""
    def __init__(self, start):
        self.start = start
        self.touched = []
        self.untouched = []

    def __len__(self):
        return len(self.touched) + len(self.untouched)


class DiffHunk(object):
    diff_header_regex = re.compile('@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@')

    def __init__(self, line):
        match = DiffHunk.diff_header_regex.match(line)
        if not match:
            raise RuntimeError("Malformed diff hunk header '%s'" % line)

        self.__from_line = int(match.group(1))
        self.__to_line = int(match.group(3))
        self.__from = DiffHunkSide(self.__from_line)
        self.__to = DiffHunkSide(self.__to_line)

        self.lines = []

    def __repr__(self):
        s = '%s@@ -%u,%u +%u,%u @@\n' % \
                (Colours.fg(Colours.Cyan),
                 self.__from.start, len(self.__from),
                 self.__to.start, len(self.__to))

        for line in self.lines:
            if line[0] == '-':
                s += Colours.fg(Colours.Red)
            elif line[0] == '+':
                s += Colours.fg(Colours.Green)

            if line[0] == '-':
                spaces = 0
                for i in range(len(line)):
                    if line[-i-1].isspace():
                        spaces += 1
                    else:
                        break
                spaces = len(line) - spaces
                line = line[0:spaces] + Colours.bg(Colours.Red) + line[spaces:]

            s += line
            s += Colours.reset()
            s += '\n'

        return s[:-1]

    def append(self, line):
        if line[0] == ' ':
            self.__from.untouched.append(self.__from_line)
            self.__from_line += 1
            self.__to.untouched.append(self.__to_line)
            self.__to_line += 1
        elif line[0] == '-':
            self.__from.touched.append(self.__from_line)
            self.__from_line += 1
        elif line[0] == '+':
            self.__to.touched.append(self.__to_line)
            self.__to_line += 1

        self.lines.append(line.rstrip('\n'))

    def intersects(self, lines):
        for line in lines:
            if line in self.__from.touched:
                return True
        return False

    def side(self, side):
        if side == 'from':
            return self.__from
        else:
            return self.__to


def parse_diff(diff):
    hunks = []
    hunk = None
    for line in diff:
        if line.startswith('@@'):
            if hunk:
                hunks.append(hunk)
            hunk = DiffHunk(line)

        elif hunk is not None:
            hunk.append(line)

    if hunk:
        hunks.append(hunk)

    return hunks


# ------------------------------------------------------------------------------
# Style Checkers
#

_style_checkers = []

class StyleCheckerRegistry(type):
    def __new__(cls, clsname, bases, attrs):
        newclass = super(StyleCheckerRegistry, cls).__new__(cls, clsname, bases, attrs)
        if clsname != 'StyleChecker':
            _style_checkers.append(newclass)
        return newclass


class StyleChecker(metaclass=StyleCheckerRegistry):
    def __init__(self):
        pass

    #
    # Class methods
    #
    @classmethod
    def checkers(cls, filename):
        for checker in _style_checkers:
            if checker.supports(filename):
                yield checker

    @classmethod
    def supports(cls, filename):
        for pattern in cls.patterns:
            if fnmatch.fnmatch(os.path.basename(filename), pattern):
                return True
        return False

    @classmethod
    def all_patterns(cls):
        patterns = set()
        for checker in _style_checkers:
            patterns.update(checker.patterns)

        return patterns


class StyleIssue(object):
    def __init__(self, line_number, line, msg):
        self.line_number = line_number
        self.line = line
        self.msg = msg


class LogCategoryChecker(StyleChecker):
    log_regex = re.compile('\\bLOG\((Debug|Info|Warning|Error|Fatal)\)')
    patterns = ('*.cpp',)

    def __init__(self, content):
        super().__init__()
        self.__content = content

    def check(self, line_numbers):
        issues = []
        for line_number in line_numbers:
            line = self.__content[line_number-1]
            if not LogCategoryChecker.log_regex.search(line):
                continue

            issues.append(StyleIssue(line_number, line, 'LOG() should use categories'))

        return issues


class MesonChecker(StyleChecker):
    patterns = ('meson.build',)

    def __init__(self, content):
        super().__init__()
        self.__content = content

    def check(self, line_numbers):
        issues = []
        for line_number in line_numbers:
            line = self.__content[line_number-1]
            if line.find('\t') != -1:
                issues.append(StyleIssue(line_number, line, 'meson.build should use spaces for indentation'))
        return issues


class Pep8Checker(StyleChecker):
    patterns = ('*.py',)
    results_regex = re.compile('stdin:([0-9]+):([0-9]+)(.*)')

    def __init__(self, content):
        super().__init__()
        self.__content = content

    def check(self, line_numbers):
        issues = []
        data = ''.join(self.__content).encode('utf-8')

        try:
            ret = subprocess.run(['pep8', '--ignore=E501', '-'],
                                 input=data, stdout=subprocess.PIPE)
        except FileNotFoundError: