rbus 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.
Files changed (59) hide show
  1. data/CHANGELOG.txt +9 -0
  2. data/COPYING.txt +341 -0
  3. data/HACKING.txt +104 -0
  4. data/Manifest.txt +58 -0
  5. data/README.txt +17 -0
  6. data/Rakefile +97 -0
  7. data/TUTORIAL.txt +303 -0
  8. data/bin/rbus-send +165 -0
  9. data/examples/async_rb_and_notify.rb +56 -0
  10. data/examples/async_rb_loop.rb +52 -0
  11. data/examples/glib_async_rb_loop.rb +57 -0
  12. data/examples/glib_rhythmbox.rb +54 -0
  13. data/examples/hal_device_info.rb +54 -0
  14. data/examples/listnames.rb +37 -0
  15. data/examples/network_manager_get_properties.rb +50 -0
  16. data/examples/notification_bubble.rb +46 -0
  17. data/examples/rhythmbox_print_playing_uri.rb +41 -0
  18. data/examples/rhythmbox_signal_print_playing.rb +54 -0
  19. data/examples/rhythmbox_start_service.rb +39 -0
  20. data/examples/rhythmbox_toggle_playing.rb +46 -0
  21. data/lib/rbus.rb +25 -0
  22. data/lib/rbus/auth/auth.rb +53 -0
  23. data/lib/rbus/auth/dbus_cookie_sha1.rb +66 -0
  24. data/lib/rbus/auth/dummy.rb +37 -0
  25. data/lib/rbus/auth/external.rb +34 -0
  26. data/lib/rbus/auth/state_machine.rb +168 -0
  27. data/lib/rbus/bus/bus.rb +101 -0
  28. data/lib/rbus/bus/proxy.rb +137 -0
  29. data/lib/rbus/bus/transport.rb +125 -0
  30. data/lib/rbus/default.rb +29 -0
  31. data/lib/rbus/etc/exception.rb +44 -0
  32. data/lib/rbus/etc/log.rb +100 -0
  33. data/lib/rbus/etc/types.rb +56 -0
  34. data/lib/rbus/etc/version.rb +34 -0
  35. data/lib/rbus/glib.rb +29 -0
  36. data/lib/rbus/mainloop/glib.rb +77 -0
  37. data/lib/rbus/mainloop/mainloop.rb +84 -0
  38. data/lib/rbus/mainloop/observers.rb +149 -0
  39. data/lib/rbus/mainloop/thread.rb +78 -0
  40. data/lib/rbus/message/constants.rb +51 -0
  41. data/lib/rbus/message/marshal.rb +139 -0
  42. data/lib/rbus/message/message.rb +110 -0
  43. data/lib/rbus/message/reader.rb +108 -0
  44. data/lib/rbus/message/serial_generator.rb +48 -0
  45. data/lib/rbus/message/unmarshal.rb +171 -0
  46. data/lib/rbus/message/writer.rb +69 -0
  47. data/setup.rb +1608 -0
  48. data/spec/auth_spec.rb +123 -0
  49. data/spec/bus_spec.rb +178 -0
  50. data/spec/helpers/bus_mocks.rb +64 -0
  51. data/spec/helpers/spec_helper.rb +24 -0
  52. data/spec/mainloop_spec.rb +74 -0
  53. data/spec/marshal_spec.rb +91 -0
  54. data/spec/message_spec.rb +61 -0
  55. data/spec/observers_spec.rb +28 -0
  56. data/spec/proxy_spec.rb +120 -0
  57. data/spec/transport_spec.rb +187 -0
  58. data/spec/unmarshal_spec.rb +186 -0
  59. metadata +118 -0
@@ -0,0 +1,58 @@
1
+ CHANGELOG.txt
2
+ COPYING.txt
3
+ HACKING.txt
4
+ Manifest.txt
5
+ README.txt
6
+ Rakefile
7
+ TUTORIAL.txt
8
+ bin/rbus-send
9
+ examples/async_rb_and_notify.rb
10
+ examples/async_rb_loop.rb
11
+ examples/glib_async_rb_loop.rb
12
+ examples/glib_rhythmbox.rb
13
+ examples/hal_device_info.rb
14
+ examples/listnames.rb
15
+ examples/network_manager_get_properties.rb
16
+ examples/notification_bubble.rb
17
+ examples/rhythmbox_print_playing_uri.rb
18
+ examples/rhythmbox_signal_print_playing.rb
19
+ examples/rhythmbox_start_service.rb
20
+ examples/rhythmbox_toggle_playing.rb
21
+ lib/rbus.rb
22
+ lib/rbus/auth/auth.rb
23
+ lib/rbus/auth/dbus_cookie_sha1.rb
24
+ lib/rbus/auth/dummy.rb
25
+ lib/rbus/auth/external.rb
26
+ lib/rbus/auth/state_machine.rb
27
+ lib/rbus/bus/bus.rb
28
+ lib/rbus/bus/proxy.rb
29
+ lib/rbus/bus/transport.rb
30
+ lib/rbus/default.rb
31
+ lib/rbus/etc/exception.rb
32
+ lib/rbus/etc/log.rb
33
+ lib/rbus/etc/types.rb
34
+ lib/rbus/etc/version.rb
35
+ lib/rbus/glib.rb
36
+ lib/rbus/mainloop/glib.rb
37
+ lib/rbus/mainloop/mainloop.rb
38
+ lib/rbus/mainloop/observers.rb
39
+ lib/rbus/mainloop/thread.rb
40
+ lib/rbus/message/constants.rb
41
+ lib/rbus/message/marshal.rb
42
+ lib/rbus/message/message.rb
43
+ lib/rbus/message/reader.rb
44
+ lib/rbus/message/serial_generator.rb
45
+ lib/rbus/message/unmarshal.rb
46
+ lib/rbus/message/writer.rb
47
+ setup.rb
48
+ spec/auth_spec.rb
49
+ spec/bus_spec.rb
50
+ spec/helpers/bus_mocks.rb
51
+ spec/helpers/spec_helper.rb
52
+ spec/mainloop_spec.rb
53
+ spec/marshal_spec.rb
54
+ spec/message_spec.rb
55
+ spec/observers_spec.rb
56
+ spec/proxy_spec.rb
57
+ spec/transport_spec.rb
58
+ spec/unmarshal_spec.rb
@@ -0,0 +1,17 @@
1
+ = README for R-Bus
2
+
3
+ R-Bus is a native implementation of the D-Bus protocol, with these goals in mind:
4
+
5
+ * Ruby + standard library is the only dependencies
6
+ * A Rubyish API, approach and way of doing things
7
+ * Complete client functionality
8
+ * A comprehensive test suite.
9
+
10
+ It's possible that server functionality will be added later, but for now the
11
+ goal is to talk to existing D-Bus servers.
12
+
13
+ == See also
14
+
15
+ * {TUTORIAL}[link:files/TUTORIAL_txt.html].
16
+ * {HACKING}[link:files/HACKING_txt.html] for some info on how to help the
17
+ development.
@@ -0,0 +1,97 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'fileutils'
10
+ require 'hoe'
11
+ include FileUtils
12
+ require File.join(File.dirname(__FILE__), 'lib', 'rbus', 'etc', 'version')
13
+
14
+ AUTHOR = "Kristoffer Lundén" # can also be an array of Authors
15
+ EMAIL = "kristoffer.lunden@gmail.com"
16
+ DESCRIPTION = "A native implementation of the D-Bus protocol."
17
+ GEM_NAME = "rbus" # what ppl will type to install your gem
18
+ RUBYFORGE_PROJECT = "rbus" # The unix name for your project
19
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
20
+
21
+
22
+ NAME = "rbus"
23
+ REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
24
+ VERS = ENV['VERSION'] || (RBus::VERSION::STRING + (REV ? ".#{REV}" : ""))
25
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
26
+ RDOC_OPTS = ['--quiet', '--title', "R-Bus documentation",
27
+ "--opname", "index.html",
28
+ "--line-numbers",
29
+ "--main", "README.txt",
30
+ "--charset", "utf-8",
31
+ "--inline-source"]
32
+
33
+ class Hoe
34
+ def extra_deps
35
+ @extra_deps.reject { |x| Array(x).first == 'hoe' }
36
+ end
37
+ end
38
+
39
+ # Generate all the Rake tasks
40
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
41
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
42
+ p.author = AUTHOR
43
+ p.description = DESCRIPTION
44
+ p.email = EMAIL
45
+ p.summary = DESCRIPTION
46
+ p.url = HOMEPATH
47
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
48
+ p.test_globs = ["test/**/*_test.rb"]
49
+ p.clean_globs = CLEAN #An array of file patterns to delete on clean.
50
+ p.spec_extras = {
51
+ :rdoc_options => RDOC_OPTS,
52
+ :extra_rdoc_files => ['README.txt','TUTORIAL.txt','HACKING.txt','COPYING.txt','CHANGELOG.txt','bin/rbus-send']
53
+ }
54
+ # == Optional
55
+ #p.changes - A description of the release's latest changes.
56
+ #p.extra_deps - An array of rubygem dependencies.
57
+ #p.spec_extras - A hash of extra values to set in the gemspec.
58
+ end
59
+
60
+ desc "Find all TODO's"
61
+ task :todo do
62
+ FileList['**/*.rb'].egrep(/TODO/)
63
+ end
64
+
65
+ desc "Insert GPL into all source files"
66
+ task :GPL do
67
+ gpl = File.readlines('GPL_header.txt')
68
+ FileList['**/*.rb'].each do |filename|
69
+ File.open(filename, 'r+') do |file|
70
+ lines = file.readlines
71
+ # Skip shebang line
72
+ i = (lines[0].index('#!') == 0) ? 1 : 0
73
+ # Already have header?
74
+ next if lines[i].index('#--') == 0
75
+
76
+ puts "Liberating #{filename}"
77
+
78
+ file.pos = 0
79
+ file.print lines.insert(i, gpl).flatten
80
+ file.truncate(file.pos)
81
+ end
82
+ end
83
+ end
84
+
85
+ require 'spec/rake/spectask'
86
+
87
+ desc "Run all specs"
88
+ Spec::Rake::SpecTask.new('spec') do |t|
89
+ t.spec_files = FileList['spec/**/*.rb']
90
+ end
91
+
92
+ desc "Run all specs with RCov"
93
+ Spec::Rake::SpecTask.new('rcov') do |t|
94
+ t.spec_files = FileList['spec/**/*.rb']
95
+ t.rcov_dir = 'spec/coverage'
96
+ t.rcov = true
97
+ end
@@ -0,0 +1,303 @@
1
+ = TUTORIAL
2
+
3
+ Kristoffer Lundén <kristoffer.lunden@gmail.com>
4
+
5
+ <em>This is a work in progress. Please also see the examples for more info
6
+ on how to use this library.</em>
7
+
8
+ == Definitions
9
+
10
+ [D-Bus] is the protocol, see http://dbus.freedesktop.org
11
+ [R-Bus] is a native implementation of the D-Bus protocol, written in Ruby.
12
+
13
+ == Connecting to a Bus
14
+
15
+ To communicate via D-Bus, you need to connect to a bus daemon, that will forward
16
+ the messages between connected application. You do this in R-Bus by creating an
17
+ instance of RBus::Bus, which represents this connection.
18
+
19
+ On most systems, you will have a Session Bus, that is per login, and a System
20
+ Bus, that is global for the machine. For special needs and testing, you may also
21
+ want to connect to a non-standard bus.
22
+
23
+ The Session and System Bus objects are Singleton objects, meaning that you
24
+ always get the same object and connection even if you get it several times.
25
+ Apart from this, you can communicate with as many buses as you wish in the same
26
+ application.
27
+
28
+ There are three convenience methods
29
+ available for making these connections:
30
+
31
+ === Connecting to the Session Bus
32
+
33
+ require 'rbus'
34
+ bus = RBus.session_bus
35
+
36
+ This connects to the Session Bus, or returns the already active connection.
37
+
38
+ === Connecting to the System Bus
39
+
40
+ require 'rbus'
41
+ bus = RBus.system_bus
42
+
43
+ This connects to the System Bus, or returns the already active connection.
44
+
45
+ === Connecting to a specified address
46
+
47
+ require 'rbus'
48
+ tcp_bus = RBus.get_bus('tcp:host=192.168.0.1,port=6666')
49
+ unix_bus = RBus.get_bus('unix:path=/tmp/dbus-test')
50
+ abstract_bus = RBus.get_bus('unix:abstract=/tmp/dbus-lafi7lJhll,guid=a190e4459a37da0c62586ac4de5fa400')
51
+
52
+ This connects to the given address, which is given in the same form as
53
+ reported by the Bus Daemon.
54
+
55
+ == Remote objects
56
+
57
+ D-Bus applications can export objects that other applications can call methods
58
+ on, and receive signals from. A remote object has a <em>well-known name</em>, which
59
+ represents the exporting application, and a <em>object path</em>, which represents the
60
+ object itself.
61
+
62
+ Examples of well-known names are <tt>org.freedesktop.NetworkManager</tt> and
63
+ <tt>org.gnome.Rhythmbox</tt>. Examples of object paths are
64
+ <tt>/org/freedesktop/NetworkManager/Devices/eth0</tt> and
65
+ <tt>/org/gnome/Rhythmbox/Player</tt>.
66
+
67
+ Later in this document we will see how to export
68
+ our own objects, but first we will look at interacting with other objects.
69
+
70
+ == Proxy objects
71
+
72
+ To communicate with the remote object, we need a local representation of it, a
73
+ Proxy object, that we can call methods on and receive signals from. We can
74
+ obtain a Proxy from the Bus via the method +get_object+, which takes a
75
+ well-known name and an object path as describe above.
76
+
77
+ # get the Player object from Rhythmbox:
78
+ rb_player = session_bus.get_object('org.gnome.Rhythmbox', '/org/gnome/Rhythmbox/Player')
79
+
80
+ == Method calls
81
+
82
+ To make method calls on the remote object, we simply call the exact same
83
+ method on the Proxy object. Any method not defined in the Proxy class will be
84
+ forwarded to the other side of the wire. To avoid any collisions with built-in
85
+ methods, the Proxy class is almost completely emptied. Also, to avoid almost any
86
+ possible clash, the few methods that operate on Proxy objects end in '!', such
87
+ as <tt>method!</tt> and <tt>interface!</tt>. It may not be not 100% idiomatic Ruby, so better
88
+ suggestions are welcome. :)
89
+
90
+ Some examples of method calls:
91
+
92
+ # get the uri of the playing song
93
+ uri = rb_player.getPlayingUri()
94
+
95
+ # get information on a HAL device
96
+ info = device.GetProperty('info.product')
97
+
98
+ # pop up a notification bubble
99
+ notify.Notify('R-Bus',0,'info','R-Bus Notification',
100
+ 'A test example to see that everything works.',[],{},-1)
101
+
102
+ == Signatures, introspection and data types
103
+
104
+ D-Bus
105
+ {defines a number of standard data types}[http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-signatures]
106
+ that can be used to send and receive data between applications.
107
+ R-Bus is able to use Rubys standard types for the most part, as long as there is
108
+ a signature available for the conversion. When a new Proxy object is created,
109
+ R-Bus will ask the remote application for a list of methods and signatures via
110
+ the standard methos +Introspect+. If the application provides such a list,
111
+ everything should just work. :)
112
+
113
+ If the introspection fails, the Proxy object may not know how to call the
114
+ method. In this case you can add the method manually with +method!+:
115
+
116
+ obj.method!(:MethodName, 'as')
117
+
118
+ This would define a method +MethodName+ that takes an Array of Strings.
119
+
120
+ == Interfaces
121
+
122
+ D-Bus uses interfaces to provide namespaces for methods. An interface groups
123
+ methods and signals under a common name, which is a dot-separated name starting
124
+ with a reversed domain name. They are in many cases
125
+ optional, only if an object have multiple methods with the same name, an
126
+ interface is actually needed.
127
+
128
+ You can use the <tt>interface!</tt> method to set an interface on an object.
129
+
130
+ Some examples:
131
+
132
+ rb_player.interface!('org.gnome.Rhythmbox.Player')
133
+ puts rb_player.getPlayingUri()
134
+
135
+ eth0.interface!('org.freedesktop.NetworkManager.Devices')
136
+ eth0.getProperties()
137
+
138
+ <tt>interface!</tt> also returns the object itself, so commands can be chained:
139
+
140
+ rb_player.interface!('org.gnome.Rhythmbox.Player').getPlayingUri()
141
+
142
+ If you want to set an interface temporarily, you can use a block, which will
143
+ get a copy of the object with a temporary interface:
144
+
145
+ rb_player.interface!('org.gnome.Rhythmbox.SomethingElse')
146
+ rb_player.interface!('org.gnome.Rhythmbox.Player') do |p|
147
+ p.getPlayingUri()
148
+ end
149
+ # ...here the interface is +SomethingElse+ again.
150
+
151
+ == Asynchronous method calls
152
+
153
+ To make an asynchronous call, simply attach a block to the method call instead
154
+ of waiting for a reply.
155
+
156
+ # from async_rb_loop.rb / glib_async_rb_loop.rb
157
+ 10.times do |i|
158
+ rb.getPlayingUri() do |uri|
159
+ sleep(rand(2))
160
+ puts "#{i}: #{uri}"
161
+ end
162
+ end
163
+
164
+ With the default version of R-Bus, that uses threads, the above code will return
165
+ a list of uris in random order.
166
+
167
+ With the GLib version, the mainloop needs to be started (see
168
+ Event loop) and the calls will be returned in the order they were queued.
169
+ You can use the threaded version with GLib/Gtk+ applications if you
170
+ need the extra flexibility though, however, mostly you don't. :)
171
+
172
+ == Receiving signals
173
+
174
+ Remote objects can notify listeners of events via signals. To listen to a
175
+ signal on the remote object, call the <tt>connect!</tt> method with a block to
176
+ process the signal:
177
+
178
+ # from rhythmbox_signal_print_playing.rb:
179
+ # get notifications from Rhythmbox when the song changes
180
+ rb_player.connect!(:playingUriChanged) { |uri|
181
+ properties = rb_shell.getSongProperties(uri)
182
+ duration = Time.at(properties['duration'].to_i).strftime("%M:%S")
183
+ artist = properties['artist']
184
+ title = properties['title']
185
+ puts "Now playing: #{artist} - #{title} (#{duration})"
186
+ printf "File-size: %.2f MB\n", properties['file-size'].to_f/1024/1024
187
+ }
188
+
189
+ RBus.mainloop
190
+
191
+ The block will receive whatever data the signal sends.
192
+
193
+ <tt>connect!</tt> takes the name of the signal, and an optional Hash of
194
+ extra match rules. See
195
+ {Match rules}[http://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules]
196
+ in the D-Bus specification for a list of rules.
197
+
198
+ obj.connect!(:SignalName, {:arg0 => 'Foo', :arg1 => 'Bar'}) do |*args|
199
+ # ...
200
+ end
201
+
202
+ To receive signals, you need a mainloop. See the next section for a few more
203
+ details.
204
+
205
+ == Event loop
206
+
207
+ To be able to receive signals and answers to asynchronous calls, an event loop
208
+ needs to be running and listening for them. In the dfault version, using
209
+ <tt>require 'rbus'</tt>, such a loop is always started automatically and in
210
+ most cases nothing more neds to be done. However, a word of caution: as it uses
211
+ threads you need to be wary of the same kind of concurrency issues as with
212
+ any Ruby threads.
213
+
214
+ With the GLib loop, used with <tt>require 'rbus/glib'</tt>, you need to start
215
+ the loop. You can either use <tt>RBus.mainloop</tt> or <tt>Gtk.main</tt>. The
216
+ former simply calls the latter, but you can use it for compatibility with
217
+ other R-Bus mainloops.
218
+
219
+ You can use <tt>RBus.mainloop</tt> in the threaded version as well to just sit
220
+ idle and wait for signals and method replies.
221
+
222
+ == Exporting objects
223
+
224
+ <em>This section has yet to be written, and the functionality is
225
+ currently missing.</em>
226
+
227
+ == Starting Services
228
+
229
+ require 'rbus'
230
+ session_bus = RBus.session_bus()
231
+
232
+ # Start Rhythmbox
233
+ session_bus.StartServiceByName('org.gnome.Rhythmbox', 0)
234
+
235
+ == Extra utilities
236
+
237
+ === rbus-send
238
+
239
+ R-Bus comes with its own variant of <em>dbus-send</em>, called
240
+ <em>{rbus-send}[link:files/bin/rbus-send.html]</em>, which should be pretty much compatible, except for these
241
+ known issues:
242
+
243
+ * There is no <tt>--reply-timeout</tt>. Trivial to fix in seconds, though
244
+ milli-seconds may not be.
245
+ * Currently dumps the any server answer in YAML[http://www.yaml.org/] instead of
246
+ the dbus-send format.
247
+
248
+ The invokation looks like this:
249
+
250
+ rbus-send <destination object path> <message name> [options] [arguments ...]
251
+ --dest=NAME Specify the name of the connection to receive the message.
252
+ --print-reply Block for a reply to the message sent, and print any reply received.
253
+ --system Send to the system message bus.
254
+ --session Send to the session message bus. (This is the default.)
255
+ --type=TYPE Specify "method_call" or "signal" (defaults to "signal").
256
+
257
+ The object path and the name of the message to send must always be specified.
258
+ Any following non-options are message arguments. These are given as
259
+ type-specified values and may include containers (arrays, dicts, and variants)
260
+ as follows:
261
+
262
+ <arguments> ::= <item> | <container> [ <item> | <container>...]
263
+ <item> ::= <type>:<value>
264
+ <container> ::= <array> | <dict> | <variant>
265
+ <array> ::= array:<type>:<value>[,<value>...]
266
+ <dict> ::= dict:<type>:<type>:<key>,<value>[,<key>,<value>...]
267
+ <variant> ::= variant:<type>:<value>
268
+ <type> ::= string | int16 | uint16 | int32 | uint32 | int64 | uint64 | double | byte | boolean | objpath
269
+
270
+ Some sample queries:
271
+
272
+ # List all HAL properties:
273
+ $ rbus-send --system --print-reply --dest=org.freedesktop.Hal \
274
+ /org/freedesktop/Hal/devices/computer \
275
+ org.freedesktop.Hal.Device.GetAllProperties
276
+
277
+ # Print URI of current song in Rhythmbox:
278
+ $ rbus-send --dest='org.gnome.Rhythmbox' --print-reply \
279
+ /org/gnome/Rhythmbox/Player org.gnome.Rhythmbox.Player.getPlayingUri
280
+
281
+ # Toggle play/pause in Rhytmbox:
282
+ $ rbus-send --dest='org.gnome.Rhythmbox' /org/gnome/Rhythmbox/Player \
283
+ org.gnome.Rhythmbox.Player.playPause boolean:true
284
+
285
+ # Find services possible to start on the Bus:
286
+ $ rbus-send --print-reply --dest=org.freedesktop.DBus \
287
+ /org/freedesktop/DBus org.freedesktop.DBus.ListActivatableNames
288
+
289
+ # Start such a service (Rhythmbox in this case):
290
+ $ rbus-send --type=method_call --dest=org.freedesktop.DBus \
291
+ /org/freedesktop/DBus org.freedesktop.DBus.StartServiceByName \
292
+ string:'org.gnome.Rhythmbox' uint32:0
293
+
294
+ # Pop up a notification bubble:
295
+ $ rbus-send --dest=org.freedesktop.Notifications \
296
+ /org/freedesktop/Notifications org.freedesktop.Notifications.Notify \
297
+ string:'R-Bus' uint32:0 string:'info' string:'R-Bus Notification' \
298
+ string:'A test example to see that everything works.' \
299
+ array:string: dict:string:variant: int32:-1
300
+
301
+ If you have not installed the library, you can use
302
+ <tt>ruby -Ilib bin/rbus-send [invocation]</tt> from the root of the project
303
+ to try it out.