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.
- data/CHANGELOG +4 -0
- data/GPL.txt +340 -0
- data/LICENSE.txt +52 -0
- data/README +37 -0
- data/Rakefile +334 -0
- data/bin/botcontrol +230 -0
- data/data/butler/config_template.yaml +4 -0
- data/data/butler/dialogs/backup.rb +19 -0
- data/data/butler/dialogs/botcontrol.rb +4 -0
- data/data/butler/dialogs/config.rb +1 -0
- data/data/butler/dialogs/create.rb +53 -0
- data/data/butler/dialogs/delete.rb +3 -0
- data/data/butler/dialogs/en/backup.yaml +6 -0
- data/data/butler/dialogs/en/botcontrol.yaml +5 -0
- data/data/butler/dialogs/en/create.yaml +11 -0
- data/data/butler/dialogs/en/delete.yaml +2 -0
- data/data/butler/dialogs/en/help.yaml +17 -0
- data/data/butler/dialogs/en/info.yaml +13 -0
- data/data/butler/dialogs/en/list.yaml +4 -0
- data/data/butler/dialogs/en/notyetimplemented.yaml +2 -0
- data/data/butler/dialogs/en/rename.yaml +3 -0
- data/data/butler/dialogs/en/start.yaml +3 -0
- data/data/butler/dialogs/en/sync_plugins.yaml +3 -0
- data/data/butler/dialogs/en/uninstall.yaml +5 -0
- data/data/butler/dialogs/en/unknown_command.yaml +2 -0
- data/data/butler/dialogs/help.rb +11 -0
- data/data/butler/dialogs/info.rb +27 -0
- data/data/butler/dialogs/interactive.rb +1 -0
- data/data/butler/dialogs/list.rb +10 -0
- data/data/butler/dialogs/notyetimplemented.rb +1 -0
- data/data/butler/dialogs/rename.rb +4 -0
- data/data/butler/dialogs/selectbot.rb +2 -0
- data/data/butler/dialogs/start.rb +5 -0
- data/data/butler/dialogs/sync_plugins.rb +30 -0
- data/data/butler/dialogs/uninstall.rb +17 -0
- data/data/butler/dialogs/unknown_command.rb +1 -0
- data/data/butler/plugins/core/logout.rb +41 -0
- data/data/butler/plugins/core/plugins.rb +134 -0
- data/data/butler/plugins/core/privilege.rb +103 -0
- data/data/butler/plugins/core/user.rb +166 -0
- data/data/butler/plugins/dev/eval.rb +64 -0
- data/data/butler/plugins/dev/nometa.rb +14 -0
- data/data/butler/plugins/dev/onhandlers.rb +93 -0
- data/data/butler/plugins/dev/raw.rb +36 -0
- data/data/butler/plugins/dev/rawlog.rb +77 -0
- data/data/butler/plugins/games/eightball.rb +54 -0
- data/data/butler/plugins/games/mastermind.rb +174 -0
- data/data/butler/plugins/irc/action.rb +36 -0
- data/data/butler/plugins/irc/join.rb +38 -0
- data/data/butler/plugins/irc/notice.rb +36 -0
- data/data/butler/plugins/irc/part.rb +38 -0
- data/data/butler/plugins/irc/privmsg.rb +36 -0
- data/data/butler/plugins/irc/quit.rb +36 -0
- data/data/butler/plugins/operator/deop.rb +41 -0
- data/data/butler/plugins/operator/devoice.rb +41 -0
- data/data/butler/plugins/operator/limit.rb +47 -0
- data/data/butler/plugins/operator/op.rb +41 -0
- data/data/butler/plugins/operator/voice.rb +41 -0
- data/data/butler/plugins/public/help.rb +69 -0
- data/data/butler/plugins/public/login.rb +72 -0
- data/data/butler/plugins/public/usage.rb +49 -0
- data/data/butler/plugins/service/clones.rb +56 -0
- data/data/butler/plugins/service/define.rb +47 -0
- data/data/butler/plugins/service/log.rb +183 -0
- data/data/butler/plugins/service/svn.rb +91 -0
- data/data/butler/plugins/util/cycle.rb +98 -0
- data/data/butler/plugins/util/load.rb +41 -0
- data/data/butler/plugins/util/pong.rb +29 -0
- data/data/butler/strings/random/acknowledge.en.yaml +5 -0
- data/data/butler/strings/random/gratitude.en.yaml +3 -0
- data/data/butler/strings/random/hello.en.yaml +4 -0
- data/data/butler/strings/random/ignorance.en.yaml +7 -0
- data/data/butler/strings/random/ignorance_about.en.yaml +3 -0
- data/data/butler/strings/random/insult.en.yaml +3 -0
- data/data/butler/strings/random/rejection.en.yaml +12 -0
- data/data/man/botcontrol.1 +17 -0
- data/lib/access.rb +187 -0
- data/lib/access/admin.rb +16 -0
- data/lib/access/privilege.rb +122 -0
- data/lib/access/role.rb +102 -0
- data/lib/access/savable.rb +18 -0
- data/lib/access/user.rb +180 -0
- data/lib/access/yamlbase.rb +126 -0
- data/lib/butler.rb +188 -0
- data/lib/butler/bot.rb +247 -0
- data/lib/butler/control.rb +93 -0
- data/lib/butler/dialog.rb +64 -0
- data/lib/butler/initialvalues.rb +40 -0
- data/lib/butler/irc/channel.rb +135 -0
- data/lib/butler/irc/channels.rb +96 -0
- data/lib/butler/irc/client.rb +351 -0
- data/lib/butler/irc/hostmask.rb +53 -0
- data/lib/butler/irc/message.rb +184 -0
- data/lib/butler/irc/parser.rb +125 -0
- data/lib/butler/irc/parser/commands.rb +83 -0
- data/lib/butler/irc/parser/generic.rb +343 -0
- data/lib/butler/irc/socket.rb +378 -0
- data/lib/butler/irc/string.rb +186 -0
- data/lib/butler/irc/topic.rb +15 -0
- data/lib/butler/irc/user.rb +265 -0
- data/lib/butler/irc/users.rb +112 -0
- data/lib/butler/plugin.rb +249 -0
- data/lib/butler/plugin/configproxy.rb +35 -0
- data/lib/butler/plugin/mapper.rb +85 -0
- data/lib/butler/plugin/matcher.rb +55 -0
- data/lib/butler/plugin/onhandlers.rb +70 -0
- data/lib/butler/plugin/trigger.rb +58 -0
- data/lib/butler/plugins.rb +147 -0
- data/lib/butler/version.rb +17 -0
- data/lib/cloptions.rb +217 -0
- data/lib/cloptions/adapters.rb +24 -0
- data/lib/cloptions/switch.rb +132 -0
- data/lib/configuration.rb +223 -0
- data/lib/dialogline.rb +296 -0
- data/lib/dialogline/localizations.rb +24 -0
- data/lib/durations.rb +57 -0
- data/lib/event.rb +295 -0
- data/lib/event/at.rb +64 -0
- data/lib/event/every.rb +56 -0
- data/lib/event/timed.rb +112 -0
- data/lib/installer.rb +75 -0
- data/lib/iterator.rb +34 -0
- data/lib/log.rb +68 -0
- data/lib/log/comfort.rb +85 -0
- data/lib/log/converter.rb +23 -0
- data/lib/log/entry.rb +152 -0
- data/lib/log/fakeio.rb +55 -0
- data/lib/log/file.rb +54 -0
- data/lib/log/filereader.rb +81 -0
- data/lib/log/forward.rb +49 -0
- data/lib/log/methods.rb +39 -0
- data/lib/log/nolog.rb +18 -0
- data/lib/log/splitter.rb +26 -0
- data/lib/ostructfixed.rb +26 -0
- data/lib/ruby/array/columnize.rb +38 -0
- data/lib/ruby/dir/mktree.rb +28 -0
- data/lib/ruby/enumerable/join.rb +13 -0
- data/lib/ruby/exception/detailed.rb +24 -0
- data/lib/ruby/file/append.rb +11 -0
- data/lib/ruby/file/write.rb +11 -0
- data/lib/ruby/hash/zip.rb +15 -0
- data/lib/ruby/kernel/bench.rb +15 -0
- data/lib/ruby/kernel/daemonize.rb +42 -0
- data/lib/ruby/kernel/non_verbose.rb +17 -0
- data/lib/ruby/kernel/safe_fork.rb +18 -0
- data/lib/ruby/range/stepped.rb +11 -0
- data/lib/ruby/string/arguments.rb +72 -0
- data/lib/ruby/string/chunks.rb +15 -0
- data/lib/ruby/string/post_arguments.rb +44 -0
- data/lib/ruby/string/unescaped.rb +17 -0
- data/lib/scheduler.rb +164 -0
- data/lib/scriptfile.rb +101 -0
- data/lib/templater.rb +86 -0
- data/test/cloptions.rb +134 -0
- data/test/cv.rb +28 -0
- data/test/irc/client.rb +85 -0
- data/test/irc/client_login.txt +53 -0
- data/test/irc/client_subscribe.txt +8 -0
- data/test/irc/message.rb +30 -0
- data/test/irc/messages.txt +64 -0
- data/test/irc/parser.rb +13 -0
- data/test/irc/profile_parser.rb +12 -0
- data/test/irc/users.rb +28 -0
- 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
|