wires 0.1.11 → 0.1.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/wires/channels.rb +82 -80
- data/lib/wires/events.rb +19 -0
- data/lib/wires/expect_type.rb +6 -0
- data/lib/wires/hub.rb +5 -8
- data/lib/wires/time.rb +147 -55
- data/lib/wires.rb +2 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00826eb63c92ee41f33d4b9afca45cf79f6074ef
|
4
|
+
data.tar.gz: 7547996adc41b8f58905b22b50f317f403ef56b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 64f55f73930e572d8fdb589a8c84f302396a87ff56ec851e0a6fe24b7666dd8f572ee6638169a0571728e6364ea9b85d38d6d843b133656690cd22072cd00e8c
|
7
|
+
data.tar.gz: efac20e8fc999a1e219ff778ee92cbeb0c5ebfd2e520708b382bd86411a6133c6ceac8ab7084620439e6c8ae4a4ff98bdece2136aa3f4bcc9a73dc3bb968bf71
|
data/lib/wires/channels.rb
CHANGED
@@ -1,105 +1,107 @@
|
|
1
1
|
|
2
2
|
def on(events, channels='*', &codeblock)
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
channels = [channels] unless channels.is_a? Array
|
4
|
+
for channel in channels
|
5
|
+
Channel.new(channel).register(events, codeblock)
|
6
|
+
end
|
7
7
|
nil end
|
8
8
|
|
9
9
|
|
10
10
|
def fire(event, channel='*')
|
11
|
-
|
11
|
+
Channel.new(channel).fire(event, blocking:false)
|
12
12
|
nil end
|
13
13
|
|
14
14
|
def fire_and_wait(event, channel='*')
|
15
|
-
|
15
|
+
Channel.new(channel).fire(event, blocking:true)
|
16
16
|
nil end
|
17
17
|
|
18
18
|
|
19
19
|
def Channel(*args) Channel.new(*args) end
|
20
20
|
|
21
21
|
class Channel
|
22
|
+
|
23
|
+
attr_reader :name
|
24
|
+
attr_reader :target_list
|
25
|
+
|
26
|
+
def initialize(name)
|
27
|
+
@name = name
|
28
|
+
@target_list = Set.new
|
29
|
+
nil end
|
30
|
+
|
31
|
+
|
32
|
+
# Redefine this class method to use an alternate Hub
|
33
|
+
def self.hub; Hub; end
|
34
|
+
# Don't redefine this instance method!
|
35
|
+
def hub; self.class.hub; end
|
36
|
+
|
37
|
+
# Channel registry hash and star channel reference are values
|
38
|
+
# In this Hash with the key being the reference to the Hub
|
39
|
+
@@channel_hash = Hash.new
|
40
|
+
@@channel_star = Hash.new
|
41
|
+
|
42
|
+
# Give out references to the star channel
|
43
|
+
def self.channel_star; @@channel_star[self.hub]; end
|
44
|
+
def channel_star; @@channel_star[self.hub]; end
|
45
|
+
|
46
|
+
# Ensure that there is only one instance of Channel per name
|
47
|
+
@@new_lock = Mutex.new
|
48
|
+
def self.new(*args, &block)
|
49
|
+
@@channel_star[self.hub] ||= Channel.new('*') unless (args[0]=='*')
|
50
|
+
@@new_lock.synchronize do
|
51
|
+
@@channel_hash[self.hub] ||= Hash.new
|
52
|
+
@@channel_hash[self.hub][args[0]] ||= super(*args, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Register a proc to be triggered by an event on this channel
|
57
|
+
def register(events, proc)
|
22
58
|
|
23
|
-
|
24
|
-
|
59
|
+
if not proc.is_a?(Proc) then raise SyntaxError, \
|
60
|
+
"No Proc given to execute on event: #{events}" end
|
25
61
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
62
|
+
# Convert all events to strings
|
63
|
+
events = [events] unless events.is_a? Array
|
64
|
+
events.flatten!
|
65
|
+
events.map! { |e| (e.is_a?(Class) ? e.codestring : e.to_s) }
|
66
|
+
events.uniq!
|
30
67
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
@@channel_hash[args[0]] ||= super(*args, &block)
|
37
|
-
end
|
38
|
-
end
|
68
|
+
@target_list << [events, proc]
|
69
|
+
nil end
|
70
|
+
|
71
|
+
# Fire an event on this channel
|
72
|
+
def fire(event, blocking:false)
|
39
73
|
|
40
|
-
#
|
41
|
-
|
74
|
+
# Create an instance object from one of several acceptable input forms
|
75
|
+
event = Event.new_from event
|
42
76
|
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
# Convert all events to strings
|
50
|
-
events = [events] unless events.is_a? Array
|
51
|
-
events.flatten!
|
52
|
-
events.map! { |e| (e.is_a?(Class) ? e.codestring : e.to_s) }
|
53
|
-
events.uniq!
|
54
|
-
|
55
|
-
@target_list << [events, proc]
|
56
|
-
nil end
|
77
|
+
# Fire to each relevant target on each channel
|
78
|
+
for chan in relevant_channels()
|
79
|
+
for target in chan.target_list
|
80
|
+
for string in target[0] & event.class.codestrings
|
81
|
+
self.class.hub << [string, event, blocking, *target[1..-1]]
|
82
|
+
end end end
|
57
83
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
_event = [_event] unless _event.is_a? Array
|
63
|
-
_event, *args = _event
|
64
|
-
|
65
|
-
# Create event object from event as an object, class, or symbol/string
|
66
|
-
event = case _event
|
67
|
-
when Event
|
68
|
-
_event
|
69
|
-
when Class
|
70
|
-
_event.new(*args) if _event < Event
|
71
|
-
else
|
72
|
-
cls = Event.from_codestring(_event.to_s).new(*args)
|
73
|
-
end
|
74
|
-
|
75
|
-
# Fire to each relevant target on each channel
|
76
|
-
for chan in relevant_channels()
|
77
|
-
for target in chan.target_list
|
78
|
-
for string in target[0] & event.class.codestrings
|
79
|
-
Hub << [string, event, blocking, *target[1..-1]]
|
80
|
-
end end end
|
81
|
-
|
82
|
-
nil end
|
84
|
+
nil end
|
85
|
+
|
86
|
+
def relevant_channels
|
87
|
+
return @@channel_hash[hub].values if self==channel_star
|
83
88
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
self.name.channel_name == c.name.channel_name
|
99
|
-
else
|
100
|
-
self.name.to_s == c.name.to_s
|
101
|
-
end
|
89
|
+
if self.name.is_a?(Regexp) then raise TypeError,
|
90
|
+
"Cannot fire on Regexp channel: #{self.name}."\
|
91
|
+
" Regexp channels can only used in event handlers." end
|
92
|
+
|
93
|
+
relevant = [channel_star]
|
94
|
+
for c in @@channel_hash[hub].values
|
95
|
+
relevant << c if \
|
96
|
+
if c.name.is_a?(Regexp)
|
97
|
+
self.name =~ c.name
|
98
|
+
elsif (defined?(c.name.channel_name) and
|
99
|
+
defined?(self.name.channel_name))
|
100
|
+
self.name.channel_name == c.name.channel_name
|
101
|
+
else
|
102
|
+
self.name.to_s == c.name.to_s
|
102
103
|
end
|
103
|
-
return relevant.uniq
|
104
104
|
end
|
105
|
+
return relevant.uniq
|
106
|
+
end
|
105
107
|
end
|
data/lib/wires/events.rb
CHANGED
@@ -69,7 +69,26 @@ class Event < Object # explicit for the sake of Event.ancestry
|
|
69
69
|
"No known Event subclass with codestring: '#{str}'" end
|
70
70
|
cls
|
71
71
|
end
|
72
|
+
|
73
|
+
# Convert an event from 'array notation' to an Event subclass instance
|
74
|
+
# TODO: List acceptable input forms here for documentation
|
75
|
+
def new_from(input)
|
76
|
+
|
77
|
+
# Standardize to array and pull out arguments if they exist
|
78
|
+
input = [input] unless input.is_a? Array
|
79
|
+
input, *args = input
|
72
80
|
|
81
|
+
# Create event object from event as an object, class, or symbol/string
|
82
|
+
event = case input
|
83
|
+
when Event
|
84
|
+
input
|
85
|
+
when Class
|
86
|
+
input.new(*args) if input < Event
|
87
|
+
else
|
88
|
+
Event.from_codestring(input.to_s).new(*args)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
73
92
|
# Create attributes and accessors for all arguments to the constructor.
|
74
93
|
# This is done here rather than in initialize so that the functionality
|
75
94
|
# will remain if the user developer overrides initialize in the subclass.
|
data/lib/wires/hub.rb
CHANGED
@@ -62,25 +62,21 @@ class Hub
|
|
62
62
|
# [+:blocking+] calling thread won't be done until Hub thread is done
|
63
63
|
def kill(*flags)
|
64
64
|
@finish_all = (flags.include? :finish_all)
|
65
|
-
@state=:dying
|
66
|
-
@thread.join if (flags.include? :blocking)
|
65
|
+
@state=:dying
|
66
|
+
@thread.join if (dying? and flags.include? :blocking)
|
67
67
|
nil end
|
68
68
|
|
69
69
|
# Register hook to execute before kill - can call multiple times
|
70
70
|
def before_kill(proc=nil, retain:false, &block)
|
71
71
|
func = (block or proc)
|
72
|
-
|
73
|
-
raise TypeError, "Expected a Proc or code block to execute."
|
74
|
-
end
|
72
|
+
expect_type func, Proc
|
75
73
|
@before_kills << [func, retain]
|
76
74
|
nil end
|
77
75
|
|
78
76
|
# Register hook to execute after kill - can call multiple times
|
79
77
|
def after_kill(proc=nil, retain:false, &block)
|
80
78
|
func = (block or proc)
|
81
|
-
|
82
|
-
raise TypeError, "Expected a Proc or code block to execute."
|
83
|
-
end
|
79
|
+
expect_type func, Proc
|
84
80
|
@after_kills << [func, retain]
|
85
81
|
nil end
|
86
82
|
|
@@ -195,6 +191,7 @@ class Hub
|
|
195
191
|
unhandled_exception(e)
|
196
192
|
end
|
197
193
|
end
|
194
|
+
|
198
195
|
end
|
199
196
|
|
200
197
|
nil end
|
data/lib/wires/time.rb
CHANGED
@@ -1,45 +1,137 @@
|
|
1
|
-
require 'pry'
|
2
1
|
|
3
|
-
class
|
4
|
-
class
|
5
|
-
|
2
|
+
class TimeSchedulerStartEvent < Event; end
|
3
|
+
class TimeSchedulerAnonEvent < Event; end
|
4
|
+
|
5
|
+
|
6
|
+
class TimeSchedulerItem
|
7
|
+
|
8
|
+
attr_reader :time, :event, :channel, :interval
|
9
|
+
|
10
|
+
def initialize(time, event, channel='*',
|
11
|
+
interval:0.seconds, count:1,
|
12
|
+
ignore_past:false, cancel:false)
|
13
|
+
|
14
|
+
expect_type time, Time
|
15
|
+
|
16
|
+
@active = (not cancel)
|
17
|
+
tempcount = count
|
18
|
+
|
19
|
+
while (time < Time.now) and (tempcount > 0)
|
20
|
+
time += interval
|
21
|
+
tempcount -= 1
|
22
|
+
end
|
23
|
+
if not ignore_past
|
24
|
+
time -= interval
|
25
|
+
self.count = count
|
26
|
+
else
|
27
|
+
self.count = tempcount
|
28
|
+
end
|
29
|
+
|
30
|
+
@time = time
|
31
|
+
@event = Event.new_from(event)
|
32
|
+
@channel = channel
|
33
|
+
@interval = interval
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def active?; @active end
|
38
|
+
def inactive?; not @active end
|
39
|
+
def ready?; @active and (Time.now >= @time) end
|
40
|
+
def time_until; (@active ? [(Time.now - @time), 0].max : nil) end
|
41
|
+
|
42
|
+
def cancel; @active = false ;nil end
|
43
|
+
|
44
|
+
# Get/set @count (and apply constraints on set)
|
45
|
+
def count; @count end
|
46
|
+
#TODO: handle explicit cancel?
|
47
|
+
def count=(x); @count=[x,0].max; @active&&=(count>0) ;nil end
|
48
|
+
|
49
|
+
# Inc/dec @count. Necessary because += and -= outside of lock are not atomic!
|
50
|
+
def count_inc(x=1); self.count=(@count+x) end
|
51
|
+
def count_dec(x=1); self.count=(@count-x) end
|
52
|
+
|
53
|
+
# Fire the event now, regardless of time or active status
|
54
|
+
def fire(*args)
|
55
|
+
Channel.new(@channel).fire(@event, *args)
|
56
|
+
count_dec
|
57
|
+
@time += @interval if @active
|
58
|
+
nil end
|
59
|
+
|
60
|
+
# Fire the event only if it is ready
|
61
|
+
def fire_if_ready(*args); self.fire(*args) if ready? end
|
62
|
+
|
63
|
+
# Block until event is ready
|
64
|
+
def wait_until_ready; sleep 0 until ready? end
|
65
|
+
|
66
|
+
# Block until event is ready, then fire and block until it is done
|
67
|
+
def fire_when_ready(*args);
|
68
|
+
wait_until_ready
|
69
|
+
fire_if_ready(*args)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Lock (almost) all instance methods with common re-entrant lock
|
73
|
+
threadlock instance_methods-superclass.instance_methods-[
|
74
|
+
:block_until_ready,
|
75
|
+
:fire_when_ready]
|
76
|
+
end
|
6
77
|
|
7
78
|
# A singleton class to schedule future firing of events
|
8
79
|
class TimeScheduler
|
9
80
|
@schedule = Array.new
|
10
|
-
@schedule_lock = Mutex.new
|
11
81
|
@thread = Thread.new {nil}
|
12
|
-
@
|
82
|
+
@schedule_lock = Monitor.new
|
83
|
+
@dont_sleep = false
|
13
84
|
|
14
85
|
# Operate on the metaclass as a type of singleton pattern
|
15
86
|
class << self
|
16
87
|
|
88
|
+
# Add an event to the schedule
|
89
|
+
def add(new_item)
|
90
|
+
expect_type new_item, TimeSchedulerItem
|
91
|
+
schedule_add(new_item)
|
92
|
+
wakeup
|
93
|
+
nil end
|
94
|
+
# Add an event to the schedule using << operator
|
95
|
+
alias_method :<<, :add
|
96
|
+
|
17
97
|
# Get a copy of the event schedule from outside the class
|
18
|
-
def list; @
|
98
|
+
def list; @schedule.clone end
|
19
99
|
# Clear the event schedule from outside the class
|
20
|
-
def clear; @
|
100
|
+
def clear; @schedule.clear end
|
21
101
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
# Ignore past events if flag is set
|
29
|
-
if ignore_past and time < Time.now; return nil; end
|
30
|
-
|
31
|
-
# Under mutex, push the event into the schedule and sort
|
32
|
-
@schedule_lock.synchronize do
|
33
|
-
@schedule << {time:time, event:event, channel:channel}
|
34
|
-
@schedule.sort! { |a,b| a[:time] <=> b[:time] }
|
35
|
-
end
|
36
|
-
|
37
|
-
# Wakeup main_loop thread if it is sleeping
|
38
|
-
begin @thread.wakeup; rescue ThreadError; end
|
39
|
-
|
102
|
+
private
|
103
|
+
|
104
|
+
def schedule_reshuffle
|
105
|
+
@schedule.select! {|x| x.active?}
|
106
|
+
@schedule.sort! {|a,b| a.time <=> b.time}
|
40
107
|
nil end
|
41
108
|
|
42
|
-
|
109
|
+
def schedule_add(new_item)
|
110
|
+
@schedule << new_item
|
111
|
+
schedule_reshuffle
|
112
|
+
nil end
|
113
|
+
|
114
|
+
def schedule_concat(other_list)
|
115
|
+
@schedule.concat other_list
|
116
|
+
schedule_reshuffle
|
117
|
+
nil end
|
118
|
+
|
119
|
+
def schedule_pull
|
120
|
+
pending = Array.new
|
121
|
+
while ((not @schedule.empty?) and @schedule[0].ready?)
|
122
|
+
pending << @schedule.shift
|
123
|
+
end
|
124
|
+
[pending, @schedule[0]]
|
125
|
+
end
|
126
|
+
|
127
|
+
# Put all functions dealing with @schedule under @schedule_lock
|
128
|
+
threadlock :list,
|
129
|
+
:clear,
|
130
|
+
:schedule_reshuffle,
|
131
|
+
:schedule_add,
|
132
|
+
:schedule_concat,
|
133
|
+
:schedule_pull,
|
134
|
+
lock: :@schedule_lock
|
43
135
|
|
44
136
|
# Do scheduled firing of events as long as Hub is alive
|
45
137
|
def main_loop
|
@@ -51,60 +143,56 @@ class TimeScheduler
|
|
51
143
|
|
52
144
|
while @keepgoing
|
53
145
|
|
54
|
-
#
|
55
|
-
pending
|
56
|
-
|
57
|
-
|
58
|
-
(Time.now > @schedule[0][:time]))
|
59
|
-
pending << @schedule.shift
|
60
|
-
end
|
61
|
-
on_deck = @schedule[0]
|
62
|
-
end
|
63
|
-
|
64
|
-
# Fire pending events
|
65
|
-
pending.each { |x| Channel(x[:channel]).fire(x[:event]) }
|
146
|
+
# Pull, fire, and requeue relevant events
|
147
|
+
pending, on_deck = schedule_pull
|
148
|
+
pending.each { |x| x.fire }
|
149
|
+
schedule_concat pending
|
66
150
|
|
151
|
+
@sleepzone = true
|
67
152
|
# Calculate the time to sleep based on next event's time
|
68
153
|
if on_deck
|
69
|
-
sleep
|
154
|
+
sleep on_deck.time_until
|
70
155
|
else # sleep until wakeup if no event is on deck
|
71
|
-
sleep
|
156
|
+
sleep
|
72
157
|
end
|
73
|
-
|
158
|
+
@sleepzone = false
|
74
159
|
end
|
75
160
|
|
76
161
|
nil end
|
77
162
|
|
163
|
+
def wakeup
|
164
|
+
sleep 0 until @sleepzone==true
|
165
|
+
sleep 0 until @thread.status=='sleep'
|
166
|
+
@thread.wakeup
|
167
|
+
nil end
|
78
168
|
|
79
169
|
end
|
80
170
|
|
81
171
|
# Use fired event to only start scheduler when Hub is running
|
82
172
|
# This also gets the scheduler loop its own thread within the Hub's threads
|
83
173
|
on :time_scheduler_start, self do; main_loop; end;
|
84
|
-
Channel(self).fire(:time_scheduler_start)
|
174
|
+
Channel.new(self).fire(:time_scheduler_start)
|
85
175
|
|
86
176
|
# Stop the main loop upon death of Hub
|
87
177
|
Hub.before_kill(retain:true) do
|
88
|
-
@
|
89
|
-
|
90
|
-
|
91
|
-
end
|
92
|
-
sleep 0
|
178
|
+
sleep 0 until @sleepzone==true
|
179
|
+
@keepgoing=false
|
180
|
+
wakeup
|
93
181
|
end
|
182
|
+
|
94
183
|
# Refire the start event after Hub dies in case it restarts
|
95
184
|
Hub.after_kill(retain:true) do
|
96
|
-
Channel(self).fire(:time_scheduler_start)
|
185
|
+
Channel.new(self).fire(:time_scheduler_start)
|
97
186
|
end
|
98
187
|
|
99
|
-
|
100
188
|
end
|
101
189
|
|
102
190
|
|
103
191
|
# Reopen the Time class and add the fire method to enable nifty syntax like:
|
104
192
|
# 32.minutes.from_now.fire :event
|
105
193
|
class Time
|
106
|
-
def fire(*
|
107
|
-
TimeScheduler.
|
194
|
+
def fire(event, channel='*', **kwargs)
|
195
|
+
TimeScheduler << TimeSchedulerItem.new(self, event, channel, **kwargs)
|
108
196
|
end
|
109
197
|
end
|
110
198
|
|
@@ -116,7 +204,7 @@ class ActiveSupport::Duration
|
|
116
204
|
alias :__original_since :since
|
117
205
|
def since(*args, &block)
|
118
206
|
if block
|
119
|
-
on :time_scheduler_anon, block.object_id do block.call end
|
207
|
+
on :time_scheduler_anon, block.object_id do |e| block.call(e) end
|
120
208
|
__original_since(*args).fire(:time_scheduler_anon,
|
121
209
|
block.object_id)
|
122
210
|
nil
|
@@ -129,7 +217,7 @@ class ActiveSupport::Duration
|
|
129
217
|
alias :__original_ago :ago
|
130
218
|
def ago(*args, &block)
|
131
219
|
if block
|
132
|
-
on :time_scheduler_anon, block.object_id do block.call end
|
220
|
+
on :time_scheduler_anon, block.object_id do |e| block.call(e) end
|
133
221
|
__original_ago(*args).fire(:time_scheduler_anon,
|
134
222
|
block.object_id)
|
135
223
|
nil
|
@@ -139,4 +227,8 @@ class ActiveSupport::Duration
|
|
139
227
|
end
|
140
228
|
alias :until :ago
|
141
229
|
|
142
|
-
end
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
# TODO: Repeatable event sugar?
|
234
|
+
# TODO: Tests for all new functionality
|
data/lib/wires.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'thread'
|
3
3
|
require 'active_support/core_ext' # Convenience functions from Rails
|
4
|
+
require 'threadlock' # Easily add re-entrant lock to instance methods
|
4
5
|
|
6
|
+
require 'wires/expect_type'
|
5
7
|
require 'wires/events'
|
6
8
|
require 'wires/hub'
|
7
9
|
require 'wires/channels'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wires
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe McIlvain
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-07-
|
11
|
+
date: 2013-07-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: threadlock
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
description: An asynchronous (threaded) event routing framework in Ruby. Patch your
|
28
42
|
objects together with wires. Inspired by the python 'circuits' framework.
|
29
43
|
email: joe.eli.mac@gmail.com
|
@@ -34,6 +48,7 @@ files:
|
|
34
48
|
- lib/wires.rb
|
35
49
|
- lib/wires/time.rb
|
36
50
|
- lib/wires/hub.rb
|
51
|
+
- lib/wires/expect_type.rb
|
37
52
|
- lib/wires/events.rb
|
38
53
|
- lib/wires/channels.rb
|
39
54
|
- LICENSE
|