def find_rule_classes(extra_path):
    """
    Searches a given directory or python module for rule classes. This is done by
    adding the directory path to the python path, importing the modules and then finding
    any Rule class in those modules.

    :param extra_path: absolute directory or file path to search for rule classes
    :return: The list of rule classes that are found in the given directory or module
    """

    files = []
    modules = []

    if os.path.isfile(extra_path):
        files = [os.path.basename(extra_path)]
        directory = os.path.dirname(extra_path)
    elif os.path.isdir(extra_path):
        files = os.listdir(extra_path)
        directory = extra_path
    else:
        raise UserRuleError(u"Invalid extra-path: {0}".format(extra_path))

    # Filter out files that are not python modules
    for filename in files:
        if fnmatch.fnmatch(filename, '*.py'):
            modules.append(os.path.splitext(filename)[0])

    # No need to continue if there are no modules specified
    if not modules:
        return []

    # Append the extra rules path to python path so that we can import them
    sys.path.append(directory)

    # Find all the rule classes in the found python files
    rule_classes = []
    for module in modules:
        # Import the module
        try:
            importlib.import_module(module)
        except Exception as e:
            raise UserRuleError(u"Error while importing extra-path module '{0}': {1}".format(module, ustr(e)))

        # Find all rule classes in the module. We do this my inspecting all members of the module and checking
        # 1) is it a class, if not, skip
        # 2) is the parent path the current module. If not, we are dealing with an imported class, skip
        # 3) is it a subclass of rule
        rule_classes.extend([clazz for _, clazz in inspect.getmembers(sys.modules[module])
                             if
                             inspect.isclass(clazz) and  # check isclass to ensure clazz.__module__ exists
                             clazz.__module__ == module and  # ignore imported classes
                             (issubclass(clazz, rules.LineRule) or issubclass(clazz, rules.CommitRule))])

        # validate that the rule classes are valid user-defined rules
        for rule_class in rule_classes:
            assert_valid_rule_class(rule_class)

    return rule_classes
Searches a given directory or python module for rule classes. This is done by
    adding the directory path to the python path, importing the modules and then finding
    any Rule class in those modules.

    :param extra_path: absolute directory or file path to search for rule classes
    :return: The list of rule classes that are found in the given directory or module