ratchet 0.3.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 (49) hide show
  1. data/gem_bin/ratchet +23 -0
  2. data/lib/ratchet.rb +613 -0
  3. data/lib/ratchet/aliases.rb +106 -0
  4. data/lib/ratchet/bufferparser.rb +409 -0
  5. data/lib/ratchet/commandbuffer.rb +66 -0
  6. data/lib/ratchet/commandparser.rb +668 -0
  7. data/lib/ratchet/configuration.rb +278 -0
  8. data/lib/ratchet/connections.rb +403 -0
  9. data/lib/ratchet/constants.rb +111 -0
  10. data/lib/ratchet/contrib/instance_exec.rb +21 -0
  11. data/lib/ratchet/eventparser.rb +486 -0
  12. data/lib/ratchet/gtk/bufferlistview.rb +514 -0
  13. data/lib/ratchet/gtk/bufferview.rb +167 -0
  14. data/lib/ratchet/gtk/configwindow.rb +229 -0
  15. data/lib/ratchet/gtk/connectionwindow.rb +218 -0
  16. data/lib/ratchet/gtk/keybinding.rb +356 -0
  17. data/lib/ratchet/gtk/linkwindow.rb +137 -0
  18. data/lib/ratchet/gtk/mainwindow.rb +504 -0
  19. data/lib/ratchet/gtk/networkpresenceconf.rb +567 -0
  20. data/lib/ratchet/gtk/pluginconfig.rb +94 -0
  21. data/lib/ratchet/gtk/pluginwindow.rb +146 -0
  22. data/lib/ratchet/gtk/userlistview.rb +161 -0
  23. data/lib/ratchet/help.rb +64 -0
  24. data/lib/ratchet/items.rb +271 -0
  25. data/lib/ratchet/lines.rb +63 -0
  26. data/lib/ratchet/networks.rb +652 -0
  27. data/lib/ratchet/plugins.rb +616 -0
  28. data/lib/ratchet/queue.rb +47 -0
  29. data/lib/ratchet/ratchet-version.rb +21 -0
  30. data/lib/ratchet/replies.rb +134 -0
  31. data/lib/ratchet/replyparser.rb +441 -0
  32. data/lib/ratchet/tabcomplete.rb +98 -0
  33. data/lib/ratchet/users.rb +237 -0
  34. data/lib/ratchet/utils.rb +178 -0
  35. data/share/defaults.yaml +169 -0
  36. data/share/glade/config.glade +2634 -0
  37. data/share/glade/connect.glade +950 -0
  38. data/share/glade/keybindings.glade +109 -0
  39. data/share/glade/linkwindow.glade +188 -0
  40. data/share/glade/mainwindow.glade +335 -0
  41. data/share/glade/network-presences.glade +1373 -0
  42. data/share/glade/pluginconf.glade +97 -0
  43. data/share/glade/plugins.glade +360 -0
  44. data/share/plugins/colorewrite.rb +193 -0
  45. data/share/plugins/highlighter.rb +115 -0
  46. data/share/plugins/mpdplay.rb +123 -0
  47. data/share/plugins/numberswitcher.rb +30 -0
  48. data/share/plugins/sysinfo.rb +82 -0
  49. metadata +96 -0
@@ -0,0 +1,278 @@
1
+ =begin
2
+ This file is part of the Ratchet project, a client for Icecap.
3
+ Copyright (C) 2005-6 Andrew Thompson
4
+
5
+ This program is free software; you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the Free Software
17
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
+ =end
19
+
20
+ #### configuration.rb ####
21
+ # This file contains the Connection class, a class that contains ratchet's
22
+ # config, it provides default values (loaded from a yaml file), it also
23
+ # tracks changes and allows reversion to defaults. Additionally it encodes/decodes
24
+ # data for storage in icecap's config storage.
25
+ ####
26
+
27
+ module Ratchet
28
+ class Configuration
29
+ attr_reader :values, :defaults
30
+ def initialize
31
+ #set some defaults... probably too soon be overriden by the user's config, but you gotta start somewhere :P
32
+ p = Pathname.new(__FILE__)
33
+ @values = YAML.load_file(DATADIR+'/defaults.yaml')
34
+
35
+ #store defaults
36
+ @defaults = duplicate_config
37
+
38
+ @statuscolors = [@values['defaultcolor'], @values['neweventcolor'], @values['newmessagecolor'], @values['highlightcolor']]
39
+
40
+ @oldvalues = {}
41
+ end
42
+
43
+ def dump
44
+ File.open(DATADIR+'/defaults.yaml', "w+") {|f| YAML.dump(@values, f)}
45
+ end
46
+
47
+ def gettabmodelconfig
48
+
49
+ if @values['tabsort'] == 'case sensitive'
50
+ sort = SENSITIVE
51
+ elsif @values['tabsort'] == 'case sensitive no hash'
52
+ sort = INSENSITIVE_NOHASH
53
+ elsif @values['tabsort'] == 'case sensitive no hash'
54
+ sort = SENSITIVE_NOHASH
55
+ else
56
+ sort = INSENSITIVE
57
+ end
58
+
59
+ if @values['tabstructure'] == 'flat'
60
+ structure = FLAT
61
+ else
62
+ structure = HIERARCHICAL
63
+ end
64
+
65
+ return [structure, sort]
66
+ end
67
+
68
+ #converts status into a color
69
+ def getstatuscolor(status)
70
+ if status.nil?
71
+ return @statuscolors[0]
72
+ end
73
+ return @statuscolors[status]
74
+ end
75
+
76
+ #retrieves a value
77
+ def [](value)
78
+ if @values[value]
79
+ return @values[value]
80
+ else
81
+ return false
82
+ end
83
+ end
84
+
85
+ def []=(key, value)
86
+ value = decode_value(value) if value.class == String
87
+ @values[key] = value
88
+ #update the color array on the off-chance we changed it :(
89
+ @statuscolors = [@values['defaultcolor'], @values['neweventcolor'], @values['newmessagecolor'], @values['highlightcolor']]
90
+ end
91
+
92
+ #send the config to irssi2
93
+ def changes
94
+ cmdstring = ''
95
+
96
+ configs = []
97
+
98
+ @values.each do |k, v|
99
+ value = encode_value(v)
100
+ if @oldvalues[k] != value or !@oldvalues[k]
101
+ key = 'rirc_'+k unless k['rirc_']
102
+
103
+ configs.push(key+'='+escape(value)) if k and value
104
+ puts k+" HAS changed"
105
+ puts v.inspect
106
+ else
107
+ end
108
+ end
109
+
110
+ if configs.length == 0
111
+ puts 'no changes'
112
+ return
113
+ else
114
+ cmdstring = 'config set;'+configs.join(';')
115
+ end
116
+
117
+ cmdstring
118
+ #$main.send_command('sendconfig', cmdstring)
119
+ end
120
+
121
+ #request the config from irssi2
122
+ #~ def get_config
123
+ #~ $main.send_command('getconfig', 'config get;*')
124
+ #~ while $main.replies['getconfig']
125
+ #~ sleep 1
126
+ #~ end
127
+ #~ end
128
+
129
+ #encode the values so they can be stored by irssi2
130
+ #this function recursively encodes arrays in hashes to handle n dimensional structures
131
+ def encode_value(value)
132
+ if value.class == Color
133
+ colors = value.to_a[0..2]
134
+ 'color{' + colors.map {|color| "#{color}" }.join(":")+'}'
135
+ elsif value.class == Array
136
+ 'array{' + value.map {|v| encode_value(v)}.join(":")+'}'
137
+ elsif value.class == Hash
138
+ 'hash{' + value.map {|k, v| encode_value(k) + ':' + encode_value(v) }.join("::")+'}'
139
+ elsif value =~ /^(color|array|hash)\:(\d+)\:(\d+)\:(\d+)$/
140
+ return value
141
+ elsif value.nil? #we don't want nil being encoded to "", this fux0rs arrays...
142
+ return 'nil'
143
+ elsif value.respond_to? :to_s #everythign else gets converted to a string
144
+ return value.to_s.gsub(':', '\,')
145
+ else #if we can't convert it to a string, don't store it
146
+ return nil
147
+ end
148
+ end
149
+
150
+ #decode values retrieved from irssi2
151
+ #recursively decodes arrays and hashes, gets a bit hairy though...
152
+ def decode_value(value)
153
+ value = unescape(value) if value
154
+ if value == 'true'
155
+ return true
156
+ elsif value == 'false'
157
+ return false
158
+ elsif value == 'nil'
159
+ return nil
160
+ else
161
+ if value.nil?
162
+ return nil
163
+ end
164
+ values = value.split('{', 2)
165
+ values[1].chomp!('}') if values[1]
166
+ if values[0] == 'array'
167
+ x = []
168
+ #~ values[1].split(':').each do |v|
169
+ #~ if v =~ /^(array|hash|color)\{/
170
+ #~ puts v
171
+ #~ end
172
+ #~ x.push(decode_value(v))
173
+ #~ end
174
+ string = values[1]
175
+ while string
176
+ #check if the next part of the source string is an object
177
+ if string =~ /^((?:array|hash|color)\{.+?\}(?:\:|$))/
178
+ match = $1
179
+ #trim the trailing : and decode it and then push it onto the array
180
+ x.push(decode_value($1.chomp(':')))
181
+ #remove the match from the source string and skip to the next loop iteration
182
+ string.slice!(match)
183
+ next
184
+ end
185
+ part, string = string.split(':', 2)
186
+ x.push(decode_value(part)) if part
187
+ end
188
+ x
189
+ elsif values[0] == 'hash'
190
+ x = {}
191
+ #~ values[1].split('::').each do |y|
192
+ #~ k, v = y.split(':')
193
+ #~ x[decode_value(k)] = decode_value(v)
194
+ #~ end
195
+ string = values[1]
196
+ while string
197
+ #check for an object
198
+ if string =~ /^((?:array|hash|color)\{.+?\}.+?(?:\:\:|\}$))/
199
+ #store the match in a variable so it doesn't get clobbered later on
200
+ match = $1
201
+ #heh, trim the trailing :: and split by the last :
202
+ key, value = $1.chomp('::').reverse.split(':', 2).map{|y| y.reverse}.reverse
203
+ #stick it in the hash
204
+ x[decode_value(key)] = decode_value(value)
205
+ #remove the match from the string
206
+ string.slice!(match)
207
+ next
208
+ end
209
+ part, string = string.split('::', 2)
210
+ if part
211
+ k, v = part.split(':', 2)
212
+ x[decode_value(k)] = decode_value(v)
213
+ end
214
+ end
215
+ x
216
+ elsif values[0] == 'color'
217
+ r, g, b = values[1].split(':').map {|x| x.to_i }
218
+ Color.new(r, g, b)
219
+ elsif value.numeric?
220
+ if value.include? '.'
221
+ value.to_f
222
+ else
223
+ value.to_i
224
+ end
225
+ else
226
+ value.gsub('\,', ':')
227
+ end
228
+ end
229
+ end
230
+
231
+ #parse the configs retrieved from irssi2
232
+ def parse_config(reply)
233
+ reply.lines.each do |line|
234
+ if line['key'] and line['value']
235
+ value = decode_value(line['value'])
236
+ @values[line['key'].sub('rirc_', '')] = value
237
+ #puts line['key'].sub('rirc_', '')+'=>'+value.to_s
238
+ end
239
+ end
240
+ create_config_snapshot
241
+ end
242
+
243
+ #create a copy of the config so we can compare for changes
244
+ def create_config_snapshot
245
+ @values.each do |k, v|
246
+ @oldvalues[k.dup] = encode_value(v)
247
+ end
248
+ end
249
+
250
+ def update_snapshot(hash)
251
+ hash.each{|k,v| hash[k] = encode_value(v)}
252
+ @oldvalues.merge!(hash)
253
+ end
254
+
255
+ def duplicate_config
256
+ vals = {}
257
+ @values.each do |k, v|
258
+ vals[k.dup] = decode_value(encode_value(v))
259
+ end
260
+ vals
261
+ end
262
+
263
+ def revert_to_defaults
264
+ @values = {}
265
+ @defaults.each do |k,v|
266
+ @values[k] = v
267
+ end
268
+ end
269
+
270
+ def get_pattern(name)
271
+ if @values[name].class == String
272
+ return @values[name].dup #don't escape_xml here
273
+ else
274
+ return ''
275
+ end
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,403 @@
1
+ =begin
2
+ This file is part of the Ratchet project, a client for Icecap.
3
+ Copyright (C) 2005-6 Andrew Thompson
4
+
5
+ This program is free software; you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the Free Software
17
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
+ =end
19
+
20
+ #### connections.rb ####
21
+ # This file contains the different classes for Connection objects
22
+ # which allow various methods of connection to icecap but present
23
+ # a unified interface
24
+ ####
25
+
26
+ module Ratchet
27
+ class Connection
28
+ def listen(object)
29
+ @listenthread = Thread.new do
30
+ loop do
31
+ begin
32
+ if res = select([@output], nil, nil, nil) and res[0]
33
+ line = res[0][0].gets
34
+ @main.parse_line(line.chomp) if line
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def send(data)
42
+ begin
43
+ @input.puts(data)
44
+ rescue Errno::EPIPE
45
+ puts 'Write error: '+$!
46
+ return false
47
+ end
48
+ return true
49
+ end
50
+ end
51
+ class SSHConnection < Connection
52
+ def initialize(main, settings, connectionwindow)
53
+ @main = main
54
+ require 'open3'
55
+ require 'expect'
56
+ @input = nil
57
+ @output = nil
58
+ @error = nil
59
+ cmdstring = 'setsid ssh '
60
+ cmdstring += '-l '+settings['username']+' ' if settings['username']
61
+ cmdstring += '-p '+settings['port']+' ' if settings['port']
62
+ cmdstring += settings['host']+' '+settings['binpath']
63
+ # puts cmdstring
64
+ @input, @output, @error = Open3.popen3(cmdstring)
65
+ #puts @output.gets
66
+
67
+ begin
68
+ @output.expect(/^\*;preauth;time=(\d+);$/) do |x, y|
69
+ connectionwindow.send_text('logged in')
70
+ @main.calculate_clock_drift(y)
71
+ end
72
+
73
+ rescue NoMethodError
74
+ connectionwindow.send_text('Something is borked, make sure sshd is running on selected host')
75
+ raise IOError, "one of the many things that could go wrong, has"
76
+ end
77
+
78
+ end
79
+
80
+ # def send(data)
81
+ # begin
82
+ # @input.puts(data)
83
+ # #~ rescue SystemCallError
84
+ # #~ puts 'Write error: '+$!
85
+ # #~ return false
86
+ # rescue Errno::EPIPE
87
+ # puts 'Write error: '+$!
88
+ # return false
89
+ # end
90
+ # return true
91
+ # end
92
+
93
+ # def listen(object)
94
+ # @listenthread = Thread.new do
95
+ # loop do
96
+ # begin
97
+ # while line = @output.gets
98
+ # #puts 'o: '+line
99
+ # object.parse_lines(line)
100
+ # end
101
+
102
+ # rescue Errno::EPIPE
103
+ # puts 'listen: closed stream, disconnecting '+$!
104
+ # close
105
+ # object.disconnect
106
+ # object.connect
107
+ # break
108
+ # end
109
+ # end
110
+ # end
111
+ # end
112
+
113
+ def close
114
+ @listenthread.kill if @listenthread
115
+ @input.close
116
+ @output.close
117
+ @error.close
118
+ end
119
+ end
120
+
121
+ class LocalConnection < Connection
122
+ #attr_reader :connected
123
+ def initialize(main, settings, connectionwindow)
124
+ require 'open3'
125
+ require 'expect'
126
+ @main = main
127
+ @input = nil
128
+ @output = nil
129
+ @error = nil
130
+ if settings['binpath'][0].chr == '/'
131
+ unless File.exists? settings['binpath']
132
+ raise IOError, "Cannot find binary at #{settings['binpath']}"
133
+ end
134
+ elsif settings['binpath'].include? '/'
135
+ path = Pathname.new(settings['binpath'])
136
+ unless path.file?
137
+ raise IOError, "Cannot find binary at #{path.realpath}"
138
+ end
139
+ else
140
+ exists = false
141
+ ENV['PATH'].split(':').each do |d| #: is not windows compatible, but that's not really relevant
142
+ # puts d
143
+ if File.exists? d+'/'+settings['binpath']
144
+ exists = true
145
+ break
146
+ end
147
+ end
148
+ unless exists
149
+ raise IOError, "Cannot find binary #{settings['binpath']} in $PATH"
150
+ end
151
+ end
152
+ @input, @output, @error = Open3.popen3(settings['binpath'])
153
+ begin
154
+ @output.expect(/^\*;preauth;time=(\d+);\n/) do |x, y|
155
+ connectionwindow.send_text('logged in')
156
+ @main.calculate_clock_drift(y)
157
+ end
158
+
159
+ rescue NoMethodError
160
+ connectionwindow.send_text('Something is borked')
161
+ raise IOError, "one of the many things that could go wrong, has"
162
+ end
163
+ end
164
+
165
+ # def send(data)
166
+ # begin
167
+ # @input.puts(data)
168
+ # rescue Errno::EPIPE
169
+ # puts 'Write error: '+$!
170
+ # return false
171
+ # end
172
+ # true
173
+ # end
174
+
175
+ # def listen(object)
176
+ # @listenthread = Thread.new do
177
+ # loop do
178
+ # puts 'bar'
179
+ # begin
180
+ # while line = @output.gets
181
+ #puts 'o: '+line
182
+ # object.parse_lines(line)
183
+ # end
184
+ # if res = select([@output], nil, nil, nil) and res[0]
185
+ # puts res[0].inspect
186
+ # puts res[0][0].read.class
187
+ # @main.parse_line(res[0][0].gets.chomp)
188
+
189
+ # #Thread.new do
190
+ # @main.parse_lines(res[0][0].readlines("\n"))
191
+ # #end
192
+ # else
193
+ # puts 'no IO'
194
+ # end
195
+ # rescue Errno::EPIPE
196
+ # puts 'listen: closed stream, disconnecting '+$!
197
+ # close
198
+ # object.disconnect
199
+ # object.connect
200
+ # break
201
+ # end
202
+ # end
203
+ # end
204
+ # end
205
+
206
+ def close
207
+ @listenthread.kill if @listenthread
208
+ @input.close
209
+ @output.close
210
+ @error.close
211
+ end
212
+ end
213
+
214
+ class NetSSHConnection < Connection
215
+ def initialize(main, settings, connectionwindow)
216
+ @main = main
217
+ begin
218
+ @session = Net::SSH.start(settings['host'], settings['username'], :auth_methods => %w(password), :port => settings['port'])
219
+
220
+ @input, @output, @error = @session.process.popen3(settings['binpath'])
221
+
222
+ rescue Errno::EHOSTUNREACH
223
+ raise(IOError, 'Could not connect to host')
224
+ rescue Net::SSH::AuthenticationFailed
225
+ raise(IOError, 'Authentication Failed')
226
+ rescue Errno::ECONNREFUSED
227
+ raise(IOError, 'Connection Refused')
228
+ end
229
+ sleep 2
230
+ if @error.data_available?
231
+ error = @error.read
232
+ raise(IOError, error, caller)
233
+ end
234
+
235
+ #@sshthread = Thread.new{
236
+ # @session.loop
237
+ #}
238
+ puts 'connected via ssh'
239
+ end
240
+
241
+ def send(data)
242
+ begin
243
+ @input.puts(data)
244
+ rescue Errno::EPIPE
245
+ puts 'Write error: '+$!
246
+ return false
247
+ end
248
+ return true
249
+ end
250
+
251
+ def listen(object)
252
+ @listenthread = Thread.start do
253
+ while true
254
+ begin
255
+ if @output.data_available?
256
+ out = @output.read
257
+ object.parse_lines(out)
258
+ end
259
+ sleep 0.01 #sleep a little, this seems to be important
260
+ rescue IOError
261
+ puts 'listen: closed stream, disconnecting '+$!
262
+ close
263
+ object.disconnect
264
+ object.connect
265
+ break
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ def close
272
+ @session.close if @session
273
+ @sshthread.kill if @sshthread
274
+ @input = nil
275
+ @output = nil
276
+ @error = nil
277
+ end
278
+
279
+ end
280
+
281
+
282
+ class UnixSockConnection < Connection
283
+ def initialize(settings, connectionwindow)
284
+ if File.exist?(settings['location'])
285
+ begin
286
+ @socket = UNIXSocket.open(settings['location'])
287
+ rescue
288
+ raise(IOError, 'Could not connect to socket')
289
+ end
290
+ connectionwindow.send_text('Connected via unix socket')
291
+ else
292
+ raise(IOError, 'Socket File does not exist')
293
+ end
294
+ end
295
+
296
+ def send(data)
297
+ begin
298
+ @socket.send(data, 0)
299
+ rescue SystemCallError
300
+ puts 'Broken Pipe to Irssi, disconnecting '+$!
301
+ close
302
+ return false
303
+ end
304
+ return true
305
+ end
306
+
307
+ def listen(object)
308
+ @listenthread = Thread.start{
309
+ input = ''
310
+ begin
311
+ while line = @socket.recv(70)
312
+ if line.length == 0
313
+ sleep 1
314
+ end
315
+ input += line
316
+ if input.count("\n") > 0
317
+ pos = input.rindex("\n")
318
+ string = input[0, pos]
319
+ input = input[pos, input.length]
320
+ Thread.start{
321
+ object.parse_lines(string)
322
+ }
323
+ end
324
+ end
325
+
326
+ rescue SystemCallError
327
+ puts 'Broken Pipe to Irssi, disconnecting '+$!
328
+ close
329
+ end
330
+ }
331
+ end
332
+
333
+ def close
334
+ @listenthread.kill
335
+ @socket.close
336
+ @client = nil
337
+ end
338
+
339
+ end
340
+
341
+
342
+ class InetdConnection < Connection
343
+ def initialize(main, settings, connectionwindow)
344
+ @main = main
345
+ begin
346
+ @socket = TCPSocket.new(settings['host'], settings['port'].to_i)
347
+ rescue
348
+ raise(IOError, 'Could not connect to socket')
349
+ end
350
+ connectionwindow.send_text('Connected via TCP socket')
351
+ end
352
+
353
+ def send(data)
354
+ begin
355
+ @socket.send(data, 0)
356
+ rescue SystemCallError
357
+ puts 'Broken Pipe to Irssi, disconnecting '+$!
358
+ close
359
+ return false
360
+ end
361
+ return true
362
+ end
363
+
364
+ def listen(object)
365
+ @listenthread = Thread.start{
366
+ loop do
367
+ if res = select([@socket], nil, nil, nil) and res[0]
368
+ @main.parse_line(res[0][0].gets.chomp)
369
+ end
370
+ end
371
+ }
372
+ end
373
+
374
+ def close
375
+ @listenthread.kill
376
+ @socket.close
377
+ @client = nil
378
+ end
379
+
380
+ end
381
+
382
+ class ConnectionFactory
383
+
384
+ # Create a class variable constant of the various available connection
385
+ # types.
386
+
387
+ Types = {
388
+ "ssh" => SSHConnection,
389
+ "socket" => UnixSockConnection,
390
+ "inetd" => InetdConnection,
391
+ "local" => LocalConnection,
392
+ "net_ssh" => NetSSHConnection }
393
+
394
+ def self.spawn( type, *params )
395
+ object = Types[type]
396
+ unless object
397
+ raise( ArgumentError, "Supplied connection type \"" + type + "\" is not available." )
398
+ end
399
+ return object.new( *params )
400
+ end
401
+
402
+ end
403
+ end