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.
@@ -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
@@ -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"
@@ -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 unused in certain contexts. This is useful
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
- Hook [:scope, :key] hierarchy.
25
- * Provides an optional logging extension point Hooker.log_with
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
- Then the following configuration of the job could optionally be loaded
37
- and applied to override Job defaults:
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
- Hooker can be yielded to the config file via an alternative Module
47
- name, so as not to scare your mom, or to fully encapsulate its use:
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
- Supports the config:
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-2012 David Kellum
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
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2011-2012 David Kellum
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, @scope = @scope, scp
28
+ prior = swap_scope( scp )
28
29
  yield self
29
30
  ensure
30
- @scope = prior
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( key, clr = nil, &block )
38
- applied.delete( sk( key ) )
39
- hooks[ sk( key ) ] << [ block, ( clr || caller.first ).to_s ]
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
- applied << sk( key )
55
- hooks[ sk( key ) ].each { |hook| hook[0].call( value ) }
56
- value
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
- applied << sk( key )
64
- hooks[ sk( key ) ].inject( value ) { |v, hook| hook[0].call( v ) }
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
- applied << sk( key )
71
- hooks[ sk( key ) ].inject( value ) { |v, hook| v.merge( hook[0].call ) }
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
- ( hooks.keys - applied ).each do |rkey|
113
- calls = hooks[ rkey ].map { |blk, clr| clr }
114
- yield( rkey, calls )
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
- @hooks = nil
133
- @applied = nil
134
- @logger = nil
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
- [ @scope, key ]
159
+ [ scope, key ]
146
160
  end
147
161
  end
148
162
 
149
- end
163
+ def scope
164
+ Thread.current[:hooker_scope] || :default
165
+ end
150
166
 
151
- @scope = :default
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
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2011-2012 David Kellum
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.1'
19
+ VERSION = '1.1.0'
20
20
  end
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2011-2012 David Kellum
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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env jruby
2
2
  #.hashdot.profile += jruby-shortlived
3
3
  #--
4
- # Copyright (c) 2011-2012 David Kellum
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
- prerelease:
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: 2012-09-13 00:00:00.000000000 Z
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: '2.3'
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: '2.3'
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.0'
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.0'
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.8.24
82
+ rubygems_version: 2.1.9
95
83
  signing_key:
96
- specification_version: 3
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
- ...