scorpion-ioc 0.0.1 → 0.1.0
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 +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
|
-
[](http://badge.fury.io/rb/scorpion-ioc)
|
4
4
|
[](https://codeclimate.com/github/phallguy/scorpion)
|
5
|
+
[](https://codeclimate.com/github/phallguy/scorpion/coverage)
|
5
6
|
[](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
|