docs: def assert_valid_rule_class(clazz):
      """
      Asserts that a given rule clazz is valid by checking a number of its properties:
       - Rules must extend from  LineRule or CommitRule
       - Rule classes must have id and name string attributes.
         The options_spec is optional, but if set, it must be a list of gitlint Options.
       - Rule classes must have a validate method. In case of a CommitRule, validate must take a single commit parameter.
         In case of LineRule, validate must take line and commit as first and second parameters.
       - LineRule classes must have a target class attributes that is set to either
         CommitMessageTitle or CommitMessageBody.
       - User Rule id's cannot start with R, T, B or M as these rule ids are reserved for gitlint itself.
      """
  
      # Rules must extend from LineRule or CommitRule
      if not (issubclass(clazz, rules.LineRule) or issubclass(clazz, rules.CommitRule)):
          msg = u"User-defined rule class '{0}' must extend from {1}.{2} or {1}.{3}"
          raise UserRuleError(msg.format(clazz.__name__, rules.CommitRule.__module__,
                                         rules.LineRule.__name__, rules.CommitRule.__name__))
  
      # Rules must have an id attribute
      if not hasattr(clazz, 'id') or clazz.id is None or not clazz.id:
          raise UserRuleError(u"User-defined rule class '{0}' must have an 'id' attribute".format(clazz.__name__))
  
      # Rule id's cannot start with gitlint reserved letters
      if clazz.id[0].upper() in ['R', 'T', 'B', 'M']:
          msg = u"The id '{1}' of '{0}' is invalid. Gitlint reserves ids starting with R,T,B,M"
          raise UserRuleError(msg.format(clazz.__name__, clazz.id[0]))
  
      # Rules must have a name attribute
      if not hasattr(clazz, 'name') or clazz.name is None or not clazz.name:
          raise UserRuleError(u"User-defined rule class '{0}' must have a 'name' attribute".format(clazz.__name__))
  
      # if set, options_spec must be a list of RuleOption
      if not isinstance(clazz.options_spec, list):
          msg = u"The options_spec attribute of user-defined rule class '{0}' must be a list of {1}.{2}"
          raise UserRuleError(msg.format(clazz.__name__, options.RuleOption.__module__, options.RuleOption.__name__))
  
      # check that all items in options_spec are actual gitlint options
      for option in clazz.options_spec:
          if not isinstance(option, options.RuleOption):
              msg = u"The options_spec attribute of user-defined rule class '{0}' must be a list of {1}.{2}"
              raise UserRuleError(msg.format(clazz.__name__, options.RuleOption.__module__, options.RuleOption.__name__))
  
      # Rules must have a validate method. We use isroutine() as it's both python 2 and 3 compatible.
      # For more info see http://stackoverflow.com/a/17019998/381010
      if not hasattr(clazz, 'validate') or not inspect.isroutine(clazz.validate):
          raise UserRuleError(u"User-defined rule class '{0}' must have a 'validate' method".format(clazz.__name__))
  
      # LineRules must have a valid target: rules.CommitMessageTitle or rules.CommitMessageBody
      if issubclass(clazz, rules.LineRule):
          if clazz.target not in [rules.CommitMessageTitle, rules.CommitMessageBody]:
              msg = u"The target attribute of the user-defined LineRule class '{0}' must be either {1}.{2} or {1}.{3}"
              msg = msg.format(clazz.__name__, rules.CommitMessageTitle.__module__,
                               rules.CommitMessageTitle.__name__, rules.CommitMessageBody.__name__)
              raise UserRuleError(msg)