hooker 1.0.1 → 1.1.0
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 +7 -0
- data/History.rdoc +8 -0
- data/README.rdoc +50 -12
- data/lib/hooker.rb +56 -22
- data/lib/hooker/base.rb +2 -2
- data/test/setup.rb +1 -1
- data/test/test_hooker.rb +23 -1
- metadata +11 -24
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5b0d8be31cce91bd8b60f4e0bd5ceaa29ae29062
|
4
|
+
data.tar.gz: d3fcaef32aad0b808a9207dda29e1b7cf9567bb1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f4db4e60785200caf839f3b0560ad6c16a07e943d4fed0c65670c028734d615b59ecd26baf67b5bda25b5784749a022cb05354d6fbc5ae336f98db5f44c7e0bb
|
7
|
+
data.tar.gz: 9a1ad28dc7f66176f512b26e11c02511749cf00d716330438ef9178313f1814a70c2c32954e6130a45a1b1633f9d9ecf2fbd6796918199831f16c49e4f8f3aee
|
data/History.rdoc
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
=== 1.1.0 (2013-12-6)
|
2
|
+
* Thread safety: Hooker.with via thread-local and lock around common
|
3
|
+
structures. Note: It remains generally advisable to complete all
|
4
|
+
configurable application bootstrapping in the main thread whenever
|
5
|
+
possible.
|
6
|
+
* Add Travis CI test setup (dev)
|
7
|
+
* Upgrade to minitest ~> 4.7.4 (dev)
|
8
|
+
|
1
9
|
=== 1.0.1 (2012-9-12)
|
2
10
|
* Ensure Errno::EISDIR when mistaken attempt to load a directory,
|
3
11
|
i.e. "./config"
|
data/README.rdoc
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
= Hooker
|
2
2
|
|
3
3
|
* http://github.com/dekellum/hooker
|
4
|
+
* {<img src="https://travis-ci.org/dekellum/hooker.png" />}[https://travis-ci.org/dekellum/hooker]
|
4
5
|
|
5
6
|
== Description
|
6
7
|
|
@@ -17,24 +18,37 @@ and other ruby-based configuration files.
|
|
17
18
|
Hooker.apply, Hooker.inject, Hooker.merge.
|
18
19
|
* Hook Procs are executed only when applied by the extended
|
19
20
|
application. Thus a single configuration source may include hook
|
20
|
-
Procs that are left
|
21
|
+
Procs that are left un-executed in certain contexts. This is useful
|
21
22
|
when configuring across several different (contextually loaded)
|
22
23
|
modules from one source.
|
23
|
-
* Optional or implicit scoping of keys, providing a two level
|
24
|
-
|
25
|
-
|
24
|
+
* Optional or implicit scoping of keys, providing a two level Hook
|
25
|
+
<code>[:scope, :key]</code> hierarchy. The default scope is
|
26
|
+
<code>:default</code>.
|
27
|
+
* Optional Hooker.log_with extension point
|
26
28
|
* Can check and list hooks that were not applied, including the call
|
27
|
-
site of where added
|
29
|
+
site of where added, allowing you to test your
|
30
|
+
configurations and avoid typos.
|
31
|
+
* Thread safe (though you should strive to complete all configurable
|
32
|
+
bootstrapping in the main thread).
|
28
33
|
|
29
34
|
== Synopsis
|
30
35
|
|
31
36
|
Lets say an application has a 'Job' it would like to support
|
32
|
-
hooks on
|
37
|
+
configuration hooks on. First arrange for any number of configuration
|
38
|
+
sources to be loaded via Hooker.load_file. Then apply any loaded hook
|
39
|
+
procs to an instance of your Job, like so:
|
33
40
|
|
41
|
+
require 'hooker'
|
42
|
+
|
43
|
+
Hooker.load_file( "config.rb" ) if File.exist?( "config.rb" )
|
34
44
|
job = Hooker.apply( :job, Job.new )
|
35
45
|
|
36
|
-
|
37
|
-
|
46
|
+
You could also use Hooker.register_config with an OptionParser to
|
47
|
+
support a <code>-c/--config FILE</code> flag for specifying the
|
48
|
+
configuration source.
|
49
|
+
|
50
|
+
If Job is to be configured via setters, then the configuration source
|
51
|
+
might look like this:
|
38
52
|
|
39
53
|
Hooker.with do |h|
|
40
54
|
h.setup_job do |j|
|
@@ -43,8 +57,32 @@ and applied to override Job defaults:
|
|
43
57
|
end
|
44
58
|
end
|
45
59
|
|
46
|
-
|
47
|
-
|
60
|
+
Alternatively, if Job takes a Hash on construction for configuration,
|
61
|
+
use Hooker.merge like so:
|
62
|
+
|
63
|
+
opts = Hooker.merge( :job, { workers: 2 } ) #defaults
|
64
|
+
job = Job.new( opts )
|
65
|
+
|
66
|
+
...and Hash syntax in the configuration source. Note that unlike JSON
|
67
|
+
or YAML, the configuration remains fully interpreted for greater
|
68
|
+
expressiveness, for example sharing variables across different
|
69
|
+
configured objects:
|
70
|
+
|
71
|
+
Hooker.with do |h|
|
72
|
+
cpus = 3
|
73
|
+
|
74
|
+
h.setup_job do
|
75
|
+
{ workers: cpus + 1,
|
76
|
+
timeout: 10 * 60 }
|
77
|
+
end
|
78
|
+
|
79
|
+
h.setup_connection_pool do
|
80
|
+
{ size: cpus }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
Hooker can be yielded to the configuration source via an alternative
|
85
|
+
Module.method name, to fully encapsulate its use:
|
48
86
|
|
49
87
|
class Chaplain
|
50
88
|
def self.configure( &block )
|
@@ -52,7 +90,7 @@ name, so as not to scare your mom, or to fully encapsulate its use:
|
|
52
90
|
end
|
53
91
|
end
|
54
92
|
|
55
|
-
|
93
|
+
...supports the configuration source:
|
56
94
|
|
57
95
|
Chaplain.configure do |c|
|
58
96
|
c.setup_job do |j|
|
@@ -63,7 +101,7 @@ Supports the config:
|
|
63
101
|
|
64
102
|
== License
|
65
103
|
|
66
|
-
Copyright (c) 2011-
|
104
|
+
Copyright (c) 2011-2013 David Kellum
|
67
105
|
|
68
106
|
Licensed under the Apache License, Version 2.0 (the "License"); you
|
69
107
|
may not use this file except in compliance with the License. You
|
data/lib/hooker.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2011-
|
2
|
+
# Copyright (c) 2011-2013 David Kellum
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
5
|
# may not use this file except in compliance with the License. You may
|
@@ -15,6 +15,7 @@
|
|
15
15
|
#++
|
16
16
|
|
17
17
|
require 'hooker/base'
|
18
|
+
require 'thread'
|
18
19
|
|
19
20
|
# A registry of hooks, added and applied for indirect configuration
|
20
21
|
# support.
|
@@ -24,19 +25,23 @@ module Hooker
|
|
24
25
|
|
25
26
|
# Yields self under the given scope to block.
|
26
27
|
def with( scp = :default )
|
27
|
-
prior
|
28
|
+
prior = swap_scope( scp )
|
28
29
|
yield self
|
29
30
|
ensure
|
30
|
-
|
31
|
+
swap_scope( prior )
|
31
32
|
end
|
32
33
|
|
33
34
|
# Add hook block by specified hook key. Will only be executed when
|
34
35
|
# apply, inject, or merge is later called with the same key.
|
35
36
|
# Multiple hook blocks for the same key will be called in the
|
36
37
|
# order added.
|
37
|
-
def add(
|
38
|
-
|
39
|
-
|
38
|
+
def add( k, clr = nil, &block )
|
39
|
+
key = sk( k )
|
40
|
+
clr ||= caller.first
|
41
|
+
LOCK.synchronize do
|
42
|
+
applied.delete( key )
|
43
|
+
hooks[ key ] << [ block, clr.to_s ]
|
44
|
+
end
|
40
45
|
end
|
41
46
|
|
42
47
|
# Allow method setup_<foo> as alias for add( :foo )
|
@@ -51,24 +56,27 @@ module Hooker
|
|
51
56
|
# Pass the specified initial value to each previously added Proc
|
52
57
|
# with matching key and returns the mutated value.
|
53
58
|
def apply( key, value )
|
54
|
-
|
55
|
-
|
56
|
-
|
59
|
+
sync_on_hooks( key ) do |hks|
|
60
|
+
hks.each { |hook| hook[0].call( value ) }
|
61
|
+
value
|
62
|
+
end
|
57
63
|
end
|
58
64
|
|
59
65
|
# Inject value (or nil) into the chain of previously added Procs,
|
60
66
|
# which should implement binary operations, returning desired
|
61
67
|
# value. Returns the last value from the last proc.
|
62
68
|
def inject( key, value = nil )
|
63
|
-
|
64
|
-
|
69
|
+
sync_on_hooks( key ) do |hks|
|
70
|
+
hks.inject( value ) { |v, hook| hook[0].call( v ) }
|
71
|
+
end
|
65
72
|
end
|
66
73
|
|
67
74
|
# Merge returned values from each added Proc to the initial value
|
68
75
|
# (or empty Hash).
|
69
76
|
def merge( key, value = {} )
|
70
|
-
|
71
|
-
|
77
|
+
sync_on_hooks( key ) do |hks|
|
78
|
+
hks.inject( value ) { |v, hook| v.merge( hook[0].call ) }
|
79
|
+
end
|
72
80
|
end
|
73
81
|
|
74
82
|
# Register to yield log messages to the given block.
|
@@ -109,14 +117,18 @@ module Hooker
|
|
109
117
|
# hook key added but not applied. Often this suggests a typo or
|
110
118
|
# other mistake by the hook Proc author.
|
111
119
|
def check_not_applied
|
112
|
-
|
113
|
-
|
114
|
-
|
120
|
+
LOCK.synchronize do
|
121
|
+
( hooks.keys - applied ).each do |rkey|
|
122
|
+
calls = hooks[ rkey ].map { |blk, clr| clr }
|
123
|
+
yield( rkey, calls )
|
124
|
+
end
|
115
125
|
end
|
116
126
|
end
|
117
127
|
|
118
128
|
private
|
119
129
|
|
130
|
+
LOCK = Mutex.new
|
131
|
+
|
120
132
|
# Hash of hook keys to array of procs.
|
121
133
|
def hooks
|
122
134
|
@hooks ||= Hash.new { |h, k| h[k] = [] }
|
@@ -129,9 +141,11 @@ module Hooker
|
|
129
141
|
|
130
142
|
# Clears all Hooker state.
|
131
143
|
def clear
|
132
|
-
|
133
|
-
|
134
|
-
|
144
|
+
LOCK.synchronize do
|
145
|
+
@hooks = nil
|
146
|
+
@applied = nil
|
147
|
+
@logger = nil
|
148
|
+
end
|
135
149
|
end
|
136
150
|
|
137
151
|
def log( msg )
|
@@ -142,12 +156,32 @@ module Hooker
|
|
142
156
|
if key.is_a?( Array ) && ( key.length == 2 )
|
143
157
|
key
|
144
158
|
else
|
145
|
-
[
|
159
|
+
[ scope, key ]
|
146
160
|
end
|
147
161
|
end
|
148
162
|
|
149
|
-
|
163
|
+
def scope
|
164
|
+
Thread.current[:hooker_scope] || :default
|
165
|
+
end
|
150
166
|
|
151
|
-
|
167
|
+
def swap_scope( s )
|
168
|
+
old = scope
|
169
|
+
Thread.current[:hooker_scope] = s
|
170
|
+
old
|
171
|
+
end
|
172
|
+
|
173
|
+
def sync_on_hooks( k )
|
174
|
+
key = sk( k )
|
175
|
+
LOCK.synchronize do
|
176
|
+
begin
|
177
|
+
yield hooks[ key ]
|
178
|
+
ensure
|
179
|
+
applied << key
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
152
186
|
|
153
187
|
end
|
data/lib/hooker/base.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2011-
|
2
|
+
# Copyright (c) 2011-2013 David Kellum
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
5
|
# may not use this file except in compliance with the License. You may
|
@@ -16,5 +16,5 @@
|
|
16
16
|
|
17
17
|
module Hooker
|
18
18
|
# Hooker version
|
19
|
-
VERSION = '1.0
|
19
|
+
VERSION = '1.1.0'
|
20
20
|
end
|
data/test/setup.rb
CHANGED
data/test/test_hooker.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env jruby
|
2
2
|
#.hashdot.profile += jruby-shortlived
|
3
3
|
#--
|
4
|
-
# Copyright (c) 2011-
|
4
|
+
# Copyright (c) 2011-2013 David Kellum
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
7
7
|
# may not use this file except in compliance with the License. You
|
@@ -21,6 +21,7 @@ TESTDIR = File.dirname( __FILE__ )
|
|
21
21
|
require File.join( TESTDIR, "setup" )
|
22
22
|
|
23
23
|
require 'hooker'
|
24
|
+
require 'thread'
|
24
25
|
|
25
26
|
# An alternative entry point from top level
|
26
27
|
class Chaplain
|
@@ -112,6 +113,9 @@ class TestContext < MiniTest::Unit::TestCase
|
|
112
113
|
|
113
114
|
assert_equal( [ [ :test_scope, :not_used ] ], not_used_keys )
|
114
115
|
assert_equal( 2, not_used_calls.length )
|
116
|
+
not_used_calls.each_with_index do |cl, i|
|
117
|
+
assert_match( /test_check_not_applied/, cl, i )
|
118
|
+
end
|
115
119
|
|
116
120
|
Hooker.log_not_applied
|
117
121
|
end
|
@@ -147,4 +151,22 @@ class TestContext < MiniTest::Unit::TestCase
|
|
147
151
|
end
|
148
152
|
end
|
149
153
|
|
154
|
+
def test_threaded_with
|
155
|
+
Hooker.with( :t1 ) { |h| h.add( :common ) { :t1 } }
|
156
|
+
Hooker.with( :t2 ) { |h| h.add( :common ) { :t2 } }
|
157
|
+
|
158
|
+
threads = 11.times.map do |i|
|
159
|
+
Thread.new do
|
160
|
+
Hooker.with( i.even? ? :t1 : :t2 ) do
|
161
|
+
sleep( rand * 0.100 )
|
162
|
+
Hooker.inject( :common )
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
11.times do |i|
|
168
|
+
assert_equal( i.even? ? :t1 : :t2, threads[i].value, "thread #{i}" )
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
150
172
|
end
|
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hooker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
version: 1.0.1
|
4
|
+
version: 1.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- David Kellum
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2013-12-06 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: minitest
|
@@ -17,14 +16,12 @@ dependencies:
|
|
17
16
|
requirements:
|
18
17
|
- - ~>
|
19
18
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
21
|
-
none: false
|
19
|
+
version: 4.7.4
|
22
20
|
requirement: !ruby/object:Gem::Requirement
|
23
21
|
requirements:
|
24
22
|
- - ~>
|
25
23
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
27
|
-
none: false
|
24
|
+
version: 4.7.4
|
28
25
|
prerelease: false
|
29
26
|
type: :development
|
30
27
|
- !ruby/object:Gem::Dependency
|
@@ -33,14 +30,12 @@ dependencies:
|
|
33
30
|
requirements:
|
34
31
|
- - ~>
|
35
32
|
- !ruby/object:Gem::Version
|
36
|
-
version: '2.
|
37
|
-
none: false
|
33
|
+
version: '2.1'
|
38
34
|
requirement: !ruby/object:Gem::Requirement
|
39
35
|
requirements:
|
40
36
|
- - ~>
|
41
37
|
- !ruby/object:Gem::Version
|
42
|
-
version: '2.
|
43
|
-
none: false
|
38
|
+
version: '2.1'
|
44
39
|
prerelease: false
|
45
40
|
type: :development
|
46
41
|
description: Hooker provides a simple registry of hooks or extension points, enabling decoupled run time configuration in terse but straight ruby syntax. Inspiration includes Emacs Hook Functions and other ruby-based configuration files.
|
@@ -65,6 +60,7 @@ files:
|
|
65
60
|
- test/test_load_dir/placeholder
|
66
61
|
homepage: http://github.com/dekellum/hooker
|
67
62
|
licenses: []
|
63
|
+
metadata: {}
|
68
64
|
post_install_message:
|
69
65
|
rdoc_options:
|
70
66
|
- --main
|
@@ -73,27 +69,18 @@ require_paths:
|
|
73
69
|
- lib
|
74
70
|
required_ruby_version: !ruby/object:Gem::Requirement
|
75
71
|
requirements:
|
76
|
-
- -
|
72
|
+
- - '>='
|
77
73
|
- !ruby/object:Gem::Version
|
78
74
|
version: '0'
|
79
|
-
segments:
|
80
|
-
- 0
|
81
|
-
hash: 2
|
82
|
-
none: false
|
83
75
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
76
|
requirements:
|
85
|
-
- -
|
77
|
+
- - '>='
|
86
78
|
- !ruby/object:Gem::Version
|
87
79
|
version: '0'
|
88
|
-
segments:
|
89
|
-
- 0
|
90
|
-
hash: 2
|
91
|
-
none: false
|
92
80
|
requirements: []
|
93
81
|
rubyforge_project:
|
94
|
-
rubygems_version: 1.
|
82
|
+
rubygems_version: 2.1.9
|
95
83
|
signing_key:
|
96
|
-
specification_version:
|
84
|
+
specification_version: 4
|
97
85
|
summary: Hooker provides a simple registry of hooks or extension points, enabling decoupled run time configuration in terse but straight ruby syntax.
|
98
86
|
test_files: []
|
99
|
-
...
|