wisper_next 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.
@@ -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
+ 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
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
+ ;