Added new HelpPlugin

This patch adds the always enabled HelpPlugin.  This plugin
now will respond to the 'help' or 'h' commands that will
automatically build a help string based on the number of
enabled plugins.  It will also respond to
help <plugin> with the plugin specific help
This commit is contained in:
Hemna 2021-10-08 12:01:04 -04:00
parent 2bf85db21b
commit 3b7924b13d
6 changed files with 127 additions and 15 deletions

View File

@ -178,12 +178,22 @@ def setup_logging(config, loglevel, quiet):
default="aprsd.plugins.wx.WxPlugin",
help="The plugin to run",
)
@click.option(
"-a",
"--all",
"load_all",
show_default=True,
is_flag=True,
default=False,
help="Load all the plugins in config?",
)
@click.argument("fromcall")
@click.argument("message", nargs=-1, required=True)
def test_plugin(
loglevel,
config_file,
plugin_path,
load_all,
fromcall,
message,
):
@ -199,8 +209,12 @@ def test_plugin(
client.Client(config)
pm = plugin.PluginManager(config)
pm._init()
if load_all:
pm.setup_plugins()
else:
pm._init()
obj = pm._create_class(plugin_path, plugin.APRSDPluginBase, config=config)
# Register the plugin they wanted tested.
pm._pluggy_pm.register(obj)
login = config["aprs"]["login"]
@ -212,8 +226,9 @@ def test_plugin(
}
reply = pm.run(packet)
pm.stop()
# Plugin might have threads, so lets stop them so we can exit.
obj.stop_threads()
# obj.stop_threads()
LOG.info(f"Result = '{reply}'")

View File

@ -6,6 +6,7 @@ import inspect
import logging
import os
import re
import textwrap
import threading
import pluggy
@ -62,7 +63,7 @@ class APRSDPluginBase(metaclass=abc.ABCMeta):
self.config = config
self.message_counter = 0
self.setup()
self.threads = self.create_threads()
self.threads = self.create_threads() or []
self.start_threads()
def start_threads(self):
@ -93,6 +94,9 @@ class APRSDPluginBase(metaclass=abc.ABCMeta):
def message_count(self):
return self.message_counter
def help(self):
return "Help!"
@abc.abstractmethod
def setup(self):
"""Do any plugin setup here."""
@ -198,6 +202,12 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
"""The regex to match from the caller"""
raise NotImplementedError
def help(self):
return "{}: {}".format(
self.command_name.lower(),
self.command_regex,
)
def setup(self):
"""Do any plugin setup here."""
self.enabled = True
@ -228,6 +238,7 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
self.__class__, ex,
),
)
LOG.exception(ex)
if result:
self.tx_inc()
else:
@ -236,6 +247,66 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
return result
class HelpPlugin(APRSDRegexCommandPluginBase):
"""Help Plugin that is always enabled.
This plugin is in this file to prevent a circular import.
"""
version = "1.0"
command_regex = "^[hH]"
command_name = "help"
def help(self):
return "Help: send APRS help or help <plugin>"
def process(self, packet):
LOG.info("HelpPlugin")
# fromcall = packet.get("from")
message = packet.get("message_text", None)
# ack = packet.get("msgNo", "0")
a = re.search(r"^.*\s+(.*)", message)
command_name = None
if a is not None:
command_name = a.group(1).lower()
pm = PluginManager()
if command_name and "?" not in command_name:
# user wants help for a specific plugin
reply = None
for p in pm.get_plugins():
if (
p.enabled and isinstance(p, APRSDRegexCommandPluginBase)
and p.command_name.lower() == command_name
):
reply = p.help()
if reply:
return reply
list = []
for p in pm.get_plugins():
LOG.debug(p)
if p.enabled and isinstance(p, APRSDRegexCommandPluginBase):
name = p.command_name.lower()
if name not in list and "help" not in name:
list.append(name)
list.sort()
reply = " ".join(list)
lines = textwrap.wrap(reply, 60)
replies = ["Send APRS MSG of 'help' or 'help <plugin>'"]
for line in lines:
replies.append(f"plugins: {line}")
for entry in replies:
LOG.debug(f"{len(entry)} {entry}")
LOG.debug(f"{replies}")
return replies
class PluginManager:
# The singleton instance object for this class
_instance = None
@ -365,8 +436,12 @@ class PluginManager:
"""Create the plugin manager and register plugins."""
LOG.info("Loading APRSD Plugins")
enabled_plugins = self.config["aprsd"].get("enabled_plugins", None)
self._init()
# Help plugin is always enabled.
_help = HelpPlugin(self.config)
self._pluggy_pm.register(_help)
enabled_plugins = self.config["aprsd"].get("enabled_plugins", None)
if enabled_plugins:
for p_name in enabled_plugins:
self._load_plugin(p_name)
@ -392,6 +467,12 @@ class PluginManager:
with self.lock:
return self._pluggy_pm.hook.filter(packet=packet)
def stop(self):
"""Stop all threads created by all plugins."""
with self.lock:
for p in self.get_plugins():
p.stop_threads()
def register_msg(self, obj):
"""Register the plugin."""
self._pluggy_pm.register(obj)

View File

@ -1,6 +1,6 @@
import logging
from aprsd import messaging, packets, plugin
from aprsd import packets, plugin
LOG = logging.getLogger("APRSD")
@ -46,4 +46,3 @@ class NotifySeenPlugin(plugin.APRSDWatchListPluginBase):
wl.max_delta(),
),
)
return messaging.NULL_MESSAGE

View File

@ -54,7 +54,7 @@ class TimeOpenCageDataPlugin(TimePlugin):
version = "1.0"
command_regex = "^[tT]"
command_name = "Time"
command_name = "time"
@trace.trace
def process(self, packet):
@ -120,7 +120,7 @@ class TimeOWMPlugin(TimePlugin):
version = "1.0"
command_regex = "^[tT]"
command_name = "Time"
command_name = "time"
@trace.trace
def process(self, packet):

View File

@ -25,7 +25,7 @@ class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
version = "1.0"
command_regex = "^[wW]"
command_name = "weather"
command_name = "USWeather"
@trace.trace
def process(self, packet):
@ -88,7 +88,7 @@ class USMetarPlugin(plugin.APRSDRegexCommandPluginBase):
version = "1.0"
command_regex = "^[metar]"
command_name = "Metar"
command_name = "USMetar"
@trace.trace
def process(self, packet):
@ -180,7 +180,16 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
version = "1.0"
command_regex = "^[wW]"
command_name = "Weather"
command_name = "OpenWeatherMap"
def help(self):
_help = [
"openweathermap: Send {} to get weather "
"from your location".format(self.command_regex),
"openweathermap: Send {} <callsign> to get "
"weather from <callsign>".format(self.command_regex),
]
return _help
@trace.trace
def process(self, packet):
@ -301,7 +310,16 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
version = "1.0"
command_regex = "^[mM]"
command_name = "Weather"
command_name = "AVWXWeather"
def help(self):
_help = [
"avwxweather: Send {} to get weather "
"from your location".format(self.command_regex),
"avwxweather: Send {} <callsign> to get "
"weather from <callsign>".format(self.command_regex),
]
return _help
@trace.trace
def process(self, packet):

View File

@ -302,12 +302,11 @@ class APRSDProcessPacketThread(APRSDThread):
# If the message was for us and we didn't have a
# response, then we send a usage statement.
if tocall == self.config["aprs"]["login"] and not replied:
reply = "Usage: weather, locate [call], time, fortune, ping"
LOG.warning("Sending help!")
msg = messaging.TextMessage(
self.config["aprs"]["login"],
fromcall,
reply,
"Unknown command! Send 'help' message for help",
)
msg.send()
except Exception as ex: