wireless 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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).
|
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: []
|