butler 1.8.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 (164) hide show
  1. data/CHANGELOG +4 -0
  2. data/GPL.txt +340 -0
  3. data/LICENSE.txt +52 -0
  4. data/README +37 -0
  5. data/Rakefile +334 -0
  6. data/bin/botcontrol +230 -0
  7. data/data/butler/config_template.yaml +4 -0
  8. data/data/butler/dialogs/backup.rb +19 -0
  9. data/data/butler/dialogs/botcontrol.rb +4 -0
  10. data/data/butler/dialogs/config.rb +1 -0
  11. data/data/butler/dialogs/create.rb +53 -0
  12. data/data/butler/dialogs/delete.rb +3 -0
  13. data/data/butler/dialogs/en/backup.yaml +6 -0
  14. data/data/butler/dialogs/en/botcontrol.yaml +5 -0
  15. data/data/butler/dialogs/en/create.yaml +11 -0
  16. data/data/butler/dialogs/en/delete.yaml +2 -0
  17. data/data/butler/dialogs/en/help.yaml +17 -0
  18. data/data/butler/dialogs/en/info.yaml +13 -0
  19. data/data/butler/dialogs/en/list.yaml +4 -0
  20. data/data/butler/dialogs/en/notyetimplemented.yaml +2 -0
  21. data/data/butler/dialogs/en/rename.yaml +3 -0
  22. data/data/butler/dialogs/en/start.yaml +3 -0
  23. data/data/butler/dialogs/en/sync_plugins.yaml +3 -0
  24. data/data/butler/dialogs/en/uninstall.yaml +5 -0
  25. data/data/butler/dialogs/en/unknown_command.yaml +2 -0
  26. data/data/butler/dialogs/help.rb +11 -0
  27. data/data/butler/dialogs/info.rb +27 -0
  28. data/data/butler/dialogs/interactive.rb +1 -0
  29. data/data/butler/dialogs/list.rb +10 -0
  30. data/data/butler/dialogs/notyetimplemented.rb +1 -0
  31. data/data/butler/dialogs/rename.rb +4 -0
  32. data/data/butler/dialogs/selectbot.rb +2 -0
  33. data/data/butler/dialogs/start.rb +5 -0
  34. data/data/butler/dialogs/sync_plugins.rb +30 -0
  35. data/data/butler/dialogs/uninstall.rb +17 -0
  36. data/data/butler/dialogs/unknown_command.rb +1 -0
  37. data/data/butler/plugins/core/logout.rb +41 -0
  38. data/data/butler/plugins/core/plugins.rb +134 -0
  39. data/data/butler/plugins/core/privilege.rb +103 -0
  40. data/data/butler/plugins/core/user.rb +166 -0
  41. data/data/butler/plugins/dev/eval.rb +64 -0
  42. data/data/butler/plugins/dev/nometa.rb +14 -0
  43. data/data/butler/plugins/dev/onhandlers.rb +93 -0
  44. data/data/butler/plugins/dev/raw.rb +36 -0
  45. data/data/butler/plugins/dev/rawlog.rb +77 -0
  46. data/data/butler/plugins/games/eightball.rb +54 -0
  47. data/data/butler/plugins/games/mastermind.rb +174 -0
  48. data/data/butler/plugins/irc/action.rb +36 -0
  49. data/data/butler/plugins/irc/join.rb +38 -0
  50. data/data/butler/plugins/irc/notice.rb +36 -0
  51. data/data/butler/plugins/irc/part.rb +38 -0
  52. data/data/butler/plugins/irc/privmsg.rb +36 -0
  53. data/data/butler/plugins/irc/quit.rb +36 -0
  54. data/data/butler/plugins/operator/deop.rb +41 -0
  55. data/data/butler/plugins/operator/devoice.rb +41 -0
  56. data/data/butler/plugins/operator/limit.rb +47 -0
  57. data/data/butler/plugins/operator/op.rb +41 -0
  58. data/data/butler/plugins/operator/voice.rb +41 -0
  59. data/data/butler/plugins/public/help.rb +69 -0
  60. data/data/butler/plugins/public/login.rb +72 -0
  61. data/data/butler/plugins/public/usage.rb +49 -0
  62. data/data/butler/plugins/service/clones.rb +56 -0
  63. data/data/butler/plugins/service/define.rb +47 -0
  64. data/data/butler/plugins/service/log.rb +183 -0
  65. data/data/butler/plugins/service/svn.rb +91 -0
  66. data/data/butler/plugins/util/cycle.rb +98 -0
  67. data/data/butler/plugins/util/load.rb +41 -0
  68. data/data/butler/plugins/util/pong.rb +29 -0
  69. data/data/butler/strings/random/acknowledge.en.yaml +5 -0
  70. data/data/butler/strings/random/gratitude.en.yaml +3 -0
  71. data/data/butler/strings/random/hello.en.yaml +4 -0
  72. data/data/butler/strings/random/ignorance.en.yaml +7 -0
  73. data/data/butler/strings/random/ignorance_about.en.yaml +3 -0
  74. data/data/butler/strings/random/insult.en.yaml +3 -0
  75. data/data/butler/strings/random/rejection.en.yaml +12 -0
  76. data/data/man/botcontrol.1 +17 -0
  77. data/lib/access.rb +187 -0
  78. data/lib/access/admin.rb +16 -0
  79. data/lib/access/privilege.rb +122 -0
  80. data/lib/access/role.rb +102 -0
  81. data/lib/access/savable.rb +18 -0
  82. data/lib/access/user.rb +180 -0
  83. data/lib/access/yamlbase.rb +126 -0
  84. data/lib/butler.rb +188 -0
  85. data/lib/butler/bot.rb +247 -0
  86. data/lib/butler/control.rb +93 -0
  87. data/lib/butler/dialog.rb +64 -0
  88. data/lib/butler/initialvalues.rb +40 -0
  89. data/lib/butler/irc/channel.rb +135 -0
  90. data/lib/butler/irc/channels.rb +96 -0
  91. data/lib/butler/irc/client.rb +351 -0
  92. data/lib/butler/irc/hostmask.rb +53 -0
  93. data/lib/butler/irc/message.rb +184 -0
  94. data/lib/butler/irc/parser.rb +125 -0
  95. data/lib/butler/irc/parser/commands.rb +83 -0
  96. data/lib/butler/irc/parser/generic.rb +343 -0
  97. data/lib/butler/irc/socket.rb +378 -0
  98. data/lib/butler/irc/string.rb +186 -0
  99. data/lib/butler/irc/topic.rb +15 -0
  100. data/lib/butler/irc/user.rb +265 -0
  101. data/lib/butler/irc/users.rb +112 -0
  102. data/lib/butler/plugin.rb +249 -0
  103. data/lib/butler/plugin/configproxy.rb +35 -0
  104. data/lib/butler/plugin/mapper.rb +85 -0
  105. data/lib/butler/plugin/matcher.rb +55 -0
  106. data/lib/butler/plugin/onhandlers.rb +70 -0
  107. data/lib/butler/plugin/trigger.rb +58 -0
  108. data/lib/butler/plugins.rb +147 -0
  109. data/lib/butler/version.rb +17 -0
  110. data/lib/cloptions.rb +217 -0
  111. data/lib/cloptions/adapters.rb +24 -0
  112. data/lib/cloptions/switch.rb +132 -0
  113. data/lib/configuration.rb +223 -0
  114. data/lib/dialogline.rb +296 -0
  115. data/lib/dialogline/localizations.rb +24 -0
  116. data/lib/durations.rb +57 -0
  117. data/lib/event.rb +295 -0
  118. data/lib/event/at.rb +64 -0
  119. data/lib/event/every.rb +56 -0
  120. data/lib/event/timed.rb +112 -0
  121. data/lib/installer.rb +75 -0
  122. data/lib/iterator.rb +34 -0
  123. data/lib/log.rb +68 -0
  124. data/lib/log/comfort.rb +85 -0
  125. data/lib/log/converter.rb +23 -0
  126. data/lib/log/entry.rb +152 -0
  127. data/lib/log/fakeio.rb +55 -0
  128. data/lib/log/file.rb +54 -0
  129. data/lib/log/filereader.rb +81 -0
  130. data/lib/log/forward.rb +49 -0
  131. data/lib/log/methods.rb +39 -0
  132. data/lib/log/nolog.rb +18 -0
  133. data/lib/log/splitter.rb +26 -0
  134. data/lib/ostructfixed.rb +26 -0
  135. data/lib/ruby/array/columnize.rb +38 -0
  136. data/lib/ruby/dir/mktree.rb +28 -0
  137. data/lib/ruby/enumerable/join.rb +13 -0
  138. data/lib/ruby/exception/detailed.rb +24 -0
  139. data/lib/ruby/file/append.rb +11 -0
  140. data/lib/ruby/file/write.rb +11 -0
  141. data/lib/ruby/hash/zip.rb +15 -0
  142. data/lib/ruby/kernel/bench.rb +15 -0
  143. data/lib/ruby/kernel/daemonize.rb +42 -0
  144. data/lib/ruby/kernel/non_verbose.rb +17 -0
  145. data/lib/ruby/kernel/safe_fork.rb +18 -0
  146. data/lib/ruby/range/stepped.rb +11 -0
  147. data/lib/ruby/string/arguments.rb +72 -0
  148. data/lib/ruby/string/chunks.rb +15 -0
  149. data/lib/ruby/string/post_arguments.rb +44 -0
  150. data/lib/ruby/string/unescaped.rb +17 -0
  151. data/lib/scheduler.rb +164 -0
  152. data/lib/scriptfile.rb +101 -0
  153. data/lib/templater.rb +86 -0
  154. data/test/cloptions.rb +134 -0
  155. data/test/cv.rb +28 -0
  156. data/test/irc/client.rb +85 -0
  157. data/test/irc/client_login.txt +53 -0
  158. data/test/irc/client_subscribe.txt +8 -0
  159. data/test/irc/message.rb +30 -0
  160. data/test/irc/messages.txt +64 -0
  161. data/test/irc/parser.rb +13 -0
  162. data/test/irc/profile_parser.rb +12 -0
  163. data/test/irc/users.rb +28 -0
  164. metadata +256 -0
@@ -0,0 +1,35 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ class Butler
10
+ class Plugin
11
+ class ConfigProxy
12
+ def initialize(config, base="")
13
+ @config = config
14
+ @base = base
15
+ end
16
+
17
+ def []=(key, value)
18
+ @config[key.empty? ? @base : "#{@base}.#{key}"] = value
19
+ end
20
+
21
+ def [](key, *args)
22
+ @config[(key.empty? ? @base : "#{@base}.#{key}"), *args]
23
+ end
24
+
25
+ def exist?(key)
26
+ @config.exist?(key.empty? ? @base : "#{@base}.#{key}")
27
+ end
28
+ alias has_key? exist?
29
+
30
+ def delete(key)
31
+ @config.delete(key.empty? ? @base : "#{@base}.#{key}")
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,85 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'butler/plugin'
10
+ require 'ostruct'
11
+
12
+
13
+
14
+ class Butler
15
+ class Plugin
16
+ class Mapper
17
+ attr_reader :authorization
18
+ attr_reader :hash
19
+ attr_reader :language
20
+ attr_reader :name
21
+ attr_reader :plugin
22
+ attr_reader :trigger
23
+ attr_reader :hash
24
+
25
+ def initialize(plugin, meth, language, expression)
26
+ @plugin = plugin
27
+ @method = meth
28
+ @language = language
29
+ @expression = expression
30
+ @trigger = expression[/^\w+/].downcase
31
+ @captures = []
32
+ @authorization = "plugin/#{@plugin.base}".freeze
33
+ @name = "mapper:#{@language}:#{@expression}".freeze
34
+ @hash = @name.hash
35
+
36
+ tiles = expression.strip.scan(/(?:^|\s)[:*]\w+(?:\[[^\]]+\])?|\S+/).map { |word|
37
+ word.strip!
38
+ case word
39
+ when /:(\w+)(.*)/
40
+ @captures << [$1.to_sym, $2]
41
+ '(\S+)'
42
+ when /\*(\w+)(.*)/
43
+ @captures << [$1.to_sym, $2]
44
+ '(\S+(?:\s+\S+)*?)'
45
+ else
46
+ Regexp.escape(word)
47
+ end
48
+ }
49
+ @regexp = Regexp.new('^'+tiles.join('\s+')+'$')
50
+ end
51
+
52
+ def invoked_by?(message)
53
+ if match = @regexp.match(message.post_arguments[0]) then
54
+ params = {}
55
+ @captures.zip(match.captures) { |(name, valid), value|
56
+ params[name] = value
57
+ }
58
+ [OpenStruct.new(params)]
59
+ else
60
+ nil
61
+ end
62
+ end
63
+
64
+ def call(message, params)
65
+ @plugin.new(message).send(@method, params)
66
+ end
67
+
68
+ def priority
69
+ -10
70
+ end
71
+
72
+ def <=>(other)
73
+ -10 <=> other.priority
74
+ end
75
+
76
+ def abort_invocations?
77
+ true
78
+ end
79
+
80
+ def eql?(other)
81
+ other.kind_of?(Mapper) && @name.eql?(other.name)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,55 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ class Butler
10
+ class Plugin
11
+ class Matcher
12
+ attr_reader :priority
13
+ attr_reader :hash
14
+ attr_reader :en_match
15
+ attr_reader :plugin
16
+
17
+ def initialize(matcher, priority=0, &callback)
18
+ @matcher = case matcher
19
+ when Hash: matcher
20
+ when Regexp: { "en" => matcher }
21
+ else raise ArgumentError, "Matcher must be either Regexp or Hash, but is #{matcher.class}"
22
+ end
23
+ raise ArgumentError, "Matcher must supply an english variant" unless @matcher["en"]
24
+ @priority = priority
25
+ @callback = callback
26
+ @en_matcher = @matcher["en"]
27
+ @hash = @en_matcher.hash
28
+ end
29
+
30
+ def name
31
+ "matcher:#{@en_matcher}"
32
+ end
33
+
34
+ def <=>(other)
35
+ @priority <=> other.priority
36
+ end
37
+
38
+ def eql?(other)
39
+ other.kind_of?(Matcher) && @en_matcher.eql?(other.en_matcher)
40
+ end
41
+
42
+ def ===(message)
43
+ if @matcher.has_key?(message.language) then
44
+ @matcher[message.language] =~ message.text
45
+ else
46
+ @matcher["en"] =~ message.text
47
+ end
48
+ end
49
+
50
+ def call(*args)
51
+ @callback.call(self, *args)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,70 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ class Butler
10
+ class Plugin
11
+ module OnHandlers
12
+ def method_added(name, *a, &b)
13
+ subscribe_on_handler(name)
14
+ end
15
+
16
+ def subscribe_on_handler(handler, priority=0)
17
+ case handler
18
+ when :on_join: subscribe(:JOIN, priority) { |listener, message| new(message).on_join(listener, message.from, message.channel) }
19
+ when :on_privmsg: subscribe(:PRIVMSG, priority) { |listener, message| new(message).on_privmsg(listener, message.from, message.text) }
20
+ when :on_notice: subscribe(:NOTICE, priority) { |listener, message| new(message).on_notice(listener, message.from, message.text) }
21
+ when :on_nick: subscribe(:NICK, priority) { |listener, message| new(message).on_nick(listener, message.from, message.old_nick) }
22
+ when :on_topic: subscribe(:TOPIC, priority) { |listener, message| new(message).on_topic(listener, message.from, message.channel, message.text) }
23
+ when :on_part: subscribe(:PART, priority) { |listener, message| new(message).on_part(listener, message.from, message.channel) }
24
+ when :on_quit: subscribe(:QUIT, priority) { |listener, message| new(message).on_quit(listener, message.from, message.text) }
25
+ when :on_kick: subscribe(:KICK, priority) { |listener, message| new(message).on_kick(listener) }
26
+ when :on_kill: subscribe(:KILL, priority) { |listener, message| new(message).on_kill(listener) }
27
+ when :on_kline: subscribe(:KLINE, priority) { |listener, message| new(message).on_kline(listener) }
28
+ when :on_invocation
29
+ subscribe(:PRIVMSG, priority) { |listener, message|
30
+ new(message).on_invocation(listener) if message.invocation
31
+ }
32
+ subscribe(:NOTICE, priority) { |listener, message|
33
+ new(message).on_invocation(listener) if message.invocation
34
+ }
35
+ when :on_ban
36
+ subscribe(:MODE, priority) { |listener, message|
37
+ bans = message.modes.select { |dir, type, target| dir == '+' && type == 'b' }
38
+ new(message).on_ban(listener, *bans.map { |a,b,c| c }) unless bans.empty?
39
+ }
40
+ when :on_unban
41
+ subscribe(:MODE, priority) { |listener, message|
42
+ unbans = message.modes.select { |dir, type, target| dir == '-' && type == 'b' }
43
+ new(message).on_unban(listener, *unbans.map { |a,b,c| c }) unless unbans.empty?
44
+ }
45
+ when :on_op
46
+ subscribe(:MODE, priority) { |listener, message|
47
+ ops = message.modes.select { |dir, type, target| dir == '+' && type == 'o' }
48
+ new(message).on_op(listener, *ops.map { |a,b,c| c }) unless ops.empty?
49
+ }
50
+ when :on_deop
51
+ subscribe(:MODE, priority) { |listener, message|
52
+ deops = message.modes.select { |dir, type, target| dir == '-' && type == 'o' }
53
+ new(message).on_deop(listener, *deops.map { |a,b,c| c }) unless deops.empty?
54
+ }
55
+ when :on_voice
56
+ subscribe(:MODE, priority) { |listener, message|
57
+ voices = message.modes.select { |dir, type, target| dir == '+' && type == 'v' }
58
+ new(message).on_voice(listener, *voices.map { |a,b,c| c }) unless voices.empty?
59
+ }
60
+ when :on_devoice
61
+ subscribe(:MODE, priority) { |listener, message|
62
+ devoices = message.modes.select { |dir, type, target| dir == '-' && type == 'v' }
63
+ new(message).on_devoice(listener, *devoices.map { |a,b,c| c }) unless devoices.empty?
64
+ }
65
+ # :on_action => :PRIVMSG, :NOTICE
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,58 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'butler/plugin'
10
+
11
+
12
+
13
+ class Butler
14
+ class Plugin
15
+ class Trigger
16
+ attr_reader :authorization
17
+ attr_reader :hash
18
+ attr_reader :language
19
+ attr_reader :name
20
+ attr_reader :plugin
21
+ attr_reader :trigger
22
+ attr_reader :hash
23
+
24
+ def initialize(plugin, language, trigger)
25
+ @plugin = plugin
26
+ @language = language
27
+ @trigger = trigger.downcase
28
+ @authorization = "plugin/#{@plugin.base}".freeze
29
+ @name = "trigger:#{@language}:#{@trigger}".freeze
30
+ @hash = @name.hash
31
+ end
32
+
33
+ def invoked_by?(message)
34
+ []
35
+ end
36
+
37
+ def call(message)
38
+ @plugin.new(message).on_trigger
39
+ end
40
+
41
+ def priority
42
+ 0
43
+ end
44
+
45
+ def <=>(other)
46
+ 0 <=> other.priority
47
+ end
48
+
49
+ def abort_invocations?
50
+ true
51
+ end
52
+
53
+ def eql?(other)
54
+ other.kind_of?(Trigger) && @name.eql?(other.name)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,147 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'scriptfile'
10
+ require 'butler/plugin'
11
+ require 'butler/dialog'
12
+ require 'iterator'
13
+ require 'log/comfort'
14
+ require 'ruby/exception/detailed'
15
+
16
+
17
+
18
+ class String
19
+ # CamelCase a string, e.g. "foo_bar" becomes "FooBar"
20
+ def camelcase
21
+ scan(/[^_]+/).map { |s| s.capitalize }.join("")
22
+ end
23
+ end
24
+
25
+ class Butler
26
+ # Plugins manages the plugins in Butler
27
+ # It uses
28
+ class Plugins
29
+ include Enumerable
30
+ include Log::Comfort
31
+
32
+ Suffix = ".rb"
33
+
34
+ attr_reader :dir
35
+ def initialize(butler, plugin_dir)
36
+ @dir = File.expand_path(plugin_dir).freeze
37
+ raise ArgumentError, "#{@dir} is not a directory" unless File.directory?(@dir)
38
+ @plugins = {}
39
+ @constants = {}
40
+ @butler = butler
41
+ end
42
+
43
+ # returns a list with first-level groups
44
+ def groups
45
+ slice = @dir.length+1..-1
46
+ Dir[@dir+"/*"].reject { |file|
47
+ File.extname(file).downcase == Suffix
48
+ }.map { |file|
49
+ file[slice]
50
+ }
51
+ end
52
+
53
+ def instances
54
+ @plugins.values
55
+ end
56
+
57
+ # returns a list with the bases of all plugins
58
+ def all
59
+ slice = @dir.length+1..-(Suffix.length+1)
60
+ Dir["#{@dir}/**/*#{Suffix}"].select { |file| File.file?(file) }.map { |file|
61
+ file[slice]
62
+ }
63
+ end
64
+
65
+ # returns a list with the names of active plugins
66
+ def active
67
+ @plugins.keys
68
+ end
69
+
70
+ def inactive
71
+ all-active
72
+ end
73
+
74
+ def loaded?(base)
75
+ @plugins.has_key?(base)
76
+ end
77
+
78
+ def [](base)
79
+ @plugins[base]
80
+ end
81
+
82
+ def identify(name)
83
+ plugin = Dir["#{@dir}/**/*#{name}#{Suffix}"].first
84
+ plugin && plugin[@dir.length+1..-(Suffix.length+1)]
85
+ end
86
+
87
+ def load_all
88
+ all.each { |base|
89
+ load(base)
90
+ }
91
+ end
92
+
93
+ def load_group(group)
94
+ slice = @dir.length+1..-(Suffix.length+1)
95
+ Dir["#{@dir}/#{group}/**/*#{Suffix}"].select { |file| File.file?(file) }.each { |file|
96
+ load(file[slice])
97
+ }
98
+ end
99
+
100
+ def load(base)
101
+ path = "#{@dir}/#{base.downcase}#{Suffix}"
102
+ name = File.basename(base).camelcase
103
+ codefile = path
104
+
105
+ unless File.file?(codefile) then
106
+ raise ArgumentError, "Invalid plugin '#{base}' (codefile: #{codefile})."
107
+ end
108
+ begin
109
+ unload(base) if loaded?(base) # a plugin may not be loaded twice
110
+ rescue Exception => e
111
+ exception(e)
112
+ end
113
+ begin
114
+ constant = "%s_%08X" % [name, rand(0xffffffff)]
115
+ end while Butler::Plugins.const_defined?(constant)
116
+ plugin = Butler::Plugins.const_set(constant, Class.new(Plugin))
117
+ plugin.log_device = @butler.log_device
118
+ begin
119
+ plugin.load_plugin(@butler, base, path)
120
+ plugin.class_eval(File.read(codefile), codefile)
121
+ plugin.on_load
122
+ rescue Exception => e
123
+ e.extend Exception::Detailed
124
+ e.prepend "Loading plugin #{base} failed."
125
+ exception(e)
126
+ end
127
+ @constants[base] = constant.freeze
128
+ @plugins[base] = plugin
129
+ end
130
+ alias reload load
131
+
132
+ def unload(base)
133
+ begin
134
+ @plugins[base].on_unload
135
+ @plugins[base].unload_plugin
136
+ rescue Exception => e
137
+ e.extend Exception::Detailed
138
+ e.prepend "Exception raised while unloading plugin #{base}."
139
+ exception(e)
140
+ end
141
+ Butler::Plugins.send(:remove_const, @constants[base])
142
+ @plugins.delete(base)
143
+ @constants.delete(base)
144
+ true
145
+ end
146
+ end
147
+ end