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 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: []