nutella_lib 0.3.1 → 0.4.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,271 @@
1
+ module Nutella
2
+ module Net
3
+
4
+ # This module implements the pub/sub and request/response APIs at the application level
5
+ module App
6
+
7
+
8
+ # @!group Application-level communication APIs
9
+
10
+ # Subscribes to a channel or to a set of channels at the application-level.
11
+ #
12
+ # @param [String] channel the application-level channel or filter we are subscribing to. Can contain wildcard(s)
13
+ # @param [Proc] callback a lambda expression that is fired whenever a message is received.
14
+ # The passed callback takes the following parameters:
15
+ # - [String] message: the received message. Messages that are not JSON are discarded.
16
+ # - [String] channel: the application-level channel the message was received on (optional, only for wildcard subscriptions)
17
+ # - [Hash] from: the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
18
+ def App.subscribe (channel, callback)
19
+ Nutella::Net.subscribe_to(channel, callback, Nutella.app_id, nil)
20
+ end
21
+
22
+
23
+ # Un-subscribes from an application-level channel
24
+ #
25
+ # @param [String] channel the application level channel we want to unsubscribe from. Can contain wildcard(s).
26
+ def App.unsubscribe( channel )
27
+ Nutella::Net.unsubscribe_to(channel, Nutella.app_id, nil)
28
+ end
29
+
30
+
31
+ # Publishes a message to an application-level channel
32
+ #
33
+ # @param [String] channel the application-level channel we want to publish the message to. *CANNOT* contain wildcard(s)!
34
+ # @param [Object] message the message we are publishing. This can be,
35
+ # nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
36
+ def App.publish(channel, message=nil)
37
+ Nutella::Net.publish_to(channel, message, Nutella.app_id, nil)
38
+ end
39
+
40
+
41
+ # Performs a synchronous request at the application-level
42
+ #
43
+ # @param [String] channel the application-level channel we want to make the request to. *CANNOT* contain wildcard(s)!
44
+ # @param [Object] message the body of request. This can be,
45
+ # nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
46
+ def App.sync_request ( channel, message=nil )
47
+ Nutella::Net.sync_request_to(channel, message, Nutella.app_id, nil)
48
+ end
49
+
50
+
51
+ # Performs an asynchronous request at the application-level
52
+ #
53
+ # @param [String] channel the application-level channel we want to make the request to. *CANNOT* contain wildcard(s)!
54
+ # @param [Object] message the body of request. This can be,
55
+ # nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
56
+ # @param [Proc] callback the callback that is fired whenever a response is received. It takes one parameter (response).
57
+ def App.async_request ( channel, message=nil, callback )
58
+ Nutella::Net.async_request_to(channel, message, callback, Nutella.app_id, nil)
59
+ end
60
+
61
+
62
+ # Handles requests on a certain application-level channel
63
+ #
64
+ # @param [String] channel tha application-level channel we want to listen for requests on. Can contain wildcard(s).
65
+ # @param [Proc] callback a lambda expression that is fired whenever a message is received.
66
+ # The passed callback takes the following parameters:
67
+ # - [String] the received message (payload). Messages that are not JSON are discarded.
68
+ # - [Hash] the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
69
+ # - [*returns* Hash] The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT.
70
+ def App.handle_requests( channel, callback )
71
+ Nutella::Net.handle_requests_on(channel, callback, Nutella.app_id, nil)
72
+ end
73
+
74
+ # @!endgroup
75
+
76
+
77
+ # @!group Application-level APIs to communicate at the run-level
78
+
79
+ # Allows application-level APIs to subscribe to a run-level channel within a specific run
80
+ #
81
+ # @param [String] run_id the specific run we are subscribing to
82
+ # @param [String] channel the run-level channel we are subscribing to. Can be wildcard.
83
+ # @param [Proc] callback the callback that is fired whenever a message is received on the channel.
84
+ # The passed callback takes the following parameters:
85
+ # - [String] message: the received message. Messages that are not JSON are discarded.
86
+ # - [String] channel: the application-level channel the message was received on (optional, only for wildcard subscriptions)
87
+ # - [Hash] from: the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
88
+ def App.subscribe_to_run( run_id, channel, callback )
89
+ Nutella::Net.subscribe_to(channel, callback, Nutella.app_id, run_id)
90
+ end
91
+
92
+
93
+ # Allows application-level APIs to unsubscribe from a run-level channel within a specific run
94
+ #
95
+ # @param [String] run_id the specific run we are un-subscribing from
96
+ # @param [String] channel the run-level channel we want to unsubscribe from. Can contain wildcard(s).
97
+ def App.unsubscribe_to_run( run_id, channel )
98
+ Nutella::Net.unsubscribe_to(channel, Nutella.app_id, run_id)
99
+ end
100
+
101
+
102
+ # Allows application-level APIs to publish to a run-level channel within a specific run
103
+ #
104
+ # @param [String] run_id the specific run we are publishing to
105
+ # @param [String] channel the run-level channel we want to publish the message to. *CANNOT* contain wildcard(s)!
106
+ # @param [String] message the message we are publishing. This can be,
107
+ # nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
108
+ def App.publish_to_run( run_id, channel, message )
109
+ Nutella::Net.publish_to(channel, message, Nutella.app_id, run_id)
110
+ end
111
+
112
+
113
+ # Allows application-level APIs to make a synchronous request to a run-level channel within a specific run
114
+ #
115
+ # @param [String] run_id the specific run we are making the request to
116
+ # @param [String] channel the channel we want to make the request to. *CANNOT* contain wildcard(s)!
117
+ # @param [Object] request the body of request. This can be,
118
+ # nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
119
+ def App.sync_request_to_run( run_id, channel, request)
120
+ Nutella::Net.sync_request_to(channel, request, Nutella.app_id, run_id)
121
+ end
122
+
123
+
124
+ # Allows application-level APIs to make an asynchronous request to a run-level channel within a specific run
125
+ #
126
+ # @param [String] run_id the specific run we are making the request to
127
+ # @param [String] channel the channel we want to make the request to. *CANNOT* contain wildcard(s)!
128
+ # @param [Object] request the body of request. This can be,
129
+ # nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
130
+ # @param [Proc] callback the callback that is fired whenever a response is received. It takes one parameter (response).
131
+ def App.async_request_to_run( run_id, channel, request, callback)
132
+ Nutella::Net.async_request_to(channel, request, callback, Nutella.app_id, run_id)
133
+ end
134
+
135
+
136
+ # Allows application-level APIs to handle requests on a run-level channel within a specific run
137
+ #
138
+ # @param [String] run_id the specific run requests are coming from
139
+ # @param [String] channel we want to listen for requests on. Can contain wildcard(s).
140
+ # @param [Proc] callback a lambda expression that is fired whenever a message is received.
141
+ # The passed callback takes the following parameters:
142
+ # - [String] the received message (payload). Messages that are not JSON are discarded.
143
+ # - [Hash] the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
144
+ # - [*returns* Hash] The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT.
145
+ def App.handle_requests_on_run( run_id, channel, callback )
146
+ Nutella::Net.handle_requests_on(channel, callback, Nutella.app_id, run_id)
147
+ end
148
+
149
+ # @!endgroup
150
+
151
+
152
+ # @!group Application level APIs to communicate at the run-level (broadcast)
153
+
154
+ # Allows application-level APIs to subscribe to a run-level channel *for ALL runs*
155
+ #
156
+ # @param [String] channel the run-level channel we are subscribing to. Can be wildcard.
157
+ # @param [Proc] callback the callback that is fired whenever a message is received on the channel.
158
+ # The passed callback takes the following parameters:
159
+ # - [String] message: the received message. Messages that are not JSON are discarded.
160
+ # - [String] run_id: the run_id of the channel the message was sent on
161
+ # - [Hash] from: the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
162
+ def App.subscribe_to_all_runs( channel, callback )
163
+ # Check the passed callback has the right number of arguments
164
+ raise 'You need to pass a callback with 3 parameters (payload, run_id, from) when subscribing to all runs!' if callback.parameters.length!=3
165
+ # Pad channel
166
+ padded_channel = Nutella::Net.pad_channel(channel, Nutella.app_id, '+')
167
+ mqtt_cb = lambda do |mqtt_message, mqtt_channel|
168
+ begin
169
+ type, from, payload, _ = Nutella::Net.extract_fields_from_message mqtt_message
170
+ run_id = extract_run_id_from_ch(Nutella.app_id, mqtt_channel)
171
+ callback.call(payload, run_id, from) if type=='publish'
172
+ rescue JSON::ParserError
173
+ # Make sure the message is JSON, if not drop the message
174
+ return
175
+ rescue
176
+ # Check the passed callback has the right number of arguments
177
+ STDERR.puts "The callback you passed to subscribe has the #{$!}: it needs 'payload', 'run_id' and 'from'"
178
+ end
179
+ end
180
+ # Add to subscriptions, save mqtt callback and subscribe
181
+ Nutella::Net.subscriptions.push padded_channel
182
+ Nutella::Net.callbacks.push mqtt_cb
183
+ Nutella.mqtt.subscribe( padded_channel, mqtt_cb )
184
+ end
185
+
186
+
187
+ # Allows application-level APIs to unsubscribe from a run-level channel *for ALL runs*
188
+ #
189
+ # @param [String] channel the run-level channel we want to unsubscribe from. Can contain wildcard(s).
190
+ def App.unsubscribe_from_all_runs( channel )
191
+ Nutella::Net.unsubscribe_to(channel, Nutella.app_id, '+')
192
+ end
193
+
194
+
195
+ # Allows application-level APIs to publish a message to a run-level channel *for ALL runs*
196
+ #
197
+ # @param [String] channel the run-level channel we want to publish the message to. *CANNOT* contain wildcard(s)!
198
+ # @param [Object] message the message we are publishing. This can be,
199
+ # nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
200
+ def App.publish_to_all_runs( channel, message )
201
+ Nutella.app_runs_list.each do |run_id|
202
+ Nutella::Net.publish_to(channel, message, Nutella.app_id, run_id)
203
+ end
204
+ end
205
+
206
+
207
+ # Allows application-level APIs to send a request to a run-level channel *for ALL runs*
208
+ #
209
+ # @param [String] channel the run-level channel we want to make the request to. *CANNOT* contain wildcard(s)!
210
+ # @param [Object] request the body of request. This can be,
211
+ # nil/empty (default), a string, a hash and, in general, anything with a .to_json method.
212
+ # @param [Proc] callback the callback that is fired whenever a response is received. It takes one parameter (response).
213
+ def App.async_request_to_all_runs(channel, request, callback)
214
+ Nutella.app_runs_list.each do |run_id|
215
+ Nutella::Net.async_request_to(channel, request, callback, Nutella.app_id, run_id)
216
+ end
217
+ end
218
+
219
+
220
+ # Allows application-level APIs to handle requests to a run-level channel *for ALL runs*
221
+ #
222
+ # @param [String] channel tha run-level channel we want to listen for requests on. Can contain wildcard(s).
223
+ # @param [Proc] callback a lambda expression that is fired whenever a message is received.
224
+ # The passed callback takes the following parameters:
225
+ # - [String] the received message (request). Messages that are not JSON are discarded.
226
+ # - [String] run_id: the run_id of the channel the message was sent on
227
+ # - [Hash] the sender's identifiers (from containing, run_id, app_id, component_id and optionally resource_id)
228
+ # - [*returns* Hash] The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT.
229
+ def App.handle_requests_on_all_runs(channel, callback)
230
+ # Check the passed callback has the right number of arguments
231
+ raise 'You need to pass a callback with 3 parameters (request, run_id, from) when handling requests!' if callback.parameters.length!=3
232
+ # Pad channel
233
+ padded_channel = Nutella::Net.pad_channel(channel, Nutella.app_id, '+')
234
+ mqtt_cb = lambda do |request, mqtt_channel|
235
+ begin
236
+ # Extract nutella fields
237
+ type, from, payload, id = Nutella::Net.extract_fields_from_message request
238
+ run_id = extract_run_id_from_ch(Nutella.app_id, mqtt_channel)
239
+ # Only handle requests that have proper id set
240
+ return if type!='request' || id.nil?
241
+ # Execute callback and send response
242
+ m = Net.prepare_message_for_response( callback.call( payload, run_id, from), id )
243
+ Nutella.mqtt.publish( mqtt_channel, m )
244
+ rescue JSON::ParserError
245
+ # Make sure that request contains JSON, if not drop the message
246
+ return
247
+ rescue
248
+ # Check the passed callback has the right number of arguments
249
+ STDERR.puts "The callback you passed to subscribe has the #{$!}: it needs 'request', 'run_id' and 'from'"
250
+ end
251
+ end
252
+ # Subscribe to the channel
253
+ Nutella.mqtt.subscribe( padded_channel, mqtt_cb )
254
+ end
255
+
256
+
257
+ # @!endgroup
258
+
259
+
260
+ private
261
+
262
+ def App.extract_run_id_from_ch( app_id, mqtt_channel )
263
+ head = "/nutella/apps/#{app_id}/runs/"
264
+ mqtt_channel.gsub(head, '').split('/')[0]
265
+ end
266
+
267
+
268
+ end
269
+
270
+ end
271
+ end
@@ -1,9 +1,11 @@
1
1
  require 'json'
2
2
  require 'pstore'
3
3
  require 'fileutils'
4
+ require 'thread'
4
5
 
5
6
  module Nutella
6
7
 
8
+ # Implements basic run-dependent persistence for nutella bots.
7
9
  module Persist
8
10
 
9
11
  # This module exposes a single method to retrieve a JSONStore
@@ -32,6 +34,11 @@ module Nutella
32
34
 
33
35
  class JSONStore < PStore
34
36
 
37
+ def initialize(path)
38
+ super
39
+ @semaphore = Mutex.new
40
+ end
41
+
35
42
  def dump(table)
36
43
  table.to_json
37
44
  end
@@ -40,6 +47,13 @@ module Nutella
40
47
  JSON.parse(content)
41
48
  end
42
49
 
50
+ def transaction(read_only=false, &block)
51
+ @semaphore.synchronize {
52
+ # access shared resource
53
+ super
54
+ }
55
+ end
56
+
43
57
  # Dumps the whole store to hash
44
58
  # example:
45
59
  # store = JSONStore.new("my_file.json")
@@ -68,6 +82,4 @@ module Nutella
68
82
  end
69
83
 
70
84
 
71
- end
72
-
73
-
85
+ end
data/lib/nutella_lib.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'nutella_lib/core'
2
2
  require 'nutella_lib/net'
3
+ require 'nutella_lib/net_app'
3
4
  require 'nutella_lib/persist'
4
5
  require 'simple_mqtt_client/simple_mqtt_client'
5
6
 
@@ -12,15 +13,3 @@ require 'set'
12
13
  unless defined?(Nutella::NO_EXT)
13
14
  require 'nutella_lib/ext/kernel'
14
15
  end
15
-
16
- # Adding a convenience method to the string class
17
- # to test if it contains properly formatted JSON
18
- class String
19
- def is_json?
20
- begin
21
- !!JSON.parse(self)
22
- rescue
23
- false
24
- end
25
- end
26
- end
@@ -1,35 +1,45 @@
1
1
  require 'mqtt'
2
2
 
3
- # Simple asynchronous MQTT client
4
- # @author Alessandro Gnoli <tebemis@gmail.com>
3
+ # A simple MQTT client
5
4
  class SimpleMQTTClient
6
-
5
+
6
+ ## Private instance variables
7
+ # `@channels` is a hash that associates to each channels a set of callbacks
8
+ # `@client` low-level mqtt client
9
+ # `@thread` the packet reader thread
10
+
11
+
7
12
  # Creates a new MQTT client
8
13
  # @param [String] host the hostname of the MQTT broker we are connecting to
9
14
  # @param [String] client_id the **unique** client identifier
10
- def initialize(host, client_id=nil)
11
- @host = host
15
+ def initialize( host, client_id=nil )
12
16
  @channels = Hash.new
13
17
  @client = client_id.nil? ? MQTT::Client.connect(:host => host) : MQTT::Client.connect(host: host, client_id: client_id)
14
18
  @thread = Thread.new('mqtt') do
15
19
  @client.get do |channel, message|
20
+ # Execute all the appropriate callbacks:
21
+ # the ones specific to this channel with a single parameter (message)
22
+ # the ones associated to a wildcard channel, with two parameters (message and channel)
16
23
  cbs = get_callbacks channel
17
- # If there is no callback (cb=nil) do nothing, otherwise call the right callback:
18
- # single channel callback with one parameter, wildcard channel callback with two.
19
- unless cbs.nil?
20
- (@channels.has_key? channel) ? cbs.each { |cb| cb.call(message) } : cbs.each { |cb| cb.call(message, channel) }
24
+ begin
25
+ cbs.each { |cb| cb.parameters.length==1 ? cb.call(message) : cb.call(message, channel) }
26
+ rescue ArgumentError
27
+ STDERR.puts "The callback you passed for #{channel} has the #{$!}"
21
28
  end
22
29
  end
23
30
  end
24
31
  end
25
32
 
26
33
  # Subscribes to a channel and registers a callback
27
- # Single channel callbacks take only one parameter: the received message
28
- # Wildcard callbacks take two parameters: the received message and the channel the message was sent to
34
+ # Single channel callbacks take only one parameter: the received message.
35
+ # Wildcard callbacks take two parameters: the received message and the channel the message was sent to.
36
+ # It is possible to register multiple callbacks per channel. All of them will be executed whenever a message
37
+ # on that channel is received.
38
+ # Note that overlaps between channel-specific callbacks and wildcard-filters are allowed.
29
39
  # @param [String] channel the channel or filter we are subscribing to
30
40
  # @param [Proc] callback the callback that gets called
31
41
  # whenever a messages is received
32
- def subscribe(channel, callback)
42
+ def subscribe( channel, callback )
33
43
  if @channels.include?(channel)
34
44
  @channels[channel] << callback
35
45
  else
@@ -41,7 +51,7 @@ class SimpleMQTTClient
41
51
  # Un-subscribes a specific callback from a channel
42
52
  # @param [String] channel the channel we are un-subscribing from
43
53
  # @param [Proc] callback the specific callback we want to remove
44
- def unsubscribe(channel, callback)
54
+ def unsubscribe( channel, callback )
45
55
  if @channels.include? channel
46
56
  @channels[channel].delete(callback)
47
57
  end
@@ -51,14 +61,29 @@ class SimpleMQTTClient
51
61
  end
52
62
  end
53
63
 
54
- # Returns the a hash of all the channels this client is currently subscribed to
55
- # with relative callbacks
56
- # @return [Hash] the hash of all the channels this client is currently subscribed to and relative callbacks
64
+ # Publishes a message to a channel
65
+ # @param [String] channel the channel we are publishing to
66
+ # @param [String] message the message we are publishing
67
+ def publish( channel, message )
68
+ # Check we are not publishing to a wildcard channel
69
+ STDERR.puts 'Can\'t publish to a wildcard channel!' if is_channel_wildcard? channel
70
+ @client.publish(channel, message)
71
+ end
72
+
73
+ # Disconnects this simple MQTT client instance from the broker
74
+ def disconnect
75
+ @thread.exit
76
+ @client.disconnect
77
+ @channels.clear
78
+ end
79
+
80
+ # Returns a hash of all the channels this client is currently subscribed to with relative callbacks
81
+ # @return [Hash] all channels this client is currently subscribed to, and relative callbacks
57
82
  def get_subscribed_channels
58
83
  @channels
59
84
  end
60
85
 
61
- # Returns true
86
+ # Returns true if a channel is a wildcard channel
62
87
  # @return [Boolean] true if the channel is a wildcard channel. See MQTT specification for wildcard channels
63
88
  # {http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718106 here}
64
89
  # @param [String] channel the channel we are testing for wildcard
@@ -66,53 +91,48 @@ class SimpleMQTTClient
66
91
  channel.include?('#') || channel.include?('+')
67
92
  end
68
93
 
69
- # Publishes a message to a channel
70
- # @param [String] channel the channel we are publishing to
71
- # @param [String] message the message we are publishing
72
- def publish(channel, message)
73
- @client.publish(channel, message)
74
- end
94
+ private
75
95
 
76
- # Disconnects this simple MQTT client instance from the broker
77
- def disconnect
78
- @client.disconnect
96
+ # Gets all the callbacks associated to a channel
97
+ def get_callbacks( channel )
98
+ cbs = Array.new
99
+ # First, fetch all the channel-specific callbacks...
100
+ cbs.concat @channels[channel] if @channels.has_key? channel
101
+ # ...then fetch the callbacks matching all wildcard-filters
102
+ cbs.concat wildcard_callbacks channel
103
+ cbs
79
104
  end
80
105
 
81
- private
82
-
83
- # Gets the right callback associated to a channel
84
- # Specific callback gets precedence over wildcards
85
- def get_callbacks(channel)
86
- # First try to see if a callback for the exact channel exists
87
- return @channels[channel] if @channels.has_key? channel
88
- # If it doesn't then let's try to find a wildcard match
89
- pattern = wildcard_match channel
90
- return @channels[pattern] unless pattern.nil?
91
- # If there's no exact match or wildcard we have to return nil
92
- nil
106
+ # Gets all wildcard callbacks associated to a channel
107
+ # Among the filters we are subscribed to, which ones match the channel?
108
+ def wildcard_callbacks( channel )
109
+ # First select all filters
110
+ filters = @channels.keys.select { |ch| is_channel_wildcard? ch}
111
+ # Then select filters that match channel
112
+ matching_filters = filters.select { |filter| matches_wildcard_pattern(channel, filter) }
113
+ # Add all callbacks that are associated to matching filters
114
+ cbs = Array.new
115
+ matching_filters.each { |ch| cbs.concat @channels[ch] }
116
+ cbs
93
117
  end
94
118
 
95
- # Returns the wildcard pattern, among the ones we subscribed to, that matches the channel.
96
- # This IGNORES exact matches!!!
97
- # It returns nil if the channel doesn't match any of the channels/filers we are subscribed to.
98
- def wildcard_match(channel)
99
- @channels.keys.each do |pattern|
100
- return pattern if matches_generic_pattern(channel, pattern)
101
- end
102
- # If we go through the whole list of channels and there is no generic pattern then return nil
103
- nil
119
+ # Returns true if the string matches a pattern
120
+ # See http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718107
121
+ # for a formal description of the rules
122
+ def matches_wildcard_pattern(str, pattern)
123
+ # First we need to build a regex out of the pattern
124
+ regex = build_regex_from_pattern pattern
125
+ # Then we check if the regex matches the string
126
+ !!(regex =~ str)
104
127
  end
105
128
 
106
- # Returns true if the string matches a pattern (including the exact pattern)
107
- def matches_generic_pattern(str, pattern)
108
- # If multi-level wildcard is the only character in pattern, then any string will match
109
- return true if pattern=='#'
110
- # Handle all other multi-level wildcards
111
- p_wo_wildcard = pattern[0..-2]
112
- str_wo_details = str[0..pattern.length-2]
113
- return true if pattern[-1, 1]=='#' && p_wo_wildcard==str_wo_details
114
- # TODO Handle single-level wildcards (+)
115
- false
129
+ # Escape '/'
130
+ # Substitute '+' for '[^"\/"]+' (a string of one or more characters that is are not '/')
131
+ # Substitute '/#' with '.*' (a string of zero or more characters)
132
+ # Substitute '#' for '.*' (a string of zero or more characters)
133
+ def build_regex_from_pattern( pattern )
134
+ regex_str = pattern.gsub('/','\\/').gsub('+','[^"\/"]+').gsub('\/#','.*').gsub('#','.*')
135
+ Regexp.new regex_str
116
136
  end
117
137
 
118
138
  end
data/nutella_lib.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: nutella_lib 0.3.1 ruby lib
5
+ # stub: nutella_lib 0.4.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "nutella_lib"
9
- s.version = "0.3.1"
9
+ s.version = "0.4.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Alessandro Gnoli"]
14
- s.date = "2015-02-17"
14
+ s.date = "2015-03-13"
15
15
  s.description = "Implements the nutella protocol and exposes it natively to ruby developers"
16
16
  s.email = "tebemis@gmail.com"
17
17
  s.extra_rdoc_files = [
@@ -30,17 +30,19 @@ Gem::Specification.new do |s|
30
30
  "lib/nutella_lib/core.rb",
31
31
  "lib/nutella_lib/ext/kernel.rb",
32
32
  "lib/nutella_lib/net.rb",
33
+ "lib/nutella_lib/net_app.rb",
33
34
  "lib/nutella_lib/noext.rb",
34
35
  "lib/nutella_lib/persist.rb",
35
36
  "lib/simple_mqtt_client/simple_mqtt_client.rb",
36
37
  "nutella_lib.gemspec",
37
38
  "test/helper.rb",
38
- "test/test_nutella_lib.rb",
39
+ "test/test_nutella_net.rb",
40
+ "test/test_nutella_net_app.rb",
39
41
  "test/test_simple_mqtt_client.rb"
40
42
  ]
41
43
  s.homepage = "https://github.com/nutella-framework/nutella_lib.rb"
42
44
  s.licenses = ["MIT"]
43
- s.rubygems_version = "2.2.2"
45
+ s.rubygems_version = "2.4.3"
44
46
  s.summary = "nutella protocol library for ruby"
45
47
 
46
48
  if s.respond_to? :specification_version then
@@ -0,0 +1,79 @@
1
+ require 'helper'
2
+
3
+ class TestNutellaNet < MiniTest::Test
4
+
5
+
6
+ # def test_send_receive
7
+ # nutella.init('localhost', 'my_app_id', 'my_run_id' , 'my_component_id')
8
+ # cb_executed = false
9
+ # cb = lambda do |message, from|
10
+ # cb_executed = true
11
+ # puts "Received message from #{from['component_id']}/#{from['resource_id']}. Message: #{message}"
12
+ # end
13
+ # nutella.net.subscribe('demo0', cb)
14
+ # sleep 1
15
+ # nutella.net.publish('demo0', 'test_message')
16
+ # # Make sure we wait for the message to be delivered
17
+ # sleep 1
18
+ # assert cb_executed
19
+ # end
20
+ #
21
+ #
22
+ # def test_send_receive_wildcard
23
+ # cb_executed = false
24
+ # nutella.init('localhost', 'my_app_id', 'my_run_id' , 'my_component_id')
25
+ # nutella.set_resource_id 'my_resource_id_1'
26
+ # cb = lambda do |message, channel, from|
27
+ # cb_executed = true
28
+ # puts "Received message on #{channel} from #{from['component_id']}/#{from['resource_id']}. Message: #{message}"
29
+ # end
30
+ # nutella.net.subscribe('demo1/#', cb)
31
+ # sleep 1
32
+ # nutella.net.publish('demo1/demo', 'test_message')
33
+ # # Make sure we wait for the message to be delivered
34
+ # sleep 1
35
+ # assert cb_executed
36
+ # end
37
+ #
38
+ #
39
+ # def test_multiple_subscriptions
40
+ # nutella.init('localhost', 'my_app_id', 'my_run_id' , 'my_component_id')
41
+ # nutella.set_resource_id 'my_resource_id_2'
42
+ # cb = lambda do |message, from|
43
+ # puts "Received message #{from['component_id']}/#{from['resource_id']}. Message: #{message}"
44
+ # end
45
+ # assert_raises RuntimeError do
46
+ # nutella.net.subscribe('demo2', cb)
47
+ # nutella.net.subscribe('demo2', cb)
48
+ # end
49
+ # nutella.net.unsubscribe('demo2')
50
+ # nutella.net.subscribe('demo2', cb)
51
+ # end
52
+ #
53
+ #
54
+ # def test_request_response
55
+ # nutella.init('localhost', 'my_app_id', 'my_run_id' , 'my_component_id')
56
+ # nutella.set_resource_id 'my_resource_id_3'
57
+ #
58
+ # nutella.net.subscribe('demo3', lambda do |message, from|
59
+ # puts "Received a message from #{from['component_id']}/#{from['resource_id']}. Message: #{message}"
60
+ # end)
61
+ #
62
+ # nutella.net.handle_requests( 'demo3', lambda do |message, from|
63
+ # puts "We received a request: message #{message}, from #{from['component_id']}/#{from['resource_id']}."
64
+ # #Then we are going to return some random JSON
65
+ # {my:'json'}
66
+ # end)
67
+ #
68
+ # response = nutella.net.sync_request( 'demo3', 'my request is a string' )
69
+ # assert_equal({'my' => 'json'}, response)
70
+ #
71
+ # nutella.net.async_request( 'demo3', 'my request is a string', lambda do |response|
72
+ # assert_equal({'my' => 'json'}, response)
73
+ # end)
74
+ #
75
+ # sleep(2)
76
+ # end
77
+
78
+
79
+ end