hookr 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +22 -0
- data/README.txt +491 -0
- data/Rakefile +28 -0
- data/bin/hookr +8 -0
- data/lib/hookr.rb +653 -0
- data/spec/hookr_spec.rb +1041 -0
- data/spec/spec_helper.rb +16 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +192 -0
- data/tasks/git.rake +40 -0
- data/tasks/manifest.rake +48 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +39 -0
- data/tasks/rdoc.rake +50 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +279 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/test/test_hookr.rb +0 -0
- metadata +77 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.txt
|
4
|
+
Rakefile
|
5
|
+
bin/hookr
|
6
|
+
lib/hookr.rb
|
7
|
+
spec/hookr_spec.rb
|
8
|
+
spec/spec_helper.rb
|
9
|
+
tasks/ann.rake
|
10
|
+
tasks/bones.rake
|
11
|
+
tasks/gem.rake
|
12
|
+
tasks/git.rake
|
13
|
+
tasks/manifest.rake
|
14
|
+
tasks/notes.rake
|
15
|
+
tasks/post_load.rake
|
16
|
+
tasks/rdoc.rake
|
17
|
+
tasks/rubyforge.rake
|
18
|
+
tasks/setup.rb
|
19
|
+
tasks/spec.rake
|
20
|
+
tasks/svn.rake
|
21
|
+
tasks/test.rake
|
22
|
+
test/test_hookr.rb
|
data/README.txt
ADDED
@@ -0,0 +1,491 @@
|
|
1
|
+
= HookR
|
2
|
+
by Avdi Grimm
|
3
|
+
http://hookr.rubyforge.org
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
HookR is a publish/subscribe callback hook facility for Ruby.
|
8
|
+
|
9
|
+
=== What is it?
|
10
|
+
|
11
|
+
HookR can be understood in a few different ways.
|
12
|
+
|
13
|
+
* If you are familiar with Events and Event Listeners in
|
14
|
+
Java[http://java.sun.com/docs/books/tutorial/javabeans/events/index.html] or
|
15
|
+
C#[http://msdn.microsoft.com/en-us/library/aa645739(VS.71).aspx];
|
16
|
+
Hooks[http://www.gnu.org/software/emacs/manual/html_node/elisp/Hooks.html#Hooks]
|
17
|
+
in Emacs-lisp; or signals-and-slots as implemented in the
|
18
|
+
Qt[http://doc.trolltech.com/4.4/signalsandslots.html],
|
19
|
+
Boost.Signals[http://www.boost.org/doc/libs/1_37_0/doc/html/signals.html], or
|
20
|
+
libsigc++[http://libsigc.sourceforge.net/] frameworks - HookR provides a
|
21
|
+
very similar facility.
|
22
|
+
* If youve ever used the Observer standard library, but wished you could
|
23
|
+
have more than one type of notification per observable object, HookR is the
|
24
|
+
library for you.
|
25
|
+
* HookR is an easy way to add
|
26
|
+
Rails-style[http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html]
|
27
|
+
before- and after-filters to your own classes.
|
28
|
+
* HookR is an
|
29
|
+
Inversion-of-Control[http://martinfowler.com/bliki/InversionOfControl.html]
|
30
|
+
framework in that it makes it easy to write event-driven code.
|
31
|
+
* HookR is a way to support a limited, structured form of Aspect Oriented
|
32
|
+
Programming (AOP[http://en.wikipedia.org/wiki/Aspect-oriented_programming])
|
33
|
+
where the advisable events are explicitly defined.
|
34
|
+
|
35
|
+
=== What HookR is not:
|
36
|
+
|
37
|
+
* HookR is not (yet) an asynchronous event notification system. No provision is
|
38
|
+
made for multi-threaded operation or event queueing.
|
39
|
+
* HookR will show you a good time, but it will not make you breakfast in the
|
40
|
+
morning.
|
41
|
+
|
42
|
+
== FEATURES:
|
43
|
+
|
44
|
+
* Fully spec'd
|
45
|
+
* Provides class-level and instance-level callbacks
|
46
|
+
* Inheritance-safe
|
47
|
+
* Supports both iterative and recursive callback models
|
48
|
+
* "Wildcard" callbacks can observe all events
|
49
|
+
* Three types of callback supported - internal (instance-eval'd), external, and
|
50
|
+
method callbacks.
|
51
|
+
|
52
|
+
== SYNOPSIS:
|
53
|
+
|
54
|
+
require 'rubygems'
|
55
|
+
require 'hookr'
|
56
|
+
|
57
|
+
class ZeroWing
|
58
|
+
include HookR::Hooks
|
59
|
+
define_hook :we_get_signal, :message
|
60
|
+
|
61
|
+
def start_game
|
62
|
+
execute_hook(:we_get_signal, "How are you gentlemen?")
|
63
|
+
end
|
64
|
+
|
65
|
+
def bomb(event, message)
|
66
|
+
puts "somebody set us up the bomb!"
|
67
|
+
end
|
68
|
+
|
69
|
+
we_get_signal do |event, message|
|
70
|
+
puts "Main screen turn on!"
|
71
|
+
puts "Cats: #{message}"
|
72
|
+
end
|
73
|
+
|
74
|
+
we_get_signal :bomb
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
zw = ZeroWing.new
|
79
|
+
zw.we_get_signal do
|
80
|
+
puts "Take off every zig!"
|
81
|
+
end
|
82
|
+
|
83
|
+
zw.start_game
|
84
|
+
# >> Main screen turn on!
|
85
|
+
# >> Cats: How are you gentlemen?
|
86
|
+
# >> somebody set us up the bomb!
|
87
|
+
# >> Take off every zig!
|
88
|
+
|
89
|
+
== DETAILS
|
90
|
+
|
91
|
+
Pour yourself a drink, loosen your tie, and let's get cozy with HookR.
|
92
|
+
|
93
|
+
=== Hooks
|
94
|
+
|
95
|
+
Hooks are at the center of HookR's functionality. A hook is a named attachment
|
96
|
+
point for arbitrary callbacks. It is the "publish" portion of
|
97
|
+
publish/subscribe. From the event-handling perspective, hooks define interesting
|
98
|
+
events in an object's lifetime. For example, an XML parser might define hooks
|
99
|
+
named <code>:tag_start</code> and <code>:tag_end</code> hooks. A network
|
100
|
+
protocol class might define <code>:connected</code> and
|
101
|
+
<code>:message_received</code> hooks. A database-backed model might define
|
102
|
+
<code>:before_save</code> and <code>:after_save</code> hooks.
|
103
|
+
|
104
|
+
Hooks are defined at the class level, using the <code>define_hook</code> method:
|
105
|
+
|
106
|
+
class ZeroWing
|
107
|
+
define_hook :we_get_signal
|
108
|
+
end
|
109
|
+
|
110
|
+
==== Hook Parameters
|
111
|
+
|
112
|
+
Sometimes we want to pass some data along with our events. Hooks can define
|
113
|
+
named parameters by passing extra symbol arguments to <code>define_hook</code>:
|
114
|
+
|
115
|
+
class ZeroWing
|
116
|
+
include HookR::Hooks
|
117
|
+
define_hook :we_get_signal, :message
|
118
|
+
end
|
119
|
+
|
120
|
+
==== Listing Hooks
|
121
|
+
|
122
|
+
You can access the full set of hooks defined for a particular class by calling
|
123
|
+
the #hooks class method:
|
124
|
+
|
125
|
+
ZeroWing.hooks # => #<Hookr::HookSet: ... >
|
126
|
+
|
127
|
+
If you are playing along at home you may notice a <code>:__wildcards__</code> hook in this
|
128
|
+
list. We'll talk about that in the Advanced section.
|
129
|
+
|
130
|
+
=== Callbacks
|
131
|
+
|
132
|
+
Hooks aren't much use without callbacks. Callbacks represent a piece of code to
|
133
|
+
be run when a hook is executed. They are the "subscribe" part of the
|
134
|
+
publish/subscribe duo.
|
135
|
+
|
136
|
+
HookR defines three types of callback:
|
137
|
+
|
138
|
+
==== Internal Callbacks
|
139
|
+
|
140
|
+
An internal callback represents a block of code which will be run in the context
|
141
|
+
of the source object (the object executing the hook). That is, it will be run
|
142
|
+
using #instance_eval. In general this type of callback should only be defined
|
143
|
+
internally to the class having the hook, since the called code will have full
|
144
|
+
access to private members and instance variables of the source object.
|
145
|
+
|
146
|
+
One drawback of internal callbacks is that due to limitations of
|
147
|
+
<code>#instance_eval</code>, they cannot receive arguments.
|
148
|
+
|
149
|
+
==== External Callbacks
|
150
|
+
|
151
|
+
An external callback is a block of code which will be executed in the context in
|
152
|
+
which it was defined. That is, a Proc which will be called with the Event
|
153
|
+
object (see below) and any parameters defined by the hook.
|
154
|
+
|
155
|
+
==== Method Callbacks
|
156
|
+
|
157
|
+
A method callback is a callback which when executed will call an instance method
|
158
|
+
on the source object (the object executing the hook). Like internal callbacks,
|
159
|
+
these should usually only be added internally by the source class, since private
|
160
|
+
methods may be called. The method will receive as arguments the Event (see
|
161
|
+
below), and an argument for each parameter defined on the hook.
|
162
|
+
|
163
|
+
==== Named Callbacks
|
164
|
+
|
165
|
+
A callback may be *named* or *anonymous*. Naming callbacks makes it easier to
|
166
|
+
reference them after adding them, for instance if you want to remove a
|
167
|
+
callback. Naming calbacks also ensures that only one callback with the given name
|
168
|
+
will be added to a hook.
|
169
|
+
|
170
|
+
==== Adding Callbacks
|
171
|
+
|
172
|
+
There are several ways to add callbacks to hooks.
|
173
|
+
|
174
|
+
===== Adding callbacks In the class definition
|
175
|
+
|
176
|
+
The first way to define a callback is to do it in the class definition:
|
177
|
+
|
178
|
+
class ZeroWing
|
179
|
+
include HookR::Hooks
|
180
|
+
define_hook :we_get_signal, :message
|
181
|
+
|
182
|
+
we_get_signal do
|
183
|
+
main_screen.turn_on!
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
HookR creates class-level macros for each defined hook. The above example
|
188
|
+
demonstrates an anonymous *internal* callback being defined on the hook
|
189
|
+
<code>:we_get_signal</code>. Why internal? HookR uses a set of rules to
|
190
|
+
determine what kind of callback to generate. If the block passed to the
|
191
|
+
callback macro has no arguments, it will generate an internal callback. If,
|
192
|
+
however, the block defines arguments:
|
193
|
+
|
194
|
+
class ZeroWing
|
195
|
+
include HookR::Hooks
|
196
|
+
define_hook :we_get_signal, :message
|
197
|
+
|
198
|
+
we_get_signal do |event, message|
|
199
|
+
puts message.what_you_say?
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
An *external* callback will be defined. Why external? As discussed earlier,
|
204
|
+
it is impossible for <code>instance_eval</code>-ed code to receive arguments. So in order
|
205
|
+
to supply the defined parameters an external callback must be defined.
|
206
|
+
|
207
|
+
If no block is passed, but a method name is supplied, a *method* callback will
|
208
|
+
be generated:
|
209
|
+
|
210
|
+
class ZeroWing
|
211
|
+
include HookR::Hooks
|
212
|
+
|
213
|
+
def take_off_every_zig(event, message)
|
214
|
+
# ...
|
215
|
+
end
|
216
|
+
|
217
|
+
define_hook :we_get_signal, :message
|
218
|
+
|
219
|
+
we_get_signal :take_off_every_zig
|
220
|
+
end
|
221
|
+
|
222
|
+
For any of the variations demonstrated above, an explicit symbolic callback
|
223
|
+
handle may be supplied. This handle can then be used to access or remove the
|
224
|
+
callback.
|
225
|
+
|
226
|
+
class ZeroWing
|
227
|
+
include HookR::Hooks
|
228
|
+
|
229
|
+
define_hook :we_get_signal, :message
|
230
|
+
|
231
|
+
we_get_signal :zig do
|
232
|
+
take_off_every_zig
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
ZeroWing.remove_callback(:zig)
|
237
|
+
|
238
|
+
===== In instance methods
|
239
|
+
|
240
|
+
In instance methods of the class defining the hook, it is possible to explicitly
|
241
|
+
add the different types of callback using the methods <code>add_external_callback</code>,
|
242
|
+
<code>add_internal_callback</code>, and #add_method_callback. See the method documentation
|
243
|
+
for details.
|
244
|
+
|
245
|
+
The methods all return a callback *handle*, which can be used to access or remove
|
246
|
+
the callback. This will be the same as the <code>handle</code> argument, if one is supplied.
|
247
|
+
|
248
|
+
===== In client code
|
249
|
+
|
250
|
+
In code that uses a hook-enabled object, callbacks can be easily added using a
|
251
|
+
method with the same name as the hook:
|
252
|
+
|
253
|
+
zw = ZeroWing.new
|
254
|
+
zw.we_get_signal do |event, message|
|
255
|
+
puts "it's you!"
|
256
|
+
end
|
257
|
+
|
258
|
+
Only *external* callbacks may be added using this method. This is consistent
|
259
|
+
with public/private class protection.
|
260
|
+
|
261
|
+
Like the <code>add_*_callback</code> methods described above, this method may be passed an
|
262
|
+
explicit symbolic handle. Whether an explicit handle is supplied or not, it
|
263
|
+
will always return a handle which can be used to access or remove the added
|
264
|
+
callback.
|
265
|
+
|
266
|
+
==== Removing Callbacks
|
267
|
+
|
268
|
+
<code>remove_callback</code> methods are available at both the class and instance levels.
|
269
|
+
They can be used to remove class- or instance-level callbacks, respectively.
|
270
|
+
Both forms take either a callback index or a handle.
|
271
|
+
|
272
|
+
=== Listeners
|
273
|
+
|
274
|
+
Listeners embody an alternative model of publish/subscribe event handling. A
|
275
|
+
Listener is an object which "listens" to another object. Instead of attaching
|
276
|
+
callbacks to individual hooks, you attach a listener to an entire object.
|
277
|
+
Anytime a hook is executed on the object being listened to, a method with a name
|
278
|
+
corresponding to the hook is called on the listener. These handler methods
|
279
|
+
should take arguments corresponding to the parameters defined on the hook.
|
280
|
+
|
281
|
+
This model is similar to the SAX XML event model, and to the Java
|
282
|
+
Event/EventListener model.
|
283
|
+
|
284
|
+
For more convenient listener definition, HookR can generate a base class for you
|
285
|
+
to base your listeners on. The base class will provide default do-nothing
|
286
|
+
methods for each hook, so you only have to redefine the methods you care about.
|
287
|
+
|
288
|
+
class ZeroWing
|
289
|
+
include HookR::Hooks
|
290
|
+
|
291
|
+
define_hook :we_get_signal, :message
|
292
|
+
|
293
|
+
define_hook :set_us_up_the_bomb
|
294
|
+
end
|
295
|
+
|
296
|
+
class MyListener < ZeroWing::Listener
|
297
|
+
def we_get_signal(message)
|
298
|
+
# ...
|
299
|
+
end
|
300
|
+
|
301
|
+
# :set_us_up_the_bomb events are silently ignored
|
302
|
+
end
|
303
|
+
|
304
|
+
zw = ZeroWing.new
|
305
|
+
l = MyListener.new
|
306
|
+
|
307
|
+
zw.add_listener(l)
|
308
|
+
|
309
|
+
=== Events
|
310
|
+
|
311
|
+
Events represent the execution of a hook. They encapsulate information about
|
312
|
+
the hook, the object executing the hook, and any parameters passed when the hook
|
313
|
+
was executed (see Execution, below). Events are normally passed as the first
|
314
|
+
argument to external callbacks and method callbacks.
|
315
|
+
|
316
|
+
Events have a few important attributes:
|
317
|
+
|
318
|
+
==== Source
|
319
|
+
|
320
|
+
The event *source* is the object which initiated the hook execution. Ordinarily
|
321
|
+
this is an instance of the class which defines the hook.
|
322
|
+
|
323
|
+
==== Name
|
324
|
+
|
325
|
+
Name is the hook name of the hook being executed. For instance, given the
|
326
|
+
following hook definition:
|
327
|
+
|
328
|
+
class ZeroWing
|
329
|
+
include HookR::Hooks
|
330
|
+
|
331
|
+
define_hook :we_get_signal, :message
|
332
|
+
end
|
333
|
+
|
334
|
+
the <code>name</code> would be <code>:we_get_signal</code>.
|
335
|
+
|
336
|
+
==== Arguments
|
337
|
+
|
338
|
+
Event <code>arguments</code> are the extra arguments passed to #execute_hook, corresponding
|
339
|
+
to the hook parameters (if any).
|
340
|
+
|
341
|
+
=== Execution
|
342
|
+
|
343
|
+
An instance of the hook-bearing class can initiate hook execution by calling
|
344
|
+
#execute_hook. It takes as arguments the hook name and an argument for every
|
345
|
+
parameter defined by the hook. Example:
|
346
|
+
|
347
|
+
class ZeroWing
|
348
|
+
include HookR::Hooks
|
349
|
+
|
350
|
+
define_hook :we_get_signal, :message
|
351
|
+
|
352
|
+
def game_start
|
353
|
+
execute_hook(:we_get_signal, "You have no chance to survive")
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
There are two models of callback execution. Each is described below.
|
358
|
+
|
359
|
+
==== Iterative
|
360
|
+
|
361
|
+
In the simple case, callback execution follows the iterative model. Each
|
362
|
+
callback is executed in turn (in order of addition). Callback return calues are
|
363
|
+
ignored.
|
364
|
+
|
365
|
+
==== Recursive
|
366
|
+
|
367
|
+
When #execute_hook is called with a block argument, recursive execution is
|
368
|
+
triggered. E.g.:
|
369
|
+
|
370
|
+
class ZeroWing
|
371
|
+
include HookR::Hooks
|
372
|
+
|
373
|
+
define_hook :we_get_signal, :message
|
374
|
+
|
375
|
+
def game_start
|
376
|
+
execute_hook(:we_get_signal, "You have no chance to survive") do |event, message|
|
377
|
+
puts message
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
In this model, the most recently defined hook will be called first. As usual,
|
383
|
+
it will be passed an event as its first argument. In order to continue
|
384
|
+
execution to the next callback, the first callback must call <code>event.next</code>. This
|
385
|
+
will cause the next-most-recently-defined callback to be executed, which will
|
386
|
+
again be passed an event with a #next method, and so on. Finally, when the last
|
387
|
+
callback is executed and calls <code>event.next</code>, the block passed to #execute_hook
|
388
|
+
will be called.
|
389
|
+
|
390
|
+
In this way, it is possible to "wrap" an event with callbacks or, in the
|
391
|
+
language of AOP, "around advice". At any point in the chain, a callback can opt
|
392
|
+
to pass new arguments to Event#next, which will then override the original
|
393
|
+
arguments for any callbacks further down the chain. This enables callbacks to
|
394
|
+
act as "filters" on the callback arguments.
|
395
|
+
|
396
|
+
WARNING: This area is still under active development, and the API may change.
|
397
|
+
Some ideas under consideration include automatically executing the next callback
|
398
|
+
even if Event#next is not explicitly called; and an Event#cancel method which
|
399
|
+
will prevent further callbacks from running.
|
400
|
+
|
401
|
+
=== Advanced
|
402
|
+
|
403
|
+
In which we take a look under HookR's clothes.
|
404
|
+
|
405
|
+
==== Adding multiple callbacks with the same name
|
406
|
+
|
407
|
+
When adding callbacks with an explicit handle, only one callback for that handle
|
408
|
+
can be added to a given hook. Subsequent attempts to add a callback with the
|
409
|
+
same name will silently fail. This makes adding named callbacks an idempotent
|
410
|
+
operation.
|
411
|
+
|
412
|
+
==== Hook Chaining
|
413
|
+
|
414
|
+
Every hook has a parent, to which it delegates execution when it is finished
|
415
|
+
executing its own callbacks. This is how class inheritance is handles, and how
|
416
|
+
it is possible for callbacks to be added at both the class and instanve levels.
|
417
|
+
Under normal circumstances, however, this is an implementation detail which Just
|
418
|
+
Works, and you can safely ignore it.
|
419
|
+
|
420
|
+
==== Wildcard Callbacks
|
421
|
+
|
422
|
+
It is possible to define a "wildcard" callback which will be called when *any*
|
423
|
+
hook is executed, using the #add_wildcard_callback class and instance methods.
|
424
|
+
|
425
|
+
==== Callback Arity
|
426
|
+
|
427
|
+
External and method callbacks must take at least as many arguments as
|
428
|
+
there are parameters on the hook. For instance, given the following hook
|
429
|
+
definition:
|
430
|
+
|
431
|
+
class ZeroWing
|
432
|
+
include HookR::Hooks
|
433
|
+
|
434
|
+
define_hook :take_off_every, :what, :why
|
435
|
+
end
|
436
|
+
|
437
|
+
Callbacks for <code>:take_off_every</code> must accept at least two arguments. If they accept
|
438
|
+
exactly two arguments, they will be passed the two arguments only. If they
|
439
|
+
accept three arguments, they will be passed the event object followed by the two
|
440
|
+
arguments. More than three arguments would be an error.
|
441
|
+
|
442
|
+
==== Custom Hook Classes
|
443
|
+
|
444
|
+
In some special cases it may be desirable to customize the specific class of
|
445
|
+
Hook generated by #define_hook. When this is the case you may define a custom
|
446
|
+
<code>make_hook</code> class method. This method will be passed a hook name, a parent
|
447
|
+
hook, and a list of parameters, and should return an instance of a subclass of
|
448
|
+
HookR::Hook or something that behaves very similarly.
|
449
|
+
|
450
|
+
== REQUIREMENTS:
|
451
|
+
|
452
|
+
* FailFast (http://fail-fast.rubyforge.org)
|
453
|
+
|
454
|
+
== INSTALL:
|
455
|
+
|
456
|
+
* sudo gem install hookr
|
457
|
+
|
458
|
+
== KNOWN BUGS
|
459
|
+
|
460
|
+
* It is currently not possible to define a method callback before the method has
|
461
|
+
been defined. This is either a bug or a feature, depending on your point of
|
462
|
+
view.
|
463
|
+
|
464
|
+
== SUPPORT/CONTRIBUTING
|
465
|
+
|
466
|
+
Questions, comments, suggestions, bug reports: Email Avdi Grimm at mailto:avdi@avdi.org
|
467
|
+
|
468
|
+
== LICENSE:
|
469
|
+
|
470
|
+
(The MIT License)
|
471
|
+
|
472
|
+
Copyright (c) 2008
|
473
|
+
|
474
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
475
|
+
a copy of this software and associated documentation files (the
|
476
|
+
'Software'), to deal in the Software without restriction, including
|
477
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
478
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
479
|
+
permit persons to whom the Software is furnished to do so, subject to
|
480
|
+
the following conditions:
|
481
|
+
|
482
|
+
The above copyright notice and this permission notice shall be
|
483
|
+
included in all copies or substantial portions of the Software.
|
484
|
+
|
485
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
486
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
487
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
488
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
489
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
490
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
491
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|