siftery-wisper 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +4 -0
  4. data/.travis.yml +22 -0
  5. data/CHANGELOG.md +129 -0
  6. data/CONTRIBUTING.md +61 -0
  7. data/Gemfile +13 -0
  8. data/README.md +373 -0
  9. data/Rakefile +10 -0
  10. data/bin/console +8 -0
  11. data/bin/setup +6 -0
  12. data/gem-public_cert.pem +21 -0
  13. data/lib/wisper.rb +65 -0
  14. data/lib/wisper/broadcasters/logger_broadcaster.rb +37 -0
  15. data/lib/wisper/broadcasters/send_broadcaster.rb +9 -0
  16. data/lib/wisper/configuration.rb +44 -0
  17. data/lib/wisper/global_listeners.rb +74 -0
  18. data/lib/wisper/publisher.rb +89 -0
  19. data/lib/wisper/registration/block.rb +11 -0
  20. data/lib/wisper/registration/object.rb +77 -0
  21. data/lib/wisper/registration/registration.rb +19 -0
  22. data/lib/wisper/temporary_listeners.rb +41 -0
  23. data/lib/wisper/value_objects/events.rb +61 -0
  24. data/lib/wisper/value_objects/prefix.rb +29 -0
  25. data/lib/wisper/version.rb +3 -0
  26. data/siftery-wisper.gemspec +31 -0
  27. data/spec/lib/global_listeners_spec.rb +82 -0
  28. data/spec/lib/integration_spec.rb +56 -0
  29. data/spec/lib/simple_example_spec.rb +21 -0
  30. data/spec/lib/temporary_global_listeners_spec.rb +67 -0
  31. data/spec/lib/wisper/broadcasters/logger_broadcaster_spec.rb +93 -0
  32. data/spec/lib/wisper/broadcasters/send_broadcaster_spec.rb +28 -0
  33. data/spec/lib/wisper/configuration/broadcasters_spec.rb +11 -0
  34. data/spec/lib/wisper/configuration_spec.rb +36 -0
  35. data/spec/lib/wisper/publisher_spec.rb +331 -0
  36. data/spec/lib/wisper/registrations/object_spec.rb +14 -0
  37. data/spec/lib/wisper/value_objects/events_spec.rb +107 -0
  38. data/spec/lib/wisper/value_objects/prefix_spec.rb +46 -0
  39. data/spec/lib/wisper_spec.rb +99 -0
  40. data/spec/spec_helper.rb +24 -0
  41. metadata +101 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4f6bf677ec7224bac38caa11cb49620dedb6a0e2d28095b940bc0f6e1563d54b
4
+ data.tar.gz: 7007c32fce8fdc5ceebb740c965210dab06cd4ba12ff6e639d671e705252d296
5
+ SHA512:
6
+ metadata.gz: 0b39cec008bc8bae5b15e560a225b99afda15db75ebffdb81602642ca6734636fe8d903958ed2dd0a187926643a55c06a0ed7f2daa76180b0b4889d35fdf2401
7
+ data.tar.gz: 03dd49ce611320e862a6af823e8a58eaad516d8d336eae5aafedd60db7d2f96555e801c0db05f241d3dbb92125e3172b7000dc64fcee532414e34f8736949f04
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .ruby-version
4
+ .ruby-gemspec
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
20
+ tags
21
+ vendor
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --format progress
3
+ --require spec_helper
4
+ --warnings
@@ -0,0 +1,22 @@
1
+ language: ruby
2
+ before_install:
3
+ - gem update --system
4
+ - gem update bundler
5
+ bundler_args: --without=extras
6
+ script: rspec spec
7
+ sudo: false
8
+ rvm:
9
+ - '2.1.10'
10
+ - '2.2.6'
11
+ - '2.3.3'
12
+ - '2.4.0'
13
+ - jruby-9.1.6.0
14
+ # - rbx-2
15
+ ### ALLOWED FAILURES ###
16
+ # see how compatible we are with dev versions, but do not fail the build
17
+ - ruby-head
18
+ - jruby-head
19
+ matrix:
20
+ allow_failures:
21
+ - rvm: ruby-head
22
+ - rvm: jruby-head
@@ -0,0 +1,129 @@
1
+ ## HEAD (unreleased)
2
+
3
+ ## 2.0.0 (7th Mar 2017)
4
+
5
+ Authors: Sergey Mostovoy, Andrew Kozin, Kyle Tolle, Martin, Rob Miller, Mike
6
+ Dalto, orthographic-pedant, Drew Ulmer, Mikey Hogarth, Attila Domokos, Josh
7
+ Miltz, Pascal Betz, Vasily Kolesnikov, Julien Letessier, Kris Leech
8
+
9
+ * Fix: logger raises exception if hash is passed as an argument to a listener #133, #136
10
+ * Fix: deprecation warnings #120
11
+ * Doc improvements: #106, #111, #116, #122, #128, #130, #147, #149, #150, #151
12
+ * Adds: Allow configuration of default prefix when using `prefix: true`. #105
13
+ * Adds: Allow unsubscribing of global listeners #118
14
+ * Adds: Helpful error message when passing a block to `#subscribe` of `#on` #125
15
+ * Adds: raise an error message when `#on` is not passed a block #146
16
+ * Adds: Support for JRuby 9.x #148
17
+ * Adds: Support for MRI 2.4.0 #155
18
+ * Refactor specs #126, #131
19
+
20
+ ## 2.0.0.rc1 (17 Dec 2014)
21
+
22
+ Authors: Kris Leech
23
+
24
+ * remove: deprecated methods
25
+ * remove: rspec matcher and stubbing (moved to [wisper-rspec](https://github.com/krisleech/wisper-rspec))
26
+ * feature: add regexp support to `on` argument
27
+ * remove: announce alias for broadcasting
28
+ * docs: add Code of Conduct
29
+ * drop support for Ruby 1.9
30
+
31
+ ## 1.6.0 (25 Oct 2014)
32
+
33
+ Authors: Kris Leech
34
+
35
+ * deprecate: add_listener, add_block_listener and respond_to
36
+ * internal: make method naming more consistent
37
+
38
+ ## 1.5.0 (6th Oct 2014)
39
+
40
+ Authors: Kris Leech
41
+
42
+ * feature: allow events to be published asynchronously
43
+ * feature: broadcasting of events is plugable and configurable
44
+ * feature: broadcasters can be aliased via a symbol
45
+ * feature: logging broadcaster
46
+
47
+ ## 1.4.0 (8th Sept 2014)
48
+
49
+ Authors: Kris Leech, Marc Ignacio, Ahmed Abdel Razzak, kmehkeri, Jake Hoffner
50
+
51
+ * feature: matcher for rspec 3
52
+ * fix: temporary global listeners are cleared if an exception is raised
53
+ * refactor: update all specs to rspec 3 expect syntax
54
+ * docs: update README to rspec 3 expect syntax
55
+ * feature: combine global and temporary listener methods as `Wisper.subscribe`
56
+ * deprecate: `Wisper.add_listener` and `Wisper.with_listeners,` use `Wisper.subscribe` instead
57
+
58
+ ## 1.3.0 (18th Jan 2014)
59
+
60
+ Authors: Kris Leech, Yan Pritzker, Charlie Tran
61
+
62
+ * feature: global subscriptions can be scoped to a class (and sub-classes)
63
+ * upgrade: use rspec 3
64
+ * feature: allow prefixing of events with 'on'
65
+ * feature: Allow stubbed publisher method to accept arbitrary args
66
+
67
+ ## 1.2.1 (7th Oct 2013)
68
+
69
+ Authors: Kris Leech, Tomasz Szymczyszyn, Alex Heeton
70
+
71
+ * feature: global subscriptions can be passed options
72
+ * docs: improve README examples
73
+ * docs: add license to gemspec
74
+
75
+ ## 1.2.0 (21st July 2013)
76
+
77
+ Authors: Kris Leech, Darren Coxall
78
+
79
+ * feature: support for multiple events at once
80
+ * fix: clear global listeners after each spec
81
+
82
+ ## 1.1.0 (7th June 2013)
83
+
84
+ Authors: Kris Leech, chatgris
85
+
86
+ * feature: add temporary global listeners
87
+ * docs: improve ActiveRecord example
88
+ * refactor: improve specs
89
+ * upgrade: add Ruby 2.0 support
90
+ * fix: make listener collection immutable
91
+ * remove: async publishing and Celluloid dependency
92
+ * fix: Make global listeners getter and setter threadsafe [9]
93
+
94
+ ## 1.0.1 (2nd May 2013)
95
+
96
+ Authors: Kris Leech, Yan Pritzker
97
+
98
+ * feature: add async publishing using Celluloid
99
+ * docs: improve README examples
100
+ * feature: `stub_wisper_publisher` rspec helper
101
+ * feature: global listeners
102
+ * refactor: improve specs
103
+
104
+ ## 1.0.0 (7th April 2013)
105
+
106
+ Authors: Kris Leech
107
+
108
+ * refactor: specs
109
+ * refactor: registrations
110
+ * feature: Add `with` argument to `subscribe`
111
+ * docs: improve README examples
112
+ * feature: Allow subscriptions to be chainable
113
+ * feature: Add `on` syntax for block subscription
114
+ * remove: Remove support for Ruby 1.8.7
115
+ * docs: Add badges to README
116
+
117
+ ## 0.0.2 (30th March 2013)
118
+
119
+ Authors: Kris Leech
120
+
121
+ * remove: ActiveSupport dependency
122
+ * docs: fix syntax highlighting in README
123
+
124
+ ## 0.0.1 (30th March 2013)
125
+
126
+ Authors: Kris Leech
127
+
128
+ * docs: add README
129
+ * feature: registration of objects and blocks
@@ -0,0 +1,61 @@
1
+ # How to Contribute
2
+
3
+ We very much welcome contributions to Wisper.
4
+
5
+ This project is intended to be a safe, welcoming space for collaboration,
6
+ and contributors are expected to adhere to the
7
+ [Contributor Covenant](http://contributor-covenant.org) code of conduct.
8
+
9
+ ## Getting started
10
+
11
+ Please first check the existing [Issues](https://github.com/krisleech/wisper/issues)
12
+ and [Pull Requests](https://github.com/krisleech/wisper/pulls) to ensure your
13
+ issue has not already been discused.
14
+
15
+ ## Bugs
16
+
17
+ Please submit a bug report to the issue tracker, with the version of Wisper
18
+ and Ruby you are using and a small code sample (or better yet a failing test).
19
+
20
+ ## Features
21
+
22
+ Please open an issue with your proposed feature. We can discuss the feature and
23
+ if it is acceptable we can also discuss implimentation details. You will in
24
+ most cases have to submit a PR which adds the feature.
25
+
26
+ Wisper is a micro library and will remain lean. Some features would be most
27
+ appropriate as an extension to Wisper.
28
+
29
+ We also have a [Gitter channel](https://gitter.im/krisleech/wisper) if you wish to discuss your ideas.
30
+
31
+ ## Backlog / Roadmap
32
+
33
+ The backlog for Wisper and related gems is on [Waffle](https://waffle.io/krisleech/wisper).
34
+
35
+ ## Questions
36
+
37
+ Try the [Wiki](https://github.com/krisleech/wisper/wiki) first, the examples
38
+ and how to sections have lots of information.
39
+
40
+ Please ask questions on StackOverflow, [tagged wisper](https://stackoverflow.com/questions/tagged/wisper).
41
+
42
+ Feel free to ping me the URL on [Twitter](https://twitter.com/krisleech).
43
+
44
+ ## Pull requests
45
+
46
+ * Fork the project, create a new branch from `v1` or `master`.
47
+ * Squash commits which are related.
48
+ * Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
49
+ * Documentation only changes should have `[skip ci]` in the commit message
50
+ * Follow existing code style in terms of syntax, indentation etc.
51
+ * Add an entry to the CHANGELOG
52
+ * Do not bump the VERSION, but do indicate in the CHANGELOG if the change is
53
+ not backwards compatible.
54
+ * Issue a Pull Request
55
+
56
+ ## Versions
57
+
58
+ The `v1` branch is a long lived branch and you should
59
+ branch from this if you wish to fix an issue in version `~> 1.0`.
60
+
61
+ The `master` branch will target the next version of Wisper.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
6
+ gem 'rspec'
7
+ gem 'coveralls', require: false
8
+
9
+ group :extras do
10
+ gem 'flay'
11
+ gem 'pry'
12
+ gem 'yard'
13
+ end
@@ -0,0 +1,373 @@
1
+ # Wisper
2
+
3
+ *A micro library providing Ruby objects with Publish-Subscribe capabilities*
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/wisper.svg)](http://badge.fury.io/rb/wisper)
6
+ [![Code Climate](https://codeclimate.com/github/krisleech/wisper.svg)](https://codeclimate.com/github/krisleech/wisper)
7
+ [![Build Status](https://travis-ci.org/krisleech/wisper.svg?branch=master)](https://travis-ci.org/krisleech/wisper)
8
+ [![Coverage Status](https://coveralls.io/repos/krisleech/wisper/badge.svg?branch=master)](https://coveralls.io/r/krisleech/wisper?branch=master)
9
+
10
+ * Decouple core business logic from external concerns in Hexagonal style architectures
11
+ * Use as an alternative to ActiveRecord callbacks and Observers in Rails apps
12
+ * Connect objects based on context without permanence
13
+ * React to events synchronously or asynchronously
14
+
15
+ Note: Wisper was originally extracted from a Rails codebase but is not dependant on Rails.
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'wisper', '2.0.0'
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ Any class with the `Wisper::Publisher` module included can broadcast events
28
+ to subscribed listeners. Listeners subscribe, at runtime, to the publisher.
29
+
30
+ ### Publishing
31
+
32
+ ```ruby
33
+ class CancelOrder
34
+ include Wisper::Publisher
35
+
36
+ def call(order_id)
37
+ order = Order.find_by_id(order_id)
38
+
39
+ # business logic...
40
+
41
+ if order.cancelled?
42
+ broadcast(:cancel_order_successful, order.id)
43
+ else
44
+ broadcast(:cancel_order_failed, order.id)
45
+ end
46
+ end
47
+ end
48
+ ```
49
+
50
+ When a publisher broadcasts an event it can include any number of arguments.
51
+
52
+ The `broadcast` method is also aliased as `publish`.
53
+
54
+ You can also include `Wisper.publisher` instead of `Wisper::Publisher`.
55
+
56
+ ### Subscribing
57
+
58
+ #### Objects
59
+
60
+ Any object can be subscribed as a listener.
61
+
62
+ ```ruby
63
+ cancel_order = CancelOrder.new
64
+
65
+ cancel_order.subscribe(OrderNotifier.new)
66
+
67
+ cancel_order.call(order_id)
68
+ ```
69
+
70
+ The listener would need to implement a method for every event it wishes to receive.
71
+
72
+ ```ruby
73
+ class OrderNotifier
74
+ def cancel_order_successful(order_id)
75
+ order = Order.find_by_id(order_id)
76
+
77
+ # notify someone ...
78
+ end
79
+ end
80
+ ```
81
+
82
+ #### Blocks
83
+
84
+ Blocks can be subscribed to single events and can be chained.
85
+
86
+ ```ruby
87
+ cancel_order = CancelOrder.new
88
+
89
+ cancel_order.on(:cancel_order_successful) { |order_id| ... }
90
+ .on(:cancel_order_failed) { |order_id| ... }
91
+
92
+ cancel_order.call(order_id)
93
+ ```
94
+
95
+ You can also subscribe to multiple events using `on` by passing
96
+ additional events as arguments.
97
+
98
+ ```ruby
99
+ cancel_order = CancelOrder.new
100
+
101
+ cancel_order.on(:cancel_order_successful) { |order_id| ... }
102
+ .on(:cancel_order_failed,
103
+ :cancel_order_invalid) { |order_id| ... }
104
+
105
+ cancel_order.call(order_id)
106
+ ```
107
+
108
+ Do not `return` from inside a subscribed block, due to the way
109
+ [Ruby treats blocks](http://product.reverb.com/2015/02/28/the-strange-case-of-wisper-and-ruby-blocks-behaving-like-procs/)
110
+ this will prevent any subsequent listeners having their events delivered.
111
+
112
+ ### Handling Events Asynchronously
113
+
114
+ ```ruby
115
+ cancel_order.subscribe(OrderNotifier.new, async: true)
116
+ ```
117
+
118
+ You can also pass additional configuration options to the job using the following syntax:
119
+
120
+ ```ruby
121
+ cancel_order.subscribe(OrderNotifier.new, async: { queue: 'custom', retry: false })
122
+ ```
123
+
124
+ The list of supported handler options is determined by adapter
125
+
126
+ Wisper has various adapters for asynchronous event handling, please refer to
127
+ [wisper-celluloid](https://github.com/krisleech/wisper-celluloid),
128
+ [wisper-sidekiq](https://github.com/krisleech/wisper-sidekiq),
129
+ [wisper-activejob](https://github.com/krisleech/wisper-activejob), or
130
+ [wisper-que](https://github.com/joevandyk/wisper-que).
131
+
132
+ Depending on the adapter used the listener may need to be a class instead of an object. In this situation, every method corresponding to events should be declared as a class method, too. For example:
133
+
134
+ ```ruby
135
+ class OrderNotifier
136
+ # declare a class method if you are subscribing the listener class instead of its instance like:
137
+ # cancel_order.subscribe(OrderNotifier)
138
+ #
139
+ def self.cancel_order_successful(order_id)
140
+ order = Order.find_by_id(order_id)
141
+
142
+ # notify someone ...
143
+ end
144
+ end
145
+ ```
146
+
147
+ ### ActionController
148
+
149
+ ```ruby
150
+ class CancelOrderController < ApplicationController
151
+
152
+ def create
153
+ cancel_order = CancelOrder.new
154
+
155
+ cancel_order.subscribe(OrderMailer, async: true)
156
+ cancel_order.subscribe(ActivityRecorder, async: true)
157
+ cancel_order.subscribe(StatisticsRecorder, async: true)
158
+
159
+ cancel_order.on(:cancel_order_successful) { |order_id| redirect_to order_path(order_id) }
160
+ cancel_order.on(:cancel_order_failed) { |order_id| render action: :new }
161
+
162
+ cancel_order.call(order_id)
163
+ end
164
+ end
165
+ ```
166
+
167
+ ### ActiveRecord
168
+
169
+ If you wish to publish directly from ActiveRecord models you can broadcast events from callbacks:
170
+
171
+ ```ruby
172
+ class Order < ActiveRecord::Base
173
+ include Wisper::Publisher
174
+
175
+ after_commit :publish_creation_successful, on: :create
176
+ after_validation :publish_creation_failed, on: :create
177
+
178
+ private
179
+
180
+ def publish_creation_successful
181
+ broadcast(:order_creation_successful, self)
182
+ end
183
+
184
+ def publish_creation_failed
185
+ broadcast(:order_creation_failed, self) if errors.any?
186
+ end
187
+ end
188
+ ```
189
+
190
+ There are more examples in the [Wiki](https://github.com/krisleech/wisper/wiki).
191
+
192
+ ## Global Listeners
193
+
194
+ Global listeners receive all broadcast events which they can respond to.
195
+
196
+ This is useful for cross cutting concerns such as recording statistics, indexing, caching and logging.
197
+
198
+ ```ruby
199
+ Wisper.subscribe(MyListener.new)
200
+ ```
201
+
202
+ In a Rails app you might want to add your global listeners in an initalizer.
203
+
204
+ Global listeners are threadsafe.
205
+
206
+ ### Scoping by publisher class
207
+
208
+ You might want to globally subscribe a listener to publishers with a certain
209
+ class.
210
+
211
+ ```ruby
212
+ Wisper.subscribe(MyListener.new, scope: :MyPublisher)
213
+ Wisper.subscribe(MyListener.new, scope: MyPublisher)
214
+ Wisper.subscribe(MyListener.new, scope: "MyPublisher")
215
+ Wisper.subscribe(MyListener.new, scope: [:MyPublisher, :MyOtherPublisher])
216
+ ```
217
+
218
+ This will subscribe the listener to all instances of the specified class(es) and their
219
+ subclasses.
220
+
221
+ Alternatively you can also do exactly the same with a publisher class itself:
222
+
223
+ ```ruby
224
+ MyPublisher.subscribe(MyListener.new)
225
+ ```
226
+
227
+ ## Temporary Global Listeners
228
+
229
+ You can also globally subscribe listeners for the duration of a block.
230
+
231
+ ```ruby
232
+ Wisper.subscribe(MyListener.new, OtherListener.new) do
233
+ # do stuff
234
+ end
235
+ ```
236
+
237
+ Any events broadcast within the block by any publisher will be sent to the
238
+ listeners.
239
+
240
+ This is useful for capturing events published by objects to which you do not have access in a given context.
241
+
242
+ Temporary Global Listeners are threadsafe.
243
+
244
+ ## Subscribing to selected events
245
+
246
+ By default a listener will get notified of all events it can respond to. You
247
+ can limit which events a listener is notified of by passing a string, symbol,
248
+ array or regular expression to `on`:
249
+
250
+ ```ruby
251
+ post_creator.subscribe(PusherListener.new, on: :create_post_successful)
252
+ ```
253
+
254
+ ## Prefixing broadcast events
255
+
256
+ If you would prefer listeners to receive events with a prefix, for example
257
+ `on`, you can do so by passing a string or symbol to `prefix:`.
258
+
259
+ ```ruby
260
+ post_creator.subscribe(PusherListener.new, prefix: :on)
261
+ ```
262
+
263
+ If `post_creator` were to broadcast the event `post_created` the subscribed
264
+ listeners would receive `on_post_created`. You can also pass `true` which will
265
+ use the default prefix, "on".
266
+
267
+ ## Mapping an event to a different method
268
+
269
+ By default the method called on the listener is the same as the event
270
+ broadcast. However it can be mapped to a different method using `with:`.
271
+
272
+ ```ruby
273
+ report_creator.subscribe(MailResponder.new, with: :successful)
274
+ ```
275
+
276
+ This is pretty useless unless used in conjunction with `on:`, since all events
277
+ will get mapped to `:successful`. Instead you might do something like this:
278
+
279
+ ```ruby
280
+ report_creator.subscribe(MailResponder.new, on: :create_report_successful,
281
+ with: :successful)
282
+ ```
283
+
284
+ If you pass an array of events to `on:` each event will be mapped to the same
285
+ method when `with:` is specified. If you need to listen for select events
286
+ _and_ map each one to a different method subscribe the listener once for
287
+ each mapping:
288
+
289
+ ```ruby
290
+ report_creator.subscribe(MailResponder.new, on: :create_report_successful,
291
+ with: :successful)
292
+
293
+ report_creator.subscribe(MailResponder.new, on: :create_report_failed,
294
+ with: :failed)
295
+ ```
296
+
297
+ You could also alias the method within your listener, as such
298
+ `alias successful create_report_successful`.
299
+
300
+ ## Testing
301
+
302
+ Testing matchers and stubs are in seperate gems.
303
+
304
+ * [wisper-rspec](https://github.com/krisleech/wisper-rspec)
305
+ * [wisper-minitest](https://github.com/digitalcuisine/wisper-minitest)
306
+
307
+ ### Clearing Global Listeners
308
+
309
+ If you use global listeners in non-feature tests you _might_ want to clear them
310
+ in a hook to prevent global subscriptions persisting between tests.
311
+
312
+ ```ruby
313
+ after { Wisper.clear }
314
+ ```
315
+
316
+ ## Need help?
317
+
318
+ The [Wiki](https://github.com/krisleech/wisper/wiki) has more examples,
319
+ articles and talks.
320
+
321
+ Got a specific question, try the
322
+ [Wisper tag on StackOverflow](http://stackoverflow.com/questions/tagged/wisper).
323
+
324
+ ## Compatibility
325
+
326
+ Tested with MRI 2.x, JRuby and Rubinius.
327
+
328
+ See the [build status](https://travis-ci.org/krisleech/wisper) for details.
329
+
330
+ ## Running Specs
331
+
332
+ ```
333
+ bundle exec rspec
334
+ ```
335
+
336
+ To run the specs on code changes try [entr](http://entrproject.org/):
337
+
338
+ ```
339
+ ls **/*.rb | entr bundle exec rspec
340
+ ```
341
+
342
+ ## Contributing
343
+
344
+ Please read the [Contributing Guidelines](https://github.com/krisleech/wisper/blob/master/CONTRIBUTING.md).
345
+
346
+ ## Security
347
+
348
+ * gem releases are [signed](http://guides.rubygems.org/security/) ([public key](https://github.com/krisleech/wisper/blob/master/gem-public_cert.pem))
349
+ * commits are GPG signed ([public key](https://pgp.mit.edu/pks/lookup?op=get&search=0x3ABC74851F7CCC88))
350
+
351
+ ## License
352
+
353
+ (The MIT License)
354
+
355
+ Copyright (c) 2013 Kris Leech
356
+
357
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
358
+ this software and associated documentation files (the 'Software'), to deal in
359
+ the Software without restriction, including without limitation the rights to
360
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
361
+ of the Software, and to permit persons to whom the Software is furnished to do
362
+ so, subject to the following conditions:
363
+
364
+ The above copyright notice and this permission notice shall be included in all
365
+ copies or substantial portions of the Software.
366
+
367
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
368
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
369
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
370
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
371
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
372
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
373
+ SOFTWARE.