Last active
November 4, 2015 19:57
-
-
Save abargnesi/8c9e27620b8ce0369dcb to your computer and use it in GitHub Desktop.
plugin proof of concept
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
module Hardwire::Plugins | |
module Bar | |
class BarPlugin | |
include Hardwire::PluginDefinition | |
def self.plugin_file_path | |
__FILE__ | |
end | |
def identifiers | |
:bar | |
end | |
def name | |
:Bar | |
end | |
def create(options = {}) | |
BarShazam.new | |
end | |
end | |
class BarShazam | |
def drink(number = 1) | |
number.times do | |
puts "gulp..." | |
sleep 3 | |
end | |
end | |
end | |
end | |
end |
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
module Hardwire::Plugins | |
module Foo | |
class FooPlugin | |
include Hardwire::PluginDefinition | |
def self.plugin_file_path | |
__FILE__ | |
end | |
def identifiers | |
:foo | |
end | |
def name | |
:Foo | |
end | |
def create(options = {}) | |
FooShazam.new | |
end | |
end | |
class FooShazam | |
def learn(skill) | |
puts "meditating..." | |
sleep 3 | |
puts "* you know #{skill}" | |
end | |
end | |
end | |
end |
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
# Usage | |
# | |
# Require: | |
# require './hardwire_vendored.rb' | |
# Load a Container given a base path found on $LOAD_PATH. | |
# Hardwire::Container.new('hardwire/examples') | |
# Retrieve all available plugins in Container. | |
# Hardwire::Container.new('hardwire/examples').plugins.to_a | |
# Use a plugin in a Container. This activates then creates the identified | |
# plugin. | |
# Hardwire::Container.new('hardwire/examples').use(:foo) | |
module Hardwire | |
VERSION = "0.1.0" | |
end | |
module Hardwire | |
module Plugins | |
# Single, global namespace where plugins are defined. | |
# Having a global namespace could cause overlap if multiple instances of | |
# Hardwire are used in the same process, but this wouldn't be a short-term | |
# problem worth accounting for now. | |
end | |
end | |
module Hardwire | |
# Defines a contract that a plugin instance should respond to. Methods that | |
# need to be overriden will throw {NotImplementedError}. Methods that are | |
# optional will be empty (i.e. no-op). | |
module PluginDefinition | |
def self.included(base) | |
base.extend ClassMethods | |
base.include InstanceMethods | |
end | |
module ClassMethods | |
def plugin_file_path | |
msg = %Q{ | |
#{__method__} is not implemented. | |
The plugin_file_path method must be defined to return __FILE__ | |
so that plugin containers know if its defined in *their* base path. | |
__FILE__ is lexically scoped so it must be defined within the file | |
defining the plugin definition (e.g. foo/def.rb). | |
} | |
raise NotImplementedError.new(msg) | |
end | |
end | |
module InstanceMethods | |
# Returns the identifiers that can be used to find this plugin. | |
def identifiers | |
raise NotImplementedError.new("#{__method__} is not implemented.") | |
end | |
# Returns the name of this plugin. | |
def name | |
raise NotImplementedError.new("#{__method__} is not implemented.") | |
end | |
# Returns the description of this plugin. | |
def description | |
end | |
# Activates this plugin for use. Any exceptions raised will result in a | |
# call to {#on_failed_activation}, passing the raised exception, to allow | |
# this plugin to provide assistance. | |
# | |
# Example usage: | |
# | |
# - require plugin code plus dependencies | |
# - initial configuration | |
# - test external connection to database | |
def activate | |
end | |
# Create this plugin for use and returns a plugin object for use. | |
def create(options = {}) | |
raise NotImplementedError.new("#{__method__} is not implemented.") | |
end | |
# Called when this plugin has been successfully activated. | |
def on_successful_activation | |
end | |
# Called when this plugin has failed to activate. In this case an | |
# exception would have occurred. | |
def on_failed_activation(exception) | |
#TODO Can we reraise with #activate is on the top of the backtrace? | |
raise exception | |
end | |
end | |
end | |
end | |
require 'pathname' | |
require 'rubygems' | |
module Hardwire::Mixin | |
module Query | |
# Retrieves plugins that are available on the {$LOAD_PATH} given the | |
# {#plugin_base_path} in {Hardwire::Container}. | |
def plugins(*ids) | |
plugin_definition_paths.each do |path| | |
Kernel.require path | |
end | |
defs = plugin_definition_classes | |
extensions = defs.map { |desc| desc.new } | |
if block_given? | |
extensions.each do |ext| | |
yield ext | |
end | |
else | |
Enumerator.new do |yielder| | |
extensions.each do |ext| | |
yielder << ext | |
end | |
end | |
end | |
end | |
# Retrieves the objects that include {included_mod}, starting from a base | |
# {mod} module. | |
def plugin_definition_classes( | |
mod = Hardwire::Plugins, | |
included_mod = Hardwire::PluginDefinition::InstanceMethods) | |
targets = [] | |
# Walk constants recursively. | |
mod.constants.each do |sym| | |
const = mod.const_get(sym) | |
if const.instance_of?(Class) && | |
const.include?(included_mod) && | |
contains_plugin?(const) | |
targets << const | |
elsif const.instance_of?(Module) | |
targets.concat(plugin_definition_classes(const, included_mod)) | |
end | |
end | |
# Return objects that include? included_mod. | |
targets | |
end | |
# Finds the plugin definition paths on the {LOAD_PATH}. | |
def plugin_definition_paths | |
glob = Pathname(plugin_base_path) + "**" + "def.rb" | |
Gem.find_files_from_load_path(glob) | |
end | |
# Returns true if {plugin}'s path is defined within the container or false | |
# if it is not. | |
def contains_plugin?(plugin) | |
plugin_definition_paths.any? { |path| | |
path == File.expand_path(plugin.plugin_file_path) | |
} | |
end | |
end | |
end | |
module Hardwire | |
class Container | |
include Mixin::Query | |
attr_reader :plugin_base_path | |
def initialize(plugin_base_path) | |
@plugin_base_path = plugin_base_path | |
end | |
def use(id, options = {}) | |
id_sym = id.to_s.to_sym | |
plugin = plugins.find { |p| | |
[p.identifiers].flatten.map(&:to_s).map(&:to_sym).include?(id_sym) | |
} | |
return nil unless plugin | |
begin | |
# Activate plugin. | |
plugin.activate | |
# Activation was successful because no exceptions were raised. | |
plugin.on_successful_activation | |
plugin.create(options) | |
rescue Exception => exception | |
# Catch all exceptions and pass to plugin to handle. | |
plugin.on_failed_activation(exception) | |
end | |
end | |
end | |
end | |
# | |
# Examples Only | |
# | |
module Hardwire::Plugins | |
module Foo | |
class FooPlugin | |
include Hardwire::PluginDefinition | |
def self.plugin_file_path | |
__FILE__ | |
end | |
def identifiers | |
:foo | |
end | |
def name | |
:Foo | |
end | |
def create(options = {}) | |
FooShazam.new | |
end | |
end | |
class FooShazam | |
def learn(skill) | |
puts "meditating..." | |
sleep 3 | |
puts "* you know #{skill}" | |
end | |
end | |
end | |
end | |
module Hardwire::Plugins | |
module Bar | |
class BarPlugin | |
include Hardwire::PluginDefinition | |
def self.plugin_file_path | |
__FILE__ | |
end | |
def identifiers | |
:bar | |
end | |
def name | |
:Bar | |
end | |
def create(options = {}) | |
BarShazam.new | |
end | |
end | |
class BarShazam | |
def drink(number = 1) | |
number.times do | |
puts "gulp..." | |
sleep 3 | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment