Skip to content

Commit cf93be5

Browse files
authored
Merge pull request #176 from Source-Python-Dev-Team/plugin_info_update
Plugin info update
2 parents c017ec0 + aaadc2d commit cf93be5

File tree

5 files changed

+326
-131
lines changed

5 files changed

+326
-131
lines changed

addons/source-python/packages/source-python/core/command/__init__.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
from core import core_logger
1717
from core.manager import core_plugin_manager
1818
from core.version import VERSION
19-
# Cvars
20-
from cvars import ConVar
2119
# Engines
2220
from engines.server import execute_server_command
2321
from engines.server import queue_command_string
@@ -56,7 +54,7 @@ class _CoreCommandManager(SubCommandManager):
5654

5755
def print_plugins(self):
5856
"""List all currently loaded plugins.
59-
57+
6058
.. todo:: Move this to :class:`plugins.command.SubCommandManager`?
6159
"""
6260
# Get header messages
@@ -69,24 +67,30 @@ def print_plugins(self):
6967

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

73-
# Add message with the current plugin's name
74-
message += plugin_name + ':\n'
72+
if info.author is not None:
73+
message += ' author: {}\n'.format(info.author)
74+
75+
if info.description is not None:
76+
message += ' description: {}\n'.format(info.description)
77+
78+
if info.version != 'unversioned':
79+
message += ' version: {}\n'.format(info.version)
7580

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

79-
# Is the value a ConVar?
80-
if isinstance(value, ConVar):
84+
if info.permissions:
85+
message += ' permissions:\n'
86+
for permission, description in info.permissions:
87+
message += ' {}:'.format(permission).ljust(30) + description + '\n'
8188

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

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

9195
# Was no PluginInfo instance found?
9296
else:
@@ -133,7 +137,7 @@ def print_credits(self):
133137
self._log_message(message + '=' * 61 + '\n\n')
134138

135139
# Get the _CoreCommandManager instance
136-
_core_command = _CoreCommandManager('sp', 'Source.Python base command.')
140+
_core_command = _CoreCommandManager('sp')
137141

138142

139143
# =============================================================================

addons/source-python/packages/source-python/plugins/command.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,17 @@ class SubCommandManager(AutoUnload, list):
4949
logger = plugins_command_logger
5050
translations = _plugin_strings
5151

52-
def __init__(self, command, description='', prefix=''):
52+
def __init__(self, command, prefix=''):
5353
"""Called on instance initialization."""
5454
# Re-call OrderedDict's __init__ to properly setup the object
5555
super().__init__()
5656

5757
# Does the class have a proper manager object assigned?
58-
if not (hasattr(self, 'manager') and
59-
isinstance(self.manager, PluginManager)):
60-
61-
# If not, raise an error
58+
if not isinstance(self.manager, PluginManager):
6259
raise PluginManagerError(PluginManagerError.__doc__)
6360

6461
# Does the class have a proper instance class assigned?
65-
if not (hasattr(self, 'instance') and
66-
issubclass(self.instance, LoadedPlugin)):
67-
68-
# If not, raise an error
62+
if not issubclass(self.instance, LoadedPlugin):
6963
raise PluginInstanceError(PluginInstanceError.__doc__)
7064

7165
# Store the command

addons/source-python/packages/source-python/plugins/info.py

Lines changed: 108 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
# =============================================================================
66
# >> IMPORTS
77
# =============================================================================
8-
# Python Imports
9-
# Collections
10-
from collections import OrderedDict
8+
# Source.Python Imports
9+
# Cvars
10+
from cvars.public import PublicConVar
1111

1212

1313
# =============================================================================
@@ -20,32 +20,115 @@
2020
# =============================================================================
2121
# >> CLASSES
2222
# =============================================================================
23-
class PluginInfo(OrderedDict):
24-
"""Stores information for a plugin."""
23+
class PluginInfo(dict):
24+
"""Store information for a plugin."""
2525

26-
def __getattr__(self, attribute):
27-
"""Redirect to __getitem__."""
28-
# Is the attribute private?
29-
if attribute.startswith('_'):
26+
def __init__(self, name, verbose_name=None, author=None, description=None,
27+
version=None, url=None, permissions=None, public_convar=True,
28+
display_in_listing=None, **kwargs):
29+
"""Initialize the instance.
3030
31-
# Raise an error
32-
# This is done to fix an issue with OrderedDict.__init__
33-
raise AttributeError('Private attributes not allowed')
31+
:param str name:
32+
Name of the plugin on the file system.
33+
:param str verbose_name:
34+
A verbose name for the plugin (e.g. GunGame).
35+
:param str author:
36+
Name of the author.
37+
:param str description:
38+
A short description of what the plugin does.
39+
:param str version:
40+
Current version of the plugin.
41+
:param str url:
42+
A link to a thread in the 'Plugin Releases' forum section or the
43+
plugin's SPPM link.
44+
:param list permissions:
45+
A list of permissions defined or used by the plugin. The list
46+
should contain tuples that define the permission and a short
47+
description of the permission.
48+
:param public_convar:
49+
If set to ``True``, a public convar will be generated based on the
50+
plugin name, verbose name and version. Set it to ``False`` if you
51+
don't want a public convar or set it to a dictionary containing
52+
the parameters to create a :class:`cvars.public.PublicConvar`
53+
instance.
54+
:param list display_in_listing:
55+
A list that contains custom attributes that should appear in the
56+
plugin listing (e.g. sp plugin list).
57+
:param kwargs:
58+
Any additional attributes you want to set. If you want those
59+
attributes to appear in the plugin listing, update
60+
:attr:`display_in_listing`.
61+
"""
62+
super().__init__(**kwargs)
63+
self.name = name
64+
self._verbose_name = verbose_name
65+
self.author = author
66+
self.description = description
67+
self._version = version
68+
self.url = url
3469

35-
# Redirect to __getitem__
36-
return self[attribute]
70+
# All permissions defined by this plugin
71+
# A list that contains tuples:
72+
# Example:
73+
# [('test1.kick', 'Permission to kick players.'),
74+
# ('test1.ban', 'Permission to ban players.'),
75+
# ('test1.start_vote', 'Permission to start a vote.')]
76+
self.permissions = [] if permissions is None else permissions
77+
self.public_convar = public_convar
3778

38-
def __setattr__(self, attribute, value):
39-
"""Redirect to __setitem__."""
40-
# Is the attribute private?
41-
if attribute.startswith('_'):
79+
self.display_in_listing = [] if display_in_listing is None else display_in_listing
4280

43-
# Re-call __setattr__
44-
# This is done to fix an issue with OrderedDict.__init__
45-
super().__setattr__(attribute, value)
81+
def _create_public_convar(self):
82+
"""Create a public convar if :attr:`public_convar` is set to True."""
83+
name = '{}_version'.format(self.name)
84+
description = '{} version.'.format(self.verbose_name)
85+
if self.public_convar is True:
86+
self.public_convar = PublicConVar(
87+
name,
88+
self.version,
89+
description
90+
)
91+
elif isinstance(self.public_convar, dict):
92+
self.public_convar = PublicConVar(
93+
self.public_convar.pop('name', name),
94+
self.public_convar.pop('value', self.version),
95+
self.public_convar.pop('description', description),
96+
**self.public_convar)
4697

47-
# No need to go further
48-
return
98+
def get_verbose_name(self):
99+
"""Return the verbose name of the plugin.
49100
50-
# Redirect to __setitem__
51-
self[attribute] = value
101+
If no verbose name has been set, the plugin name will be titled.
102+
103+
:rtype: str
104+
"""
105+
if self._verbose_name is None:
106+
return self.name.replace('_', ' ').title()
107+
108+
return self._verbose_name
109+
110+
def set_verbose_name(self, value):
111+
"""Set the verbose name of the plugin."""
112+
self._verbose_name = value
113+
114+
verbose_name = property(get_verbose_name, set_verbose_name)
115+
116+
def get_version(self):
117+
"""Return the plugin's version.
118+
119+
:rtype: str
120+
"""
121+
if self._version is None:
122+
return 'unversioned'
123+
124+
return self._version
125+
126+
def set_version(self, value):
127+
"""Set the plugin's version."""
128+
self._version = value
129+
130+
version = property(get_version, set_version)
131+
132+
# Redirect __getitem__ and __setitem__ to __getattr__ and __setattr__
133+
__getattr__ = dict.__getitem__
134+
__setattr__ = dict.__setitem__

addons/source-python/packages/source-python/plugins/instance.py

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -40,64 +40,72 @@
4040
class LoadedPlugin(object):
4141
"""Stores a plugin's instance."""
4242

43-
def __init__(self, plugin_name, base_import):
44-
"""Called when a plugin's instance is initialized."""
45-
# Does the object have a logger set?
46-
if not hasattr(self, 'logger'):
43+
logger = None
44+
translations = None
45+
prefix = None
4746

48-
# If not, set the default logger
49-
self.logger = plugins_instance_logger
47+
def __init__(self, plugin_name, manager):
48+
"""Called when a plugin's instance is initialized.
5049
51-
# Does the object have a translations value set?
52-
if not hasattr(self, 'translations'):
50+
:param str plugin_name:
51+
Name of the plugin to load.
52+
:param PluginManager manager:
53+
A plugin manager instance.
54+
"""
55+
self.manager = manager
56+
self.file_path = None
57+
self.import_name = None
58+
self.globals = None
59+
self.plugin_name = plugin_name
60+
self.directory = self.manager.get_plugin_directory(plugin_name)
61+
self.file_path = self.directory / plugin_name + '.py'
62+
self.info = self.manager._create_plugin_info(self.plugin_name)
63+
self.info._create_public_convar()
64+
self._plugin = None
65+
66+
# Fall back to the default logger if none was set
67+
if self.logger is None:
68+
self.logger = plugins_instance_logger
5369

54-
# If not, set the default translations
70+
# Fall back to the default translations if none was set
71+
if self.translations is None:
5572
self.translations = _plugin_strings
5673

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

61-
# Get the plugin's main file
62-
file_path = PLUGIN_PATH.joinpath(*tuple(
63-
base_import.split('.')[:~0] + [plugin_name, plugin_name + '.py']))
64-
6578
# Does the plugin's main file exist?
66-
if not file_path.isfile():
79+
if not self.file_path.isfile():
6780

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

7487
# Raise an error so that the plugin
7588
# is not added to the PluginManager
7689
raise PluginFileNotFoundError
7790

78-
# Get the base import
79-
import_name = base_import + plugin_name + '.' + plugin_name
80-
81-
# Import the plugin
82-
self._plugin = import_module(import_name)
91+
# Get the import name
92+
self.import_name = (self.manager.base_import + plugin_name +
93+
'.' + plugin_name)
8394

84-
# Set the globals value
85-
self._globals = {
95+
def _load(self):
96+
"""Actually load the plugin."""
97+
self._plugin = import_module(self.import_name)
98+
self.globals = {
8699
x: getattr(self._plugin, x) for x in dir(self._plugin)}
87100

88-
@property
89-
def globals(self):
90-
"""Return the plugin's globals."""
91-
return self._globals
92-
93-
@property
94-
def info(self):
95-
"""Return the plugin's PluginInfo object.
96-
97-
If no PluginInfo was found, None will be returned.
98-
"""
99-
for obj in self.globals.values():
100-
if isinstance(obj, PluginInfo):
101-
return obj
102-
103-
return None
101+
if 'load' in self.globals:
102+
self.globals['load']()
103+
104+
def _unload(self):
105+
"""Actually unload the plugin."""
106+
if 'unload' in self.globals:
107+
# Use a try/except here to still allow the plugin to be unloaded
108+
try:
109+
self.globals['unload']()
110+
except:
111+
except_hooks.print_exception()

0 commit comments

Comments
 (0)