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 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
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
from core import core_logger
from core.manager import core_plugin_manager
from core.version import VERSION
# Cvars
from cvars import ConVar
# Engines
from engines.server import execute_server_command
from engines.server import queue_command_string
Expand Down Expand Up @@ -56,7 +54,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 +67,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 Expand Up @@ -133,7 +137,7 @@ def print_credits(self):
self._log_message(message + '=' * 61 + '\n\n')

# Get the _CoreCommandManager instance
_core_command = _CoreCommandManager('sp', 'Source.Python base command.')
_core_command = _CoreCommandManager('sp')


# =============================================================================
Expand Down
12 changes: 3 additions & 9 deletions addons/source-python/packages/source-python/plugins/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,17 @@ class SubCommandManager(AutoUnload, list):
logger = plugins_command_logger
translations = _plugin_strings

def __init__(self, command, description='', prefix=''):
def __init__(self, command, prefix=''):
"""Called on instance initialization."""
# Re-call OrderedDict's __init__ to properly setup the object
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
133 changes: 108 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,115 @@
# =============================================================================
# >> 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, name, 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 name:
Name of the plugin on the file system.
: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, verbose name and version. Set it to ``False`` if you
don't want a public convar or set it to a dictionary containing
the parameters to create a :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`.
"""
super().__init__(**kwargs)
self.name = name
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

def __setattr__(self, attribute, value):
"""Redirect to __setitem__."""
# Is the attribute private?
if attribute.startswith('_'):
self.display_in_listing = [] if display_in_listing is None else display_in_listing

# Re-call __setattr__
# This is done to fix an issue with OrderedDict.__init__
super().__setattr__(attribute, value)
def _create_public_convar(self):
"""Create a public convar if :attr:`public_convar` is set to True."""
name = '{}_version'.format(self.name)
description = '{} version.'.format(self.verbose_name)
if self.public_convar is True:
self.public_convar = PublicConVar(
name,
self.version,
description
)
elif isinstance(self.public_convar, dict):
self.public_convar = PublicConVar(
self.public_convar.pop('name', name),
self.public_convar.pop('value', self.version),
self.public_convar.pop('description', description),
**self.public_convar)

# No need to go further
return
def get_verbose_name(self):
"""Return the verbose name of the plugin.

# Redirect to __setitem__
self[attribute] = value
If no verbose name has been set, the plugin name will be titled.

:rtype: str
"""
if self._verbose_name is None:
return self.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__
84 changes: 46 additions & 38 deletions addons/source-python/packages/source-python/plugins/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,64 +40,72 @@
class LoadedPlugin(object):
"""Stores a plugin's instance."""

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'):
logger = None
translations = None
prefix = None

# If not, set the default logger
self.logger = plugins_instance_logger
def __init__(self, plugin_name, manager):
"""Called when a plugin's instance is initialized.

# Does the object have a translations value set?
if not hasattr(self, 'translations'):
:param str plugin_name:
Name of the plugin to load.
:param PluginManager manager:
A plugin manager instance.
"""
self.manager = manager
self.file_path = None
self.import_name = None
self.globals = None
self.plugin_name = plugin_name
self.directory = self.manager.get_plugin_directory(plugin_name)
self.file_path = self.directory / plugin_name + '.py'
self.info = self.manager._create_plugin_info(self.plugin_name)
self.info._create_public_convar()
self._plugin = None

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

# 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(
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

# Import the plugin
self._plugin = import_module(import_name)
# Get the import name
self.import_name = (self.manager.base_import + plugin_name +
'.' + plugin_name)

# Set the globals value
self._globals = {
def _load(self):
"""Actually load the plugin."""
self._plugin = import_module(self.import_name)
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

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

If no PluginInfo was found, None will be returned.
"""
for obj in self.globals.values():
if isinstance(obj, PluginInfo):
return obj

return None
if 'load' in self.globals:
self.globals['load']()

def _unload(self):
"""Actually unload the plugin."""
if 'unload' in self.globals:
# Use a try/except here to still allow the plugin to be unloaded
try:
self.globals['unload']()
except:
except_hooks.print_exception()
Loading