PerfectlyNormal-Flexo 0.4.0 → 0.4.2

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.
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