wireless 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,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: []