PerfectlyNormal-Flexo 0.4.0 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/flexo.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "Flexo"
3
- s.version = "0.4.0"
4
- s.date = "2008-10-15"
3
+ s.version = "0.4.2"
4
+ s.date = "2008-10-19"
5
5
  s.author = "Per Christian B. Viken"
6
6
  s.email = "perchr@northblue.org"
7
7
  s.homepage = "http://eastblue.org/dev/flexo/"
@@ -9,7 +9,7 @@ module Flexo
9
9
 
10
10
  CHANPREFIX = ['#', '&', '+', '!']
11
11
 
12
- FLEXO_VERSION = '0.4.0'
12
+ FLEXO_VERSION = '0.4.2'
13
13
  FLEXO_RELEASE = 'Bendless Love'
14
14
  end
15
15
  end
@@ -96,24 +96,14 @@ module Flexo
96
96
  end
97
97
 
98
98
  # Removes an event handler.
99
- # Known to be broken.
100
- # DO NOT USE.
101
- # See bug #28
102
99
  def unsubscribe(handler)
103
100
  @manager.logger.debug("Going to remove handler #{handler}")
104
- events = []
105
- @manager.mutex do
106
- @handlers.each { |e,handlers|
107
- if handlers.delete(handler)
108
- events << e
109
- @manager.logger.debug("Adding #{e} to removal queue")
110
- end
111
- }
112
- events.each { |e|
113
- @manager.logger.debug("Removing #{e}")
114
- @handlers.delete(e)
115
- }
101
+
102
+ @handlers.each do |event,handlers|
103
+ handlers.delete handler
116
104
  end
105
+
106
+ @manager.logger.debug("Handler #{handler} removed")
117
107
  end
118
108
 
119
109
  # Easy way to add a trigger.
@@ -121,7 +111,8 @@ module Flexo
121
111
  def add_trigger(pattern, &block)
122
112
  trigger = Flexo::Trigger.new(pattern, &block)
123
113
  @manager.mutex { @triggers << trigger }
124
- trigger
114
+
115
+ return trigger
125
116
  end
126
117
 
127
118
  # Easy way to remove a trigger.
data/lib/flexo/manager.rb CHANGED
@@ -55,8 +55,11 @@ module Flexo
55
55
 
56
56
  @logger.debug "Creating PluginManager"
57
57
  @pluginmanager = Flexo::PluginManager.new(@pluginmutex)
58
- @pluginmanager.load_plugin('control')
59
- @pluginmanager.load_plugin('auth')
58
+
59
+ @config['plugin.autoload'] ||= []
60
+ @config['plugin.autoload'].each do |plugin|
61
+ @pluginmanager.load_plugin(plugin)
62
+ end
60
63
 
61
64
  @logger.debug "Creating Sender"
62
65
  @sender = Flexo::Sender.new
@@ -80,21 +83,16 @@ module Flexo
80
83
  @logger.info "Setting up default handlers"
81
84
  dispatcher.subscribe(:PING) { |ping| ping.pong }
82
85
  dispatcher.subscribe(Flexo::Events::PrivmsgEvent) { |msg|
83
- return unless msg.ctcp?
84
-
85
- case msg.text.split(' ')[0].upcase
86
- when 'VERSION'
87
- l = "\001VERSION Flexo #{Flexo::Constants::FLEXO_VERSION} "
88
- l += "(#{Flexo::Constants::FLEXO_RELEASE})\001"
89
- @sender.notice(msg.data.hostmask.nickname, l)
90
- when 'PING'
91
- arg = msg.text.split(' ')[1]
92
- cmd = "\001PING #{arg}\001"
93
- @sender.notice(msg.data.hostmask.nickname, cmd)
94
- when 'QUIT'
95
- if msg.text.split(' ')[1] == 'lololz'
96
- @server.disconnect
97
- exit
86
+ if msg.ctcp?
87
+ case msg.text.split(' ')[0].upcase
88
+ when 'VERSION'
89
+ l = "\001VERSION Flexo #{Flexo::Constants::FLEXO_VERSION} "
90
+ l += "(#{Flexo::Constants::FLEXO_RELEASE})\001"
91
+ @sender.notice(msg.data.hostmask.nickname, l)
92
+ when 'PING'
93
+ arg = msg.text.split(' ')[1]
94
+ cmd = "\001PING #{arg}\001"
95
+ @sender.notice(msg.data.hostmask.nickname, cmd)
98
96
  end
99
97
  end
100
98
  }
@@ -146,9 +144,17 @@ module Flexo
146
144
  return @server
147
145
  end
148
146
 
147
+ def plugin
148
+ return @pluginmanager
149
+ end
150
+
149
151
  def load_plugin(plugin)
150
152
  return @pluginmanager.load_plugin(plugin)
151
153
  end
154
+
155
+ def find_plugins
156
+ return @pluginmanager.find_plugins
157
+ end
152
158
 
153
159
  def nickname; return @nickname; end
154
160
  def nickname=(nick); @nickname = nick; nick; end
data/lib/flexo/plugin.rb CHANGED
@@ -18,10 +18,31 @@ module Flexo
18
18
 
19
19
  # Variable, mutex, check.
20
20
  def initialize(plugins, *args)
21
- @manager = Flexo::Manager.instance
22
- @plugins = plugins
21
+ @manager = Flexo::Manager.instance
22
+ @plugins = plugins
23
+ @triggers = []
24
+ @handlers = []
23
25
  @manager.logger.debug "Created #{self.class}, with #{@plugins.to_a}"
24
26
  end
27
+
28
+ # Unloading a plugin. This default action unsubscribes all triggers
29
+ # and handlers, so nothing goes wrong when the plugin is unloaded and
30
+ # the dispatcher still tries to run their blocks
31
+ def unload
32
+ @manager.logger.debug "Plugin: Unloading"
33
+ @manager.logger.debug " has #{@triggers.length} triggers"
34
+ @manager.logger.debug " and #{@handlers.length} handlers"
35
+
36
+ @triggers.each do |trigger|
37
+ @manager.logger.debug " Removing trigger #{trigger}"
38
+ remove_trigger trigger
39
+ end
40
+
41
+ @handlers.each do |handler|
42
+ @manager.logger.debug " Removing handler #{handler}"
43
+ unsubscribe handler
44
+ end
45
+ end
25
46
 
26
47
  # Overriding method_missing to let plugins use commands
27
48
  # from other plugins. If the plugin tries to use a function
@@ -48,7 +69,7 @@ module Flexo
48
69
 
49
70
  # Convenience. See Flexo::Dispatcher.subscribe
50
71
  def subscribe(event, &block)
51
- @manager.dispatcher.subscribe(event, &block)
72
+ mutex { @handlers << @manager.dispatcher.subscribe(event, &block) }
52
73
  end
53
74
 
54
75
  # Convenience. See Flexo::Dispatcher.unsubscribe
@@ -58,7 +79,8 @@ module Flexo
58
79
 
59
80
  # Convenience. See Flexo::Dispatcher.add_trigger
60
81
  def add_trigger(pattern, &block)
61
- @manager.dispatcher.add_trigger(pattern, &block)
82
+ @manager.logger.debug("Adding a trigger against #{pattern}")
83
+ mutex { @triggers << @manager.dispatcher.add_trigger(pattern, &block) }
62
84
  end
63
85
 
64
86
  # Convenience. See Flexo::Dispatcher.remove_trigger
@@ -5,6 +5,9 @@ module Flexo
5
5
  # Here's the interesting parts. This class is responsible for...
6
6
  # Managing plugins! Who would've thought that...
7
7
  class PluginManager
8
+ attr_reader :plugins_loaded
9
+ attr_reader :plugins_available
10
+
8
11
  # As usual. Set up some things. Create some arrays, some hashes,
9
12
  # get a mutex. All that
10
13
  def initialize(mutex)
@@ -44,6 +47,7 @@ module Flexo
44
47
  "The plugin name #{klass::NAME} is already being used by another plugin."
45
48
  end
46
49
  @plugins_available << klass
50
+ @plugins_available.uniq!
47
51
  end
48
52
  end
49
53
  end
@@ -99,8 +103,16 @@ module Flexo
99
103
  @manager.logger.debug " Already loaded #{klass.class}"
100
104
  plugin = @plugins_loaded[klass::NAME]
101
105
  end
102
-
106
+
103
107
  plugin
104
108
  end
109
+
110
+ # Unloads a plugin
111
+ def unload_plugin(plugin)
112
+ @manager.logger.debug "Going to unload plugin #{plugin}"
113
+ klass = @plugins_loaded[plugin]
114
+ @manager.mutex { @plugins_loaded.delete(plugin) }
115
+ klass.unload
116
+ end
105
117
  end
106
118
  end
data/lib/flexo/server.rb CHANGED
@@ -10,13 +10,11 @@ module Flexo
10
10
  attr_reader :thread
11
11
  attr_reader :server
12
12
  attr_reader :nickname
13
- attr_reader :monitor
14
13
 
15
14
  def initialize
16
15
  @manager = Flexo::Manager.instance
17
16
  @socket = nil
18
17
  @server = nil
19
- @monitor = nil
20
18
  end
21
19
 
22
20
  def setup
@@ -29,17 +27,11 @@ module Flexo
29
27
  end
30
28
  disconnect
31
29
  rescue Errno::ETIMEDOUT
32
- @manager.logger.warn "Connection lost. Reconnecting"
33
- connect
34
- end
35
- end
36
-
37
- # Set up a thread to check the connection once
38
- # in a while. See bug #15
39
- @monitor = Thread.new do
40
- while true
41
- sleep 45
42
- check_connection
30
+ @manager.logger.error "Connection lost. Reconnecting"
31
+ reconnect
32
+ rescue Errno::ECONNRESET
33
+ @manager.logger.error "Connection reset by peer. Reconnecting after five seconds"
34
+ reconnect 5
43
35
  end
44
36
  end
45
37
  end
@@ -51,19 +43,49 @@ module Flexo
51
43
  # Called by Sender.quote. This function just writes the
52
44
  # prepared line to the socket.
53
45
  def quote(data)
54
- @socket.print("#{data.chomp}\r\n")
46
+ begin
47
+ @socket.print("#{data.chomp}\r\n")
48
+ @socket.flush
49
+ rescue IOError => e
50
+ @manager.logger.error("Connection to #{@server} lost")
51
+ disconnect
52
+ exit 1
53
+ end
55
54
  end
56
55
 
57
56
  # Does the connecting, looping through the defined
58
57
  # servers, trying the next one if it fails.
59
58
  def connect
60
59
  debug "Starting to connect!"
60
+ host = @manager.config['core.hostname'].to_s
61
61
  @manager.config['core.server'].each do |server|
62
62
  debug "Connecting to #{server[:host]}:#{server[:port]}"
63
+ debug " using host #{host}" if host.length > 0
64
+ debug " using SSL" if server[:ssl]
63
65
  timeout(30) do
64
66
  err = 0
65
67
  begin
66
- @socket = TCPSocket.new(server[:host], server[:port])
68
+ if host.length > 0
69
+ @socket = TCPSocket.new(server[:host], server[:port], host)
70
+ else
71
+ @socket = TCPSocket.new(server[:host], server[:port])
72
+ end
73
+
74
+ if(server[:ssl])
75
+ begin
76
+ require 'openssl'
77
+ rescue LoadError
78
+ @manager.logger.error "SSL library not available."
79
+ continue
80
+ end
81
+
82
+ context = OpenSSL::SSL::SSLContext.new()
83
+ # FIXME: Might want to change this one, or at least have it configurable
84
+ context.verify_mode = OpenSSL::SSL::VERIFY_NONE
85
+ @socket = OpenSSL::SSL::SSLSocket.new(@socket, context)
86
+ @socket.sync_close = true
87
+ @socket.connect
88
+ end
67
89
  rescue Errno::ECONNREFUSED => h
68
90
  msg = "Connection to #{server[:host]} at port #{server[:port]} was refused"
69
91
  err = 1
@@ -76,6 +98,9 @@ module Flexo
76
98
  rescue Errno::EADDRNOTAVAIL => h
77
99
  msg = "Unable to assign requested address"
78
100
  err = 2
101
+ rescue OpenSSL::SSL::SSLError => h
102
+ msg = "Unable to connect using SSL (#{h.message})"
103
+ err = 1
79
104
  end
80
105
 
81
106
  if(err != 0)
@@ -87,6 +112,7 @@ module Flexo
87
112
  end
88
113
  else
89
114
  @manager.logger.info("Connected to #{server[:host]}")
115
+ @server = server[:host]
90
116
  login
91
117
  return
92
118
  end
@@ -94,13 +120,6 @@ module Flexo
94
120
  end
95
121
  end
96
122
 
97
- def check_connection
98
- if(@socket == nil || @socket.closed?)
99
- @manager.logger.error("Not connected to a server. Trying again.")
100
- connect
101
- end
102
- end
103
-
104
123
  # This function goes through the login-process
105
124
  def login
106
125
  config = @manager.config
@@ -126,5 +145,12 @@ module Flexo
126
145
  def disconnect
127
146
  @socket.close unless @socket.closed?
128
147
  end
148
+
149
+ # Reconnects
150
+ def reconnect(s = 0)
151
+ disconnect
152
+ sleep s
153
+ connect
154
+ end
129
155
  end
130
156
  end
data/lib/flexo/trigger.rb CHANGED
@@ -40,5 +40,9 @@ module Flexo
40
40
  def to_proc
41
41
  proc { |event| call(event) }
42
42
  end
43
+
44
+ def unsubscribe
45
+ @manager.dispatcher.unsubscribe @handler
46
+ end
43
47
  end
44
48
  end
data/plugins/auth.rb CHANGED
@@ -10,7 +10,7 @@ module Flexo
10
10
  # executes.
11
11
  class AuthPlugin < Flexo::Plugin
12
12
  NAME = 'auth'
13
- VERSION = '0.1.3'
13
+ VERSION = '0.1.4'
14
14
  SUMMARY = 'Access control list for managing users and their permissions.'
15
15
  DEPENDS = ['pstore', 'ruby-digest/md5']
16
16
 
@@ -28,8 +28,8 @@ module Flexo
28
28
  add_restricted_trigger(/^user remove ([a-zA-Z][a-zA-Z0-9_-]*)$/, :level => 256, &method(:cmd_remove))
29
29
  add_trigger(/^user lookup ([a-zA-Z][a-zA-Z0-9_-]*)$/, &method(:cmd_lookup))
30
30
 
31
- pstore_open("#{@manager.config.path}/user.db")
32
- pstore_transaction do |pstore|
31
+ @db = pstore_open("#{@manager.config.path}/user.db")
32
+ @db.transaction do |pstore|
33
33
  pstore[:users] ||= Hash.new
34
34
  end
35
35
  end
@@ -37,7 +37,7 @@ module Flexo
37
37
  # Returns the number of users currently registered
38
38
  # in the database.
39
39
  def usercount
40
- pstore_transaction do |pstore|
40
+ @db.transaction do |pstore|
41
41
  return pstore[:users].length
42
42
  end
43
43
  end
@@ -47,6 +47,7 @@ module Flexo
47
47
  # whether the user is allowed to run the command or not.
48
48
  def add_restricted_trigger(pattern, opts = {}, &block)
49
49
  opts = { :level => 0 }.merge(opts)
50
+ @manager.logger.debug("Adding restricted trigger against #{pattern}")
50
51
  add_trigger(pattern) do |privmsg,*args|
51
52
  @manager.logger.debug "Trying to run a restricted method"
52
53
  level = opts[:level].to_i
@@ -63,6 +64,12 @@ module Flexo
63
64
  block.call(privmsg,*args)
64
65
  end
65
66
  end
67
+
68
+ # Plenty of plugins use this method, and their trigger
69
+ # gets added to this class's triggers. Therefore
70
+ # we pop it, and return it, leaving it up to the plugin to
71
+ # keep track of those
72
+ return @triggers.pop
66
73
  end
67
74
 
68
75
  # Finds a user in the database
@@ -71,7 +78,7 @@ module Flexo
71
78
  @manager.logger.debug "AuthPlugin: Trying to find #{nick}"
72
79
  user = nil
73
80
 
74
- pstore_transaction do |pstore|
81
+ @db.transaction do |pstore|
75
82
  user = pstore[:users].find do |name,user|
76
83
  user[:hostmasks].any? do |dbnick,dbhost|
77
84
  dbnick.downcase == nick.downcase &&\
@@ -95,7 +102,7 @@ module Flexo
95
102
  def username_exists?(username)
96
103
  found = false
97
104
  @manager.logger.debug "Going to see if #{username} exists"
98
- pstore_transaction do |pstore|
105
+ @db.transaction do |pstore|
99
106
  pstore[:users].keys.any? { |dbname|
100
107
  @manager.logger.debug " Checking #{dbname} against #{username}"
101
108
  found = (dbname.downcase == username.downcase)
@@ -111,7 +118,7 @@ module Flexo
111
118
  @manager.logger.debug "Auth: Host: Checking if #{nick} and #{mask} exists"
112
119
  found = false
113
120
 
114
- pstore_transaction do |pstore|
121
+ @db.transaction do |pstore|
115
122
  pstore[:users].any? do |name,user|
116
123
  user[:hostmasks].any? do |dbnick,dbmask|
117
124
  found = true if (dbnick.downcase == nick.downcase && dbmask.downcase == mask.downcase)
@@ -126,7 +133,7 @@ module Flexo
126
133
  # Returns true if the username/password combination
127
134
  # matches, and false if it does not.
128
135
  def is_correct?(user, pass)
129
- c = pstore_transaction do |pstore|
136
+ c = @db.transaction do |pstore|
130
137
  pstore[:users][user.downcase][:password] == Digest::MD5.hexdigest(pass).to_s
131
138
  end
132
139
 
@@ -145,14 +152,14 @@ module Flexo
145
152
  :level => level.to_i
146
153
  }
147
154
 
148
- pstore_transaction do |pstore|
155
+ @db.transaction do |pstore|
149
156
  pstore[:users][user] = tmpuser
150
157
  end
151
158
  end
152
159
 
153
160
  # Adds a nickname/hostmask combo to a given user
154
161
  def addmask(user, nick, mask)
155
- pstore_transaction do |pstore|
162
+ @db.transaction do |pstore|
156
163
  pstore[:users][user.downcase][:hostmasks] << [nick, mask]
157
164
  end
158
165
  end
@@ -189,7 +196,7 @@ module Flexo
189
196
  def cmd_changelevel(privmsg, username, level)
190
197
  return notify_user_not_found(privmsg) if !username_exists?(username)
191
198
 
192
- pstore_transaction do |pstore|
199
+ @db.transaction do |pstore|
193
200
  pstore[:users][username.downcase][:level] = level.to_i
194
201
  end
195
202
 
@@ -201,7 +208,7 @@ module Flexo
201
208
  @manager.logger.debug "AuthPlugin: Going to remove #{username}"
202
209
  return notify_user_not_found(privmsg) if !username_exists?(username)
203
210
 
204
- pstore_transaction do |pstore|
211
+ @db.transaction do |pstore|
205
212
  pstore[:users].delete(username.downcase)
206
213
  end
207
214
 
@@ -219,7 +226,7 @@ module Flexo
219
226
 
220
227
  level = 0
221
228
 
222
- pstore_transaction do |pstore|
229
+ @db.transaction do |pstore|
223
230
  level = pstore[:users][username.downcase][:level]
224
231
  end
225
232
 
@@ -253,4 +260,4 @@ module Flexo
253
260
  end
254
261
  end
255
262
  end
256
- end
263
+ end
data/plugins/control.rb CHANGED
@@ -14,11 +14,13 @@ module Flexo
14
14
  super
15
15
 
16
16
  @manager.logger.debug("ControlPlugin started")
17
- add_restricted_trigger(/^ctrl quit ?(.+)*$/, :level => 256, &method(:cmd_quit))
18
- add_restricted_trigger(/^ctrl join (#\S+)$/, :level => 192, &method(:cmd_join))
19
- add_restricted_trigger(/^ctrl part (#\S+)$/, :level => 192, &method(:cmd_part))
20
- add_restricted_trigger(/^ctrl plugin load (\S+)$/, :level => 256, &method(:cmd_loadplugin))
21
- add_restricted_trigger(/^ctrl plugin unload (\S+)$/, :level => 256, &method(:cmd_unloadplugin))
17
+ @triggers << add_restricted_trigger(/^ctrl quit ?(.+)*$/, :level => 256, &method(:cmd_quit))
18
+ @triggers << add_restricted_trigger(/^ctrl join (#\S+)$/, :level => 192, &method(:cmd_join))
19
+ @triggers << add_restricted_trigger(/^ctrl part (#\S+)$/, :level => 192, &method(:cmd_part))
20
+ @triggers << add_restricted_trigger(/^ctrl plugin load (\S+)$/, :level => 256, &method(:cmd_loadplugin))
21
+ @triggers << add_restricted_trigger(/^ctrl plugin unload (\S+)$/, :level => 256, &method(:cmd_unloadplugin))
22
+ @triggers << add_restricted_trigger(/^ctrl plugin find$/, :level => 256, &method(:cmd_findplugins))
23
+ add_trigger(/^ctrl plugin list$/, &method(:cmd_listplugin))
22
24
  end
23
25
 
24
26
  private
@@ -31,7 +33,8 @@ module Flexo
31
33
  @manager.sender.part(channel)
32
34
  end
33
35
 
34
- def cmd_quit(privmsg, message = "Leaving!")
36
+ def cmd_quit(privmsg, message)
37
+ message ||= "Leaving!"
35
38
  @manager.logger.info("Requested to quit...")
36
39
  @manager.sender.quit(message)
37
40
  end
@@ -46,8 +49,33 @@ module Flexo
46
49
  end
47
50
 
48
51
  def cmd_unloadplugin(privmsg, plugin)
49
- privmsg.reply "Not implemented yet"
52
+ loaded = @manager.plugin.plugins_loaded
53
+
54
+ if(!loaded.include?(plugin))
55
+ privmsg.reply "The plugin #{plugin} is not loaded"
56
+ else
57
+ @manager.plugin.unload_plugin(plugin)
58
+ privmsg.reply "Successfully unloaded #{plugin}"
59
+ end
60
+ end
61
+
62
+ def cmd_listplugin(privmsg)
63
+ loaded = @manager.plugin.plugins_loaded
64
+ available = @manager.plugin.plugins_available
65
+
66
+ available.each do |plug|
67
+ str = "#{plug::NAME} (#{plug::VERSION}"
68
+ str += ", loaded" if loaded.include?(plug::NAME)
69
+ str += ") - #{plug::SUMMARY}"
70
+
71
+ privmsg.reply str
72
+ end
73
+ end
74
+
75
+ def cmd_findplugins(privmsg)
76
+ @manager.find_plugins
77
+ privmsg.reply "Reloaded list of plugins"
50
78
  end
51
79
  end
52
80
  end
53
- end
81
+ end
data/plugins/pstore.rb CHANGED
@@ -2,31 +2,37 @@ module Flexo
2
2
  module Plugins
3
3
  class PStorePlugin < Flexo::Plugin
4
4
  NAME = 'pstore'
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.3'
6
6
  SUMMARY = 'A plugin that provides permanent storage for other plugins'
7
7
  DEPENDS = ['ruby-pstore']
8
8
 
9
9
  def initialize(*args)
10
10
  super
11
- @pstore = nil
12
11
  @manager = Flexo::Manager.instance
13
12
  end
14
13
 
15
14
  # Opens a database.
16
15
  def pstore_open(file)
17
16
  begin
18
- @pstore = PStore.new(file)
17
+ return PStoreWrapper.new(file)
19
18
  rescue
20
19
  raise PluginError("PStore: Unable to open database")
21
20
  end
22
21
  end
23
-
22
+ end
23
+
24
+ class PStoreWrapper
25
+ def initialize(file)
26
+ @manager = Flexo::Manager.instance
27
+ @pstore = PStore.new(file)
28
+ end
29
+
24
30
  # Convenience function for utilizing the mutex, creating
25
31
  # a transaction on our PStore-object, and running a code
26
32
  # block against it.
27
- def pstore_transaction(&block)
33
+ def transaction(&block)
28
34
  @manager.logger.debug "PStore: Starting transaction"
29
- mutex { @pstore.transaction(&block) }
35
+ @manager.pluginmutex { @pstore.transaction(&block) }
30
36
  @manager.logger.debug "PStore: Transaction complete"
31
37
  end
32
38
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: PerfectlyNormal-Flexo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Per Christian B. Viken
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-10-15 00:00:00 -07:00
12
+ date: 2008-10-19 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15