wires 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 369f57cc3ba7cc10459a5c96c64de94ea0620993
4
- data.tar.gz: d204b15eee9ef1c0ca74fa2edbeeb53fa17c6281
3
+ metadata.gz: 84333d4617ce57228fdd29e5255aa9e30f1ce474
4
+ data.tar.gz: 445840683d9422bae1292a8655253d32510cca87
5
5
  SHA512:
6
- metadata.gz: c8e1098cdc6fc1f761cba1ad67818b63e688d15033df550a4dca9184a512cd21fd805a05ce7a7be45f67a57b02e12e48f73198ada199de4f46fd9ab3b189c88b
7
- data.tar.gz: 7c4e58d4a7acc0422dbe72d44b176be64fb12d1a641d94943c3ef46373471dfb36194bfcb21cb65a11b455503a883009888d5f660a0624652d8fd0522bec42f9
6
+ metadata.gz: 68bc50ed50729dcae3f913cf9db1dcc36bdf112710a7c03d9dfcc76ab7da05fccfc83f6e65846e29169ba325d3dba30e100d3570efe589cbcac00c46aedbecd8
7
+ data.tar.gz: eaa90eea5b0051e1f1f9b773b51aa14290b440d29ea54b3d08ae2883a92fc1f5aed96dc812bd8c6ad94316c9d887c2c12387a7186fbfcf19fbfc332c5947eb2d
data/LICENSE ADDED
@@ -0,0 +1,2 @@
1
+ Copyright (c) 2013 : Joe McIlvain
2
+ All rights reserved.
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
@@ -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
@@ -1,6 +1,6 @@
1
1
  require 'set'
2
2
  require 'thread'
3
3
 
4
- require_relative 'events'
5
- require_relative 'hub'
6
- require_relative 'channels'
4
+ require 'wires/events'
5
+ require 'wires/hub'
6
+ require 'wires/channels'
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.9
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 temporarily reserved '
28
+ - 'Copyright (c) Joe McIlvain. All rights reserved '
24
29
  metadata: {}
25
30
  post_install_message:
26
31
  rdoc_options: []