Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/source/plugin/anatomy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ Sopel identifies a callable as a rule when it has been decorated with any of
these decorators from :mod:`sopel.plugin`:

* :term:`Generic rule`: :func:`~sopel.plugin.rule`,
:func:`~sopel.plugin.find`, and :func:`~sopel.plugin.search`
:func:`~sopel.plugin.find`, and :func:`~sopel.plugin.search` (and their lazy
versions: :func:`~sopel.plugin.rule_lazy`, :func:`~sopel.plugin.find_lazy`,
and :func:`~sopel.plugin.search_lazy`)
* :term:`Named rule`: :func:`~sopel.plugin.command`,
:func:`~sopel.plugin.action_command`, and
:func:`~sopel.plugin.nickname_command`
* :term:`URL callback`: :func:`~sopel.plugin.url` and
:func:`~sopel.plugin.url_lazy`
* :term:`URL callback`: :func:`~sopel.plugin.url` (and its lazy version,
:func:`~sopel.plugin.url_lazy`)

Additionally, Sopel identifies a callable as a generic rule when these
decorators are used alone:
Expand Down
36 changes: 35 additions & 1 deletion sopel/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,26 +592,60 @@ def register_callables(self, callables):

for callbl in callables:
rules = getattr(callbl, 'rule', [])
lazy_rules = getattr(callbl, 'rule_lazy_loaders', [])
find_rules = getattr(callbl, 'find_rules', [])
lazy_find_rules = getattr(callbl, 'find_rules_lazy_loaders', [])
search_rules = getattr(callbl, 'search_rules', [])
lazy_search_rules = getattr(callbl, 'search_rules_lazy_loaders', [])
commands = getattr(callbl, 'commands', [])
nick_commands = getattr(callbl, 'nickname_commands', [])
action_commands = getattr(callbl, 'action_commands', [])
is_rule = any([rules, find_rules, search_rules])
is_rule = any([
rules,
lazy_rules,
find_rules,
lazy_find_rules,
search_rules,
lazy_search_rules,
])
is_command = any([commands, nick_commands, action_commands])

if rules:
rule = plugin_rules.Rule.from_callable(settings, callbl)
self._rules_manager.register(rule)

if lazy_rules:
try:
rule = plugin_rules.Rule.from_callable_lazy(
settings, callbl)
self._rules_manager.register(rule)
except plugins.exceptions.PluginError as err:
LOGGER.error('Cannot register rule: %s', err)

if find_rules:
rule = plugin_rules.FindRule.from_callable(settings, callbl)
self._rules_manager.register(rule)

if lazy_find_rules:
try:
rule = plugin_rules.FindRule.from_callable_lazy(
settings, callbl)
self._rules_manager.register(rule)
except plugins.exceptions.PluginError as err:
LOGGER.error('Cannot register find rule: %s', err)

if search_rules:
rule = plugin_rules.SearchRule.from_callable(settings, callbl)
self._rules_manager.register(rule)

if lazy_search_rules:
try:
rule = plugin_rules.SearchRule.from_callable_lazy(
settings, callbl)
self._rules_manager.register(rule)
except plugins.exceptions.PluginError as err:
LOGGER.error('Cannot register search rule: %s', err)

if commands:
rule = plugin_rules.Command.from_callable(settings, callbl)
self._rules_manager.register_command(rule)
Expand Down
6 changes: 6 additions & 0 deletions sopel/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,11 @@ def is_limitable(obj):

allowed_attrs = (
'rule',
'rule_lazy_loaders',
'find_rules',
'find_rules_lazy_loaders',
'search_rules',
'search_rules_lazy_loaders',
'event',
'intents',
'commands',
Expand Down Expand Up @@ -199,8 +202,11 @@ def is_triggerable(obj):

allowed_attrs = (
'rule',
'rule_lazy_loaders',
'find_rules',
'find_rules_lazy_loaders',
'search_rules',
'search_rules_lazy_loaders',
'event',
'intents',
'commands',
Expand Down
147 changes: 147 additions & 0 deletions sopel/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
'event',
'example',
'find',
'find_lazy',
'interval',
'label',
'nickname_command',
Expand All @@ -41,7 +42,9 @@
'require_privilege',
'require_privmsg',
'rule',
'rule_lazy',
'search',
'search_lazy',
'thread',
'unblockable',
'url',
Expand Down Expand Up @@ -245,6 +248,54 @@ def add_attribute(function):
return add_attribute


def rule_lazy(*loaders):
"""Decorate a callable as a rule with lazy loading.

:param loaders: one or more functions to generate a list of **compiled**
regexes to match URLs
:type loaders: :term:`function`

Each ``loader`` function must accept a ``settings`` parameter and return a
list (or tuple) of **compiled** regular expressions::

import re

def loader(settings):
return [re.compile(r'<your_rule_pattern>')]

It will be called by Sopel when the bot parses the plugin to register rules
to get its regexes. The ``settings`` argument will be the bot's
:class:`sopel.config.Config` object.

If any of the ``loader`` functions raises a
:exc:`~sopel.plugins.exceptions.PluginError` exception, the rule will be
ignored; it will not fail the plugin's loading.

The decorated function will behave like any other :func:`callable`::

from sopel import plugin

@plugin.rule_lazy(loader)
def my_rule_handler(bot, trigger):
bot.say('Rule triggered by: %s' % trigger.group(0))

.. versionadded:: 7.1

.. seealso::

When more than one loader is provided, they will be chained together
with the :func:`sopel.tools.chain_loaders` function.

"""
def decorator(function):
function._sopel_callable = True
if not hasattr(function, 'rule_lazy_loaders'):
function.rule_lazy_loaders = []
function.rule_lazy_loaders.extend(loaders)
return function
return decorator


def find(*patterns):
"""Decorate a function to be called for each time a pattern is found in a line.

Expand Down Expand Up @@ -300,6 +351,54 @@ def add_attribute(function):
return add_attribute


def find_lazy(*loaders):
"""Decorate a callable as a find rule with lazy loading.

:param loaders: one or more functions to generate a list of **compiled**
regexes to match patterns in a line
:type loaders: :term:`function`

Each ``loader`` function must accept a ``settings`` parameter and return a
list (or tuple) of **compiled** regular expressions::

import re

def loader(settings):
return [re.compile(r'<your_rule_pattern>')]

It will be called by Sopel when the bot parses the plugin to register the
find rules to get its regexes. The ``settings`` argument will be the bot's
:class:`sopel.config.Config` object.

If any of the ``loader`` functions raises a
:exc:`~sopel.plugins.exceptions.PluginError` exception, the find rule will
be ignored; it will not fail the plugin's loading.

The decorated function will behave like any other :func:`callable`::

from sopel import plugin

@plugin.find_lazy(loader)
def my_find_rule_handler(bot, trigger):
bot.say('Rule triggered by: %s' % trigger.group(0))

.. versionadded:: 7.1

.. seealso::

When more than one loader is provided, they will be chained together
with the :func:`sopel.tools.chain_loaders` function.

"""
def decorator(function):
function._sopel_callable = True
if not hasattr(function, 'find_rules_lazy_loaders'):
function.find_rules_lazy_loaders = []
function.find_rules_lazy_loaders.extend(loaders)
return function
return decorator


def search(*patterns):
"""Decorate a function to be called when a pattern matches anywhere in a line.

Expand Down Expand Up @@ -358,6 +457,54 @@ def add_attribute(function):
return add_attribute


def search_lazy(*loaders):
"""Decorate a callable as a search rule with lazy loading.

:param loaders: one or more functions to generate a list of **compiled**
regexes to match patterns in a line
:type loaders: :term:`function`

Each ``loader`` function must accept a ``settings`` parameter and return a
list (or tuple) of **compiled** regular expressions::

import re

def loader(settings):
return [re.compile(r'<your_rule_pattern>')]

It will be called by Sopel when the bot parses the plugin to register the
search rules to get its regexes. The ``settings`` argument will be the
bot's :class:`sopel.config.Config` object.

If any of the ``loader`` functions raises a
:exc:`~sopel.plugins.exceptions.PluginError` exception, the find rule will
be ignored; it will not fail the plugin's loading.

The decorated function will behave like any other :func:`callable`::

from sopel import plugin

@plugin.search_lazy(loader)
def my_search_rule_handler(bot, trigger):
bot.say('Rule triggered by: %s' % trigger.group(0))

.. versionadded:: 7.1

.. seealso::

When more than one loader is provided, they will be chained together
with the :func:`sopel.tools.chain_loaders` function.

"""
def decorator(function):
function._sopel_callable = True
if not hasattr(function, 'search_rules_lazy_loaders'):
function.search_rules_lazy_loaders = []
function.search_rules_lazy_loaders.extend(loaders)
return function
return decorator


def thread(value):
"""Decorate a function to specify if it should be run in a separate thread.

Expand Down
Loading