plugins 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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: []