wireless 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 875d0a0704d8e905a826e1a171d887877990c1c90e92b73026305efd419337d2
4
+ data.tar.gz: 0ea0ce2e1f08e7b923361514eb2529a57ca6787beb182285bd3be8298ae62d92
5
+ SHA512:
6
+ metadata.gz: 330cfceecbc844ecf795d8f6b102ba00da16d5a512f3125c061d364b19e7d9d2635e1bd06c4872c8337fad46de99586acb997a35ca1f9bee5a966493ad3a773c
7
+ data.tar.gz: 94390e87a8b56cd0d31ce37bde2f47ce1162a84639ef345464a9eb7e6e20644ffcf415b306b3fff407f2e6778eebc2f674624d4ba3bae83c6e54a8f22b9b7782
@@ -0,0 +1,3 @@
1
+ ## 0.0.1 - 2018-06-16
2
+
3
+ * initial release
@@ -0,0 +1,192 @@
1
+ The Artistic License 2.0
2
+ ========================
3
+
4
+ _Copyright © 2000-2006, The Perl Foundation._
5
+
6
+ Everyone is permitted to copy and distribute verbatim copies
7
+ of this license document, but changing it is not allowed.
8
+
9
+ ### Preamble
10
+
11
+ This license establishes the terms under which a given free software
12
+ Package may be copied, modified, distributed, and/or redistributed.
13
+ The intent is that the Copyright Holder maintains some artistic
14
+ control over the development of that Package while still keeping the
15
+ Package available as open source and free software.
16
+
17
+ You are always permitted to make arrangements wholly outside of this
18
+ license directly with the Copyright Holder of a given Package. If the
19
+ terms of this license do not permit the full use that you propose to
20
+ make of the Package, you should contact the Copyright Holder and seek
21
+ a different licensing arrangement.
22
+
23
+ ### Definitions
24
+
25
+ “Copyright Holder” means the individual(s) or organization(s)
26
+ named in the copyright notice for the entire Package.
27
+
28
+ “Contributor” means any party that has contributed code or other
29
+ material to the Package, in accordance with the Copyright Holder's
30
+ procedures.
31
+
32
+ “You” and “your” means any person who would like to copy,
33
+ distribute, or modify the Package.
34
+
35
+ “Package” means the collection of files distributed by the
36
+ Copyright Holder, and derivatives of that collection and/or of
37
+ those files. A given Package may consist of either the Standard
38
+ Version, or a Modified Version.
39
+
40
+ “Distribute” means providing a copy of the Package or making it
41
+ accessible to anyone else, or in the case of a company or
42
+ organization, to others outside of your company or organization.
43
+
44
+ “Distributor Fee” means any fee that you charge for Distributing
45
+ this Package or providing support for this Package to another
46
+ party. It does not mean licensing fees.
47
+
48
+ “Standard Version” refers to the Package if it has not been
49
+ modified, or has been modified only in ways explicitly requested
50
+ by the Copyright Holder.
51
+
52
+ “Modified Version” means the Package, if it has been changed, and
53
+ such changes were not explicitly requested by the Copyright
54
+ Holder.
55
+
56
+ “Original License” means this Artistic License as Distributed with
57
+ the Standard Version of the Package, in its current version or as
58
+ it may be modified by The Perl Foundation in the future.
59
+
60
+ “Source” form means the source code, documentation source, and
61
+ configuration files for the Package.
62
+
63
+ “Compiled” form means the compiled bytecode, object code, binary,
64
+ or any other form resulting from mechanical transformation or
65
+ translation of the Source form.
66
+
67
+ ### Permission for Use and Modification Without Distribution
68
+
69
+ **(1)** You are permitted to use the Standard Version and create and use
70
+ Modified Versions for any purpose without restriction, provided that
71
+ you do not Distribute the Modified Version.
72
+
73
+ ### Permissions for Redistribution of the Standard Version
74
+
75
+ **(2)** You may Distribute verbatim copies of the Source form of the
76
+ Standard Version of this Package in any medium without restriction,
77
+ either gratis or for a Distributor Fee, provided that you duplicate
78
+ all of the original copyright notices and associated disclaimers. At
79
+ your discretion, such verbatim copies may or may not include a
80
+ Compiled form of the Package.
81
+
82
+ **(3)** You may apply any bug fixes, portability changes, and other
83
+ modifications made available from the Copyright Holder. The resulting
84
+ Package will still be considered the Standard Version, and as such
85
+ will be subject to the Original License.
86
+
87
+ ### Distribution of Modified Versions of the Package as Source
88
+
89
+ **(4)** You may Distribute your Modified Version as Source (either gratis
90
+ or for a Distributor Fee, and with or without a Compiled form of the
91
+ Modified Version) provided that you clearly document how it differs
92
+ from the Standard Version, including, but not limited to, documenting
93
+ any non-standard features, executables, or modules, and provided that
94
+ you do at least ONE of the following:
95
+
96
+ * **(a)** make the Modified Version available to the Copyright Holder
97
+ of the Standard Version, under the Original License, so that the
98
+ Copyright Holder may include your modifications in the Standard
99
+ Version.
100
+ * **(b)** ensure that installation of your Modified Version does not
101
+ prevent the user installing or running the Standard Version. In
102
+ addition, the Modified Version must bear a name that is different
103
+ from the name of the Standard Version.
104
+ * **(c)** allow anyone who receives a copy of the Modified Version to
105
+ make the Source form of the Modified Version available to others
106
+ under
107
+ * **(i)** the Original License or
108
+ * **(ii)** a license that permits the licensee to freely copy,
109
+ modify and redistribute the Modified Version using the same
110
+ licensing terms that apply to the copy that the licensee
111
+ received, and requires that the Source form of the Modified
112
+ Version, and of any works derived from it, be made freely
113
+ available in that license fees are prohibited but Distributor
114
+ Fees are allowed.
115
+
116
+ ### Distribution of Compiled Forms of the Standard Version
117
+ ### or Modified Versions without the Source
118
+
119
+ **(5)** You may Distribute Compiled forms of the Standard Version without
120
+ the Source, provided that you include complete instructions on how to
121
+ get the Source of the Standard Version. Such instructions must be
122
+ valid at the time of your distribution. If these instructions, at any
123
+ time while you are carrying out such distribution, become invalid, you
124
+ must provide new instructions on demand or cease further distribution.
125
+ If you provide valid instructions or cease distribution within thirty
126
+ days after you become aware that the instructions are invalid, then
127
+ you do not forfeit any of your rights under this license.
128
+
129
+ **(6)** You may Distribute a Modified Version in Compiled form without
130
+ the Source, provided that you comply with Section 4 with respect to
131
+ the Source of the Modified Version.
132
+
133
+ ### Aggregating or Linking the Package
134
+
135
+ **(7)** You may aggregate the Package (either the Standard Version or
136
+ Modified Version) with other packages and Distribute the resulting
137
+ aggregation provided that you do not charge a licensing fee for the
138
+ Package. Distributor Fees are permitted, and licensing fees for other
139
+ components in the aggregation are permitted. The terms of this license
140
+ apply to the use and Distribution of the Standard or Modified Versions
141
+ as included in the aggregation.
142
+
143
+ **(8)** You are permitted to link Modified and Standard Versions with
144
+ other works, to embed the Package in a larger work of your own, or to
145
+ build stand-alone binary or bytecode versions of applications that
146
+ include the Package, and Distribute the result without restriction,
147
+ provided the result does not expose a direct interface to the Package.
148
+
149
+ ### Items That are Not Considered Part of a Modified Version
150
+
151
+ **(9)** Works (including, but not limited to, modules and scripts) that
152
+ merely extend or make use of the Package, do not, by themselves, cause
153
+ the Package to be a Modified Version. In addition, such works are not
154
+ considered parts of the Package itself, and are not subject to the
155
+ terms of this license.
156
+
157
+ ### General Provisions
158
+
159
+ **(10)** Any use, modification, and distribution of the Standard or
160
+ Modified Versions is governed by this Artistic License. By using,
161
+ modifying or distributing the Package, you accept this license. Do not
162
+ use, modify, or distribute the Package, if you do not accept this
163
+ license.
164
+
165
+ **(11)** If your Modified Version has been derived from a Modified
166
+ Version made by someone other than you, you are nevertheless required
167
+ to ensure that your Modified Version complies with the requirements of
168
+ this license.
169
+
170
+ **(12)** This license does not grant you the right to use any trademark,
171
+ service mark, tradename, or logo of the Copyright Holder.
172
+
173
+ **(13)** This license includes the non-exclusive, worldwide,
174
+ free-of-charge patent license to make, have made, use, offer to sell,
175
+ sell, import and otherwise transfer the Package with respect to any
176
+ patent claims licensable by the Copyright Holder that are necessarily
177
+ infringed by the Package. If you institute patent litigation
178
+ (including a cross-claim or counterclaim) against any party alleging
179
+ that the Package constitutes direct or contributory patent
180
+ infringement, then this Artistic License to you shall terminate on the
181
+ date that such litigation is filed.
182
+
183
+ **(14)** **Disclaimer of Warranty:**
184
+
185
+ THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
186
+ IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
187
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
188
+ NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
189
+ LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
190
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
191
+ DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
192
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,157 @@
1
+ # Wireless
2
+
3
+ [![Build Status](https://travis-ci.org/chocolateboy/wireless.svg)](https://travis-ci.org/chocolateboy/wireless)
4
+ [![Gem Version](https://img.shields.io/gem/v/wireless.svg)](https://rubygems.org/gems/wireless)
5
+
6
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
7
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
8
+
9
+ - [NAME](#name)
10
+ - [INSTALLATION](#installation)
11
+ - [SYNOPSIS](#synopsis)
12
+ - [DESCRIPTION](#description)
13
+ - [WHY?](#why)
14
+ - [Why Wireless?](#why-wireless)
15
+ - [Why Service Locators?](#why-service-locators)
16
+ - [VERSION](#version)
17
+ - [SEE ALSO](#see-also)
18
+ - [Gems](#gems)
19
+ - [Articles](#articles)
20
+ - [AUTHOR](#author)
21
+ - [COPYRIGHT AND LICENSE](#copyright-and-license)
22
+
23
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
24
+
25
+ # NAME
26
+
27
+ Wireless - a lightweight, declarative dependency-provider
28
+
29
+ # INSTALLATION
30
+
31
+ ```ruby
32
+ gem "wireless"
33
+ ```
34
+
35
+ # SYNOPSIS
36
+
37
+ ```ruby
38
+ require "wireless"
39
+
40
+ WL = Wireless.new do
41
+ count = 0
42
+
43
+ # factory: return a new value every time
44
+ on(:foo) do
45
+ [:foo, count += 1]
46
+ end
47
+
48
+ # singleton: return the cached value
49
+ once(:bar) do
50
+ [:bar, 42]
51
+ end
52
+
53
+ # depend on other dependencies
54
+ on(:baz) do |wl|
55
+ [:baz, wl[:foo], wl[:bar]]
56
+ end
57
+ end
58
+
59
+ # factory
60
+ WL[:foo] # [:foo, 1]
61
+ WL[:foo] # [:foo, 2]
62
+
63
+ # singleton
64
+ WL[:bar] # [:bar, 42]
65
+ WL[:bar] # [:bar, 42]
66
+
67
+ # dependencies
68
+ WL[:baz] # [:baz, [:foo, 3], [:bar, 42]]
69
+ WL[:baz] # [:baz, [:foo, 4], [:bar, 42]]
70
+
71
+ # mixin
72
+ class Example
73
+ include WL.mixin %i[foo bar baz]
74
+
75
+ def test
76
+ foo # [:foo, 5]
77
+ bar # [:bar, 42]
78
+ baz # [:baz, [:foo, 6], [:bar, 42]]
79
+ end
80
+ end
81
+ ```
82
+
83
+ # DESCRIPTION
84
+
85
+ Wireless is a declarative dependency-provider (AKA [service locator](https://en.wikipedia.org/wiki/Service_locator_pattern)),
86
+ which has the following features:
87
+
88
+ * Simplicity
89
+
90
+ It's just an object which dependencies can be added to and retrieved from. It can
91
+ be passed around and stored like any other object. No "injection", no containers,
92
+ no framework, no dependencies.
93
+
94
+ * Inclusion
95
+
96
+ Inclusion of dependency getters into a class or module with control over their visibility.
97
+
98
+ * Laziness
99
+
100
+ As well as dependencies being resolved lazily, they can also be *registered* lazily i.e.
101
+ at the point of creation, rather than forcing everything to be declared up-front.
102
+
103
+ * Safety
104
+
105
+ Dependency resolution is thread-safe. Dependency cycles are checked and raise a fatal
106
+ error as soon as they are detected.
107
+
108
+ # WHY?
109
+
110
+ ## Why Wireless?
111
+
112
+ I wanted a simple service locator like [DiFTW](https://github.com/jhollinger/ruby-diftw),
113
+ with cycle detection and control over the [visibility of getters](https://github.com/jhollinger/ruby-diftw/issues/1).
114
+
115
+ ## Why Service Locators?
116
+
117
+ Service locators make it easy to handle shared (AKA
118
+ [cross-cutting](https://en.wikipedia.org/wiki/Cross-cutting_concern)) dependencies i.e.
119
+ values and services that are required by multiple otherwise-unrelated parts of a system.
120
+ Examples include:
121
+
122
+ * logging
123
+ * configuration data
124
+ * storage backends
125
+ * authorisation
126
+
127
+ Rather than wiring these dependencies together manually, service locators allow them to be
128
+ registered and retrieved in a declarative way. This is similar to the difference between
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 creation and connections.
131
+
132
+ # VERSION
133
+
134
+ 0.0.1
135
+
136
+ # SEE ALSO
137
+
138
+ ## Gems
139
+
140
+ - [Canister](https://github.com/mlibrary/canister) - a simple Service Locator for Ruby inspired by Jim Weirich's [article](https://archive.li/shxeA) on Dependency Injection
141
+ - [DiFtw](https://github.com/jhollinger/ruby-diftw) - the original inspiration for this module: a similar API with a focus on testing/mocking
142
+
143
+ ## Articles
144
+
145
+ - Martin Fowler - [Inversion of Control Containers and the Dependency Injection pattern](https://www.martinfowler.com/articles/injection.html) [2004]
146
+ - Jim Weirich - [Dependency Injection in Ruby](https://archive.li/shxeA) [2004]
147
+
148
+ # AUTHOR
149
+
150
+ [chocolateboy](mailto:chocolate@cpan.org)
151
+
152
+ # COPYRIGHT AND LICENSE
153
+
154
+ Copyright © 2018 by chocolateboy.
155
+
156
+ This is free software; you can redistribute it and/or modify it under the
157
+ terms of the [Artistic License 2.0](http://www.opensource.org/licenses/artistic-license-2.0.php).
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The Wireless namespace houses the top-level dependency-provider/service-locator
4
+ # object (Wireless::Registry) and also exposes a static method, Wireless.new, which
5
+ # forwards to and instantiates the registry.
6
+ module Wireless
7
+ class Error < StandardError; end
8
+ class CycleError < Error; end
9
+ class NameError < Error; end
10
+
11
+ # a shortcut which allows:
12
+ #
13
+ # WL = Wireless::Registry.new do
14
+ # # ...
15
+ # end
16
+ #
17
+ # to be written as:
18
+ #
19
+ # WL = Wireless.new do
20
+ # # ...
21
+ # end
22
+ def self.new(*args, &block)
23
+ Wireless::Registry.new(*args, &block)
24
+ end
25
+ end
26
+
27
+ require_relative 'wireless/version'
28
+ require_relative 'wireless/resolver'
29
+ require_relative 'wireless/resolver/factory'
30
+ require_relative 'wireless/resolver/singleton'
31
+ require_relative 'wireless/fetch'
32
+ require_relative 'wireless/registry'
33
+ require_relative 'wireless/fetcher'
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wireless
4
+ # A mixin which provides the #fetch method (and #[] alias) shared by Wireless::Registry
5
+ # and the cut-down version, Wireless::Fetcher, which is passed to the blocks used
6
+ # to resolve dependencies.
7
+ #
8
+ # In both cases, @registry and @seen need to be defined as instance variables.
9
+ # @registry is a hash of { name (Symbol) => dependency (Object) } pairs, and
10
+ # @seen is an immutable Set of symbols, which is used to detect dependency cycles.
11
+ module Fetch
12
+ # Fetches the dependency with the specified name. Creates the dependency if
13
+ # it doesn't exist. Raises a Wireless::NameError if the dependency is not
14
+ # defined or a Wireless::CycleError if resolving the dependency results in
15
+ # a cycle.
16
+ def fetch(name)
17
+ name = name.to_sym
18
+
19
+ if @seen.include?(name)
20
+ path = [*@seen, name].join(' -> ')
21
+ raise Wireless::CycleError, "cycle detected: #{path}"
22
+ end
23
+
24
+ unless (resolver = @registry[name])
25
+ raise Wireless::NameError, "dependency not found: #{name}"
26
+ end
27
+
28
+ fetcher = lambda do
29
+ seen = @seen.dup
30
+ seen.add(name)
31
+ Fetcher.new(registry: @registry, seen: seen)
32
+ end
33
+
34
+ resolver.resolve(fetcher)
35
+ end
36
+
37
+ alias [] fetch
38
+ end
39
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wireless
4
+ # The object passed to the factory (`on`) or singleton (`once`) block:
5
+ # a cut-down version of Wireless::Registry which only provides read access
6
+ # (via #fetch or its #[] alias) to the underlying dependency store
7
+ class Fetcher
8
+ include Fetch
9
+
10
+ def initialize(registry:, seen:)
11
+ @registry = registry
12
+ @seen = seen
13
+ end
14
+
15
+ def to_s
16
+ hash = { registry: @registry.keys, seen: @seen.to_a }
17
+ "#<#{self.class}: #{hash.inspect}>"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require_relative 'synchronized_store'
5
+
6
+ module Wireless
7
+ # The hash-like object which is the public API of the dependency provider
8
+ # (AKA service locator). It maps names (symbols) to dependencies (objects) via blocks
9
+ # which resolve the dependency either every time (factory) or once (singleton).
10
+ #
11
+ # The block can be supplied as a class, in which case it is equivalent to a block
12
+ # which calls +new+ on the class e.g.:
13
+ #
14
+ # WL = Wireless.new do
15
+ # on(:foo, Foo)
16
+ # end
17
+ #
18
+ # is equivalent to:
19
+ #
20
+ # WL = Wireless.new do
21
+ # on(:foo) { Foo.new }
22
+ # end
23
+ class Registry
24
+ DEFAULT_EXPORTS = { private: [], protected: [], public: [] }
25
+ DEFAULT_VISIBILITY = :private
26
+
27
+ include Fetch
28
+
29
+ def initialize(args = DEFAULT_VISIBILITY, &block)
30
+ @default_visibility = args
31
+ @module_cache = {}
32
+ @registry = SynchronizedStore.new
33
+ @seen = Set.new
34
+ instance_eval(&block) if block
35
+ end
36
+
37
+ # Registers a dependency which is resolved every time its value is fetched.
38
+ def factory(name, klass = nil, &block)
39
+ register(Resolver::Factory, name, block || klass)
40
+ end
41
+
42
+ # Returns true if a service with the specified name has been registered, false
43
+ # otherwise
44
+ def include?(key)
45
+ @registry.include?(key)
46
+ end
47
+
48
+ # Registers a dependency which is only resolved the first time its value is
49
+ # fetched. On subsequent fetches, the cached value is returned.
50
+ def singleton(name, klass = nil, &block)
51
+ register(Resolver::Singleton, name, block || klass)
52
+ end
53
+
54
+ # Takes an array or hash specifying the dependencies to export, and returns
55
+ # a module which exposes those dependencies as getters.
56
+ #
57
+ # class Test
58
+ # # hash (specify visibilities)
59
+ # include Services.mixin private: :foo, protected: %i[bar baz], public: :quux
60
+ #
61
+ # # or an array of imports using the default visibility (:private by default)
62
+ # include Services.mixin %i[foo bar baz quux]
63
+ #
64
+ # def test
65
+ # foo + bar + baz + quux # access the dependencies
66
+ # end
67
+ # end
68
+ #
69
+ # The visibility of the generated getters can be controlled by passing a hash
70
+ # with { visibility => imports } pairs, where imports is an array of import
71
+ # specifiers. An import specifier is a symbol (method name == dependency name)
72
+ # or a hash with { dependency_name => method_name } pairs (aliases). If
73
+ # there's only one import specifier, its enclosing array can be omitted e.g.:
74
+ #
75
+ # include Services.mixin(private: :foo, protected: { :baz => :quux })
76
+ #
77
+ # is equivalent to:
78
+ #
79
+ # include Services.mixin(private: [:foo], protected: [{ :baz => :quux }])
80
+ #
81
+ def mixin(args)
82
+ # normalize the supplied argument (array or hash) into a hash of
83
+ # { visibility => exports } pairs, where `visibility` is a symbol and
84
+ # `exports` is a hash of { dependency_name => method_name } pairs
85
+ if args.is_a?(Array)
86
+ args = { @default_visibility => args }
87
+ elsif !args.is_a?(Hash)
88
+ raise ArgumentError, "invalid mixin argument: expected array or hash, got: #{args.class}"
89
+ end
90
+
91
+ # slurp each array of name (symbol) or name => alias (hash) imports into
92
+ # a normalized hash of { dependency_name => method_name } pairs e.g.:
93
+ #
94
+ # before:
95
+ #
96
+ # [:foo, { :bar => :baz }, :quux]
97
+ #
98
+ # after:
99
+ #
100
+ # { :foo => :foo, :bar => :baz, :quux => :quux }
101
+
102
+ # XXX transform_values isn't available on ruby 2.3 and we don't want to
103
+ # pull in ActiveSupport for just one method in this case
104
+ #
105
+ # args = DEFAULT_EXPORTS.merge(args).transform_values do |exports|
106
+ # Array(exports).reduce({}) do |a, b|
107
+ # a.merge(b.is_a?(Hash) ? b : { b => b })
108
+ # end
109
+ # end
110
+
111
+ args = DEFAULT_EXPORTS.merge(args).each_with_object({}) do |(key, exports), merged|
112
+ merged[key] = Array(exports).reduce({}) do |a, b|
113
+ a.merge(b.is_a?(Hash) ? b : { b => b })
114
+ end
115
+ end
116
+
117
+ @module_cache[args] ||= module_for(args)
118
+ end
119
+
120
+ alias on factory
121
+ alias once singleton
122
+
123
+ private
124
+
125
+ # Create a module with the specified exports
126
+ def module_for(args)
127
+ registry = self
128
+ mod = Module.new
129
+
130
+ args.each do |visibility, exports|
131
+ exports.each do |dependency_name, method_name|
132
+ # equivalent to (e.g.):
133
+ #
134
+ # def foo
135
+ # registry.fetch(:foo)
136
+ # end
137
+ mod.send(:define_method, method_name) do
138
+ registry.fetch(dependency_name)
139
+ end
140
+
141
+ # equivalent to (e.g.):
142
+ #
143
+ # private :foo
144
+ mod.send(visibility, method_name)
145
+ end
146
+ end
147
+
148
+ mod
149
+ 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
+ end
157
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wireless
4
+ # The registry is a key/value store (Hash) whose keys are symbols and whose values
5
+ # are instances of this class. Resolvers are responsible for returning their
6
+ # dependencies, which they do by calling their corresponding block. They can wrap
7
+ # additional behaviors around this call e.g. singletons (Wireless::Resolver::Singleton)
8
+ # cache the result so that the block is only called once.
9
+ class Resolver
10
+ def initialize(block = nil)
11
+ if block.is_a?(Class)
12
+ @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
+ else
16
+ @block = block
17
+ end
18
+ end
19
+
20
+ # Abstract method: must be implemented in subclasses
21
+ def resolve(_fetcher)
22
+ raise NotImplementedError, '#resolve is an abstract method'
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wireless
4
+ class Resolver
5
+ # A dependency resolver which runs its block every time the value is fetched.
6
+ class Factory < Resolver
7
+ # return the dependency provided by the block
8
+ def resolve(fetcher)
9
+ @block.call(fetcher.call)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wireless
4
+ class Resolver
5
+ # A dependency resolver which only runs its block the first time the value
6
+ # is read. On subsequent reads, the cached value is returned.
7
+ class Singleton < Resolver
8
+ def initialize(block)
9
+ super(block)
10
+ @lock = Mutex.new
11
+ @seen = false
12
+ @value = nil
13
+ end
14
+
15
+ # Resolve the dependency once. On subsequent calls, return the cached
16
+ # version.
17
+ def resolve(fetcher)
18
+ @lock.synchronize do
19
+ unless @seen
20
+ @value = @block.call(fetcher.call)
21
+ @seen = true
22
+ end
23
+
24
+ @value
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wireless
4
+ # A Hash wrapper which synchronizes get ([]), set ([]=) and include? methods.
5
+ # Implemented as a wrapper rather than a subclass so we don't have to worry about
6
+ # every possible mutator.
7
+ class SynchronizedStore
8
+ def initialize
9
+ @store = {}
10
+ @lock = Mutex.new
11
+ end
12
+
13
+ # Retrieve a new value from the underlying hash in a thread-safe way
14
+ def [](key)
15
+ @lock.synchronize { @store[key] }
16
+ end
17
+
18
+ # Assign a new value to the underlying hash in a thread-safe way
19
+ def []=(key, value)
20
+ @lock.synchronize do
21
+ if @store.include?(key)
22
+ raise Wireless::NameError, "resolver already exists: #{key}"
23
+ end
24
+
25
+ @store[key] = value
26
+ end
27
+ end
28
+
29
+ # Returns true if the underlying hash contains the key, false otherwise
30
+ def include?(key)
31
+ @lock.synchronize { @store.include?(key) }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wireless
4
+ VERSION = '0.0.1'
5
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wireless
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - chocolateboy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-06-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.11'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.11'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest-power_assert
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.3.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.3.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-reporters
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.54.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.54.0
97
+ description:
98
+ email: chocolate@cpan.org
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - CHANGELOG.md
104
+ - LICENSE.md
105
+ - README.md
106
+ - lib/wireless.rb
107
+ - lib/wireless/fetch.rb
108
+ - lib/wireless/fetcher.rb
109
+ - lib/wireless/registry.rb
110
+ - lib/wireless/resolver.rb
111
+ - lib/wireless/resolver/factory.rb
112
+ - lib/wireless/resolver/singleton.rb
113
+ - lib/wireless/synchronized_store.rb
114
+ - lib/wireless/version.rb
115
+ homepage: https://github.com/chocolateboy/wireless
116
+ licenses:
117
+ - Artistic-2.0
118
+ metadata:
119
+ allowed_push_host: https://rubygems.org
120
+ bug_tracker_uri: https://github.com/chocolateboy/wireless/issues
121
+ changelog_uri: https://github.com/chocolateboy/wireless/blob/master/CHANGELOG.md
122
+ source_code_uri: https://github.com/chocolateboy/wireless
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 2.7.7
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: A lightweight, declarative dependency-provider
143
+ test_files: []