plugins 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 95e5717b6ed8f94cb9b10852dd0ffe859694411c
4
+ data.tar.gz: 38b115995d982f05f361b66724af2b7b24e4f514
5
+ SHA512:
6
+ metadata.gz: 96d3f53560dcb1f37190b5ac7bd093de98b164a7238ea09cdd5cc5eae98bcb3d53aff42d72aa9c4f1f83b35452ae68d1fc8ac604797f40f9b090d5e02e064708
7
+ data.tar.gz: 14322d806fa42ab7f954b5393eeffc5a6a5fcfaddf3fd7bb256b326051a1d001e926ea149a2b17c49ecea617feea711ba351f5b5c6889c43e74ad798c0096254
@@ -0,0 +1,105 @@
1
+ class Ribbon::Plugins
2
+ class AroundStack
3
+ attr_reader :subject
4
+ attr_accessor :scope
5
+
6
+ def initialize(subject, scope=nil)
7
+ @subject = subject.to_sym
8
+ @scope = scope
9
+ @_stack = []
10
+ end
11
+
12
+ def push(&block)
13
+ raise Errors::Error, "Must pass block" unless block_given?
14
+
15
+ AroundWrapper.new(self, subject, &block).tap { |wrapper|
16
+ @_stack.push(wrapper)
17
+ }
18
+ end
19
+
20
+ def dup
21
+ AroundStack.new(subject, scope).tap { |stack|
22
+ @_stack.each { |wrapper|
23
+ stack.push(&wrapper.block)
24
+ }
25
+ }
26
+ end
27
+
28
+ def call(*args, &block)
29
+ raise Errors::Error, "Must pass block" unless block_given?
30
+ call_stack = @_stack.dup
31
+
32
+ inner_most = WrappedBlock.new(&block)
33
+ call_stack.unshift(inner_most)
34
+
35
+ outer_most = call_stack.pop
36
+ outer_most.call(call_stack, *args)
37
+
38
+ # This shouldn't happen unless the AroundStack isn't functioning properly.
39
+ raise Errors::Error, "Block passed was not called!" unless inner_most.called?
40
+
41
+ inner_most.retval
42
+ end
43
+
44
+ class AroundWrapper
45
+ attr_reader :stack, :subject, :block
46
+
47
+ def initialize(stack, subject, &block)
48
+ @stack = stack
49
+ @subject = subject
50
+ @block = block
51
+ end
52
+
53
+ def scope
54
+ stack.scope
55
+ end
56
+
57
+ def method_missing(meth, *args, &block)
58
+ super unless scope
59
+ scope.send(meth, *args, &block)
60
+ end
61
+
62
+ def call(call_stack, *args)
63
+ wrapped = call_stack.pop
64
+ raise Errors::Error, 'call stack too short' unless wrapped
65
+
66
+ define_singleton_method("perform_#{subject}") { |*new_args|
67
+ args = new_args unless new_args.empty?
68
+ wrapped.call(call_stack, *args)
69
+ }
70
+
71
+ singleton_class.instance_exec(subject) { |subject|
72
+ alias_method subject, "perform_#{subject}"
73
+
74
+ # Don't allow these to be overriden
75
+ attr_reader :stack, :subject, :block
76
+ }
77
+
78
+ instance_exec(*args, &block)
79
+ end
80
+ end
81
+
82
+ class WrappedBlock
83
+ attr_reader :block
84
+ attr_reader :retval
85
+
86
+ def initialize(&block)
87
+ @block = block
88
+ end
89
+
90
+ def called?
91
+ !!@_called
92
+ end
93
+
94
+ ##
95
+ # Call the wrapped block, ignoring the scope and call_stack arguments.
96
+ def call(call_stack, *args)
97
+ raise Errors::Error, 'receiving non-empty call stack' unless call_stack.empty?
98
+ block.call(*args).tap { |retval|
99
+ @retval = retval
100
+ @_called = true
101
+ }
102
+ end
103
+ end
104
+ end # AroundStack
105
+ end # Ribbon::Plugins
@@ -0,0 +1,30 @@
1
+ class Ribbon::Plugins
2
+ class BlockStack
3
+ attr_accessor :scope
4
+
5
+ def initialize(scope=nil)
6
+ @scope = scope
7
+ @_stack = []
8
+ end
9
+
10
+ def dup
11
+ BlockStack.new.tap { |stack|
12
+ @_stack.each { |block| stack.push(&block) }
13
+ }
14
+ end
15
+
16
+ def push(&block)
17
+ @_stack.push(block)
18
+ end
19
+
20
+ def call(*args)
21
+ @_stack.reverse_each { |block|
22
+ if scope
23
+ scope.instance_exec(*args, &block)
24
+ else
25
+ block.call(*args)
26
+ end
27
+ }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,47 @@
1
+ module Ribbon
2
+ class Plugins
3
+ ##
4
+ # Intended to be mixed into any class utilizing the plugins functionality.
5
+ module ComponentMixin
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ ##
12
+ # Get or define the plugin loader.
13
+ #
14
+ # This block will be used to load a plugin given the value passed to the
15
+ # +plugin+ instance method. It's the responsibility of this block to
16
+ # translate the inputted value into either a Class that extends Plugin
17
+ # or a Proc.
18
+ #
19
+ # If for a particular value you wish to not perform any translation,
20
+ # return falsey.
21
+ def plugin_loader(&block)
22
+ if block_given?
23
+ @_plugin_loader = block
24
+ else
25
+ @_plugin_loader
26
+ end
27
+ end
28
+ end
29
+
30
+ ###
31
+ # Instance Methods
32
+ ###
33
+
34
+ ##
35
+ # Reference to the Plugins instance for the component.
36
+ def plugins
37
+ @plugins ||= Plugins.new(self, &self.class.plugin_loader)
38
+ end
39
+
40
+ ##
41
+ # Add a plugin.
42
+ def plugin(*args, &block)
43
+ plugins.add(*args, &block)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+ class Ribbon::Plugins
2
+ module Errors
3
+ class Error < StandardError; end
4
+ class LoadError < Error; end
5
+ end
6
+ end
@@ -0,0 +1,62 @@
1
+ class Ribbon::Plugins
2
+ class Plugin
3
+ class << self
4
+ def create(&block)
5
+ Class.new(Plugin).tap { |k| k.class_eval(&block) if block }
6
+ end
7
+
8
+ def method_missing(meth, *args, &block)
9
+ if /^(before|after|around)_(\w+)$/.match(meth.to_s)
10
+ _define_callback($1, $2, &block)
11
+ else
12
+ super
13
+ end
14
+ end
15
+
16
+ def _define_callback(type, subject, &block)
17
+ _callbacks(type, subject).push(&block)
18
+ end
19
+
20
+ def _callbacks(type, subject)
21
+ ((@__callbacks ||= {})[type.to_sym] ||= {})[subject.to_sym] ||= _empty_stack_for(type, subject)
22
+ end
23
+
24
+ def _empty_stack_for(type, subject)
25
+ case type.to_sym
26
+ when :before, :after
27
+ BlockStack.new
28
+ when :around
29
+ AroundStack.new(subject)
30
+ else
31
+ raise Errors::Error, "Invalid type: #{type}"
32
+ end
33
+ end
34
+ end
35
+
36
+ attr_reader :plugins
37
+
38
+ def initialize(plugins=nil)
39
+ @plugins = plugins
40
+ end
41
+
42
+ def before(subject, *args)
43
+ _callbacks(:before, subject).call(*args)
44
+ end
45
+
46
+ def after(subject, *args)
47
+ _callbacks(:after, subject).call(*args)
48
+ end
49
+
50
+ def around(subject, *args, &block)
51
+ _callbacks(:around, subject).call(*args, &block)
52
+ end
53
+
54
+ private
55
+ def _callbacks(type, subject)
56
+ ((@_callbacks ||= {})[type.to_sym] ||= {})[subject.to_sym] ||=
57
+ self.class._callbacks(type, subject).dup.tap { |stack|
58
+ stack.scope = self
59
+ }
60
+ end
61
+ end # Plugin
62
+ end # Ribbon::Plugins
@@ -0,0 +1,5 @@
1
+ module Ribbon
2
+ class Plugins
3
+ VERSION = "0.2.4"
4
+ end
5
+ end
@@ -0,0 +1,97 @@
1
+ require 'ribbon/plugins/version'
2
+
3
+ module Ribbon
4
+ class Plugins
5
+ autoload(:Errors, 'ribbon/plugins/errors')
6
+ autoload(:Plugin, 'ribbon/plugins/plugin')
7
+ autoload(:AroundStack, 'ribbon/plugins/around_stack')
8
+ autoload(:BlockStack, 'ribbon/plugins/block_stack')
9
+ autoload(:ComponentMixin, 'ribbon/plugins/component_mixin')
10
+
11
+ attr_reader :component, :plugin_loader
12
+
13
+ def initialize(component=nil, &block)
14
+ @component = component
15
+ @plugin_loader = block
16
+ end
17
+
18
+ def add(plugin=nil, *args, &block)
19
+ plugin = _load(plugin, &block)
20
+ _add_plugin(plugin.new(self, *args))
21
+ end
22
+
23
+ def clear
24
+ @_plugins = nil
25
+ @_around_stack = nil
26
+ end
27
+
28
+ def before(subject, *args)
29
+ _plugins.reverse_each { |plugin| plugin.before(subject, *args) }
30
+ end
31
+
32
+ def after(subject, *args)
33
+ _plugins.reverse_each { |plugin| plugin.after(subject, *args) }
34
+ end
35
+
36
+ def around(subject, *args, &block)
37
+ _around_stack.call(subject, *args) { |subject, *args| block.call(*args) }
38
+ end
39
+
40
+ def perform(subject, *args, &block)
41
+ before(subject, *args)
42
+ retval = around(subject, *args, &block)
43
+ after(subject, *args)
44
+
45
+ retval
46
+ end
47
+
48
+ private
49
+ def _plugins
50
+ @_plugins ||= []
51
+ end
52
+
53
+ def _around_stack
54
+ @_around_stack ||= AroundStack.new(:block, self)
55
+ end
56
+
57
+ def _add_plugin(plugin)
58
+ _plugins.push(plugin)
59
+
60
+ _around_stack.push { |subject, *args|
61
+ plugin.around(subject, *args) { |*args|
62
+ perform_block(subject, *args)
63
+ }
64
+ }
65
+
66
+ plugin
67
+ end
68
+
69
+ def _load(plugin, &block)
70
+ if plugin
71
+ _load_plugin(plugin)
72
+ elsif block_given?
73
+ Plugin.create(&block)
74
+ else
75
+ raise Errors::LoadError, 'No plugin information provided'
76
+ end
77
+ end
78
+
79
+ def _load_plugin(plugin)
80
+ _call_plugin_loader(plugin).tap { |p| plugin = p if p }
81
+
82
+ case plugin
83
+ when Class
84
+ plugin < Plugin && plugin or
85
+ raise Errors::LoadError, "Invalid plugin class: #{plugin.inspect} Must extend Plugin."
86
+ when Proc
87
+ Plugin.create(&plugin)
88
+ else
89
+ raise Errors::LoadError, "Invalid plugin identifier: #{plugin.inspect}"
90
+ end
91
+ end
92
+
93
+ def _call_plugin_loader(plugin)
94
+ plugin_loader && component.instance_exec(plugin, &plugin_loader)
95
+ end
96
+ end # Plugins
97
+ end # Ribbon
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: plugins
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.4
5
+ platform: ruby
6
+ authors:
7
+ - Robert Honer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 3.2.0
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '3.2'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 3.2.0
33
+ description: A flexible plugins framework.
34
+ email:
35
+ - robert@ribbonpayments.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - lib/ribbon/plugins.rb
41
+ - lib/ribbon/plugins/around_stack.rb
42
+ - lib/ribbon/plugins/block_stack.rb
43
+ - lib/ribbon/plugins/component_mixin.rb
44
+ - lib/ribbon/plugins/errors.rb
45
+ - lib/ribbon/plugins/plugin.rb
46
+ - lib/ribbon/plugins/version.rb
47
+ homepage: http://github.com/ribbon/plugins
48
+ licenses:
49
+ - BSD
50
+ metadata: {}
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project:
67
+ rubygems_version: 2.4.6
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: A flexible plugins framework.
71
+ test_files: []