scorpion-ioc 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +14 -10
- data/README.md +414 -26
- data/circle.yml +5 -0
- data/config.ru +7 -0
- data/lib/scorpion.rb +87 -0
- data/lib/scorpion/attribute.rb +71 -0
- data/lib/scorpion/attribute_set.rb +96 -0
- data/lib/scorpion/error.rb +29 -0
- data/lib/scorpion/hunter.rb +54 -0
- data/lib/scorpion/hunting_map.rb +115 -0
- data/lib/scorpion/king.rb +150 -0
- data/lib/scorpion/locale/en.yml +5 -0
- data/lib/scorpion/nest.rb +37 -0
- data/lib/scorpion/prey.rb +94 -0
- data/lib/scorpion/prey/builder_prey.rb +32 -0
- data/lib/scorpion/prey/captured_prey.rb +44 -0
- data/lib/scorpion/prey/class_prey.rb +13 -0
- data/lib/scorpion/prey/hunted_prey.rb +14 -0
- data/lib/scorpion/prey/module_prey.rb +14 -0
- data/lib/scorpion/rails.rb +1 -1
- data/lib/scorpion/rails/controller.rb +89 -0
- data/lib/scorpion/version.rb +1 -1
- data/scorpion.gemspec +2 -0
- data/spec/internal/config/database.yml +3 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/combustion_test.sqlite +0 -0
- data/spec/internal/db/schema.rb +3 -0
- data/spec/internal/log/.gitignore +1 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/lib/scorpion/attribute_set_spec.rb +89 -0
- data/spec/lib/scorpion/attribute_spec.rb +8 -0
- data/spec/lib/scorpion/error_spec.rb +8 -0
- data/spec/lib/scorpion/hunter_spec.rb +69 -0
- data/spec/lib/scorpion/hunting_map_spec.rb +118 -0
- data/spec/lib/scorpion/{scorpion.rb → instance_spec.rb} +0 -0
- data/spec/lib/scorpion/king_spec.rb +129 -0
- data/spec/lib/scorpion/prey/module_prey_spec.rb +16 -0
- data/spec/lib/scorpion/prey_spec.rb +76 -0
- data/spec/lib/scorpion/rails/controller_spec.rb +111 -0
- data/spec/lib/scorpion_spec.rb +0 -1
- data/spec/spec_helper.rb +6 -0
- metadata +78 -6
- data/Procfile +0 -1
- data/lib/scorpion/rails/railtie.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19c5f928b91989643e0db1c9fef98016958d5813
|
4
|
+
data.tar.gz: 7e03d874e6714dae07b4da0fd3481404087df760
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e8b8889dafadd148472b73921eb1a1da3af9a0f006870c083abfb9bb600b2e9b21fdb24ab696f6f258621cd4ad3d6a497e0c143817399621f6dc0f2383758ad
|
7
|
+
data.tar.gz: bfee710c8d61409b0915dc98a206e0493d9c4b30a5406f41ab1817a0077c46418b27be3d960899b6bcb1904becde4978ab5ec9d1f34b6cff21af97f062fa38fb
|
data/Gemfile
CHANGED
@@ -3,15 +3,19 @@ source 'https://rubygems.org'
|
|
3
3
|
# Specify your gem's dependencies in scorpion.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
+
group :test do
|
6
7
|
|
7
|
-
gem 'guard', '~> 2.12.8'
|
8
|
-
gem 'spring'
|
9
|
-
gem 'byebug'
|
10
|
-
gem 'pry-byebug'
|
11
|
-
gem 'guard-rspec'
|
12
|
-
gem 'fuubar'
|
13
|
-
gem '
|
14
|
-
gem '
|
15
|
-
gem '
|
8
|
+
gem 'guard', '~> 2.12.8'
|
9
|
+
gem 'spring'
|
10
|
+
gem 'byebug'
|
11
|
+
gem 'pry-byebug'
|
12
|
+
gem 'guard-rspec'
|
13
|
+
gem 'fuubar'
|
14
|
+
gem 'yard'
|
15
|
+
gem 'simplecov', github: "colszowka/simplecov"
|
16
|
+
gem 'ruby_gntp', '~> 0.3.4'
|
17
|
+
gem 'awesome_print'
|
18
|
+
gem 'sqlite3'
|
16
19
|
|
17
|
-
gem "codeclimate-test-reporter", group: :test, require: nil
|
20
|
+
gem "codeclimate-test-reporter", group: :test, require: nil
|
21
|
+
end
|
data/README.md
CHANGED
@@ -1,19 +1,425 @@
|
|
1
1
|
# Scorpion
|
2
2
|
|
3
|
-
[![Gem Version](https://badge.fury.io/rb/
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/scorpion-ioc.svg)](http://badge.fury.io/rb/scorpion-ioc)
|
4
4
|
[![Code Climate](https://codeclimate.com/github/phallguy/scorpion.png)](https://codeclimate.com/github/phallguy/scorpion)
|
5
|
+
[![Test Coverage](https://codeclimate.com/github/phallguy/scorpion/badges/coverage.svg)](https://codeclimate.com/github/phallguy/scorpion/coverage)
|
5
6
|
[![Circle CI](https://circleci.com/gh/phallguy/scorpion.svg?style=svg)](https://circleci.com/gh/phallguy/scorpion)
|
6
7
|
|
7
8
|
Add IoC to rails with minimal fuss and ceremony.
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
11
|
+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
11
12
|
|
12
13
|
|
13
|
-
|
14
|
+
- [Dependency Injection](#dependency-injection)
|
15
|
+
- [Why might you _Want_ a DI FRamework?](#why-might-you-_want_-a-di-framework)
|
16
|
+
- [Using Scorpion](#using-scorpion)
|
17
|
+
- [Kings](#kings)
|
18
|
+
- [Configuration](#configuration)
|
19
|
+
- [Nests](#nests)
|
20
|
+
- [Rails](#rails)
|
21
|
+
- [Contributing](#contributing)
|
22
|
+
- [License](#license)
|
14
23
|
|
15
|
-
|
24
|
+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
16
25
|
|
26
|
+
## Dependency Injection
|
27
|
+
|
28
|
+
Dependency injection helps to break explicit dependencies between objects making
|
29
|
+
it much easier to maintain a [single
|
30
|
+
responsibility](https://en.wikipedia.org/wiki/Single_responsibility_principle)
|
31
|
+
and reduce [coupling](https://en.wikipedia.org/wiki/Coupling_(computer_programming))
|
32
|
+
in our class designs. This leads to more testable code and code that is more
|
33
|
+
resilient to change.
|
34
|
+
|
35
|
+
Several have argued that the dynamic properties of Ruby make Dependency
|
36
|
+
Injection _frameworks_ irrelevant. Some argue that you can build in defaults and
|
37
|
+
make them overridable, or just use module mixins.
|
38
|
+
|
39
|
+
Most of these counter arguments focus on testing, and given how easy it is to
|
40
|
+
mock objects in Ruby, you don't really need a framework. If testing were the
|
41
|
+
only virtue they'd be spot on. Despite it's virtues DI doesn't come without it's
|
42
|
+
own problems. However for larger projects that you expect to be long-lived, a DI
|
43
|
+
framework may help manage the complexity.
|
44
|
+
|
45
|
+
For a deeper background on Dependency Injection consider the
|
46
|
+
[Wikipedia](https://en.wikipedia.org/wiki/Dependency_injection) article on the
|
47
|
+
subject.
|
48
|
+
|
49
|
+
### Why might you _Want_ a DI FRamework?
|
50
|
+
|
51
|
+
Assuming you've embraced the general concept of DI why would you want to use a
|
52
|
+
framework. Lets consider the alternatives.
|
53
|
+
|
54
|
+
##### Setter/Default Injection
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
class Hunter
|
58
|
+
def weapon
|
59
|
+
@weapon ||= Weapon.new
|
60
|
+
end
|
61
|
+
def weapon=( value )
|
62
|
+
@weapon = value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
In this scenario the Hunter class knows how to create a weapon and provides a
|
68
|
+
sane default, but allows the dependency to be overridden if needed.
|
69
|
+
|
70
|
+
**PROS**
|
71
|
+
|
72
|
+
- Very simple to understand and debug
|
73
|
+
- Provides basic flexibility
|
74
|
+
- The dependency is clearly defined.
|
75
|
+
|
76
|
+
**CONS**
|
77
|
+
|
78
|
+
- Still coupled to a specific type of Weapon.
|
79
|
+
- If multiple classes use this approach and you decide to upgrade your armory,
|
80
|
+
you'd have to modify every line that creates new weapons. The factory pattern
|
81
|
+
can be used to address such a dependency.
|
82
|
+
- No global method of replacing a Weapon class with a specialized or augmented
|
83
|
+
class. For example a ThreadLockedWeapon.
|
84
|
+
|
85
|
+
##### Constructor/Ignorant Injection
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
class Hunter
|
89
|
+
def initialize( weapon )
|
90
|
+
@weapon = weapon
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
Here Hunters can use any weapon and can be designed to an interface Weapon that
|
96
|
+
does not have an implementation yet.
|
97
|
+
|
98
|
+
**PROS**
|
99
|
+
|
100
|
+
- Provides flexibility
|
101
|
+
- Work can proceed concurrently on Hunter and Weapon classes by different
|
102
|
+
engineers on the team.
|
103
|
+
|
104
|
+
**CONS**
|
105
|
+
|
106
|
+
- Hard to reason about Hunters and Weapons as a whole.
|
107
|
+
- The dependency is not clearly defined - what is a weapon?
|
108
|
+
- It pushes the responsibility of constructing dependencies onto the consumer of
|
109
|
+
the class. If the class is used in multiple places this becomes a maintenance
|
110
|
+
chore when changes are required.
|
111
|
+
- It becomes tedious to use classes resulting in repeated boilerplate code that
|
112
|
+
distracts from the primary responsibility of the calling code.
|
113
|
+
|
114
|
+
|
115
|
+
#### Using a Framework...like Scorpion
|
116
|
+
|
117
|
+
Using a good framework can help conserve the pros of each method while
|
118
|
+
minimizing the cons. A DI framework works like an automatic factory system
|
119
|
+
resolving dependencies cleanly like a factory but without all the effort to
|
120
|
+
create custom factories.
|
121
|
+
|
122
|
+
A good framework should
|
123
|
+
|
124
|
+
- Make dependencies clear
|
125
|
+
- Require a minimal amount of configuration or ceremony
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
class Hunter
|
129
|
+
depend_on do
|
130
|
+
weapon Weapon
|
131
|
+
end
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
Here the dependency is clearly defined - and even creates accessors for getting
|
136
|
+
and setting the weapon. When a Hunter is created it's dependencies are also
|
137
|
+
created - and any of their dependencies and so on. Usage is equally simple
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
hunter = scorpion.hunt! Hunter
|
141
|
+
hunter.weapon # => a Weapon
|
142
|
+
```
|
143
|
+
|
144
|
+
Overriding the kind of weapons used by hunters.
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
class Axe < Weapon; end
|
148
|
+
|
149
|
+
scorpion.prepare do
|
150
|
+
hunt_for Axe
|
151
|
+
end
|
152
|
+
|
153
|
+
hunter = scorpion.hunt! Hunter
|
154
|
+
hunter.weapon # => an Axe
|
155
|
+
```
|
156
|
+
|
157
|
+
Overriding hunters!
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
class Axe < Weapon; end
|
161
|
+
class Predator < Hunter; end
|
162
|
+
|
163
|
+
scorpion.prepare do
|
164
|
+
hunt_for Predator
|
165
|
+
hunt_for Axe
|
166
|
+
end
|
167
|
+
|
168
|
+
hunter = scorpion.hunt! Hunter
|
169
|
+
hunter # => Predator
|
170
|
+
hunter.weapon # => an Axe
|
171
|
+
```
|
172
|
+
|
173
|
+
|
174
|
+
## Using Scorpion
|
175
|
+
|
176
|
+
Out of the box Scorpion does not need any configuration and will work
|
177
|
+
immediately. You can hunt for any Class even if it hasn't been configured.
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
hash = Scorpion.instance.hunt! Hash
|
181
|
+
hash # => {}
|
182
|
+
```
|
183
|
+
|
184
|
+
### Kings
|
185
|
+
|
186
|
+
Scorpions feed their [Scorpion Kings](lib/scorpion/king.rb) - any object that
|
187
|
+
should be fed its dependencies when it is created. Simply include the
|
188
|
+
Scorpion::King module into your class to benefit from Scorpion injections.
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
class Keeper
|
192
|
+
include Scorpion::King
|
193
|
+
|
194
|
+
feed_on do
|
195
|
+
lunch FastFood
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
class Zoo
|
200
|
+
include Scorpion::King
|
201
|
+
|
202
|
+
feed_on do # or #depend_on if you like
|
203
|
+
keeper Zoo::Keeper
|
204
|
+
vet Zoo::Vet, lazy: true
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
zoo = scorpion.hunt! Zoo
|
209
|
+
zoo.keeper # => an instance of a Zoo::Keeper
|
210
|
+
zoo.vet? # => false it hasn't been hunted down yet
|
211
|
+
zoo.vet # => an instnace of a Zoo::Vet
|
212
|
+
zoo.keeper.lunch # => an instance of FastFood
|
213
|
+
```
|
214
|
+
|
215
|
+
All of your classes should be kings! And any dependency that is also a King will
|
216
|
+
be fed.
|
217
|
+
|
218
|
+
### Configuration
|
219
|
+
|
220
|
+
A good scorpion should be prepared to hunt. An effort that describes what the
|
221
|
+
scorpion hunts for and how it should be found. Scorpion uses Classes and Modules
|
222
|
+
as the primary means of identifying prey in favor of opaque labels or strings.
|
223
|
+
This serves two benefits:
|
224
|
+
|
225
|
+
1. The type of object expected by the dependency is clearly identified making it
|
226
|
+
easier to understand what the concrete dependencies really are.
|
227
|
+
2. Types (Classes & Modules) explicitly declare the expected behavioral contract
|
228
|
+
of an object's dependencies.
|
229
|
+
|
230
|
+
#### Classes
|
231
|
+
|
232
|
+
Most scorpion hunts will be for an instance of a specific class (or a more
|
233
|
+
derived class). In the absence of any configuration, Scorpion will simply create
|
234
|
+
an instance of the specific class requested.
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
scorpion.hunt! Hash # => Hash.new
|
238
|
+
|
239
|
+
scorpion.prepare do
|
240
|
+
hunt_for Object::HashWithIndifferentAccess
|
241
|
+
end
|
242
|
+
|
243
|
+
scorpion.hunt! Hash # => Object::HashWithIndifferentAccess.new
|
244
|
+
```
|
245
|
+
|
246
|
+
#### Modules
|
247
|
+
|
248
|
+
Modules can be hunted for in two ways.
|
249
|
+
|
250
|
+
1. If a Class has been prepared for hunting that includes the module, it will
|
251
|
+
be used to satisfy requests for that module
|
252
|
+
2. If no Class is found, the Module itself will be returned.
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
module Sharp
|
256
|
+
module_function
|
257
|
+
def poke; self.class.name end
|
258
|
+
end
|
259
|
+
|
260
|
+
class Sword
|
261
|
+
include Sharp
|
262
|
+
end
|
263
|
+
|
264
|
+
poker = scorpion.hunt! Sharp
|
265
|
+
poker.poke # => "Module"
|
266
|
+
|
267
|
+
scorpion.prepare do
|
268
|
+
hunt_for Sword
|
269
|
+
end
|
270
|
+
|
271
|
+
poker = scorpion.hunt! Sharp
|
272
|
+
poker.poke # => "Sword"
|
273
|
+
```
|
274
|
+
|
275
|
+
#### Traits
|
276
|
+
|
277
|
+
Traits can be used to distinguish between prey of the same type. For example
|
278
|
+
a scorpion may be prepare to hunt for several weapons and the king needs a
|
279
|
+
blunt weapon.
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
class Weapon; end
|
283
|
+
class Mace < Weapon; end
|
284
|
+
class Hammer < Weapon; end
|
285
|
+
class Sword < Weapon; end
|
286
|
+
|
287
|
+
scorpion.prepare do
|
288
|
+
hunt_for Hammer, :blunt
|
289
|
+
hunt_for Sword, :sharp
|
290
|
+
hunt_for Mace, :blunt, :sharp
|
291
|
+
end
|
292
|
+
|
293
|
+
scorpion.hunt! Weapon, :blunt # => Hammer.new
|
294
|
+
scorpion.hunt! Weapon, :sharp # => Sword.new
|
295
|
+
scorpion.hunt! Weapon, :sharp, :blunt # => Mace.new
|
296
|
+
```
|
297
|
+
|
298
|
+
Modules can also be used to identify specific traits desired from the hunted
|
299
|
+
prey.
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
module Color; end
|
303
|
+
module Streaming; end
|
304
|
+
class Logger; end
|
305
|
+
class Console < Logger
|
306
|
+
include Color
|
307
|
+
end
|
308
|
+
class SysLog < Logger
|
309
|
+
include Streaming
|
310
|
+
end
|
311
|
+
|
312
|
+
scorpion.prepare do
|
313
|
+
hunt_for Console
|
314
|
+
hunt_for SysLog
|
315
|
+
end
|
316
|
+
|
317
|
+
scorpion.hunt! Logger, Color # => Console.new
|
318
|
+
scorpion.hunt! Logger, Streaming # => SysLog.new
|
319
|
+
```
|
320
|
+
|
321
|
+
#### Builders
|
322
|
+
|
323
|
+
Sometimes resolving the correct dependencies is a bit more dynamic. In those
|
324
|
+
cases you can use a builder block to hunt for prey.
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
class Samurai < Sword; end
|
328
|
+
class Broad < Sword; end
|
329
|
+
|
330
|
+
scorpion.prepare do
|
331
|
+
hunt_for Sword do |scorpion|
|
332
|
+
scorpion.spawn Random.rand( 2 ) == 1 ? Samurai : Broad
|
333
|
+
end
|
334
|
+
end
|
335
|
+
```
|
336
|
+
|
337
|
+
#### Singletons
|
338
|
+
|
339
|
+
Scorpion allows you to capture prey and feed the same instance to everyone that
|
340
|
+
asks for a matching dependency.
|
341
|
+
|
342
|
+
DI singletons are different then global singletons in that each scorpion can
|
343
|
+
have a unique instance of the class that it shares with all of it's kings. This
|
344
|
+
allows, for example, global variable like support per-request without polluting
|
345
|
+
the global namespace or dealing with thread concurrency issues.
|
346
|
+
|
347
|
+
|
348
|
+
```ruby
|
349
|
+
class Logger; end
|
350
|
+
|
351
|
+
scorpion.prepare do
|
352
|
+
capture Logger
|
353
|
+
end
|
354
|
+
|
355
|
+
scorpion.hunt! Logger # => Logger.new
|
356
|
+
scorpion.hunt! Logger # => Previously captured logger
|
357
|
+
```
|
358
|
+
|
359
|
+
Captured dependencies are not shared with child scorpions (for example when
|
360
|
+
conceiving scorpions from a [Nest](Nests)). To share captured prey with children
|
361
|
+
use `share`.
|
362
|
+
|
363
|
+
### Nests
|
364
|
+
|
365
|
+
A scorpion nest is where a mother scorpion lives and conceives young -
|
366
|
+
duplicates of the mother but maintaining their own state. The scorpion nest is
|
367
|
+
used by the Rails integration to give each request it's own scorpion.
|
368
|
+
|
369
|
+
All preparation performed by the mother is shared with all the children it
|
370
|
+
conceives so that configuration is established when the application starts.
|
371
|
+
|
372
|
+
```ruby
|
373
|
+
nest.prepare do
|
374
|
+
hunt_for Logger
|
375
|
+
end
|
376
|
+
|
377
|
+
scorpion = nest.conceive
|
378
|
+
scorpion.hunt! Logger # => Logger.new
|
379
|
+
```
|
380
|
+
|
381
|
+
### Rails
|
382
|
+
|
383
|
+
Scorpion provides simple integration into for rails controllers to establish
|
384
|
+
a scorpion for each request.
|
385
|
+
|
386
|
+
```ruby
|
387
|
+
# user_service.rb
|
388
|
+
class UserService
|
389
|
+
def find( username ) ... end
|
390
|
+
end
|
391
|
+
|
392
|
+
# config/nest.rb
|
393
|
+
require 'scorpion'
|
394
|
+
|
395
|
+
Scorpion.prepare do
|
396
|
+
capture UserService # Share with all the objects that are spawned in _this_ request
|
397
|
+
|
398
|
+
share do
|
399
|
+
capture Logger # Share with every request
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
# application_controller.rb
|
404
|
+
require 'scorpion'
|
405
|
+
|
406
|
+
class ApplicationController < ActionController::Base
|
407
|
+
include Scorpion::Rails::Controller
|
408
|
+
|
409
|
+
feed_on do
|
410
|
+
users UserService, lazy: true
|
411
|
+
end
|
412
|
+
|
413
|
+
end
|
414
|
+
|
415
|
+
# users_controller.rb
|
416
|
+
class UsersController < ApplicationController
|
417
|
+
def show
|
418
|
+
user = users.find( "batman" )
|
419
|
+
logger.write "Found a user: #{ user }"
|
420
|
+
end
|
421
|
+
end
|
422
|
+
```
|
17
423
|
|
18
424
|
## Contributing
|
19
425
|
|
@@ -24,26 +430,8 @@ dependency injection design principle.
|
|
24
430
|
5. Create a new Pull Request
|
25
431
|
|
26
432
|
|
27
|
-
|
28
|
-
|
29
|
-
The MIT License (MIT)
|
30
|
-
|
31
|
-
Copyright (c) 2015 Paul Alexander
|
32
|
-
|
33
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
34
|
-
of this software and associated documentation files (the "Software"), to deal
|
35
|
-
in the Software without restriction, including without limitation the rights
|
36
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
37
|
-
copies of the Software, and to permit persons to whom the Software is
|
38
|
-
furnished to do so, subject to the following conditions:
|
433
|
+
## License
|
39
434
|
|
40
|
-
The
|
41
|
-
copies or substantial portions of the Software.
|
435
|
+
[The MIT License (MIT)](http://opensource.org/licenses/MIT)
|
42
436
|
|
43
|
-
|
44
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
45
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
46
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
47
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
48
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
49
|
-
SOFTWARE.
|
437
|
+
Copyright (c) 2015 Paul Alexander
|