wisper_next 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '0459c713e32f3b3412978d72d812d8f4e9255aedf613b4ebfbee65df9f8a3c11'
4
+ data.tar.gz: 2f51f8cc5cab7bb02eb4f90f6259410133b740a074cf560be8ef853c3830c966
5
+ SHA512:
6
+ metadata.gz: 05f51893bb546d9471f3de2537dbf9857d8649ea0170fef570fb00bd93184a67428e60a534a362dd04218ef554ae4d5ca2a5d7f78d2bc0192b6c399220b782a3
7
+ data.tar.gz: 0bfa67250f7b639f1c6d43d4ebc15bd1a863cef18e15c80c3c87b17c7f8e467c49a480f770911bc714f902a5ddb3afd9804065fb3723ff5f0fe279a33a570721
Binary file
@@ -0,0 +1,2 @@
1
+ �ʇ:�L��l��xA֑�OyG/ ��֋� �����ҋk4o��^�=����v:г��h�d8u�`-��̽�6��o�� #k6l����U��Z�V��j
2
+ �Ê8 {x잱�������&U� �*� �k~��SVU1�a@
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /.pry_history
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+
14
+ Gemfile.lock
15
+
16
+ TODO
17
+ REFACTORS
@@ -0,0 +1,14 @@
1
+ image: "ruby:2.6"
2
+
3
+ cache:
4
+ paths:
5
+ - vendor/ruby
6
+
7
+ before_script:
8
+ - ruby -v
9
+ - gem install bundler
10
+ - bundle install -j $(nproc) --path vendor
11
+
12
+ rspec:
13
+ script:
14
+ - bundle exec rspec spec
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1 @@
1
+ 2.6
@@ -0,0 +1,13 @@
1
+ # CHANGELOG
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ - Added: Ability for an object to publish events
11
+ - Added: Ability for an object to map from events to methods
12
+ - Added: An Event object for creating global pub/sub
13
+
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at kris.leech@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "bundler", "~> 2.0"
6
+ gem "rake", "~> 10.0"
7
+ gem "rspec", "~> 3.0"
8
+ gem 'pry'
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Kris Leech
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,150 @@
1
+ # WisperNext
2
+
3
+ This is the next version of Wisper. Alpha only.
4
+
5
+ ## Installation
6
+
7
+ ```ruby
8
+ gem 'wisper_next'
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Publishing
14
+
15
+ ```ruby
16
+ class MyPublisher
17
+ include WisperNext.publisher
18
+
19
+ def call
20
+ broadcast(:wow, x: rand(10), y: rand(10), at: Time.now)
21
+ end
22
+ end
23
+ ```
24
+
25
+ A publisher can broadcast events, the first argument is the event name followed
26
+ by an optional payload.
27
+
28
+ ### Listeners
29
+
30
+ #### Objects
31
+
32
+ ```ruby
33
+ class MyListener
34
+ def on_event(event_name, payload)
35
+ puts "#{event_name} received with #{payload.inspect}"
36
+ end
37
+ end
38
+ ```
39
+
40
+ We can then subscribe an instance of our listener to the publisher:
41
+
42
+ ```ruby
43
+ publisher = MyPublisher.new
44
+ publisher.subscribe(MyListener.new)
45
+ publisher.call
46
+ ```
47
+
48
+ #### Blocks
49
+
50
+ Blocks can be subscribed to a specific event name.
51
+
52
+ ```ruby
53
+ publisher = MyPublisher.new
54
+ publisher.on(:wow) { |payload| puts(payload) }
55
+ publisher.call
56
+ ```
57
+
58
+ ### Enhanced Listeners
59
+
60
+ By including `Wisper.subscriber` you can get additional features.
61
+
62
+ Firstly instead of `#on_event` you provide a method for every event the
63
+ listener may receive. For example if our publisher broadcasts a "user_created"
64
+ event then the listener would provide a `user_created` method which receives
65
+ the payload.
66
+
67
+ ```ruby
68
+ class MyListener
69
+ include Wisper.subscriber
70
+
71
+ def user_created(payload)
72
+ #...
73
+ end
74
+ end
75
+ ```
76
+
77
+ If the listener receives an event for which there is no method an exception
78
+ is raised.
79
+
80
+ You can opt-out of this behaviour by setting the `strict` option to false:
81
+
82
+ ```ruby
83
+ include Wisper.subscriber(strict: false)
84
+ ```
85
+
86
+ #### Prefixing broadcast events
87
+
88
+ The method called can be prefixed with `on_`:
89
+
90
+ ```ruby
91
+ class MyListener
92
+ include Wisper.subscriber(prefix: true)
93
+
94
+ def on_user_created(payload)
95
+ #...
96
+ end
97
+ end
98
+ ```
99
+
100
+ #### Handling Events Asynchronously
101
+
102
+ WisperNext has adapters for asynchronous event handling, please refer to
103
+ <TODO>.
104
+
105
+ ```ruby
106
+ include Wisper.subscriber(async: :true)
107
+ ```
108
+
109
+ If you are interested in building an async adapter and releasing it as a gem
110
+ please get in touch.
111
+
112
+ #### Passing options
113
+
114
+ If the value for an option being passed to subscriber is `true` then you can
115
+ use a symbol instead to mean the same thing.
116
+
117
+ So `WisperNext.subscriber(async: true)` and `WisperNext.subscriber(:async)` are
118
+ the same. You can pass multiple symbols with any key/values at the end,
119
+ e.g. `WisperNext.subscriber(:async, :prefix, strict: false)`.
120
+
121
+ ### Global Subscriptions
122
+
123
+ ```ruby
124
+ EVENTS = WisperNext::Events.new
125
+
126
+ EVENTS.subscribe(MyListener.new)
127
+
128
+ EVENTS.broadcast('user_promoted', user: user)
129
+ ```
130
+
131
+ ## Security
132
+
133
+ * gem releases are [signed]()
134
+ * commits are GPG [signed]()
135
+
136
+ ## Development
137
+
138
+ ## Contributing
139
+
140
+ Bug reports and pull requests are welcome on GitHub at https://github.com/krisleech/wisper_next.
141
+
142
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
143
+
144
+ ## License
145
+
146
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
147
+
148
+ ## Code of Conduct
149
+
150
+ Everyone interacting in the WisperNext project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/wisper_next/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "wisper_next"
5
+
6
+ require "pry"
7
+ Pry.start
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
@@ -0,0 +1,20 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDQDCCAiigAwIBAgIBATANBgkqhkiG9w0BAQsFADAlMSMwIQYDVQQDDBprcmlz
3
+ LmxlZWNoL0RDPWdtYWlsL0RDPWNvbTAeFw0xOTEwMTYxNDEzNDVaFw0yMDEwMTUx
4
+ NDEzNDVaMCUxIzAhBgNVBAMMGmtyaXMubGVlY2gvREM9Z21haWwvREM9Y29tMIIB
5
+ IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8gg/iZE6RFkl6GjisJJf9wQl
6
+ 5Tog+mrGqtylNpe9QKeyQS1fKLNR3Cqmv6sT3f3GlU8hhG+802MXmlJo7VyENs+s
7
+ HdMy85fBnYwhS/szhTzBqduXw43RAAGqB0VaHAoHdufTZBkmFSXET5c0/jUqQrPL
8
+ Zxsm2i0NV8cVpvrZlQcsW4Kf97DG1ZFNncS0IfCDqnluh21qMSgVAuiKHGoVKCYG
9
+ Igey486VuuJ8j6axwW3ii8HoBOxjF8Ka/rJ/t4sB9Ql/p6RHR4RhxP5xIS9geCpI
10
+ dsPGnCSleTW1EnXGHLdOdJpVmJXueYJEQJ1Rh/rR+9PeqAT3RA0D/dxSGIVtrQID
11
+ AQABo3sweTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUyupX2gb3
12
+ +isBRwf4/fhgLnh02sYwHwYDVR0RBBgwFoEUa3Jpcy5sZWVjaEBnbWFpbC5jb20w
13
+ HwYDVR0SBBgwFoEUa3Jpcy5sZWVjaEBnbWFpbC5jb20wDQYJKoZIhvcNAQELBQAD
14
+ ggEBAExirH+Py0Wm8+ZzNnx/oyFXSF7V9FClYhPUXwQ5FmvQG+gXRlonO9IR/HL+
15
+ wGJNFh4B19fpughgQeJKjKJsG2zk2XnSK2yQyyNGdr/9dHXp/TS5sGvX9jFYVeAr
16
+ 0XCWXRBK+IfEqNnbFvPkwZEK62WLnrL50gMs3u2ILmLP5gkO3EoLyezJvIj33CLO
17
+ HX0Plp0VwiSp8+gEELX5wtdrnJ/UjmrY1lg7szukE+jgOJ++1e7Rzr+woBewIWvF
18
+ xn3z1ObkNQCnLZMRmzEcSa40T10/AuuuEv32emAgb50u9vz+s4y2lnE9i4mWW7+X
19
+ 5vdRgX7VnzYPA+Lc39kD+AcGIrQ=
20
+ -----END CERTIFICATE-----
@@ -0,0 +1,81 @@
1
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
2
+
3
+ mQINBFd3340BEACVAXTr+HiLa19dfHtTC+bj12jN/g9G0Xay1K80CkacQw5HvBSM
4
+ 85fjBVirbbAQ10/jTx+goPFCSrQ+nseIP6QLsf6kRrn1qhmN6NxdqrooXlIud/nV
5
+ 4qj9fqg/XHnbEMouAx27tQWa+uv03eyPG0sK3OGj9V3TnXga2C6p18n32PSWxtgf
6
+ +M2jTPGvpWGjy+gBeymf8nAD6DoGI9SvcQzlKNbShoZALVXVXwXstiAFFlkyDqrj
7
+ dVoNIFAb52JcRe5XoEOr3/KgbqwO8IckPMpYsQwWxkRLQOp8ve/oqPaeYRVinHhk
8
+ Pj1sQ8IdO84vxZeeW8dsaPXv9PbxpfkwrfKau8f/AsTDdP2AqFKBwTQfyBUeuLe7
9
+ a31WkL/wLQC6VAu4ZsrxQxx4V930+zBDs+CzOef8tFClZIQrsEZaIeTTmgPUABoK
10
+ Tfdu1GL8Vm2nkcHITrqg6FNV5mLtQtihKlYVba+dsfRPSOdzLMCD5Y/csmS1kazl
11
+ ywPJKTXeL8EfIcU5BXFQPiZt8Wy4e279IS1WLt1qd/XC3e9KDWMqaWcCqj8Qe/B0
12
+ Q1J0xdfNYq63LgPlLS6cYlxpnQmP8BwbS1Kv2R/SP1nYmSqVgMpB+hH5zXpSr5i9
13
+ 55QZDegCXcN1kV/PMtNnNzAQv+x8EssmVVguPoDUYrXjmCUo1PG2/5CjFwARAQAB
14
+ tCFLcmlzIExlZWNoIDxrcmlzLmxlZWNoQGdtYWlsLmNvbT6JAj4EEwECACgFAld3
15
+ 340CGwMFCQHhM4AGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEDq8dIUffMyI
16
+ U1sP/0uTuknNTuEI5rr/yXFSnc63JkGE9PuA0I1OlPFm9vC6MIghB+mCC/PcEPAo
17
+ Y1rbujDVgqKiqPmcev/kCYXh88MbpSyi0kHjkQPSaITZxvdRDjFdkDJI06TVcoCe
18
+ 3Ot9vPClw0BZnSLnK8FGG1tHAR0/4TdXt26ZE3InUd32KGpB4dhTZU0I8XV0Tn6v
19
+ 7ilTlAkgM8CAIU8Kq2kLzkYFgvKa4zTF6IDeECWIsuZ7nDhZkmajX6FaqHDf3T7B
20
+ kezKV276RMzhZFVRmNhPOTK25tS6A2JMN81MqHPul0V90Z14GbuWcnh7mtdgXaoy
21
+ ZZqmh+VNcP3LcW9TwT072Cbyzxjk+raJXgolRAa0e/8swmA3A/AKW0zxDAKlu6EX
22
+ xAISsdMyPgGOWDFJfXF76CfixDjxilrh0P7UHrU1XKR8jxsFQ09ovss/rsmwvYNL
23
+ QdxJLJkmnL3kXz2DkjT1DwGjczj6YJK1d3cJZMQUxf7tXcWw7NfIibTWFQoKWc5B
24
+ TiYdsIONA7/SH+LPu75N6HR5/LivMWVXSczJASd9ok5EZ0eNzWw6P/uG98DQjjmW
25
+ JLeivMb8hpSU+Do9ll8KRO1oGfUvCoFHCyPYC1r+0qPcb3DIZo/uJkYUjpI9L6uD
26
+ i/Kz35XmANmsealcazKZA1p1AswJ73VyNb5Fi4CL1SMSWIDkiQJVBBMBAgA/AhsD
27
+ BgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgBYhBOB/tIm7gURwjAWaCzq8dIUffMyI
28
+ BQJdmdoRBQkIAy4EAAoJEDq8dIUffMyI210P/2B8ZkNiHPkmhGUZkrX6M9t5zJiR
29
+ iE2A1v7Tqw8TooUMvSemrlwVvi7qcDpzWZA7eol4ft7HeuChw2iPshm/nbdEOefr
30
+ tGcRNK/AV+vdR0SR40/PwfQZZwWbf/Jg2jhq7xMJOGAqrC7wHBy17i9QfYBrAcCu
31
+ VTCkSitfMKTTDPhxz2MvA4RyU1CGQWyJOv9JsDCWeK59NV1afL9JIYz+mqOF0SLK
32
+ DKDGkIOZ69vSpsdwljd4y5xvSwO1iUHTiFXiVshq+ZYeG/ClLwXRKH28zg9zMaGX
33
+ KUp4tocAoyuSdszntVJXy2p8HhcWOmBH4Uqb1d3nl0ofbP8mPcMICBpu5OX48aVE
34
+ zja6LXWD/bCtNKj9gtvBZaRayF+H/KlGC89p5lSdhpyqpj7oYmvhdbwgqYhjoHgr
35
+ qZJ4OfD7UWZBfd3IVSFm6miKdNoXHtqqELo1+ZqUCTmvGLhLJHh5XFZ+8ucVl2U9
36
+ xPO9olo9eyieRFCoiH6scSwgB3th39emclGTvbHvkOrB5ejE/eJ4RdPEDgAjWqnn
37
+ uMcmCxY2hc50qzS1w1/4I5LfOeWHWdETJtmhVDYEjJVhOA/wNfn3excyBUnwaQ2o
38
+ WdGeRM6Izlhn9wRFVd6O7sSrCi3iwFJfarlkCnbAyUyvw75B931ChJsO5z/tu6+W
39
+ p9MXO0Tbjz8dTQZyuQINBFd3340BEADfyfFPYXUl/POEFTWGN9JSsacYVzL3Zf1K
40
+ Rz7PZJlzhhTPP+SMyBK+f9LMni8U+HIss4dPu3iOgvd/lF7N9j051qQmSxfsKI3D
41
+ VgtAJnjFC+5WilBMbOcbuxVWRwIaSMqlPn73ZxYvOGA1uuh36oE4KGQz8f34oE5S
42
+ CR23ku3BpRdfYhcKEpjdGalnTlyOA99JEwktq8UMURNSglB3bU5s+7o5v8hJ5E1t
43
+ ACcLpRYF0eGm7Vm6cwJk5niibHWCdeAvJ2bUnW5lrlS1e/Z/WKbsePXRVliPikrX
44
+ JmnyJ6teDkH1q4OIsSQnf9jq0wu+dVP45vgNSRqlnbsneSJ1kumJEkrStRrQ6Rzn
45
+ i54DQfxXp/1DackKYzXGLUyDNv45pQkCtfzeLtzyGUekXAuCyQIRWbeAUeTkSx7X
46
+ Y4JIFKzGdNKMmMAO2YNL+5XvoTn/ae1+mEmg3CKKMNO/uvXuzyWQ1oZgcW70HE6j
47
+ h4wXr5+EL2ShCChBPMbd1bZWW3kGnDiez+ndFLbdZxHBOX/mfBzg+3RMXRkZdngD
48
+ JbCazQAoazYq1Sz9kFO7wDW0UnLI+ae6oR3LSw3YpGhnqquYIsBPF1nMGTAylQZI
49
+ SH66vQYSMfQM41+G0IR0qmxmOVa76fjqlu8ksaNZQ2nPUVfkyBEzmyPQ/JBTFayv
50
+ 5HvwNGrkBwARAQABiQI8BBgBAgAmAhsMFiEE4H+0ibuBRHCMBZoLOrx0hR98zIgF
51
+ Al2Z2kkFCQgDLjwACgkQOrx0hR98zIhvtA/8CXpXmI5sR5wHKIe8RL3C0k1RcaU/
52
+ nF6GPW/lj9NaE8cAZdn2Z3duiYvZbCyydKFQTY47vl5KiY3NbgodxXGALa6otkv+
53
+ RrjkEUxJH2UrAP1W2sPQpm/jAQqMqrgJ1fgTdsDBvCwSUcvW+DvtZhRDgfOZzn5K
54
+ KndFw9HwNQydyZVoLyXRe99y8NKz7LMfROG7RLaxPGefkolqSTfelI4rjHIKgfAx
55
+ WUsv/7mOfPCDxD5qiF78XSnOBGya8OO6GbjaJk37t90nn9llfyi3j1SnaeQ6mDob
56
+ dtNABE9QeivZBD59jkHXgiVelP6CruJPqE4MJo5hc2/fI+xvSXIISWAy+fBvpOE0
57
+ sTlc83e5QsjnDgTTqAe++T1WALfGpAriLq0oh3aLtLFAVeb8iaC6w0CE11VleGD1
58
+ AhWU4t6vdgFuY00WxVGHJnjl0Pdg67IT8LqklgYYwMdsDvQjmfoodlkkxmGN9GeO
59
+ 3UJ+zcb1QHNgfZKcAFF2XCANe/G9d9gHX0676dAYvU9WXETueszgTP3ip9/kFrHe
60
+ Ew5EKOGnMAd3Wd4EBJWU3fy66Jqr5BlaBX8s0cZ+LqxyLld1ItgURqAI/BAUJ20l
61
+ t4AvXw1OiUBHjxBQriqXKhyxqPbTw2OH1+quJ8NnixZJoEISIlMP/d0YSa7iBizs
62
+ j7lHQ0fCjBV7+fq5AQ0EV5niPgEIAKsI9cfZ1XZg3VWdpa+AEAaUONkRZo1D22S0
63
+ 70dTiQfzt3lIaVSwET4vz3/+x11PV4e7GNojzWUid2IIJcsMGaRu7diXauA8IPV3
64
+ PwHfCqRnCmuKCB/IJp872a/u+su4YoqjdH9d8E6bFTkrmP6zlY4Aw0nH6RZPDTFp
65
+ fD2F2I7oAT/Qv0lPZoottwu9UNYfSuAje1cDhLL+8NVI6zmC6Pw6fuWDIZYHzoWQ
66
+ ItnaiXZRK4moQclaA+rIbUfAWI5LDguZx1X9qBQaNJW0FJFUcRK8rVkVPL1wF8VY
67
+ Ky3aVSn6PmlUglCRwUmZM+TPQVn8Af4paninFhSrjvz5DKGVfJEAEQEAAYkCPAQY
68
+ AQIAJgIbDBYhBOB/tIm7gURwjAWaCzq8dIUffMyIBQJdmdpJBQkH4SuLAAoJEDq8
69
+ dIUffMyIP3cP/1fRO1m76TmaAb7kfcJL+a0Qe/bb7sAGXFK8lp2gKs26sPZggkkl
70
+ PXmVgfdn8DLrL5NJ9UyiKVBhC4bh8BditsCZvC3HDNcDpJ5votNkLu1L4iYmMAkZ
71
+ Yv6cksicLiDFxnFUKGq7GQOmGrm+aXURRZitxWUTpTi5TOnjc9mrRcX14c7QRZdK
72
+ qTjDjVewbTmQ7olVOyr+gUizH03kLCPrnRa+zEybKdP3I3/9XizZusqJ7ynF68Ce
73
+ TF2u5z0/QhL01qAjFUHzixeDaq9WUju1rReo1IysBtb9HVCdnadIbeESfCE8ZN6N
74
+ NchZbaXv8szxa8WZ3Eph+h7wcnUQ2CM/VKjuVaW7gCPZIIXB8jxZULh6N2kdEIWk
75
+ nN33+99oxboRj/sfe5UKg1nAk4fq2+ZmcKKBPNboHgNw+tjK7HGR2TPm/TyIy4Hb
76
+ Hlvd6fEXh3FwraxB3guk7i6n1b5GekBbrzMsOcARTgUJ0U8J4PtxbF4Y0Zlih97h
77
+ edZfrfyDU/5wuvsFpJrjRENKKGd+SqbPD6D4eUN+rwumw178tP3lbc+NdGqk2F5i
78
+ mFSyha+nApmLJi3tQmN0aEDpNLkWFvqVF/+hOf9v6nKYPJIrsoVRosdoe8GY05ow
79
+ f5mDAt50d/du4gpgsvVOaYGga+fcgbCCG4jsEXQek0tFclYfCcCmRZp8
80
+ =reHQ
81
+ -----END PGP PUBLIC KEY BLOCK-----
@@ -0,0 +1,16 @@
1
+ require "wisper_next/version"
2
+ require 'wisper_next/publisher'
3
+ require 'wisper_next/subscriber'
4
+ require 'wisper_next/events'
5
+
6
+ module WisperNext
7
+ class Error < StandardError; end
8
+
9
+ def self.publisher
10
+ Publisher.new
11
+ end
12
+
13
+ def self.subscriber(*args)
14
+ Subscriber.new(*args)
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ module WisperNext
2
+ # @api private
3
+ class CastToOptions
4
+ # @example
5
+ #
6
+ # def call(*args)
7
+ # CastToOptions.call(args)
8
+ # end
9
+ #
10
+ # call(:strict, :async: false) # => { strict: true, async: false }
11
+ #
12
+ def self.call(arguments)
13
+ arguments.reduce({}) do |memo, item|
14
+ case item
15
+ when Symbol
16
+ memo[item] = true
17
+ when Hash
18
+ memo.merge!(item)
19
+ else
20
+ raise(ArgumentError, "Unsupported option: #{item.inspect} (#{item.class.name})")
21
+ end
22
+
23
+ memo
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ # Global events bus
2
+ #
3
+ # @example
4
+ #
5
+ # # In application boot/initialization
6
+ # #
7
+ # EVENTS = WisperNext::Events.new
8
+ #
9
+ # EVENTS.subscribe(MyListener.new)
10
+ #
11
+ # # Domain model
12
+ # #
13
+ # class MyCommand
14
+ # def initialize(dependencies = {})
15
+ # @events = dependencies.fetch(:events) { EVENTS }
16
+ # end
17
+ #
18
+ # def call(id)
19
+ # user = User.find(id)
20
+ #
21
+ # if user.promote!
22
+ # @events.broadcast('user_promoted', user: user)
23
+ # end
24
+ # end
25
+ # end
26
+ #
27
+ # @api public
28
+ module WisperNext
29
+ class Events
30
+ include ::WisperNext::Publisher.new
31
+ public :broadcast
32
+ end
33
+ end
@@ -0,0 +1,129 @@
1
+ module WisperNext
2
+
3
+ # Extension to provide objects with subscription and publishing capabilties
4
+ #
5
+ # @example
6
+ #
7
+ # class PromoteUser
8
+ # include Wisper.publisher
9
+ #
10
+ # def call
11
+ # # ...
12
+ # broadcast(:user_promoted, user_id: user.id, ts: Time.now)
13
+ # end
14
+ # end
15
+ #
16
+ # class NotifyUserOfPromotion
17
+ # def on_event(name, payload)
18
+ # puts "#{name} => #{payload.inspect}"
19
+ # end
20
+ # end
21
+ #
22
+ # command = PromoteUser.new
23
+ # command.subscribe(NotifyUserOfPromotion.new)
24
+ # command.call
25
+ #
26
+ class Publisher < Module
27
+ def included(descendant)
28
+ super
29
+ descendant.send(:include, Methods)
30
+ end
31
+
32
+ # Exception raised when a listener is already subscribed
33
+ #
34
+ # @api public
35
+ #
36
+ ListenerAlreadyRegisteredError = Class.new(StandardError) do
37
+ # @api private
38
+ def initialize(listener)
39
+ super("listener #{listener.inspect} is already subscribed")
40
+ end
41
+ end
42
+
43
+ # Exception raised when a listener does not have an #on_event method
44
+ #
45
+ # @api public
46
+ #
47
+ NoEventHandlerError = Class.new(ArgumentError) do
48
+ # @api private
49
+ def initialize(listener)
50
+ super("listener #{listener.inspect} does not have an #on_event method")
51
+ end
52
+ end
53
+
54
+ module Methods
55
+ # returns true when given listener is already subscribed
56
+ #
57
+ # @param [Object] listener
58
+ #
59
+ # @return [Boolean]
60
+ #
61
+ # @api public
62
+ #
63
+ def subscribed?(listener)
64
+ subscribers.include?(listener)
65
+ end
66
+
67
+ # subscribes given listener
68
+ #
69
+ # @param [Object] listener
70
+ #
71
+ # @return [Object] self
72
+ #
73
+ # @api public
74
+ #
75
+ def subscribe(listener)
76
+ raise ListenerAlreadyRegisteredError.new(listener) if subscribed?(listener)
77
+ raise NoEventHandlerError.new(listener) unless listener.respond_to?(:on_event)
78
+ subscribers.push(listener)
79
+ self
80
+ end
81
+
82
+ # subscribes the given block to an event
83
+ #
84
+ # @param [String, Symbol] event name
85
+ #
86
+ # @return [Object] self
87
+ #
88
+ # @api public
89
+ #
90
+ def on(name, &block)
91
+ raise ArgumentError, 'must pass a block' unless block_given?
92
+ subscribe(CallableAdapter.new(name, block))
93
+ self
94
+ end
95
+
96
+ private
97
+
98
+ # Broadcast event to all subscribed listeners
99
+ #
100
+ # @param [String,Symbol] event name
101
+ #
102
+ # @param [Object] optional payload
103
+ #
104
+ # @return [Object] self
105
+ #
106
+ # @api public
107
+ #
108
+ def broadcast(name, payload = nil)
109
+ subscribers.each do |s|
110
+ s.public_send(:on_event, name, payload)
111
+ end
112
+
113
+ self
114
+ end
115
+
116
+ # Returns subscribed listeners
117
+ #
118
+ # @return [Array<Object>] collection of subscribers
119
+ #
120
+ # @api private
121
+ #
122
+ def subscribers
123
+ @subscribers ||= []
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ require_relative 'publisher/callable_adapter'
@@ -0,0 +1,27 @@
1
+ # Wraps a callable (typically a proc) so it conforms to the listener interface
2
+ #
3
+ # @api private
4
+ #
5
+ module WisperNext
6
+ class Publisher
7
+ class CallableAdapter
8
+ attr_reader :event_name, :callable
9
+
10
+ def initialize(event_name, callable)
11
+ @event_name = event_name
12
+ @callable = callable
13
+ freeze
14
+ end
15
+
16
+ def on_event(name, payload)
17
+ if name == @event_name
18
+ @callable.call(payload)
19
+ end
20
+ end
21
+
22
+ def ==(other)
23
+ other == callable
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,68 @@
1
+ require_relative 'cast_to_options'
2
+
3
+ module WisperNext
4
+ class Subscriber < Module
5
+ EmptyHash = {}.freeze
6
+
7
+ def initialize(*args)
8
+ options = CastToOptions.(args)
9
+
10
+ strict = options.fetch(:strict, true)
11
+ prefix = options.fetch(:prefix, false)
12
+ broadcaster = resolve_broadcaster(options)
13
+
14
+ # maps event to another method
15
+ #
16
+ # @param [Symbol] event name
17
+ #
18
+ # @return [Object] payload
19
+ #
20
+ # @api public
21
+ #
22
+ define_method :on_event do |name, payload|
23
+ method_name = ResolveMethod.(name, prefix)
24
+
25
+ if respond_to?(method_name)
26
+ broadcaster.call(self, method_name, payload)
27
+ else
28
+ if strict
29
+ raise NoMethodError.new(name)
30
+ else
31
+ # no-op
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ NoMethodError = Class.new(StandardError) do
38
+ # @api private
39
+ def initialize(event_name)
40
+ super("No method found for #{event_name} event")
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def resolve_broadcaster(options)
47
+ value = options[:async] || options[:broadcaster] || SendBroadcaster.new
48
+
49
+ case value
50
+ when Hash
51
+ broadcaster_for(:async, value)
52
+ when TrueClass
53
+ broadcaster_for(:async)
54
+ when Symbol, String
55
+ broadcaster_for(value)
56
+ else
57
+ value
58
+ end
59
+ end
60
+
61
+ def broadcaster_for(name, opts = EmptyHash)
62
+ self.class.const_get("#{name.to_s.capitalize}Broadcaster").new(opts)
63
+ end
64
+ end
65
+ end
66
+
67
+ require_relative 'subscriber/send_broadcaster'
68
+ require_relative 'subscriber/resolve_method'
@@ -0,0 +1,14 @@
1
+ module WisperNext
2
+ class Subscriber
3
+ class ResolveMethod
4
+ def self.call(name, prefix)
5
+ if prefix
6
+ "on_#{name}"
7
+ else
8
+ name
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,20 @@
1
+ # Default synchronous broadcaster which sends event to subscriber
2
+ #
3
+ # @api private
4
+ module WisperNext
5
+ class Subscriber
6
+ class SendBroadcaster
7
+ def initialize(options = {})
8
+ @public_send = options.fetch(:public_send, true)
9
+ end
10
+
11
+ def call(subscriber, event_name, payload)
12
+ if @public_send
13
+ subscriber.public_send(event_name, payload)
14
+ else
15
+ subscriber.send(event_name, payload)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module WisperNext
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,34 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "wisper_next/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "wisper_next"
7
+ spec.version = WisperNext::VERSION
8
+ spec.authors = ["Kris Leech"]
9
+ spec.email = ["kris.leech@gmail.com"]
10
+
11
+ spec.summary = "A micro library providing Ruby objects with Publish-Subscribe capabilities"
12
+ spec.description = "A micro library providing Ruby objects with Publish-Subscribe capabilities"
13
+ spec.homepage = "https://gitlab.com/kris.leech/wisper_next"
14
+ spec.license = "MIT"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+ spec.metadata["changelog_uri"] = "https://gitlab.com/kris.leech/wisper_next/blob/master/CHANGELOG.md"
19
+
20
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ signing_key = File.expand_path(ENV['HOME'].to_s + '/.ssh/gem-private_key.pem')
29
+
30
+ if File.exist?(signing_key)
31
+ spec.signing_key = signing_key
32
+ spec.cert_chain = ['keys/gem-public_cert.pem']
33
+ end
34
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wisper_next
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kris Leech
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIDQDCCAiigAwIBAgIBATANBgkqhkiG9w0BAQsFADAlMSMwIQYDVQQDDBprcmlz
14
+ LmxlZWNoL0RDPWdtYWlsL0RDPWNvbTAeFw0xOTEwMTYxNDEzNDVaFw0yMDEwMTUx
15
+ NDEzNDVaMCUxIzAhBgNVBAMMGmtyaXMubGVlY2gvREM9Z21haWwvREM9Y29tMIIB
16
+ IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8gg/iZE6RFkl6GjisJJf9wQl
17
+ 5Tog+mrGqtylNpe9QKeyQS1fKLNR3Cqmv6sT3f3GlU8hhG+802MXmlJo7VyENs+s
18
+ HdMy85fBnYwhS/szhTzBqduXw43RAAGqB0VaHAoHdufTZBkmFSXET5c0/jUqQrPL
19
+ Zxsm2i0NV8cVpvrZlQcsW4Kf97DG1ZFNncS0IfCDqnluh21qMSgVAuiKHGoVKCYG
20
+ Igey486VuuJ8j6axwW3ii8HoBOxjF8Ka/rJ/t4sB9Ql/p6RHR4RhxP5xIS9geCpI
21
+ dsPGnCSleTW1EnXGHLdOdJpVmJXueYJEQJ1Rh/rR+9PeqAT3RA0D/dxSGIVtrQID
22
+ AQABo3sweTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUyupX2gb3
23
+ +isBRwf4/fhgLnh02sYwHwYDVR0RBBgwFoEUa3Jpcy5sZWVjaEBnbWFpbC5jb20w
24
+ HwYDVR0SBBgwFoEUa3Jpcy5sZWVjaEBnbWFpbC5jb20wDQYJKoZIhvcNAQELBQAD
25
+ ggEBAExirH+Py0Wm8+ZzNnx/oyFXSF7V9FClYhPUXwQ5FmvQG+gXRlonO9IR/HL+
26
+ wGJNFh4B19fpughgQeJKjKJsG2zk2XnSK2yQyyNGdr/9dHXp/TS5sGvX9jFYVeAr
27
+ 0XCWXRBK+IfEqNnbFvPkwZEK62WLnrL50gMs3u2ILmLP5gkO3EoLyezJvIj33CLO
28
+ HX0Plp0VwiSp8+gEELX5wtdrnJ/UjmrY1lg7szukE+jgOJ++1e7Rzr+woBewIWvF
29
+ xn3z1ObkNQCnLZMRmzEcSa40T10/AuuuEv32emAgb50u9vz+s4y2lnE9i4mWW7+X
30
+ 5vdRgX7VnzYPA+Lc39kD+AcGIrQ=
31
+ -----END CERTIFICATE-----
32
+ date: 2019-11-01 00:00:00.000000000 Z
33
+ dependencies: []
34
+ description: A micro library providing Ruby objects with Publish-Subscribe capabilities
35
+ email:
36
+ - kris.leech@gmail.com
37
+ executables: []
38
+ extensions: []
39
+ extra_rdoc_files: []
40
+ files:
41
+ - ".gitignore"
42
+ - ".gitlab-ci.yml"
43
+ - ".rspec"
44
+ - ".ruby-version"
45
+ - CHANGELOG.md
46
+ - CODE_OF_CONDUCT.md
47
+ - Gemfile
48
+ - LICENSE.txt
49
+ - README.md
50
+ - Rakefile
51
+ - bin/console
52
+ - bin/setup
53
+ - keys/gem-public_cert.pem
54
+ - keys/kris.leech.gpg.public
55
+ - lib/wisper_next.rb
56
+ - lib/wisper_next/cast_to_options.rb
57
+ - lib/wisper_next/events.rb
58
+ - lib/wisper_next/publisher.rb
59
+ - lib/wisper_next/publisher/callable_adapter.rb
60
+ - lib/wisper_next/subscriber.rb
61
+ - lib/wisper_next/subscriber/resolve_method.rb
62
+ - lib/wisper_next/subscriber/send_broadcaster.rb
63
+ - lib/wisper_next/version.rb
64
+ - wisper_next.gemspec
65
+ homepage: https://gitlab.com/kris.leech/wisper_next
66
+ licenses:
67
+ - MIT
68
+ metadata:
69
+ homepage_uri: https://gitlab.com/kris.leech/wisper_next
70
+ source_code_uri: https://gitlab.com/kris.leech/wisper_next
71
+ changelog_uri: https://gitlab.com/kris.leech/wisper_next/blob/master/CHANGELOG.md
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 3.0.6
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: A micro library providing Ruby objects with Publish-Subscribe capabilities
91
+ test_files: []
@@ -0,0 +1,2 @@
1
+ W�9qg`����{��dSK�C���� ��n�E��g�ɘ����CBvVU�A�p�|ۺ�'Kzd�^H��yH�{�Uo!gƤ�[rx�q G�3�h4�}I$?G�şp =��%�2�z#H�c�ЏU��\<2߭ūC*"j N'�Y�v�! �K$��⥱�8��.������Z
2
+ ;