Skip to content

Plugin info update #176

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 12, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Updated PluginInfo to have some static attributes
Updated documentation.
Added some attributes to LoadedPlugin.
Removed redundant hasattr() checks.
Updated output of "sp plugin list".
  • Loading branch information
Ayuto committed Dec 11, 2016
commit bc6057c59da04b2737569010d3854b4ee5f23021
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class _CoreCommandManager(SubCommandManager):

def print_plugins(self):
"""List all currently loaded plugins.

.. todo:: Move this to :class:`plugins.command.SubCommandManager`?
"""
# Get header messages
Expand All @@ -69,24 +69,30 @@ def print_plugins(self):

# Was an PluginInfo instance found?
if info is not None:
message += plugin_name + ' ({}):\n'.format(info.verbose_name)

# Add message with the current plugin's name
message += plugin_name + ':\n'
if info.author is not None:
message += ' author: {}\n'.format(info.author)

if info.description is not None:
message += ' description: {}\n'.format(info.description)

if info.version != 'unversioned':
message += ' version: {}\n'.format(info.version)

# Loop through all items in the PluginInfo instance
for item, value in info.items():
if info.url is not None:
message += ' url: {}\n'.format(info.url)

# Is the value a ConVar?
if isinstance(value, ConVar):
if info.permissions:
message += ' permissions:\n'
for permission, description in info.permissions:
message += ' {}:'.format(permission).ljust(30) + description + '\n'

# Get the ConVar's text
value = '{0}:\n\t\t\t{1}: {2}'.format(
value.name,
value.help_text,
value.get_string())
if info.public_convar is not None:
message += ' public convar: {}\n'.format(info.public_convar.name)

# Add message for the current item and its value
message += '\t{0}:\n\t\t{1}\n'.format(item, value)
for attr in info.display_in_listing:
message += ' {}:'.format(attr).ljust(20) + str(getattr(info, attr)) + '\n'

# Was no PluginInfo instance found?
else:
Expand Down
10 changes: 2 additions & 8 deletions addons/source-python/packages/source-python/plugins/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,11 @@ def __init__(self, command, description='', prefix=''):
super().__init__()

# Does the class have a proper manager object assigned?
if not (hasattr(self, 'manager') and
isinstance(self.manager, PluginManager)):

# If not, raise an error
if not isinstance(self.manager, PluginManager):
raise PluginManagerError(PluginManagerError.__doc__)

# Does the class have a proper instance class assigned?
if not (hasattr(self, 'instance') and
issubclass(self.instance, LoadedPlugin)):

# If not, raise an error
if not issubclass(self.instance, LoadedPlugin):
raise PluginInstanceError(PluginInstanceError.__doc__)

# Store the command
Expand Down
129 changes: 104 additions & 25 deletions addons/source-python/packages/source-python/plugins/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
# =============================================================================
# >> IMPORTS
# =============================================================================
# Python Imports
# Collections
from collections import OrderedDict
# Source.Python Imports
# Cvars
from cvars.public import PublicConVar


# =============================================================================
Expand All @@ -20,32 +20,111 @@
# =============================================================================
# >> CLASSES
# =============================================================================
class PluginInfo(OrderedDict):
"""Stores information for a plugin."""
class PluginInfo(dict):
"""Store information for a plugin."""

def __getattr__(self, attribute):
"""Redirect to __getitem__."""
# Is the attribute private?
if attribute.startswith('_'):
def __init__(self, verbose_name=None, author=None, description=None,
version=None, url=None, permissions=None, public_convar=True,
display_in_listing=None, **kwargs):
"""Initialize the instance.

# Raise an error
# This is done to fix an issue with OrderedDict.__init__
raise AttributeError('Private attributes not allowed')
:param str verbose_name:
A verbose name for the plugin (e.g. GunGame).
:param str author:
Name of the author.
:param str description:
A short description of what the plugin does.
:param str version:
Current version of the plugin.
:param str url:
A link to a thread in the 'Plugin Releases' forum section or the
plugin's SPPM link.
:param list permissions:
A list of permissions defined or used by the plugin. The list
should contain tuples that define the permission and a short
description of the permission.
:param public_convar:
If set to ``True``, a public convar will be generated based on the
plugin name, version and description. Set it to ``False`` if you
don't want a public convar or set it to your own
:class:`cvars.public.PublicConVar` instance.
:param list display_in_listing:
A list that contains custom attributes that should appear in the
plugin listing (e.g. sp plugin list).
:param kwargs:
Any additional attributes you want to set. If you want those
attributes to appear in the plugin listing, update
:attr:`display_in_listing`.
"""
self._verbose_name = verbose_name
self.author = author
self.description = description
self._version = version
self.url = url

# Redirect to __getitem__
return self[attribute]
# All permissions defined by this plugin
# A list that contains tuples:
# Example:
# [('test1.kick', 'Permission to kick players.'),
# ('test1.ban', 'Permission to ban players.'),
# ('test1.start_vote', 'Permission to start a vote.')]
self.permissions = [] if permissions is None else permissions
self.public_convar = public_convar

self.display_in_listing = [] if display_in_listing is None else display_in_listing

def __setattr__(self, attribute, value):
"""Redirect to __setitem__."""
# Is the attribute private?
if attribute.startswith('_'):
# A reference to its LoadedPlugin instance. Will be set by the
# LoadedPlugin instance itself
self.plugin = None

for key, value in kwargs.items():
self[key] = value

# Re-call __setattr__
# This is done to fix an issue with OrderedDict.__init__
super().__setattr__(attribute, value)

# No need to go further
def _create_public_convar(self):
"""Create a public convar if :attr:`public_convar` is set to True."""
if self.public_convar is not True:
return

# Redirect to __setitem__
self[attribute] = value
self.public_convar = PublicConVar(
'{}_version'.format(self.plugin.plugin_name),
self.version,
'{} version.'.format(self.verbose_name)
)

def get_verbose_name(self):
"""Return the verbose name of the plugin.

If no verbose name has been set, the plugin name will be titled.

:rtype: str
"""
if self._verbose_name is None:
return self.plugin.plugin_name.replace('_', ' ').title()

return self._verbose_name

def set_verbose_name(self, value):
"""Set the verbose name of the plugin."""
self._verbose_name = value

verbose_name = property(get_verbose_name, set_verbose_name)

def get_version(self):
"""Return the plugin's version.

:rtype: str
"""
if self._version is None:
return 'unversioned'

return self._version

def set_version(self, value):
"""Set the plugin's version."""
self._version = value

version = property(get_version, set_version)

# Redirect __getitem__ and __setitem__ to __getattr__ and __setattr__
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
44 changes: 25 additions & 19 deletions addons/source-python/packages/source-python/plugins/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,59 +42,65 @@ class LoadedPlugin(object):

def __init__(self, plugin_name, base_import):
"""Called when a plugin's instance is initialized."""
# Does the object have a logger set?
if not hasattr(self, 'logger'):

# If not, set the default logger
self.file_path = None
self.import_name = None
self.globals = None
self.translations = None
self.base_import = base_import
self.plugin_name = plugin_name

# Fall back to the default logger if none was set
if self.logger is None:
self.logger = plugins_instance_logger

# Does the object have a translations value set?
if not hasattr(self, 'translations'):

# If not, set the default translations
# Fall back to the default translations if none was set
if self.translations is None:
self.translations = _plugin_strings

# Print message that the plugin is going to be loaded
self.logger.log_message(self.prefix + self.translations[
'Loading'].get_string(plugin=plugin_name))

# Get the plugin's main file
file_path = PLUGIN_PATH.joinpath(*tuple(
self.file_path = PLUGIN_PATH.joinpath(*tuple(
base_import.split('.')[:~0] + [plugin_name, plugin_name + '.py']))

# Does the plugin's main file exist?
if not file_path.isfile():
if not self.file_path.isfile():

# Print a message that the plugin's main file was not found
self.logger.log_message(self.prefix + self.translations[
'No Module'].get_string(
plugin=plugin_name, file=file_path.replace(
plugin=plugin_name, file=self.file_path.replace(
GAME_PATH, '').replace('\\', '/')))

# Raise an error so that the plugin
# is not added to the PluginManager
raise PluginFileNotFoundError

# Get the base import
import_name = base_import + plugin_name + '.' + plugin_name
self.import_name = base_import + plugin_name + '.' + plugin_name

# Import the plugin
self._plugin = import_module(import_name)
self._plugin = import_module(self.import_name)

# Set the globals value
self._globals = {
self.globals = {
x: getattr(self._plugin, x) for x in dir(self._plugin)}

@property
def globals(self):
"""Return the plugin's globals."""
return self._globals
# Add this instance to the plugin info for easier access
info = self.info
if info is not None:
info.plugin = self
info._create_public_convar()

@property
def info(self):
"""Return the plugin's PluginInfo object.
"""Return the plugin's PluginInfo instance.

If no PluginInfo was found, None will be returned.

:rtype: PluginInfo
"""
for obj in self.globals.values():
if isinstance(obj, PluginInfo):
Expand Down
30 changes: 20 additions & 10 deletions addons/source-python/packages/source-python/plugins/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,18 @@ def __init__(self, base_import=''):
"""Called when the class instance is initialized."""
# Re-call OrderedDict's __init__ to properly setup the object
super().__init__()
self.logger = None
self.translations = None

# Store the base import path
self._base_import = base_import

# Does the object have a logger set?
if not hasattr(self, 'logger'):

# If not, set the default logger
if self.logger is None:
self.logger = plugins_manager_logger

# Does the object have a translations value set?
if not hasattr(self, 'translations'):

# If not, set the default translations
if self.translations is None:
self.translations = _plugin_strings

def __missing__(self, plugin_name):
Expand Down Expand Up @@ -157,15 +155,24 @@ def __delitem__(self, plugin_name):

@property
def base_import(self):
"""Return the base import path for the manager."""
"""Return the base import path for the manager.

:rtype: str
"""
return self._base_import

def is_loaded(self, plugin_name):
"""Return whether or not a plugin is loaded."""
"""Return whether or not a plugin is loaded.

:rtype: bool
"""
return plugin_name in self

def get_plugin_instance(self, plugin_name):
"""Return a plugin's instance, if it is loaded."""
"""Return a plugin's instance, if it is loaded.

:rtype: LoadedPlugin
"""
# Is the plugin loaded?
if plugin_name in self:

Expand Down Expand Up @@ -204,7 +211,10 @@ def _remove_modules(self, plugin_name):

@staticmethod
def _is_related_module(base_name, module):
"""Check if a plugin's base name is related to a module name."""
"""Check if a plugin's base name is related to a module name.

:rtype: bool
"""
return (module.startswith('{}.'.format(base_name))
or module == base_name)

Expand Down