hookem 0.0.1

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.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'pry'
5
+ gem 'pry-remote'
6
+ gem 'pry-doc'
7
+ gem 'pry-nav'
8
+ gem 'pry-buffers'
9
+ gem 'pry-syntax-hacks'
10
+ gem 'pry-git', :platform => :mri
11
+ gem 'jist'
12
+ gem 'ruby18_source_location', :platform => :mri_18
13
+
14
+ gem 'zeevex_concurrency', :path => '/Users/Shared/zeevex/src/github/zeevex_concurrency'
15
+ end
16
+
17
+ # Specify your gem's dependencies in hookem.gemspec
18
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Robert Sanders
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # Hookem
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'hookem'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install hookem
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hookem/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "hookem"
8
+ gem.version = Hookem::VERSION
9
+ gem.authors = ["Robert Sanders"]
10
+ gem.email = ["robert@zeevex.com"]
11
+ gem.description = %q{An implementation of named callback hooks for objects}
12
+ gem.summary = %q{Sort of like observable, only without the 'changed' semantics and supporting multiple named hooks}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency 'rspec', '~> 2.9.0'
21
+ gem.add_development_dependency 'rake'
22
+ gem.add_development_dependency 'pry'
23
+
24
+ end
@@ -0,0 +1,104 @@
1
+ require 'hookem/version'
2
+
3
+ module Hookem
4
+ HookCallable = Struct.new(:identifier, :callable, :eventloop, :preargs) do
5
+ def initialize(hash)
6
+ super(*hash.values_at(*self.class.members.map(&:to_sym)) )
7
+ self.preargs ||= []
8
+ self.identifier ||= self.__id__
9
+ end
10
+
11
+ def call(*args)
12
+ if eventloop
13
+ eventloop.enqueue do
14
+ _execute(*args)
15
+ end
16
+ else
17
+ _execute(*args)
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def _execute(*args)
24
+ arglist = Array(preargs) + Array(args)
25
+ callable.call(*arglist)
26
+ end
27
+ end
28
+
29
+ def _initialize_hook_module
30
+ unless @_hook_module_initialized
31
+ @hooks = {}
32
+ @hook_observers = []
33
+ @_hook_module_initialized = true
34
+ end
35
+ end
36
+
37
+ def self.logger
38
+ @_hookem_logger ||= HookemNullObject.new
39
+ end
40
+
41
+ def run_hook(hook_name, *args)
42
+ hook_name = hook_name.to_sym
43
+ Hookem.logger.debug "<running hook #{hook_name}(#{args.inspect})>"
44
+ if @hooks && @hooks[hook_name]
45
+ Array(@hooks[hook_name]).each do |hook|
46
+ hook.call(self, *args)
47
+ end
48
+ end
49
+ Array(@hook_observers).each do |observer|
50
+ observer.call(hook_name, self, *args)
51
+ end
52
+ end
53
+
54
+ #
55
+ # Takes a hash of hook_name_symbol => hooklist
56
+ # hooklist can be a single proc or array of procs
57
+ #
58
+ def add_hooks(hookmap, options = {})
59
+ hookmap.each do |(name, val)|
60
+ Array(val).each do |hook|
61
+ add_hook name.to_sym, hook, options
62
+ end
63
+ end
64
+ end
65
+
66
+ def add_hook(hook_name, observer = nil, options = {}, &block)
67
+ @hooks[hook_name] ||= []
68
+ hook = _make_observer(observer || block, options)
69
+ @hooks[hook_name] << hook
70
+ hook.identifier
71
+ end
72
+
73
+ def add_hook_observer(observer = nil, options={}, &block)
74
+ hook = _make_observer(observer || block, options)
75
+ @hook_observers << hook
76
+ hook.identifier
77
+ end
78
+
79
+ def remove_hook(hook_name, identifier)
80
+ return unless @hooks[hook_name]
81
+ @hooks[hook_name].reject! {|hook| hook.identifier == identifier }
82
+ end
83
+
84
+ def remove_hook_observer(identifier)
85
+ @hook_observers.reject! {|hook| hook.identifier == identifier }
86
+ end
87
+
88
+ def use_run_loop_for_hooks(runloop)
89
+ @hook_run_loop = runloop
90
+ end
91
+
92
+ def _make_observer(callable, options = {})
93
+ raise ArgumentError, "Must provide callable or block" if callable.nil?
94
+ HookCallable.new({:eventloop => @hook_run_loop}.
95
+ merge(options).
96
+ merge(:callable => callable)).freeze
97
+ end
98
+
99
+ class HookemNullObject
100
+ def method_missing(*args, &block)
101
+ nil
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,3 @@
1
+ module Hookem
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,189 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'zeevex_concurrency/event_loop'
3
+
4
+ describe Hookem do
5
+ class HookableClass
6
+ include Hookem
7
+
8
+ def initialize
9
+ _initialize_hook_module
10
+ end
11
+ end
12
+
13
+ let :loop do
14
+ ZeevexConcurrency::EventLoop.new
15
+ end
16
+ before do
17
+ loop.start
18
+ end
19
+
20
+ subject { HookableClass.new }
21
+
22
+ let :receiver do
23
+ Object.new
24
+ end
25
+
26
+ context 'argument checking' do
27
+ it 'should require a callable or block' do
28
+ expect { subject.add_hook(:foo, nil) }.
29
+ to raise_error(ArgumentError)
30
+ end
31
+
32
+ it 'should accept a proc' do
33
+ expect { subject.add_hook(:foo, Proc.new {}) }.
34
+ not_to raise_error(ArgumentError)
35
+ end
36
+
37
+ it 'should accept a block' do
38
+ expect {
39
+ subject.add_hook(:foo) do
40
+ 1
41
+ end
42
+ }.not_to raise_error(ArgumentError)
43
+ end
44
+ end
45
+
46
+ context 'basic hook usage' do
47
+ it 'should call provided proc' do
48
+ receiver.should_receive(:process)
49
+ subject.add_hook(:foo, Proc.new {receiver.process})
50
+ subject.run_hook :foo
51
+ end
52
+
53
+ it 'should call provided block' do
54
+ receiver.should_receive(:process)
55
+ subject.add_hook(:foo) do |obj|
56
+ receiver.process
57
+ end
58
+ subject.run_hook :foo
59
+ end
60
+
61
+ it 'should invoke call if handed a non-Proc callable' do
62
+ receiver.should_receive(:call).with(subject, 50)
63
+ subject.add_hook(:foo, receiver)
64
+ subject.run_hook :foo, 50
65
+ end
66
+
67
+ it 'should allow hook to be removed' do
68
+ receiver.should_not_receive(:call)
69
+ identifier = subject.add_hook(:foo, receiver)
70
+ subject.remove_hook(:foo, identifier)
71
+ subject.run_hook :foo, 50
72
+ end
73
+
74
+ it 'should return identifier for a hook' do
75
+ subject.add_hook(:foo, receiver).should_not be_nil
76
+ end
77
+
78
+ it 'should return number identifier for an unnamed hook' do
79
+ subject.add_hook(:foo, receiver).should be_a(Numeric)
80
+ end
81
+
82
+ it 'should return unique identifier for each hook added' do
83
+ subject.add_hook(:foo, receiver).should_not ==
84
+ subject.add_hook(:foo, receiver)
85
+ end
86
+
87
+ it 'should return user-provided identifier' do
88
+ subject.add_hook(:foo, receiver, :identifier => "foobarbaz").should == "foobarbaz"
89
+ end
90
+
91
+ it 'should allow hook to be removed by user-provided identifier' do
92
+ receiver.should_not_receive(:call)
93
+ subject.add_hook(:foo, receiver, :identifier => "foobarbaz")
94
+ subject.remove_hook(:foo, "foobarbaz")
95
+ subject.run_hook :foo, 50
96
+ end
97
+
98
+ it 'should allow multiple hooks to be added and run with a given identifier' do
99
+ receiver.should_receive(:call).twice
100
+ subject.add_hook(:foo, receiver, :identifier => "foobarbaz")
101
+ subject.add_hook(:foo, receiver, :identifier => "foobarbaz")
102
+ subject.run_hook :foo, 50
103
+ end
104
+
105
+ it 'should allow all hooks with a given identifier to be removed at once' do
106
+ receiver.should_not_receive(:call)
107
+ subject.add_hook(:foo, receiver, :identifier => "foobarbaz")
108
+ subject.add_hook(:foo, receiver, :identifier => "foobarbaz")
109
+ subject.remove_hook(:foo, "foobarbaz")
110
+ subject.run_hook :foo, 50
111
+ end
112
+ end
113
+
114
+ context 'observers' do
115
+ it "should call provided proc" do
116
+ receiver.should_receive(:process).with(:foo, subject)
117
+ subject.add_hook_observer(Proc.new { |*args| receiver.process(*args) })
118
+ subject.run_hook :foo
119
+ end
120
+
121
+ it "should call provided block" do
122
+ receiver.should_receive(:process)
123
+ subject.add_hook_observer do |*args|
124
+ receiver.process(*args)
125
+ end
126
+ subject.run_hook :foo
127
+ end
128
+
129
+ it "should invoke call if handed a non-Proc callable" do
130
+ receiver.should_receive(:call).with(:foo, subject, 50)
131
+ subject.add_hook_observer(receiver)
132
+ subject.run_hook :foo, 50
133
+ end
134
+
135
+ it "should allow observer to be removed" do
136
+ receiver.should_not_receive(:call)
137
+ identifier = subject.add_hook_observer(receiver)
138
+ subject.remove_hook_observer(identifier)
139
+ subject.run_hook :foo, 50
140
+ end
141
+ end
142
+
143
+ context 'event loops' do
144
+ before do
145
+ loop.should_receive(:enqueue)
146
+ end
147
+
148
+ context 'single hook listeners' do
149
+ it 'should enqueue on object-wide loop if present' do
150
+ subject.use_run_loop_for_hooks(loop)
151
+ subject.add_hook(:foo, Proc.new {})
152
+ subject.run_hook :foo
153
+ end
154
+
155
+ it 'should enqueue on provided loop in options' do
156
+ subject.add_hook(:foo, Proc.new {}, :eventloop => loop)
157
+ subject.run_hook :foo
158
+ end
159
+
160
+ it 'should enqueue on provided loop in options even if object-wide loop provided' do
161
+ subject.use_run_loop_for_hooks(Object.new)
162
+ subject.add_hook(:foo, Proc.new {}, :eventloop => loop)
163
+ subject.run_hook :foo
164
+ end
165
+ end
166
+
167
+ context 'observers' do
168
+ it 'should enqueue on object-wide loop if present' do
169
+ subject.use_run_loop_for_hooks(loop)
170
+ subject.add_hook_observer(Proc.new {})
171
+ subject.run_hook :foo
172
+ end
173
+
174
+ it 'should enqueue on provided loop in options' do
175
+ subject.add_hook_observer(Proc.new {}, :eventloop => loop)
176
+ subject.run_hook :foo
177
+ end
178
+
179
+ it 'should enqueue on provided loop in options even if object-wide loop provided' do
180
+ subject.use_run_loop_for_hooks(Object.new)
181
+ subject.add_hook_observer(Proc.new {}, :eventloop => loop)
182
+ subject.run_hook :foo
183
+ end
184
+ end
185
+ end
186
+
187
+ context 'with pre-args'
188
+ end
189
+
@@ -0,0 +1,8 @@
1
+ require 'rspec'
2
+
3
+ $: << File.expand_path(File.dirname(__FILE__) + '../lib')
4
+ require 'hookem'
5
+
6
+ require 'pry'
7
+ require 'timeout'
8
+
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hookem
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Robert Sanders
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.9.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 2.9.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: pry
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: An implementation of named callback hooks for objects
63
+ email:
64
+ - robert@zeevex.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - LICENSE.txt
72
+ - README.md
73
+ - Rakefile
74
+ - hookem.gemspec
75
+ - lib/hookem.rb
76
+ - lib/hookem/version.rb
77
+ - spec/hooks_spec.rb
78
+ - spec/spec_helper.rb
79
+ homepage: ''
80
+ licenses: []
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ segments:
92
+ - 0
93
+ hash: 1179403147378795576
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ segments:
101
+ - 0
102
+ hash: 1179403147378795576
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 1.8.24
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: Sort of like observable, only without the 'changed' semantics and supporting
109
+ multiple named hooks
110
+ test_files:
111
+ - spec/hooks_spec.rb
112
+ - spec/spec_helper.rb
113
+ has_rdoc: