wires 0.0.9 → 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.
- checksums.yaml +4 -4
- data/LICENSE +2 -0
- data/README.md +13 -0
- data/lib/wires/channels.rb +100 -0
- data/lib/wires/events.rb +131 -0
- data/lib/wires/hub.rb +66 -0
- data/lib/wires.rb +3 -3
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 84333d4617ce57228fdd29e5255aa9e30f1ce474
|
4
|
+
data.tar.gz: 445840683d9422bae1292a8655253d32510cca87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68bc50ed50729dcae3f913cf9db1dcc36bdf112710a7c03d9dfcc76ab7da05fccfc83f6e65846e29169ba325d3dba30e100d3570efe589cbcac00c46aedbecd8
|
7
|
+
data.tar.gz: eaa90eea5b0051e1f1f9b773b51aa14290b440d29ea54b3d08ae2883a92fc1f5aed96dc812bd8c6ad94316c9d887c2c12387a7186fbfcf19fbfc332c5947eb2d
|
data/LICENSE
ADDED
data/README.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Wires
|
2
|
+
=====
|
3
|
+
|
4
|
+
An asynchronous (threaded) event routing framework in Ruby.
|
5
|
+
|
6
|
+
Patch your objects together with wires.
|
7
|
+
|
8
|
+
Inspired by [the python 'circuits' framework](http://circuitsframework.com/).
|
9
|
+
|
10
|
+
Wires is a work in progress.
|
11
|
+
|
12
|
+
Copyright (c) 2013 : Joe McIlvain
|
13
|
+
All rights reserved.
|
@@ -0,0 +1,100 @@
|
|
1
|
+
|
2
|
+
def on(events, channels='*', &codeblock)
|
3
|
+
channels = [channels] unless channels.is_a? Array
|
4
|
+
for channel in channels
|
5
|
+
Channel(channel).register(events, codeblock)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def fire(event, channel='*')
|
11
|
+
Channel(channel).fire(event)
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def Channel(*args) Channel.new(*args) end
|
16
|
+
|
17
|
+
class Channel
|
18
|
+
|
19
|
+
attr_reader :name
|
20
|
+
attr_reader :target_list
|
21
|
+
|
22
|
+
def initialize(name)
|
23
|
+
@name = name
|
24
|
+
@target_list = Set.new
|
25
|
+
@@channel_list << self
|
26
|
+
end
|
27
|
+
|
28
|
+
# Ensure that there is only one instance of Channel per name
|
29
|
+
@@channel_list = Set.new
|
30
|
+
def self.new(*args, &block)
|
31
|
+
(@@channel_list.select {|x| x.name == args[0]} [0]) \
|
32
|
+
or super(*args, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Class-wide reference to the global channel and event hub
|
36
|
+
@@channel_star = Channel('*')
|
37
|
+
@@hub = Hub.new
|
38
|
+
|
39
|
+
# Register a proc to be triggered by an event on this channel
|
40
|
+
def register(events, proc)
|
41
|
+
|
42
|
+
if not proc then raise SyntaxError, \
|
43
|
+
"No Proc given to execute on event: #{events}" end
|
44
|
+
|
45
|
+
# Convert all events to strings
|
46
|
+
events = [events] unless events.is_a? Array
|
47
|
+
events.flatten!
|
48
|
+
events.map! { |e| (e.is_a?(Class) ? e.codestring : e.to_s) }
|
49
|
+
events.uniq!
|
50
|
+
|
51
|
+
@target_list << [events, proc]
|
52
|
+
@target_list << [events, proc]
|
53
|
+
end
|
54
|
+
|
55
|
+
# Fire an event on this channel
|
56
|
+
def fire(_event)
|
57
|
+
|
58
|
+
# Pull out args from optional array notation
|
59
|
+
_event = [_event] unless _event.is_a? Array
|
60
|
+
_event, *args = _event
|
61
|
+
|
62
|
+
# Create event object from event as an object, class, or symbol/string
|
63
|
+
event =
|
64
|
+
case _event
|
65
|
+
when Event
|
66
|
+
_event
|
67
|
+
when Class
|
68
|
+
_event.new(*args) if _event < Event
|
69
|
+
else
|
70
|
+
cls = Event.from_codestring(_event.to_s).new(*args)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Fire to each relevant target on each channel
|
74
|
+
for chan in relevant_channels()
|
75
|
+
for target in chan.target_list
|
76
|
+
for string in target[0] & event.class.codestrings
|
77
|
+
@@hub.enqueue([string, event, *target[1..-1]])
|
78
|
+
end end end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
def relevant_channels
|
83
|
+
return @@channel_list if self==@@channel_star
|
84
|
+
|
85
|
+
if self.name.is_a?(Regexp) then raise TypeError,
|
86
|
+
"Cannot fire on Regexp channel: #{self.name}."\
|
87
|
+
" Regexp channels can only used in event handlers." end
|
88
|
+
|
89
|
+
relevant = [@@channel_star, self]
|
90
|
+
for c in @@channel_list
|
91
|
+
relevant << c if case c.name
|
92
|
+
when Regexp
|
93
|
+
self.name =~ c.name
|
94
|
+
else String
|
95
|
+
self.name == c.name.to_s
|
96
|
+
end
|
97
|
+
end
|
98
|
+
return relevant.uniq
|
99
|
+
end
|
100
|
+
end
|
data/lib/wires/events.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
# Store a list of all Event classes that get loaded.
|
2
|
+
class EventRegistry
|
3
|
+
@@registry = []
|
4
|
+
|
5
|
+
def self.<<(cls)
|
6
|
+
@@registry << cls
|
7
|
+
@@registry.uniq!
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.list
|
11
|
+
@@registry
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# All Event classes should inherit from this one
|
16
|
+
class Event
|
17
|
+
|
18
|
+
# Register with the EventRegistry and make subclasses do the same
|
19
|
+
EventRegistry << self
|
20
|
+
def self.inherited(subcls)
|
21
|
+
|
22
|
+
# Be sure codestring doesn't conflict
|
23
|
+
existing = self._from_codestring(subcls.codestring)
|
24
|
+
if existing then raise NameError, \
|
25
|
+
"New Event subclass '#{subcls}' conflicts with"\
|
26
|
+
" existing Event subclass '#{existing}'."\
|
27
|
+
" The generated codestring '#{subcls.codestring}'"\
|
28
|
+
" must be unique for each Event subclass." end
|
29
|
+
|
30
|
+
# Register, then call super
|
31
|
+
EventRegistry << subcls
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
# List of class inheritance lineage back to but excluding Object
|
36
|
+
def self.ancestry(cls=self)
|
37
|
+
_next = cls.superclass
|
38
|
+
[cls==Object ? [] : [cls, self.ancestry(_next)]].flatten
|
39
|
+
end
|
40
|
+
|
41
|
+
# Convert class <ClassNameEvent> to string "class_name"
|
42
|
+
def self.codestring(cls=self)
|
43
|
+
cls.to_s
|
44
|
+
.gsub(/(?<!(?:_|^))([A-Z])/, "_\\1")
|
45
|
+
.downcase
|
46
|
+
.gsub(/_event/, "")
|
47
|
+
end
|
48
|
+
|
49
|
+
# List of codestrings associated with this event and ancestors
|
50
|
+
def self.codestrings
|
51
|
+
x = self.ancestry
|
52
|
+
.map {|cls| cls.codestring}
|
53
|
+
end
|
54
|
+
|
55
|
+
# Pull class from registry by codestring
|
56
|
+
# (more reliable than crafting a reverse regexp)
|
57
|
+
def self._from_codestring(str)
|
58
|
+
return EventRegistry.list
|
59
|
+
.select{|e| e.codestring==str}[0]
|
60
|
+
end
|
61
|
+
def self.from_codestring(str)
|
62
|
+
cls = self._from_codestring(str)
|
63
|
+
if not cls then raise NameError,
|
64
|
+
"No known Event subclass with codestring: '#{str}'" end
|
65
|
+
cls
|
66
|
+
end
|
67
|
+
|
68
|
+
# Create attributes and accessors for all arguments to the constructor.
|
69
|
+
# This is done here rather than in initialize so that the functionality
|
70
|
+
# will remain if the user developer overrides initialize in the subclass.
|
71
|
+
def self.new(*args, &block)
|
72
|
+
obj = super
|
73
|
+
|
74
|
+
kwargs = args[-1].is_a?(Hash) ? args.pop : Hash.new
|
75
|
+
kwargs[:args] = args
|
76
|
+
kwargs[:proc] = block if block
|
77
|
+
for key in kwargs.keys
|
78
|
+
att = key.to_s
|
79
|
+
obj.instance_variable_set("@#{att}", kwargs[key])
|
80
|
+
self.class_eval("def #{att}; @#{att}; end")
|
81
|
+
self.class_eval("def #{att}=(val); @#{att}=val; end")
|
82
|
+
end
|
83
|
+
|
84
|
+
obj
|
85
|
+
end
|
86
|
+
|
87
|
+
# Calling super in self.new with *args will complain if this isn't here
|
88
|
+
def initialize(*args, &block) end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
#
|
93
|
+
# Comparison support for Events and Symbols/Strings
|
94
|
+
#
|
95
|
+
|
96
|
+
# Reopen Event and add comparison functions
|
97
|
+
class Event
|
98
|
+
def self.==(other)
|
99
|
+
other.is_a?(Class) ?
|
100
|
+
super : self.codestring==other.to_s
|
101
|
+
end
|
102
|
+
def self.<=(other)
|
103
|
+
other.is_a?(Class) ?
|
104
|
+
super : self.codestrings.include?(other.to_s)
|
105
|
+
end
|
106
|
+
def self.<(other)
|
107
|
+
other.is_a?(Class) ?
|
108
|
+
super : (self<=other and not self==other)
|
109
|
+
end
|
110
|
+
def self.>=(other)
|
111
|
+
other.is_a?(Class) ?
|
112
|
+
super : Event.from_codestring(other.to_s)<=self
|
113
|
+
end
|
114
|
+
def self.>(other)
|
115
|
+
other.is_a?(Class) ?
|
116
|
+
super : Event.from_codestring(other.to_s)<self
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Autogenerate the inverse comparison functions for Symbol/String
|
121
|
+
for cls in [Symbol, String]
|
122
|
+
%w(== < > <= >=).zip(%w(== > < >= <=))
|
123
|
+
.each do |ops|
|
124
|
+
op, opinv = ops # unzip operator and inverse operator
|
125
|
+
cls.class_eval(
|
126
|
+
"def #{op}(other)\n"\
|
127
|
+
" (other.is_a?(Class) and other<=Event) ? \n"\
|
128
|
+
" (other#{opinv}self) : super\n"\
|
129
|
+
"end\n")
|
130
|
+
end
|
131
|
+
end
|
data/lib/wires/hub.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
|
2
|
+
# Make sure puts goes to $stdout for all threads!
|
3
|
+
def puts(x) $stdout.puts(x) end
|
4
|
+
|
5
|
+
|
6
|
+
class Hub
|
7
|
+
def initialize
|
8
|
+
@queue = Queue.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.new
|
12
|
+
@instance ||= super
|
13
|
+
end
|
14
|
+
|
15
|
+
def kill!() @keepgoing=false end
|
16
|
+
|
17
|
+
def run
|
18
|
+
@keepgoing = true
|
19
|
+
|
20
|
+
while @keepgoing
|
21
|
+
if @queue.empty? then sleep(0)
|
22
|
+
else _process_item(@queue.shift) end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
def _process_item(x)
|
28
|
+
x, waiting_thread = x
|
29
|
+
string, event, proc = x
|
30
|
+
Thread.new do
|
31
|
+
|
32
|
+
begin
|
33
|
+
waiting_thread.wakeup
|
34
|
+
proc.call($event = event)
|
35
|
+
|
36
|
+
rescue Interrupt, SystemExit => e
|
37
|
+
@keepgoing = false
|
38
|
+
_unhandled_exception(e)
|
39
|
+
|
40
|
+
rescue Exception => e
|
41
|
+
_unhandled_exception(e)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def _unhandled_exception(x)
|
47
|
+
$stderr.puts $!
|
48
|
+
$stderr.puts $@
|
49
|
+
end
|
50
|
+
|
51
|
+
def fire(x)
|
52
|
+
@queue << [x, Thread.current]
|
53
|
+
sleep
|
54
|
+
end
|
55
|
+
|
56
|
+
def enqueue(x)
|
57
|
+
fire x
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# Run the hub in a new thread and join it at main thread exit
|
63
|
+
# However, do not join if an exception caused the exit -
|
64
|
+
# Such an exception indicates usually an error in user code
|
65
|
+
__hub_thread = Thread.new() {Hub.new.run}
|
66
|
+
at_exit { __hub_thread.join if not $! }
|
data/lib/wires.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wires
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe McIlvain
|
@@ -18,9 +18,14 @@ extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
20
|
- lib/wires.rb
|
21
|
+
- lib/wires/hub.rb
|
22
|
+
- lib/wires/events.rb
|
23
|
+
- lib/wires/channels.rb
|
24
|
+
- LICENSE
|
25
|
+
- README.md
|
21
26
|
homepage: https://github.com/jemc/wires/
|
22
27
|
licenses:
|
23
|
-
- 'All rights
|
28
|
+
- 'Copyright (c) Joe McIlvain. All rights reserved '
|
24
29
|
metadata: {}
|
25
30
|
post_install_message:
|
26
31
|
rdoc_options: []
|