-
-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #133 from nucleic/feature-plugins
Feature plugins
- Loading branch information
Showing
35 changed files
with
3,343 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#------------------------------------------------------------------------------ | ||
# Copyright (c) 2013, Nucleic Development Team. | ||
# | ||
# Distributed under the terms of the Modified BSD License. | ||
# | ||
# The full license is in the file COPYING.txt, distributed with this software. | ||
#------------------------------------------------------------------------------ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#------------------------------------------------------------------------------ | ||
# Copyright (c) 2013, Nucleic Development Team. | ||
# | ||
# Distributed under the terms of the Modified BSD License. | ||
# | ||
# The full license is in the file COPYING.txt, distributed with this software. | ||
#------------------------------------------------------------------------------ | ||
from .extension import Extension | ||
from .extension_point import ExtensionPoint | ||
from .plugin import Plugin | ||
from .plugin_manifest import PluginManifest | ||
from .workbench import Workbench |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#------------------------------------------------------------------------------ | ||
# Copyright (c) 2013, Nucleic Development Team. | ||
# | ||
# Distributed under the terms of the Modified BSD License. | ||
# | ||
# The full license is in the file COPYING.txt, distributed with this software. | ||
#------------------------------------------------------------------------------ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#------------------------------------------------------------------------------ | ||
# Copyright (c) 2013, Nucleic Development Team. | ||
# | ||
# Distributed under the terms of the Modified BSD License. | ||
# | ||
# The full license is in the file COPYING.txt, distributed with this software. | ||
#------------------------------------------------------------------------------ | ||
from .command import Command |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#------------------------------------------------------------------------------ | ||
# Copyright (c) 2013, Nucleic Development Team. | ||
# | ||
# Distributed under the terms of the Modified BSD License. | ||
# | ||
# The full license is in the file COPYING.txt, distributed with this software. | ||
#------------------------------------------------------------------------------ | ||
from atom.api import Callable, Unicode | ||
|
||
from enaml.core.declarative import Declarative, d_ | ||
|
||
|
||
class Command(Declarative): | ||
""" A declarative class for defining a workbench command. | ||
""" | ||
#: The globally unique identifier for the command. | ||
id = d_(Unicode()) | ||
|
||
#: An optional description of the command. | ||
description = d_(Unicode()) | ||
|
||
#: A required callable which handles the command. It must accept a | ||
#: single argument, which is an instance of ExecutionEvent. | ||
handler = d_(Callable()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#------------------------------------------------------------------------------ | ||
# Copyright (c) 2013, Nucleic Development Team. | ||
# | ||
# Distributed under the terms of the Modified BSD License. | ||
# | ||
# The full license is in the file COPYING.txt, distributed with this software. | ||
#------------------------------------------------------------------------------ | ||
from enaml.workbench.extension_point import ExtensionPoint | ||
from enaml.workbench.plugin_manifest import PluginManifest | ||
|
||
|
||
def core_plugin_factory(): | ||
""" A factory function which creates a CorePlugin instance. | ||
|
||
""" | ||
from .core_plugin import CorePlugin | ||
return CorePlugin() | ||
|
||
|
||
COMMANDS_DESCRIPTION = \ | ||
""" Extensions to this point may contribute `Command` objects which can | ||
be invoked via the `invoke_command` method of the CorePlugin instance. | ||
Commands can be provided by declaring them as children of the Extension | ||
and/or by declaring a factory function which takes the workbench as an | ||
argument and returns a list of Command instances. """ | ||
|
||
|
||
enamldef CoreManifest(PluginManifest): | ||
""" The manifest for the Enaml workbench core plugin. | ||
|
||
""" | ||
id = 'enaml.workbench.core' | ||
factory = core_plugin_factory | ||
ExtensionPoint: | ||
id = 'commands' | ||
description = COMMANDS_DESCRIPTION |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
#------------------------------------------------------------------------------ | ||
# Copyright (c) 2013, Nucleic Development Team. | ||
# | ||
# Distributed under the terms of the Modified BSD License. | ||
# | ||
# The full license is in the file COPYING.txt, distributed with this software. | ||
#------------------------------------------------------------------------------ | ||
from collections import defaultdict | ||
|
||
from atom.api import Typed | ||
|
||
from enaml.workbench.plugin import Plugin | ||
|
||
from .command import Command | ||
from .execution_event import ExecutionEvent | ||
|
||
|
||
COMMANDS_POINT = u'enaml.workbench.core.commands' | ||
|
||
|
||
class CorePlugin(Plugin): | ||
""" The core plugin for the Enaml workbench. | ||
""" | ||
def start(self): | ||
""" Start the plugin life-cycle. | ||
This method is called by the framework at the appropriate time. | ||
It should never be called by user code. | ||
""" | ||
self._refresh_commands() | ||
self._bind_observers() | ||
|
||
def stop(self): | ||
""" Stop the plugin life-cycle. | ||
This method is called by the framework at the appropriate time. | ||
It should never be called by user code. | ||
""" | ||
self._unbind_observers() | ||
self._commands.clear() | ||
self._command_extensions.clear() | ||
|
||
def invoke_command(self, command_id, parameters={}, trigger=None): | ||
""" Invoke the command handler for the given command id. | ||
Parameters | ||
---------- | ||
command_id : unicode | ||
The unique identifier of the command to invoke. | ||
parameters : dict, optional | ||
The parameters to pass to the command handler. | ||
trigger : object, optional | ||
The object which triggered the command. | ||
""" | ||
if command_id not in self._commands: | ||
msg = "'%s' is not a registered command id" | ||
raise ValueError(msg % command_id) | ||
|
||
command = self._commands[command_id] | ||
|
||
event = ExecutionEvent() | ||
event.command = command | ||
event.workbench = self.workbench | ||
event.parameters = parameters | ||
event.trigger = trigger | ||
|
||
command.handler(event) | ||
|
||
#-------------------------------------------------------------------------- | ||
# Private API | ||
#-------------------------------------------------------------------------- | ||
#: The mapping of command id to Command object. | ||
_commands = Typed(dict, ()) | ||
|
||
#: The mapping of extension object to list of Command objects. | ||
_command_extensions = Typed(defaultdict, (list,)) | ||
|
||
def _refresh_commands(self): | ||
""" Refresh the command objects for the plugin. | ||
""" | ||
workbench = self.workbench | ||
point = workbench.get_extension_point(COMMANDS_POINT) | ||
extensions = point.extensions | ||
if not extensions: | ||
self._commands.clear() | ||
self._command_extensions.clear() | ||
return | ||
|
||
new_extensions = defaultdict(list) | ||
old_extensions = self._command_extensions | ||
for extension in extensions: | ||
if extension in old_extensions: | ||
commands = old_extensions[extension] | ||
else: | ||
commands = self._load_commands(extension) | ||
new_extensions[extension].extend(commands) | ||
|
||
commands = {} | ||
for extension in extensions: | ||
for command in new_extensions[extension]: | ||
if command.id in commands: | ||
msg = "command '%s' is already registered" | ||
raise ValueError(msg % command.id) | ||
if command.handler is None: | ||
msg = "command '%s' does not declare a handler" | ||
raise ValueError(msg % command.id) | ||
commands[command.id] = command | ||
|
||
self._commands = commands | ||
self._command_extensions = new_extensions | ||
|
||
def _load_commands(self, extension): | ||
""" Load the command objects for the given extension. | ||
Parameters | ||
---------- | ||
extension : Extension | ||
The extension object of interest. | ||
Returns | ||
------- | ||
result : list | ||
The list of Command objects declared by the extension. | ||
""" | ||
workbench = self.workbench | ||
commands = extension.get_children(Command) | ||
if extension.factory is not None: | ||
for item in extension.factory(workbench): | ||
if not isinstance(item, Command): | ||
msg = "extension '%s' created non-Command of type '%s'" | ||
args = (extension.qualified_id, type(item).__name__) | ||
raise TypeError(msg % args) | ||
commands.append(item) | ||
return commands | ||
|
||
def _on_commands_updated(self, change): | ||
""" The observer for the commands extension point. | ||
""" | ||
self._refresh_commands() | ||
|
||
def _bind_observers(self): | ||
""" Setup the observers for the plugin. | ||
""" | ||
workbench = self.workbench | ||
point = workbench.get_extension_point(COMMANDS_POINT) | ||
point.observe('extensions', self._on_commands_updated) | ||
|
||
def _unbind_observers(self): | ||
""" Remove the observers for the plugin. | ||
""" | ||
workbench = self.workbench | ||
point = workbench.get_extension_point(COMMANDS_POINT) | ||
point.unobserve('extensions', self._on_commands_updated) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
#------------------------------------------------------------------------------ | ||
# Copyright (c) 2013, Nucleic Development Team. | ||
# | ||
# Distributed under the terms of the Modified BSD License. | ||
# | ||
# The full license is in the file COPYING.txt, distributed with this software. | ||
#------------------------------------------------------------------------------ | ||
from atom.api import Atom, Dict, Typed, Value | ||
|
||
from enaml.workbench.workbench import Workbench | ||
|
||
from .command import Command | ||
|
||
|
||
class ExecutionEvent(Atom): | ||
""" The object passed to a command handler when it is invoked. | ||
""" | ||
#: The command which is being invoked. | ||
command = Typed(Command) | ||
|
||
#: The workbench instance which owns the command. | ||
workbench = Typed(Workbench) | ||
|
||
#: The user-supplied parameters for the command. | ||
parameters = Dict() | ||
|
||
#: The user-object object which triggered the command. | ||
trigger = Value() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
#------------------------------------------------------------------------------ | ||
# Copyright (c) 2013, Nucleic Development Team. | ||
# | ||
# Distributed under the terms of the Modified BSD License. | ||
# | ||
# The full license is in the file COPYING.txt, distributed with this software. | ||
#------------------------------------------------------------------------------ | ||
from atom.api import Callable, Int, Unicode | ||
|
||
from enaml.core.declarative import Declarative, d_ | ||
|
||
|
||
class Extension(Declarative): | ||
""" A declarative class which represents a plugin extension. | ||
An Extension must be declared as a child of a PluginManifest. | ||
""" | ||
#: The globally unique identifier for the extension. | ||
id = d_(Unicode()) | ||
|
||
#: The fully qualified id of the target extension point. | ||
point = d_(Unicode()) | ||
|
||
#: An optional rank to use for order the extension among others. | ||
rank = d_(Int()) | ||
|
||
#: A callable which will create the implementation object for the | ||
#: extension point. The call signature and return type are defined | ||
#: by the extension point plugin which invokes the factory. | ||
factory = d_(Callable()) | ||
|
||
#: An optional description of the extension. | ||
description = d_(Unicode()) | ||
|
||
@property | ||
def plugin_id(self): | ||
""" Get the plugin id from the parent plugin manifest. | ||
""" | ||
return self.parent.id | ||
|
||
@property | ||
def qualified_id(self): | ||
""" Get the fully qualified extension identifer. | ||
""" | ||
this_id = self.id | ||
if u'.' in this_id: | ||
return this_id | ||
return u'%s.%s' % (self.plugin_id, this_id) | ||
|
||
def get_child(self, kind, reverse=False): | ||
""" Find a child by the given type. | ||
Parameters | ||
---------- | ||
kind : type | ||
The declartive type of the child of interest. | ||
reverse : bool, optional | ||
Whether to search in reversed order. The default is False. | ||
Returns | ||
------- | ||
result : child or None | ||
The first child found of the requested type. | ||
""" | ||
it = reversed if reverse else iter | ||
for child in it(self.children): | ||
if isinstance(child, kind): | ||
return child | ||
return None | ||
|
||
def get_children(self, kind): | ||
""" Get all the children of the given type. | ||
Parameters | ||
---------- | ||
kind : type | ||
The declartive type of the children of interest. | ||
Returns | ||
------- | ||
result : list | ||
The list of children of the request type. | ||
""" | ||
return [c for c in self.children if isinstance(c, kind)] |
Oops, something went wrong.