diff --git a/docs/development.rst b/docs/development.rst index a706836ae..b2d5d9dad 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -16,3 +16,47 @@ Basic example of running the linter from Python: .. automodule:: yamllint.linter :members: + +Develop rule plugins +--------------------- + +yamllint provides a plugin mechanism using setuptools (pkg_resources) to allow +adding custom rules. So, you can extend yamllint and add rules with your own +custom yamllint rule plugins if you developed them. + +Yamllint rule plugins must satisfy the followings. + +#. It must be a python package installable using pip and distributed under + GPLv3+ same as yamllint. +#. It must contains the entry point configuration in setup.cfg or something + similar packaging configuration files, to make it installed and working as a + yamllint plugin like below. ( is that plugin name and + is a dir where the rule modules exist.) + :: + + [options.entry_points] + yamllint.plugins = + = + +#. It must contain custom yamllint rule modules: + + - Each rule module must define a couple of global variables, ID and TYPE. ID + must not conflicts with other rules' ID. + - Each rule module must define a function named 'check' to test input data + complies with the rule. + - Each rule module may have other global variables. + + - CONF to define its configuration parameters and those types. + - DEFAULT to provide default values for each configuration parameters. + +#. It must define a global variable RULES_MAP to provide mappings of rule ID + and rule modules to yamllint like this. + :: + + RULES_MAP = { + # rule ID: rule module + a_custom_rule.ID: a_custom_rule + } + +To develop yamllint rules, the default rules themselves in yamllint may become +good references. diff --git a/yamllint/plugins.py b/yamllint/plugins.py new file mode 100644 index 000000000..3997e56b3 --- /dev/null +++ b/yamllint/plugins.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2020 Satoru SATOH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Plugin module utilizing setuptools (pkg_resources) to allow users to add their +own custom lint rules. +""" +try: + from pkg_resources import iter_entry_points +except ImportError: + def iter_entry_points(group): + """Dummy function for pkg_resources.iter_entry_points.""" + yield + + +PACKAGE_GROUP = "yamllint.plugins" + + +def validate_rule_module(rule_mod): + """Test if given rule module is valid. + """ + return getattr(rule_mod, "ID", False + ) and callable(getattr(rule_mod, "check", False)) + + +def load_plugin_rules_itr(group=PACKAGE_GROUP): + """Load custom lint rule plugins.""" + for entry in iter_entry_points(group): + try: + rules_mod = entry.load() + yield rules_mod.RULES_MAP + except AttributeError: + pass # TBD + + +def update_rules_map(rules_map): + """Update and add plugins rules to given rules' map.""" + for plugin_rules_map in load_plugin_rules_itr(): + for rule_id, rule_mod in plugin_rules_map.items(): + if not validate_rule_module(rule_mod): + continue + + if rule_id not in rules_map: + rules_map[rule_id] = rule_mod + + return rules_map diff --git a/yamllint/rules/__init__.py b/yamllint/rules/__init__.py index a084d6ee1..03ea2a19a 100644 --- a/yamllint/rules/__init__.py +++ b/yamllint/rules/__init__.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import yamllint.plugins + from yamllint.rules import ( braces, brackets, @@ -63,8 +65,11 @@ } -def get(id): - if id not in _RULES: +def get(id, rules=None): + if rules is None: + rules = yamllint.plugins.update_rules_map(_RULES) + + if id not in rules: raise ValueError('no such rule: "%s"' % id) - return _RULES[id] + return rules[id]