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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.md +192 -0
- data/README.md +157 -0
- data/lib/wireless.rb +33 -0
- data/lib/wireless/fetch.rb +39 -0
- data/lib/wireless/fetcher.rb +20 -0
- data/lib/wireless/registry.rb +157 -0
- data/lib/wireless/resolver.rb +25 -0
- data/lib/wireless/resolver/factory.rb +13 -0
- data/lib/wireless/resolver/singleton.rb +29 -0
- data/lib/wireless/synchronized_store.rb +34 -0
- data/lib/wireless/version.rb +5 -0
- metadata +143 -0
checksums.yaml
ADDED
|
@@ -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
|
data/CHANGELOG.md
ADDED
data/LICENSE.md
ADDED
|
@@ -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.
|
data/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Wireless
|
|
2
|
+
|
|
3
|
+
[](https://travis-ci.org/chocolateboy/wireless)
|
|
4
|
+
[](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).
|
data/lib/wireless.rb
ADDED
|
@@ -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
|
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: []
|