butler 1.8.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/CHANGELOG +40 -0
  2. data/README +9 -9
  3. data/Rakefile +15 -71
  4. data/bin/botcontrol +151 -146
  5. data/data/butler/dialogs/botcontrol.rb +8 -3
  6. data/data/butler/dialogs/create.rb +18 -18
  7. data/data/butler/dialogs/create_config.rb +8 -0
  8. data/data/butler/dialogs/en/create_config.yaml +2 -0
  9. data/data/butler/dialogs/en/list.yaml +2 -1
  10. data/data/butler/dialogs/info.rb +2 -2
  11. data/data/butler/dialogs/list.rb +13 -8
  12. data/data/butler/plugins/games/countdown.rb +41 -0
  13. data/data/butler/plugins/games/roll.rb +59 -0
  14. data/lib/access.rb +6 -107
  15. data/lib/access/admin.rb +3 -0
  16. data/lib/access/role.rb +37 -2
  17. data/lib/access/savable.rb +5 -0
  18. data/lib/access/user.rb +21 -2
  19. data/lib/access/yamlbase.rb +4 -0
  20. data/lib/butler.rb +4 -4
  21. data/lib/butler/bot.rb +13 -13
  22. data/lib/butler/irc/client.rb +10 -2
  23. data/lib/butler/irc/parser.rb +7 -2
  24. data/lib/butler/irc/parser/commands.rb +24 -7
  25. data/lib/butler/irc/parser/generic.rb +27 -315
  26. data/lib/butler/irc/parser/rfc2812.rb +328 -0
  27. data/lib/butler/irc/socket.rb +1 -1
  28. data/lib/butler/irc/user.rb +13 -0
  29. data/lib/butler/plugin.rb +1 -1
  30. data/lib/butler/plugin/configproxy.rb +4 -4
  31. data/lib/butler/plugins.rb +1 -1
  32. data/lib/butler/version.rb +1 -1
  33. data/lib/configuration.rb +22 -71
  34. data/lib/dialogline.rb +12 -0
  35. data/lib/event.rb +5 -2
  36. data/lib/installer.rb +52 -24
  37. data/lib/iterator.rb +17 -7
  38. data/lib/log.rb +32 -5
  39. data/lib/log/comfort.rb +33 -16
  40. data/lib/log/entry.rb +25 -5
  41. data/lib/log/fakeio.rb +1 -0
  42. data/lib/log/splitter.rb +6 -2
  43. data/lib/ostructfixed.rb +5 -0
  44. data/lib/ruby/exception/detailed.rb +3 -3
  45. data/lib/scheduler.rb +19 -4
  46. data/lib/scriptfile.rb +9 -2
  47. data/lib/string.rb +176 -0
  48. data/lib/string/ascii.rb +31 -0
  49. data/lib/string/mbencoded.rb +79 -0
  50. data/lib/string/sbencoded.rb +77 -0
  51. data/lib/string/utf8.rb +157 -0
  52. data/lib/templater.rb +68 -10
  53. data/lib/w3validator.rb +86 -0
  54. data/test/irc/serverlistings/test_rpl_hiddenhost.txt +60 -0
  55. data/test/test_access.rb +101 -0
  56. data/test/test_configuration.rb +63 -0
  57. metadata +19 -2
@@ -0,0 +1,328 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ # A list with RFC2812 compatible IRC-Commands and it's parsing instructions
10
+ # Currently many of them are still in generic.rb
11
+
12
+
13
+
14
+ # --- Text based ----------------------------------------
15
+ add("error", :ERROR) # ERROR :<error-message>
16
+ add("invite", :INVITE, /^(\S*) :(.*)/, [:invited, :channel])
17
+ add("join", :JOIN, /^:(.*)/, [:channel]) { |message, parser|
18
+ if message.from && message.channel then
19
+ message.from.add_channel(message.channel, :join)
20
+ message.channel.add_user(message.from, :join)
21
+ end
22
+ }
23
+ add("kick", :KICK, /^(\S*) (\S*) :(.*)/, [:channel, :for, :text]) { |message, parser|
24
+ parser.leave_channel(message, :kick, :kicked)
25
+ }
26
+ add("kill", :KILL, /^(\S*) (\S*) (.*)/, [:channel, :for, :text]) { |message, parser|
27
+ if message.for then
28
+ message.for.kill
29
+ parser.channels.delete_user(message.for, :kill)
30
+ end
31
+ }
32
+ # FIXME, really ':?' or just plain ':'?
33
+ add("mode", :MODE, /^(\S*) :?(.*)/, [:for, :arguments]) { |message, parser|
34
+ modifiers = message[:arguments].split(" ")
35
+ modes = modifiers.shift.split("")
36
+ flags = {"o" => User::Flags::OP, "v" => User::Flags::VOICE, "u" => User::Flags::UOP}
37
+ message.create_member(:modes, [])
38
+ argumentIndex = 0
39
+ direction = "+"
40
+
41
+ modes.each { |mode|
42
+ # MODEs direction
43
+ if (["+", "-"].include?(mode)) then
44
+ direction = mode
45
+ # MODEs taking an argument
46
+ elsif (["k", "o", "v", "u"].include?(mode) || (mode == "l" && direction == "+")) then
47
+ if ["o", "v", "u"].include?(mode) then
48
+ if (direction == "+") then
49
+ parser.users[modifiers[argumentIndex]].add_flags(message.channel, flags[mode]) #adding flags to user
50
+ else
51
+ parser.users[modifiers[argumentIndex]].delete_flags(message.channel, flags[mode]) #removing flags from user
52
+ end
53
+ end
54
+ message.modes << [direction, mode, modifiers[argumentIndex]]
55
+ argumentIndex = argumentIndex+1
56
+ # MODEs without argument
57
+ else
58
+ message.modes << [direction, mode, nil]
59
+ end
60
+ }
61
+ }
62
+ add("nick", :NICK, /^:(.*)/, [:nick]) { |message, parser|
63
+ message.create_member(:old_nick, message.from.nick)
64
+ message.from.nick = message.nick if message.from
65
+ }
66
+ add("notice", :NOTICE, /(\S+) :(.*)/, [:for, :text]) { |message, parser|
67
+ if message.channel then
68
+ message.create_member(:realm, :public)
69
+ message.create_method(:private?) { false }
70
+ message.create_method(:public?) { true }
71
+ else
72
+ message.create_member(:realm, :private)
73
+ message.create_method(:private?) { true }
74
+ message.create_method(:public?) { false }
75
+ end
76
+ }
77
+ add("part", :PART, /^([^\x00\x07\x10\x0D\x20,:]+)(?: :(.*))?/, [:channel, :reason]) { |message, parser|
78
+ parser.leave_channel(message, :part, :parted)
79
+ }
80
+ add("ping", :PING, /:(.*)/, [:pong])
81
+ add("pong", :PONG)
82
+ add("privmsg", :PRIVMSG, /(\S+) :(.*)/, [:for, :text]) { |message, parser|
83
+ if message.channel then
84
+ message.create_member(:realm, :public)
85
+ message.create_method(:private?) { false }
86
+ message.create_method(:public?) { true }
87
+ else
88
+ message.create_member(:realm, :private)
89
+ message.create_method(:private?) { true }
90
+ message.create_method(:public?) { false }
91
+ end
92
+ }
93
+ add("quit", :QUIT, /(.*)/, [:text]) { |message, parser|
94
+ if message.from then
95
+ message.from.quit
96
+ parser.channels.delete_user(message.from, :quit)
97
+ parser.users.delete(message.from, :quit)
98
+ end
99
+ }
100
+ add("topic", :TOPIC, /(\S+) :(.*)/, [:channel, :text])
101
+
102
+
103
+
104
+ # --- 0** Codes ----------------------------------------
105
+ add("001", :RPL_WELCOME)
106
+ add("002", :RPL_YOURHOST)
107
+ add("003", :RPL_CREATED)
108
+ add("004", :RPL_MYINFO)
109
+ add("005", :RPL_ISUPPORT)
110
+
111
+
112
+
113
+
114
+ # --- 2** Codes ----------------------------------------
115
+ add("200", :RPL_TRACELINK)
116
+ add("201", :RPL_TRACECONNECTING)
117
+ add("202", :RPL_TRACEHANDSHAKE)
118
+ add("203", :RPL_TRACEUNKNOWN)
119
+ add("204", :RPL_TRACEOPERATOR)
120
+ add("205", :RPL_TRACEUSER)
121
+ add("206", :RPL_TRACESERVER)
122
+ add("207", :RPL_TRACESERVICE)
123
+ add("208", :RPL_TRACENEWTYPE)
124
+ add("209", :RPL_TRACECLASS)
125
+ add("210", :RPL_TRACERECONNECT)
126
+ add("211", :RPL_STATSLINKINFO)
127
+ add("212", :RPL_STATSCOMMANDS)
128
+ add("213", :RPL_STATSCLINE)
129
+ add("214", :RPL_STATSNLINE)
130
+ add("215", :RPL_STATSILINE)
131
+ add("216", :RPL_STATSKLINE)
132
+ add("217", :RPL_STATSQLINE)
133
+ add("218", :RPL_STATSYLINE)
134
+ add("219", :RPL_ENDOFSTATS)
135
+
136
+ add("221", :RPL_UMODEIS)
137
+
138
+ add("231", :RPL_SERVICEINFO)
139
+ add("232", :RPL_ENDOFSERVICES)
140
+ add("233", :RPL_SERVICE)
141
+ add("234", :RPL_SERVLIST)
142
+ add("235", :RPL_SERVLISTEND)
143
+
144
+ add("240", :RPL_STATSVLINE)
145
+ add("241", :RPL_STATSLLINE)
146
+ add("242", :RPL_STATSUPTIME)
147
+ add("243", :RPL_STATSOLINE)
148
+ add("244", :RPL_STATSHLINE)
149
+ add("245", :RPL_STATSSLINE) # RFC 2812 seems to be erroneous, it assigns 244 double
150
+ add("246", :RPL_STATSPING)
151
+ add("247", :RPL_STATSBLINE)
152
+ add("250", :RPL_STATSCONN)
153
+ add("251", :RPL_LUSERCLIENT)
154
+ add("252", :RPL_LUSEROP)
155
+ add("253", :RPL_LUSERUNKNOWN)
156
+ add("254", :RPL_LUSERCHANNELS)
157
+ add("255", :RPL_LUSERME)
158
+ add("256", :RPL_ADMINME)
159
+ add("257", :RPL_ADMINLOC1)
160
+ add("258", :RPL_ADMINLOC2)
161
+ add("259", :RPL_ADMINEMAIL)
162
+
163
+ add("261", :RPL_TRACELOG)
164
+ add("262", :RPL_TRACEEND)
165
+
166
+ add("263", :RPL_TRYAGAIN)
167
+
168
+
169
+
170
+ # --- 3** Codes ----------------------------------------
171
+ add("300", :RPL_NONE)
172
+ add("301", :RPL_AWAY)
173
+ add("302", :RPL_USERHOST)
174
+ add("303", :RPL_ISON)
175
+ add("305", :RPL_UNAWAY)
176
+ add("306", :RPL_NOWAWAY)
177
+ add("311", :RPL_WHOISUSER, /^(\S+) (\S+) (\S+) (\S+) \* :(.*)/, [:for, :nick, :user, :host, :real])
178
+ add("312", :RPL_WHOISSERVER)
179
+ add("313", :RPL_WHOISOPERATOR)
180
+ add("314", :RPL_WHOWASUSER)
181
+ add("315", :RPL_ENDOFWHO)
182
+ add("316", :RPL_WHOISCHANOP)
183
+ add("317", :RPL_WHOISIDLE, /^(\S+) (\S+) ([^:]+) :(.*)/, [:for, :nick, :values, :descriptions]) { |message, parser|
184
+ values = message[:values].split(" ");
185
+ descriptions = message[:descriptions].split(", ").map { |desc| desc.gsub(" ", "_").to_sym };
186
+ message.delete(:values)
187
+ message.delete(:descriptions)
188
+ 0.upto([values.length, descriptions.length].min-1) { |index|
189
+ message[descriptions[index]] = values[index]
190
+ }
191
+ }
192
+ add("318", :RPL_ENDOFWHOIS)
193
+ add("319", :RPL_WHOISCHANNELS, /^(\S+) (\S+) :(.*)/, [:for, :nick, :channels]) { |message, parser|
194
+ # FIXME
195
+ message.alter_member(:channels, message.channels.split(" ").map { |channel| parser.channels.create(channel) })
196
+ }
197
+ add("321", :RPL_LISTSTART)
198
+ add("322", :RPL_LIST, /^(\S+) (\S+) (\d+) :(.*)/, [:for, :channelname, :usercount, :topic])
199
+ add("323", :RPL_LISTEND)
200
+ add("324", :RPL_CHANNELMODEIS)
201
+ add("325", :RPL_UNIQOPIS)
202
+ add("331", :RPL_NOTOPIC)
203
+ # :irc.server.net 332 YourNickname #channel :Topic
204
+ add("332", :RPL_TOPIC, /^(\S+) (\S+) :(.*)/, [:for, :channel, :topic]) { |message, parser|
205
+ message.channel.topic.text = message[:topic]
206
+ }
207
+ add("341", :RPL_INVITING)
208
+ add("342", :RPL_SUMMONING)
209
+ add("346", :RPL_INVITELIST)
210
+ add("347", :RPL_ENDOFINVITELIST)
211
+ add("348", :RPL_EXCEPTLIST)
212
+ add("349", :RPL_ENDOFEXCEPTLIST)
213
+ add("351", :RPL_VERSION)
214
+ # :irc.server.net 352 YourNickname <channel> <user> <host> <server> <nick> ( "H" / "G" > ["*"] [ ( "@" / "+" ) ] :<hopcount> <real name>
215
+ add("352", :RPL_WHOREPLY,
216
+ /(\S+) (\S+) (\S+) (\S+) (\S+) (\S+) ([HG])[*%]{0,2}([@+-]{0,3}) :(\d+) (.*)/,
217
+ [:for, :channel, :user, :host, :server, :nick, :status, :flags, :hopcount, :real]) { |message, parser|
218
+ #"for", "channel", "user", "host", "server", "nick", "status", "flags", "hopcount", "real"
219
+ user = parser.users.create(message[:nick])
220
+ user.update(message[:user], message[:host], message[:real])
221
+ user.away = message[:status] == "G"
222
+ user.add_channel(message.channel, :joined)
223
+ message.channel.add_user(user, :joined)
224
+ user.add_flags(message.channel, message[:flags].to_flags)
225
+ }
226
+ add("353", :RPL_NAMEREPLY, /(\S+) [=@] (\S+) :(.*)/, [:for, :channel, :users]) { |message, parser|
227
+ users = message[:users]
228
+ message.alter_member(:users, [])
229
+ users.split(/ /).each { |nick|
230
+ user = parser.users.create(nick)
231
+ message[:users] << user
232
+ user.add_channel(message.channel, :joined)
233
+ message.channel.add_user(user, :joined)
234
+ }
235
+ }
236
+ add("361", :RPL_KILLDONE)
237
+ add("362", :RPL_CLOSING)
238
+ add("363", :RPL_CLOSEEND)
239
+ add("364", :RPL_LINKS)
240
+ add("365", :RPL_ENDOFLINKS)
241
+ add("366", :RPL_ENDOFNAMES)
242
+ add("367", :RPL_BANLIST,
243
+ /(\S+) (\S+) (\S+) (\S+) (\d+)/,
244
+ [:for, :channel, :banmask, :banned_by, :bantime]
245
+ ) { |message, parser| #367 nickname #channel nick!user@host nickname 1140125288
246
+ message[:bantime] = Time.at(message[:bantime].to_i)
247
+ message[:banmask] = Hostmask.new(message[:banmask])
248
+ }
249
+ add("369", :RPL_ENDOFWHOWAS)
250
+
251
+ add("368", :RPL_ENDOFBANLIST)
252
+ add("371", :RPL_INFO)
253
+ add("372", :RPL_MOTD)
254
+ add("373", :RPL_INFOSTART)
255
+ add("374", :RPL_ENDOFINFO)
256
+ add("375", :RPL_MOTDSTART)
257
+ add("376", :RPL_ENDOFMOTD)
258
+ add("381", :RPL_YOUREOPER)
259
+ add("382", :RPL_REHASHING)
260
+ add("383", :RPL_YOURESERVICE)
261
+ add("384", :RPL_MYPORTIS)
262
+ add("391", :RPL_TIME)
263
+ add("392", :RPL_USERSSTART)
264
+ add("393", :RPL_USERS)
265
+ add("394", :RPL_ENDOFUSERS)
266
+ add("395", :RPL_NOUSERS)
267
+
268
+
269
+
270
+ # --- 4** Codes ----------------------------------------
271
+ add("401", :ERR_NOSUCHNICK)
272
+ add("402", :ERR_NOSUCHSERVER)
273
+ add("403", :ERR_NOSUCHCHANNEL)
274
+ add("404", :ERR_CANNOTSENDTOCHAN)
275
+ add("405", :ERR_TOOMANYCHANNELS)
276
+ add("406", :ERR_WASNOSUCHNICK)
277
+ add("407", :ERR_TOOMANYTARGETS)
278
+ add("408", :ERR_NOSUCHSERVICE)
279
+ add("409", :ERR_NOORIGIN)
280
+ add("411", :ERR_NORECIPIENT)
281
+ add("412", :ERR_NOTEXTTOSEND)
282
+ add("413", :ERR_NOTOPLEVEL)
283
+ add("414", :ERR_WILDTOPLEVEL)
284
+ add("415", :ERR_BADMASK)
285
+ add("421", :ERR_UNKNOWNCOMMAND)
286
+ add("422", :ERR_NOMOTD)
287
+ add("423", :ERR_NOADMININFO)
288
+ add("424", :ERR_FILEERROR)
289
+ add("431", :ERR_NONICKNAMEGIVEN)
290
+ add("432", :ERR_ERRONEUSNICKNAME)
291
+ add("433", :ERR_NICKNAMEINUSE)
292
+ add("436", :ERR_NICKCOLLISION)
293
+ add("437", :ERR_UNAVAILRESOURCE)
294
+ add("441", :ERR_USERNOTINCHANNEL)
295
+ add("442", :ERR_NOTONCHANNEL)
296
+ add("443", :ERR_USERONCHANNEL)
297
+ add("444", :ERR_NOLOGIN)
298
+ add("445", :ERR_SUMMONDISABLED)
299
+ add("446", :ERR_USERSDISABLED)
300
+ add("451", :ERR_NOTREGISTERED)
301
+ add("461", :ERR_NEEDMOREPARAMS)
302
+ add("462", :ERR_ALREADYREGISTRED)
303
+ add("463", :ERR_NOPERMFORHOST)
304
+ add("464", :ERR_PASSWDMISMATCH)
305
+ add("465", :ERR_YOUREBANNEDCREEP)
306
+ add("466", :ERR_YOUWILLBEBANNED)
307
+ add("467", :ERR_KEYSET)
308
+ add("471", :ERR_CHANNELISFULL)
309
+ add("472", :ERR_UNKNOWNMODE)
310
+ add("473", :ERR_INVITEONLYCHAN)
311
+ add("474", :ERR_BANNEDFROMCHAN)
312
+ add("475", :ERR_BADCHANNELKEY)
313
+ add("476", :ERR_BADCHANMASK)
314
+ add("477", :ERR_NOCHANMODES)
315
+ add("478", :ERR_BANLISTFULL)
316
+ add("481", :ERR_NOPRIVILEGES)
317
+ add("482", :ERR_CHANOPRIVSNEEDED)
318
+ add("483", :ERR_CANTKILLSERVER)
319
+ add("484", :ERR_RESTRICTED)
320
+ add("485", :ERR_UNIQOPPRIVSNEEDED)
321
+ add("491", :ERR_NOOPERHOST)
322
+ add("492", :ERR_NOSERVICEHOST)
323
+
324
+
325
+
326
+ # --- 5** Codes ----------------------------------------
327
+ add("501", :ERR_UMODEUNKNOWNFLAG)
328
+ add("502", :ERR_USERSDONTMATCH)
@@ -87,7 +87,7 @@ class Butler
87
87
  #
88
88
  def initialize(server, options={})
89
89
  options = OptionsDefault.merge(options)
90
- @log_device = options.delete(:log)
90
+ @logger = options.delete(:log)
91
91
  @server = server # options.delete(:server)
92
92
  @port = options.delete(:port)
93
93
  @eol = options.delete(:eol).dup.freeze
@@ -87,6 +87,19 @@ class Butler
87
87
  @real = real.freeze if real
88
88
  self
89
89
  end
90
+
91
+ # :nodoc:
92
+ # DO NOT USE THIS METHOD
93
+ # This method is intended to be used by IRC::Parser or IRC::Client
94
+ # in case the server alters parts about 'myself'
95
+ # examples: some ircd's change the 'user' part (prefix it), some
96
+ # ircd's allow hiding the host, ...
97
+ def force_update(user=nil, host=nil, real=nil)
98
+ @user = user.freeze if user
99
+ @host = host.freeze if host
100
+ @real = real.freeze if real
101
+ self
102
+ end
90
103
 
91
104
  # set users nickname (should only be used by Butler::IRC::Parser)
92
105
  def nick=(nick) #:nodoc:
@@ -41,7 +41,7 @@ class Butler
41
41
  @name = File.basename(base).freeze
42
42
  @commands = []
43
43
  @listener = []
44
- @config = ConfigProxy.new(@butler.config, "plugin.#{name}")
44
+ @config = ConfigProxy.new(@butler.config, "plugin/#{name}")
45
45
 
46
46
  if File.directory?(path) then
47
47
  raise "Not supported yet" # FIXME
@@ -15,20 +15,20 @@ class Butler
15
15
  end
16
16
 
17
17
  def []=(key, value)
18
- @config[key.empty? ? @base : "#{@base}.#{key}"] = value
18
+ @config[key.empty? ? @base : "#{@base}/#{key}"] = value
19
19
  end
20
20
 
21
21
  def [](key, *args)
22
- @config[(key.empty? ? @base : "#{@base}.#{key}"), *args]
22
+ @config[(key.empty? ? @base : "#{@base}/#{key}"), *args]
23
23
  end
24
24
 
25
25
  def exist?(key)
26
- @config.exist?(key.empty? ? @base : "#{@base}.#{key}")
26
+ @config.exist?(key.empty? ? @base : "#{@base}/#{key}")
27
27
  end
28
28
  alias has_key? exist?
29
29
 
30
30
  def delete(key)
31
- @config.delete(key.empty? ? @base : "#{@base}.#{key}")
31
+ @config.delete(key.empty? ? @base : "#{@base}/#{key}")
32
32
  end
33
33
  end
34
34
  end
@@ -114,7 +114,7 @@ class Butler
114
114
  constant = "%s_%08X" % [name, rand(0xffffffff)]
115
115
  end while Butler::Plugins.const_defined?(constant)
116
116
  plugin = Butler::Plugins.const_set(constant, Class.new(Plugin))
117
- plugin.log_device = @butler.log_device
117
+ plugin.logger = @butler.logger
118
118
  begin
119
119
  plugin.load_plugin(@butler, base, path)
120
120
  plugin.class_eval(File.read(codefile), codefile)
@@ -10,7 +10,7 @@ class Butler #:nodoc:
10
10
  module VERSION #:nodoc:
11
11
  MAJOR = 1
12
12
  MINOR = 8
13
- TINY = 0
13
+ TINY = 1
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY].join('.')
16
16
  end
@@ -13,10 +13,24 @@ require 'yaml'
13
13
 
14
14
  class String
15
15
  def config_key # FIXME %2e instead of . is just ugly...
16
- gsub(/\./, '%2e')
16
+ gsub(/\//, '%2f')
17
17
  end
18
18
  end
19
19
 
20
+ # == Indexing
21
+ # Author: Stefan Rusterholz
22
+ # Contact: apeiros@gmx.net>
23
+ # Version: 1.0.0
24
+ # Date: 2007-10-12
25
+ #
26
+ # == About
27
+ # Map configuration keys/data to files
28
+ #
29
+ # == Synopsis
30
+ # config = Configuration.new("my/config")
31
+ # config["some/key"] = "Some value"
32
+ # config["some/key"] = config["some/key"].upcase # can't use upcase!
33
+ #
20
34
  class Configuration
21
35
  module VERSION
22
36
  MAJOR = 0
@@ -28,7 +42,7 @@ class Configuration
28
42
  attr_reader :key
29
43
  def initialize(key)
30
44
  @key = key
31
- super("Keys must not be empty nor contain '.'.")
45
+ super("Keys must not be empty")
32
46
  end
33
47
  end
34
48
 
@@ -105,25 +119,26 @@ class Configuration
105
119
  def setup(dirs)
106
120
  FileUtils.mkdir_p(dirs.map { |dir| "#@base/#{dir}" })
107
121
  end
108
-
122
+
109
123
  private
110
124
  def key_exist?(file, key)
111
125
  File.exist?(file) && (!key || load(file).has_key?(key))
112
126
  end
113
127
 
114
128
  def split(key)
115
- keys = Array === key ? key.map { |k| String(k) } : key.split(/\./)
129
+ keys = Array === key ? key.map { |k| String(k) } : key.split(/\//)
116
130
  file = @base.dup
117
- raise InvalidKey.new(key) if (keys.empty? || keys.join.include?("."))
131
+ raise InvalidKey.new(key) if keys.empty?
118
132
  file << "/#{encode_filename(keys.shift)}" while (File.directory?(file) && keys.first)
119
133
  file << ".yaml"
120
134
  return [file, keys.empty? ? nil : keys.join(".")]
121
135
  end
122
136
 
123
137
  def encode_filename(name)
124
- name.gsub(/[\x00-\x1f%\x7f]/) { |m| "%%%02x"%m[0] } # {}()\[\]~
138
+ # FIXME: {}()\[\]~ what about those?
139
+ name.gsub(/[\x00-\x1f%\x2f\x7f]/) { |m| "%%%02x"%m[0] }.sub(/^\x2e/, '%2e')
125
140
  end
126
-
141
+
127
142
  def decode_filename(name)
128
143
  name.gsub(/%[a-f\d]{2}/) { |m| m[1,2].to_i(16).chr }
129
144
  end
@@ -157,67 +172,3 @@ class Configuration
157
172
  end
158
173
  end
159
174
  end
160
-
161
- if $0 == __FILE__ then
162
- require 'test/unit'
163
- class TestConfiguration < Test::Unit::TestCase
164
- def setup
165
- @conf = Configuration.new("./test")
166
- end
167
-
168
- def test_setup
169
- assert(@conf)
170
- assert(@conf.base)
171
- assert(File.directory?(@conf.base))
172
- end
173
-
174
- def test_accessor
175
- assert(@conf["file1"] = "bar")
176
- assert_equal(@conf[[:file1]], "bar")
177
- assert_equal(@conf[["file1"]], "bar")
178
- assert_equal(@conf["file1"], "bar")
179
- assert(File.exist?("#{@conf.base}/file1.yaml"))
180
-
181
- assert(@conf["file2"] = "foo")
182
- assert_equal(@conf[:file2], "foo")
183
- assert_equal(@conf["file2"], "foo")
184
- assert(File.exist?("#{@conf.base}/file2.yaml"))
185
-
186
- assert(@conf[[:file3, :sub]] = "baz")
187
- assert_equal(@conf[[:file3, :sub]], "baz")
188
- assert_equal(@conf["file3", "sub"], "baz")
189
- assert(File.exist?("#{@conf.base}/file3.yaml"))
190
- assert(!File.exist?("#{@conf.base}/file3"))
191
-
192
- assert(@conf.exist?(:file3))
193
- assert(!@conf.exist?(:file))
194
- #assert_raise { @conf
195
- end
196
-
197
- def test_nesting
198
- end
199
-
200
- def xtest_common_usecase
201
- @conf.setup(%w(
202
- main
203
- plugins
204
- plugins/demo
205
- ))
206
- @conf.merge(
207
- "main.language" => "de",
208
- "main.channels" => %w(foo bar baz),
209
- "plugins.demo.value" => 42
210
- )
211
- @conf["plugins.demo.other"] = 24
212
- @conf["main.language"].replace("en")
213
- @conf.update
214
- file, key = @conf.send(:split, "main.language")
215
- File.open(file, "w") { |fh| fh.write({"language" => "en"}.to_yaml) }
216
- @conf.rehash
217
- end
218
-
219
- def teardown
220
- FileUtils.rm_r(@conf.base)
221
- end
222
- end
223
- end