wireless 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +4 -4
- data/lib/wireless.rb +20 -1
- data/lib/wireless/fetch.rb +6 -2
- data/lib/wireless/fetcher.rb +0 -5
- data/lib/wireless/registry.rb +18 -22
- data/lib/wireless/resolver.rb +5 -5
- data/lib/wireless/synchronized_store.rb +56 -10
- data/lib/wireless/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 15c44fc9bf9e10af9c1a9d8d0241e2a808a7dfabdadd895581271f6fa076fdbc
|
4
|
+
data.tar.gz: 3159342e5f335e4a9b92fd6c09556c4fbb39af17bc5fada60737e0ad3150ac0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ded107266b0b9cf0d6a279a5609e102603b9e14e9a09a8b72684d5239b3bae6b61e623bf20297dba65b92d93e93f5c3a66ef5b7197b5d1030778c213eafec93
|
7
|
+
data.tar.gz: 22276968eb991723ced37a404b4e8aa0a805ccf3ca5b69746431712323bd3290235fd91b7535e9c59180ea82c40badfc59df8bec29ac2e93d736f2809fbf2d0d
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -109,7 +109,7 @@ which has the following features:
|
|
109
109
|
|
110
110
|
## Why Wireless?
|
111
111
|
|
112
|
-
I wanted a simple service locator like [
|
112
|
+
I wanted a simple service locator like [DiFtw](https://github.com/jhollinger/ruby-diftw),
|
113
113
|
with cycle detection and control over the [visibility of getters](https://github.com/jhollinger/ruby-diftw/issues/1).
|
114
114
|
|
115
115
|
## Why Service Locators?
|
@@ -127,17 +127,17 @@ Examples include:
|
|
127
127
|
Rather than wiring these dependencies together manually, service locators allow them to be
|
128
128
|
registered and retrieved in a declarative way. This is similar to the difference between
|
129
129
|
imperative build tools like Ant or Gulp and declarative build tools like Make or Rake, which
|
130
|
-
allow prerequisites to be acquired on-demand, without micromanaging their
|
130
|
+
allow prerequisites to be acquired on-demand, without micromanaging their construction or connections.
|
131
131
|
|
132
132
|
# VERSION
|
133
133
|
|
134
|
-
0.0.
|
134
|
+
0.0.2
|
135
135
|
|
136
136
|
# SEE ALSO
|
137
137
|
|
138
138
|
## Gems
|
139
139
|
|
140
|
-
- [Canister](https://github.com/mlibrary/canister) -
|
140
|
+
- [Canister](https://github.com/mlibrary/canister) - a simple service-locator inspired by Jim Weirich's [article](https://archive.li/shxeA) on Dependency Injection
|
141
141
|
- [DiFtw](https://github.com/jhollinger/ruby-diftw) - the original inspiration for this module: a similar API with a focus on testing/mocking
|
142
142
|
|
143
143
|
## Articles
|
data/lib/wireless.rb
CHANGED
@@ -6,7 +6,26 @@
|
|
6
6
|
module Wireless
|
7
7
|
class Error < StandardError; end
|
8
8
|
class CycleError < Error; end
|
9
|
-
|
9
|
+
|
10
|
+
# Raised when an attempt is made to:
|
11
|
+
#
|
12
|
+
# - retrieve a value from a key-indexed store when the key doesn't exist
|
13
|
+
# - write a value when the key exists and the store doesn't allow replacements
|
14
|
+
#
|
15
|
+
# Can be passed a message, the receiver the lookup failed on and the key. All
|
16
|
+
# are optional and default to nil.
|
17
|
+
#
|
18
|
+
# XXX eventually (i.e. in ruby 2.6), this can be a subclass of (or replaced by)
|
19
|
+
# the core KeyError class: https://bugs.ruby-lang.org/issues/14313
|
20
|
+
class KeyError < Wireless::Error
|
21
|
+
def initialize(message = nil, receiver: nil, key: nil)
|
22
|
+
super(message)
|
23
|
+
@receiver = receiver
|
24
|
+
@key = key
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :key, :receiver
|
28
|
+
end
|
10
29
|
|
11
30
|
# a shortcut which allows:
|
12
31
|
#
|
data/lib/wireless/fetch.rb
CHANGED
@@ -10,7 +10,7 @@ module Wireless
|
|
10
10
|
# @seen is an immutable Set of symbols, which is used to detect dependency cycles.
|
11
11
|
module Fetch
|
12
12
|
# Fetches the dependency with the specified name. Creates the dependency if
|
13
|
-
# it doesn't exist. Raises a Wireless::
|
13
|
+
# it doesn't exist. Raises a Wireless::KeyError if the dependency is not
|
14
14
|
# defined or a Wireless::CycleError if resolving the dependency results in
|
15
15
|
# a cycle.
|
16
16
|
def fetch(name)
|
@@ -22,7 +22,11 @@ module Wireless
|
|
22
22
|
end
|
23
23
|
|
24
24
|
unless (resolver = @registry[name])
|
25
|
-
raise Wireless::
|
25
|
+
raise Wireless::KeyError.new(
|
26
|
+
"dependency not found: #{name}",
|
27
|
+
key: name,
|
28
|
+
receiver: self
|
29
|
+
)
|
26
30
|
end
|
27
31
|
|
28
32
|
fetcher = lambda do
|
data/lib/wireless/fetcher.rb
CHANGED
data/lib/wireless/registry.rb
CHANGED
@@ -4,12 +4,12 @@ require 'set'
|
|
4
4
|
require_relative 'synchronized_store'
|
5
5
|
|
6
6
|
module Wireless
|
7
|
-
# The
|
8
|
-
#
|
9
|
-
# which resolve the dependency
|
7
|
+
# The public API of the dependency provider (AKA service locator). A hash-like
|
8
|
+
# object which maps names (symbols) to dependencies (objects) via blocks
|
9
|
+
# which either resolve the dependency every time (factory) or once (singleton).
|
10
10
|
#
|
11
|
-
#
|
12
|
-
# which calls +new+ on the class e.g.:
|
11
|
+
# A class can be supplied instead of the block, in which case it is equivalent to
|
12
|
+
# a block which calls +new+ on the class e.g.:
|
13
13
|
#
|
14
14
|
# WL = Wireless.new do
|
15
15
|
# on(:foo, Foo)
|
@@ -26,17 +26,17 @@ module Wireless
|
|
26
26
|
|
27
27
|
include Fetch
|
28
28
|
|
29
|
-
def initialize(
|
30
|
-
@default_visibility =
|
31
|
-
@module_cache =
|
32
|
-
@registry = SynchronizedStore.new
|
29
|
+
def initialize(default_visibility = DEFAULT_VISIBILITY, &block)
|
30
|
+
@default_visibility = default_visibility
|
31
|
+
@module_cache = SynchronizedStore.new(type: :module)
|
32
|
+
@registry = SynchronizedStore.new(type: :resolver)
|
33
33
|
@seen = Set.new
|
34
34
|
instance_eval(&block) if block
|
35
35
|
end
|
36
36
|
|
37
37
|
# Registers a dependency which is resolved every time its value is fetched.
|
38
38
|
def factory(name, klass = nil, &block)
|
39
|
-
|
39
|
+
@registry[name.to_sym] = Resolver::Factory.new(block || klass)
|
40
40
|
end
|
41
41
|
|
42
42
|
# Returns true if a service with the specified name has been registered, false
|
@@ -48,7 +48,7 @@ module Wireless
|
|
48
48
|
# Registers a dependency which is only resolved the first time its value is
|
49
49
|
# fetched. On subsequent fetches, the cached value is returned.
|
50
50
|
def singleton(name, klass = nil, &block)
|
51
|
-
|
51
|
+
@registry[name.to_sym] = Resolver::Singleton.new(block || klass)
|
52
52
|
end
|
53
53
|
|
54
54
|
# Takes an array or hash specifying the dependencies to export, and returns
|
@@ -99,22 +99,24 @@ module Wireless
|
|
99
99
|
#
|
100
100
|
# { :foo => :foo, :bar => :baz, :quux => :quux }
|
101
101
|
|
102
|
-
# XXX transform_values isn't available
|
103
|
-
# pull in ActiveSupport for
|
102
|
+
# XXX transform_values isn't available in ruby 2.3 and we don't want to
|
103
|
+
# pull in ActiveSupport just for one method (on this occasion)
|
104
104
|
#
|
105
105
|
# args = DEFAULT_EXPORTS.merge(args).transform_values do |exports|
|
106
|
-
#
|
106
|
+
# exports = [exports] unless exports.is_a?(Array)
|
107
|
+
# exports.reduce({}) do |a, b|
|
107
108
|
# a.merge(b.is_a?(Hash) ? b : { b => b })
|
108
109
|
# end
|
109
110
|
# end
|
110
111
|
|
111
112
|
args = DEFAULT_EXPORTS.merge(args).each_with_object({}) do |(key, exports), merged|
|
112
|
-
|
113
|
+
exports = [exports] unless exports.is_a?(Array)
|
114
|
+
merged[key] = exports.reduce({}) do |a, b|
|
113
115
|
a.merge(b.is_a?(Hash) ? b : { b => b })
|
114
116
|
end
|
115
117
|
end
|
116
118
|
|
117
|
-
@module_cache
|
119
|
+
@module_cache.get!(args) { module_for(args) }
|
118
120
|
end
|
119
121
|
|
120
122
|
alias on factory
|
@@ -147,11 +149,5 @@ module Wireless
|
|
147
149
|
|
148
150
|
mod
|
149
151
|
end
|
150
|
-
|
151
|
-
# Assign a locator for the specified dependency name
|
152
|
-
def register(locator, name, block)
|
153
|
-
name = name.to_sym
|
154
|
-
@registry[name] = locator.new(block)
|
155
|
-
end
|
156
152
|
end
|
157
153
|
end
|
data/lib/wireless/resolver.rb
CHANGED
@@ -3,17 +3,17 @@
|
|
3
3
|
module Wireless
|
4
4
|
# The registry is a key/value store (Hash) whose keys are symbols and whose values
|
5
5
|
# are instances of this class. Resolvers are responsible for returning their
|
6
|
-
# dependencies, which they do by calling their corresponding
|
6
|
+
# dependencies, which they do by calling their corresponding blocks. They can wrap
|
7
7
|
# additional behaviors around this call e.g. singletons (Wireless::Resolver::Singleton)
|
8
8
|
# cache the result so that the block is only called once.
|
9
9
|
class Resolver
|
10
10
|
def initialize(block = nil)
|
11
|
-
if block.
|
11
|
+
if block.respond_to?(:call)
|
12
|
+
@block = block
|
13
|
+
elsif block.is_a?(Class)
|
12
14
|
@block = proc { block.new }
|
13
|
-
elsif !block.respond_to?(:call)
|
14
|
-
raise ArgumentError, "invalid argument: expected a class or a block, got: #{block.class}"
|
15
15
|
else
|
16
|
-
|
16
|
+
raise ArgumentError, "invalid argument: expected a block or a class, got: #{block.class}"
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -1,32 +1,78 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Wireless
|
4
|
-
# A Hash wrapper
|
5
|
-
# Implemented as a wrapper rather than a subclass so we don't have to worry about
|
6
|
-
# every possible mutator.
|
4
|
+
# A Hash wrapper with synchronized get, set and check methods.
|
7
5
|
class SynchronizedStore
|
8
|
-
def initialize
|
9
|
-
@
|
6
|
+
def initialize(store = {}, replace: false, type: :key)
|
7
|
+
@type = type
|
8
|
+
@replace = replace
|
9
|
+
@store = store
|
10
10
|
@lock = Mutex.new
|
11
11
|
end
|
12
12
|
|
13
|
-
# Retrieve a
|
13
|
+
# Retrieve a value from the store
|
14
|
+
#
|
15
|
+
# A synchronized version of:
|
16
|
+
#
|
17
|
+
# store[key]
|
18
|
+
#
|
14
19
|
def [](key)
|
15
20
|
@lock.synchronize { @store[key] }
|
16
21
|
end
|
17
22
|
|
18
|
-
#
|
23
|
+
# Add a key/value to the store
|
24
|
+
#
|
25
|
+
# A synchronized version of:
|
26
|
+
#
|
27
|
+
# store[key] = value
|
28
|
+
#
|
19
29
|
def []=(key, value)
|
20
30
|
@lock.synchronize do
|
21
|
-
if @store.include?(key)
|
22
|
-
|
31
|
+
if !@replace && @store.include?(key)
|
32
|
+
# XXX don't expose the receiver as this class is an internal
|
33
|
+
# implementation detail
|
34
|
+
raise Wireless::KeyError.new(
|
35
|
+
"#{@type} already exists: #{key}",
|
36
|
+
key: key
|
37
|
+
)
|
23
38
|
end
|
24
39
|
|
25
40
|
@store[key] = value
|
26
41
|
end
|
27
42
|
end
|
28
43
|
|
29
|
-
#
|
44
|
+
# Retrieve a value from the store. If it doesn't exist and a block is
|
45
|
+
# supplied, create and return it; otherwise, raise a KeyError.
|
46
|
+
#
|
47
|
+
# A synchronized version of:
|
48
|
+
#
|
49
|
+
# store[key] ||= value
|
50
|
+
#
|
51
|
+
def get_or_create(key)
|
52
|
+
@lock.synchronize do
|
53
|
+
if @store.include?(key)
|
54
|
+
@store[key]
|
55
|
+
elsif block_given?
|
56
|
+
@store[key] = yield
|
57
|
+
else
|
58
|
+
# XXX don't expose the receiver as this class is an internal
|
59
|
+
# implementation detail
|
60
|
+
raise Wireless::KeyError.new(
|
61
|
+
"#{@type} not found: #{key}",
|
62
|
+
key: key
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
alias get! get_or_create
|
69
|
+
|
70
|
+
# Returns true if the store contains the key, false otherwise
|
71
|
+
#
|
72
|
+
# A synchronized version of:
|
73
|
+
#
|
74
|
+
# store.include?(key)
|
75
|
+
#
|
30
76
|
def include?(key)
|
31
77
|
@lock.synchronize { @store.include?(key) }
|
32
78
|
end
|
data/lib/wireless/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wireless
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- chocolateboy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-06-
|
11
|
+
date: 2018-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|