meimei 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +7 -0
- data/LICENSE +31 -0
- data/README +28 -0
- data/Rakefile +14 -0
- data/lib/meimei/array_extensions.rb +7 -0
- data/lib/meimei/client.rb +219 -0
- data/lib/meimei/exception_dump.rb +14 -0
- data/lib/meimei/gemspec.rb +45 -0
- data/lib/meimei/persistent_hash.rb +43 -0
- data/lib/meimei/server.rb +107 -0
- data/lib/meimei/specification.rb +128 -0
- data/lib/meimei/version.rb +18 -0
- data/lib/meimei.rb +25 -0
- metadata +66 -0
data/CHANGES
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
Copyright (c) 2008, Albert Yi
|
2
|
+
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
* Redistributions of source code must retain the above copyright notice,
|
9
|
+
this list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
this list of conditions and the following disclaimer in the
|
13
|
+
documentation and/or other materials provided with the
|
14
|
+
distribution.
|
15
|
+
|
16
|
+
* Neither the name of Albert Yi nor the
|
17
|
+
names of its contributors may be used to endorse or promote
|
18
|
+
products derived from this software without specific prior written
|
19
|
+
permission.
|
20
|
+
|
21
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
22
|
+
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
23
|
+
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
24
|
+
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
25
|
+
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
26
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
27
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
28
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
29
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
30
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
31
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
= meimei
|
2
|
+
|
3
|
+
== DESCRIPTION
|
4
|
+
Meimei is a simple IRC bot framework. It is designed to be easily extensible. In the interest of simplicity it only implements a subset RFC 1459, mainly those dealing with PRIVMSGs.
|
5
|
+
|
6
|
+
=== Usage
|
7
|
+
|
8
|
+
require 'meimei'
|
9
|
+
client = Meimei::Client.new("meimei")
|
10
|
+
client.add_server("irc.synirc.net", 6667, "#meimei, #bonklers")
|
11
|
+
client.start()
|
12
|
+
|
13
|
+
=== Plugins
|
14
|
+
|
15
|
+
# In plugins/reload.rb
|
16
|
+
define_plugin("!reload") do |msg|
|
17
|
+
load_plugins()
|
18
|
+
reply("Plugins reloaded.")
|
19
|
+
end
|
20
|
+
|
21
|
+
# In plugins/clock.rb
|
22
|
+
define_plugin("@1h") do
|
23
|
+
say("localhost", "#meimei", "One hour has passed.")
|
24
|
+
end
|
25
|
+
|
26
|
+
=== Thread Safety
|
27
|
+
|
28
|
+
Meimei does not use any global or class variables, so a single client instance should be thread safe. Obviously Meimei does make use of instance variables, so using threads from within a single client may cause issues.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# make sure our project's ./lib directory is added to the ruby search path
|
2
|
+
$: << File.join(File.dirname(__FILE__),"lib")
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require 'rake/clean'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/sshpublisher'
|
9
|
+
|
10
|
+
require 'meimei'
|
11
|
+
|
12
|
+
load 'tasks/setup.rb'
|
13
|
+
|
14
|
+
|
@@ -0,0 +1,219 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
module Meimei
|
7
|
+
class Client
|
8
|
+
# === Parameters
|
9
|
+
# - nick: The nickname your bot will use.
|
10
|
+
# - options[:username]: The username of your bot (will default to nick).
|
11
|
+
# - options[:realname]: The real name of your bot (will default to nick).
|
12
|
+
# - options[:password]: The password for your bot's account.
|
13
|
+
# - options[:plugin_dir]: An absolute path to the directory where Meimei plugins will be stored.
|
14
|
+
# - options[:log_dir]: An absolute path to where log files will be stored.
|
15
|
+
# - options[:log_level]: The log level. Can be: :error, :info, :debug.
|
16
|
+
def initialize(nick, options = {})
|
17
|
+
@servers = []
|
18
|
+
@running = false
|
19
|
+
@command_plugins = []
|
20
|
+
@timer_plugins = []
|
21
|
+
@nick = nick
|
22
|
+
@username = options[:username] || @nick
|
23
|
+
@realname = options[:realname] || @nick
|
24
|
+
@password = options[:password]
|
25
|
+
@plugin_dir = options[:plugin_dir] || "plugins"
|
26
|
+
@log_dir = options[:log_dir] || "."
|
27
|
+
@log_level = options[:log_level] || :info
|
28
|
+
@logger = Logger.new(open("#{@log_dir}/#{@nick}-#{Time.now.strftime('%Y%m%d-%H%M')}.log", "w"))
|
29
|
+
@logger.datetime_format = "%Y-%m-%d %H:%M:%S"
|
30
|
+
end
|
31
|
+
|
32
|
+
# Registers a server with the client. This method will not open a connection.
|
33
|
+
def add_server(hostname, port, autojoin)
|
34
|
+
@servers << Server.new(hostname, port, autojoin, :log_dir => @log_dir, :log_level => @log_level)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Reloads all plugins.
|
38
|
+
def load_plugins
|
39
|
+
@command_plugins.clear()
|
40
|
+
@timer_plugins.clear()
|
41
|
+
|
42
|
+
# TODO: This is hackish
|
43
|
+
Dir["#{@plugin_dir}/*.rb"].sort.each do |file|
|
44
|
+
eval(File.open(file, "r").read, binding, file)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Creates a new plugin.
|
49
|
+
#
|
50
|
+
# === Parameters
|
51
|
+
# - name: The name of the plugin. There are two special cases: (1) If the name begins with "!", then it is considered a command. For example, a plugin named "!wiki" would only be invoked if a message began with "!wiki". The message passed to the plugin will have the command stripped out. (2) If the plugin begins with "@", then it is considered a timer event. For example, a plugin named "@60" will run every 60 seconds. "@10m" will run every 10 minutes, and "@4h" will run every 4 hours.
|
52
|
+
# - options[:position]: Insert plugin at position n (lower number positions will be run first).
|
53
|
+
def define_plugin(name, options = {}, &block)
|
54
|
+
# TODO: Is there a memory leak here?
|
55
|
+
|
56
|
+
case name
|
57
|
+
when /^@(\d+)([mh])?/
|
58
|
+
interval = $1.to_i
|
59
|
+
|
60
|
+
if $2 == "m"
|
61
|
+
interval = interval * 60
|
62
|
+
elsif $2 == "h"
|
63
|
+
interval = interval * 60 * 60
|
64
|
+
end
|
65
|
+
|
66
|
+
plugins = @timer_plugins
|
67
|
+
plugin = [name, interval, Time.now, block, options]
|
68
|
+
|
69
|
+
else
|
70
|
+
plugins = @command_plugins
|
71
|
+
plugin = [name, block, options]
|
72
|
+
end
|
73
|
+
|
74
|
+
if options[:position]
|
75
|
+
plugins.insert(options[:position], plugin)
|
76
|
+
else
|
77
|
+
plugins << plugin
|
78
|
+
end
|
79
|
+
|
80
|
+
plugins.compact!
|
81
|
+
end
|
82
|
+
|
83
|
+
# Run all timer plugins.
|
84
|
+
def check_timer_plugins
|
85
|
+
@timer_plugins.each do |plugin|
|
86
|
+
begin
|
87
|
+
# Need to refer directly to the array since we'll be changing its values
|
88
|
+
# plugin[0]: name
|
89
|
+
# plugin[1]: interval
|
90
|
+
# plugin[2]: next_run_at
|
91
|
+
# plugin[3]: block
|
92
|
+
# plugin[4]: options
|
93
|
+
|
94
|
+
if plugin[2] < Time.now
|
95
|
+
plugin[3].call()
|
96
|
+
plugin[2] = Time.now + plugin[1]
|
97
|
+
end
|
98
|
+
rescue Exception => e
|
99
|
+
e.dump(@logger)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Run all command plugins.
|
105
|
+
def check_command_plugins(msg)
|
106
|
+
@command_plugins.each do |name, block, options|
|
107
|
+
begin
|
108
|
+
if name.index("!") == 0
|
109
|
+
if msg =~ /^#{name}\b/
|
110
|
+
msg = msg.gsub(/!\S+\s*/, "")
|
111
|
+
block.call(msg)
|
112
|
+
end
|
113
|
+
else
|
114
|
+
block.call(msg)
|
115
|
+
end
|
116
|
+
rescue Exception => e
|
117
|
+
reply "Error: #{e.class}"
|
118
|
+
e.dump(@current_server.logger)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns the first server that has data to be read.
|
124
|
+
def select
|
125
|
+
sockets = @servers.map {|x| x.socket}.compact
|
126
|
+
socket = Kernel.select(sockets, nil, nil, 1)
|
127
|
+
|
128
|
+
if socket == nil
|
129
|
+
return nil
|
130
|
+
else
|
131
|
+
socket = socket[0][0]
|
132
|
+
return @servers.find {|x| x.socket.__id__ == socket.__id__}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Starts the main event loop.
|
137
|
+
def start
|
138
|
+
self.load_plugins()
|
139
|
+
@running = true
|
140
|
+
|
141
|
+
while @running
|
142
|
+
@servers.each do |server|
|
143
|
+
unless server.is_connected
|
144
|
+
if server.reconnect_delay_passed? && server.connect()
|
145
|
+
server.write("PASS #{@password}") if @password
|
146
|
+
server.write("NICK #{@nick}")
|
147
|
+
server.write("USER #{@username} hostname servername :#{@username}")
|
148
|
+
server.autojoin.each do |channel|
|
149
|
+
server.write("JOIN #{channel}")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
self.check_timer_plugins()
|
155
|
+
|
156
|
+
@current_server = self.select()
|
157
|
+
|
158
|
+
if @current_server
|
159
|
+
msg = @current_server.read
|
160
|
+
|
161
|
+
if msg != nil
|
162
|
+
self.process_message(msg)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Quits all clients and ends the event loop.
|
170
|
+
def quit(msg = nil)
|
171
|
+
@servers.each do |server|
|
172
|
+
if msg
|
173
|
+
server.write("QUIT :#{msg}")
|
174
|
+
else
|
175
|
+
server.write("QUIT")
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
@running = false
|
180
|
+
end
|
181
|
+
|
182
|
+
# Respond to the current server/channel/user. If {with_nick} is true, then the messager's sender's nick will be prepended to the response.
|
183
|
+
def reply(msg, with_nick = true)
|
184
|
+
if with_nick
|
185
|
+
@current_server.write("PRIVMSG #{@current_to} :#{@current_from}: #{msg}")
|
186
|
+
else
|
187
|
+
@current_server.write("PRIVMSG #{@current_to} :#{msg}")
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Send {msg} to {to} on {server}. {to} can either be a channel or someone's nick.
|
192
|
+
def say(server, to, msg)
|
193
|
+
if server.is_a?(String)
|
194
|
+
server = @servers.find {|x| x.hostname == server}
|
195
|
+
end
|
196
|
+
|
197
|
+
server.write("PRIVMSG #{to} :#{msg}")
|
198
|
+
end
|
199
|
+
|
200
|
+
# Parses a messages and dispatches as necessary.
|
201
|
+
def process_message(msg)
|
202
|
+
@current_from = nil
|
203
|
+
@current_to = nil
|
204
|
+
@current_msg = nil
|
205
|
+
|
206
|
+
case msg
|
207
|
+
when /^:(.+?)!(.+?)@(\S+) PRIVMSG (\S+) :(.+)$/
|
208
|
+
@current_from = $1
|
209
|
+
@current_to = $4
|
210
|
+
@current_msg = $5.strip
|
211
|
+
|
212
|
+
self.check_command_plugins(@current_msg)
|
213
|
+
|
214
|
+
when /^PING (.+)$/
|
215
|
+
@current_server.write("PONG #{$1}")
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Meimei
|
2
|
+
module ExceptionDumpMethods
|
3
|
+
def dump(logger)
|
4
|
+
logger.info "* #{self.class} error thrown"
|
5
|
+
logger.info "* Message: #{self.message}"
|
6
|
+
logger.info "* Backtrace:"
|
7
|
+
self.backtrace.each do |line|
|
8
|
+
logger.info "** #{line}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Exception.__send__(:include, ExceptionDumpMethods)
|
14
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'meimei/specification'
|
3
|
+
require 'meimei/version'
|
4
|
+
require 'rake'
|
5
|
+
|
6
|
+
# The Gem Specification plus some extras for meimei.
|
7
|
+
module Meimei
|
8
|
+
SPEC = Meimei::Specification.new do |spec|
|
9
|
+
spec.name = "meimei"
|
10
|
+
spec.version = Meimei::VERSION
|
11
|
+
spec.rubyforge_project = "meimei"
|
12
|
+
spec.author = "Albert Yi"
|
13
|
+
spec.email = "r888888888@gmail.com"
|
14
|
+
spec.homepage = "http://meimei.rubyforge.org/"
|
15
|
+
|
16
|
+
spec.summary = "A simple IRC bot framework."
|
17
|
+
spec.description = <<-DESC
|
18
|
+
Meimei is a simple IRC bot framework. It is designed to be easily extensible. In the interest of simplicity it only implements a subset RFC 1459, mainly those dealing with PRIVMSGs.
|
19
|
+
DESC
|
20
|
+
|
21
|
+
spec.extra_rdoc_files = FileList["[A-Z]*"]
|
22
|
+
spec.has_rdoc = true
|
23
|
+
spec.rdoc_main = "README"
|
24
|
+
spec.rdoc_options = [ "--line-numbers" , "--inline-source" ]
|
25
|
+
|
26
|
+
spec.test_files = FileList["spec/**/*.rb", "test/**/*.rb"]
|
27
|
+
spec.files = spec.test_files + spec.extra_rdoc_files +
|
28
|
+
FileList["lib/**/*.rb", "resources/**/*"]
|
29
|
+
|
30
|
+
|
31
|
+
# add dependencies
|
32
|
+
# spec.add_dependency("somegem", ">= 0.4.2")
|
33
|
+
|
34
|
+
spec.platform = Gem::Platform::RUBY
|
35
|
+
|
36
|
+
spec.local_rdoc_dir = "doc/rdoc"
|
37
|
+
spec.remote_rdoc_dir = "#{spec.name}/rdoc"
|
38
|
+
spec.local_coverage_dir = "doc/coverage"
|
39
|
+
spec.remote_coverage_dir= "#{spec.name}/coverage"
|
40
|
+
|
41
|
+
spec.remote_site_dir = "#{spec.name}/"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class PersistentHash
|
2
|
+
def initialize(file_path, commit_interval = 1)
|
3
|
+
@commit_interval = commit_interval
|
4
|
+
@commit_count = 0
|
5
|
+
@file_path = file_path
|
6
|
+
restore!
|
7
|
+
unless @hash.is_a?(Hash)
|
8
|
+
@hash = {}
|
9
|
+
commit!
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def restore!
|
14
|
+
if File.exist?(@file_path)
|
15
|
+
mode = File::RDONLY
|
16
|
+
else
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
File.open(@file_path, mode) do |f|
|
21
|
+
@hash = Marshal.restore(f)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def commit!
|
26
|
+
File.open(@file_path, File::WRONLY | File::CREAT) do |f|
|
27
|
+
Marshal.dump(@hash, f)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def []=(key, value)
|
32
|
+
@hash[key] = value
|
33
|
+
@commit_count += 1
|
34
|
+
if @commit_count >= @commit_interval
|
35
|
+
@commit_count = 0
|
36
|
+
commit!
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(name, *params, &block)
|
41
|
+
@hash.send(name, *params, &block)
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Meimei
|
2
|
+
class Server
|
3
|
+
attr_accessor :socket, :hostname, :port, :is_connected, :logger, :autojoin
|
4
|
+
|
5
|
+
def initialize(hostname, port, autojoin, options = {})
|
6
|
+
@hostname, @port, @is_connected = hostname, port, false
|
7
|
+
@log_dir = options[:log_dir] || "."
|
8
|
+
@logger = Logger.new(open("#{@log_dir}/#{@hostname}-#{Time.now.strftime('%Y%m%d-%H%M')}.log", "w"))
|
9
|
+
@logger.datetime_format = "%Y-%m-%d %H:%M:%S"
|
10
|
+
|
11
|
+
case options[:log_level]
|
12
|
+
when :fatal
|
13
|
+
@logger.level = Logger::FATAL
|
14
|
+
|
15
|
+
when :error
|
16
|
+
@logger.level = Logger::ERROR
|
17
|
+
|
18
|
+
when :warn
|
19
|
+
@logger.level = Logger::WARN
|
20
|
+
|
21
|
+
when :info
|
22
|
+
@logger.level = Logger::INFO
|
23
|
+
|
24
|
+
when :debug
|
25
|
+
@logger.level = Logger::DEBUG
|
26
|
+
|
27
|
+
else
|
28
|
+
@logger.level = Logger::INFO
|
29
|
+
end
|
30
|
+
|
31
|
+
@autojoin = autojoin.split(/,\s*/)
|
32
|
+
end
|
33
|
+
|
34
|
+
def close
|
35
|
+
begin
|
36
|
+
@socket.close if @socket
|
37
|
+
rescue Exception => x
|
38
|
+
@is_connected = false
|
39
|
+
@logger.info "* Could not close socket"
|
40
|
+
x.dump(@logger)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def reconnect_delay_passed?
|
45
|
+
if @last_connected_at == nil
|
46
|
+
@last_connected_at = Time.now
|
47
|
+
return true
|
48
|
+
end
|
49
|
+
|
50
|
+
if Time.now - @last_connected_at > 10
|
51
|
+
@last_connected_at = Time.now
|
52
|
+
return true
|
53
|
+
else
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def connect
|
59
|
+
begin
|
60
|
+
self.close()
|
61
|
+
@socket = TCPSocket.open(@hostname, @port)
|
62
|
+
@is_connected = true
|
63
|
+
@logger.info "* Connected (resolved to #{@socket.peeraddr[3]})"
|
64
|
+
rescue Exception => x
|
65
|
+
@is_connected = false
|
66
|
+
@logger.info "* Connection failed"
|
67
|
+
x.dump(@logger)
|
68
|
+
end
|
69
|
+
|
70
|
+
return @is_connected
|
71
|
+
end
|
72
|
+
|
73
|
+
def read
|
74
|
+
begin
|
75
|
+
msg = @socket.gets
|
76
|
+
@logger.debug "> #{msg}"
|
77
|
+
return msg
|
78
|
+
rescue Errno::ECONNRESET => x
|
79
|
+
@logger.info "* Connection reset"
|
80
|
+
@is_connected = false
|
81
|
+
x.dump(@logger)
|
82
|
+
return nil
|
83
|
+
rescue Exception => x
|
84
|
+
@logger.info "* Read failed"
|
85
|
+
@is_connected = false
|
86
|
+
x.dump(@logger)
|
87
|
+
return nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def write(msg)
|
92
|
+
@logger.debug "< #{msg}"
|
93
|
+
|
94
|
+
begin
|
95
|
+
@socket.write(msg + "\n")
|
96
|
+
rescue Errno::ECONNRESET => x
|
97
|
+
@logger.info "* Connection reset"
|
98
|
+
@is_connected = false
|
99
|
+
x.dump(@logger)
|
100
|
+
rescue Exception => x
|
101
|
+
@logger.info "* Write failed"
|
102
|
+
@is_connected = false
|
103
|
+
x.dump(@logger)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rubygems/specification'
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
module Meimei
|
6
|
+
# Add some additional items to Gem::Specification
|
7
|
+
# A Meimei::Specification adds additional pieces of information the
|
8
|
+
# typical gem specification
|
9
|
+
class Specification
|
10
|
+
|
11
|
+
RUBYFORGE_ROOT = "/var/www/gforge-projects/"
|
12
|
+
|
13
|
+
# user that accesses remote site
|
14
|
+
attr_accessor :remote_user
|
15
|
+
|
16
|
+
# remote host, default 'rubyforge.org'
|
17
|
+
attr_accessor :remote_host
|
18
|
+
|
19
|
+
# name the rdoc main
|
20
|
+
attr_accessor :rdoc_main
|
21
|
+
|
22
|
+
# local directory in development holding the generated rdoc
|
23
|
+
# default 'doc'
|
24
|
+
attr_accessor :local_rdoc_dir
|
25
|
+
|
26
|
+
# remote directory for storing rdoc, default 'doc'
|
27
|
+
attr_accessor :remote_rdoc_dir
|
28
|
+
|
29
|
+
# local directory for coverage report
|
30
|
+
attr_accessor :local_coverage_dir
|
31
|
+
|
32
|
+
# remote directory for storing coverage reports
|
33
|
+
# This defaults to 'coverage'
|
34
|
+
attr_accessor :remote_coverage_dir
|
35
|
+
|
36
|
+
# local directory for generated website, default +site/public+
|
37
|
+
attr_accessor :local_site_dir
|
38
|
+
|
39
|
+
# remote directory relative to +remote_root+ for the website.
|
40
|
+
# website.
|
41
|
+
attr_accessor :remote_site_dir
|
42
|
+
|
43
|
+
# is a .tgz to be created?, default 'true'
|
44
|
+
attr_accessor :need_tar
|
45
|
+
|
46
|
+
# is a .zip to be created, default 'true'
|
47
|
+
attr_accessor :need_zip
|
48
|
+
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
@remote_user = nil
|
52
|
+
@remote_host = "rubyforge.org"
|
53
|
+
|
54
|
+
@rdoc_main = "README"
|
55
|
+
@local_rdoc_dir = "doc"
|
56
|
+
@remote_rdoc_dir = "doc"
|
57
|
+
@local_coverage_dir = "coverage"
|
58
|
+
@remote_coverage_dir = "coverage"
|
59
|
+
@local_site_dir = "site/public"
|
60
|
+
@remote_site_dir = "."
|
61
|
+
|
62
|
+
@need_tar = true
|
63
|
+
@need_zip = true
|
64
|
+
|
65
|
+
@spec = Gem::Specification.new
|
66
|
+
|
67
|
+
yield self if block_given?
|
68
|
+
|
69
|
+
# update rdoc options to take care of the rdoc_main if it is
|
70
|
+
# there, and add a default title if one is not given
|
71
|
+
if not @spec.rdoc_options.include?("--main") then
|
72
|
+
@spec.rdoc_options.concat(["--main", rdoc_main])
|
73
|
+
end
|
74
|
+
|
75
|
+
if not @spec.rdoc_options.include?("--title") then
|
76
|
+
@spec.rdoc_options.concat(["--title","'#{name} -- #{summary}'"])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# if this gets set then it overwrites what would be the
|
81
|
+
# rubyforge default. If rubyforge project is not set then use
|
82
|
+
# name. If rubyforge project and name are set, but they are
|
83
|
+
# different then assume that name is a subproject of the
|
84
|
+
# rubyforge project
|
85
|
+
def remote_root
|
86
|
+
if rubyforge_project.nil? or
|
87
|
+
rubyforge_project == name then
|
88
|
+
return RUBYFORGE_ROOT + "#{name}/"
|
89
|
+
else
|
90
|
+
return RUBYFORGE_ROOT + "#{rubyforge_project}/#{name}/"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# rdoc files is the same as what would be generated during gem
|
95
|
+
# installation. That is, everything in the require paths plus
|
96
|
+
# the rdoc_extra_files
|
97
|
+
#
|
98
|
+
def rdoc_files
|
99
|
+
flist = extra_rdoc_files.dup
|
100
|
+
@spec.require_paths.each do |rp|
|
101
|
+
flist << FileList["#{rp}/**/*.rb"]
|
102
|
+
end
|
103
|
+
flist.flatten.uniq
|
104
|
+
end
|
105
|
+
|
106
|
+
# calculate the remote directories
|
107
|
+
def remote_root_location
|
108
|
+
"#{remote_user}@#{remote_host}:#{remote_root}"
|
109
|
+
end
|
110
|
+
|
111
|
+
def remote_rdoc_location
|
112
|
+
remote_root_location + @remote_rdoc_dir
|
113
|
+
end
|
114
|
+
|
115
|
+
def remote_coverage_location
|
116
|
+
remote_root_loation + @remote_coverage_dir
|
117
|
+
end
|
118
|
+
|
119
|
+
def remote_site_location
|
120
|
+
remote_root_location + @remote_site_dir
|
121
|
+
end
|
122
|
+
|
123
|
+
# we delegate any other calls to spec
|
124
|
+
def method_missing(method_id,*params,&block)
|
125
|
+
@spec.send method_id, *params, &block
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/lib/meimei.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Meimei
|
2
|
+
|
3
|
+
ROOT_DIR = File.expand_path(File.join(File.dirname(__FILE__),".."))
|
4
|
+
LIB_DIR = File.join(ROOT_DIR,"lib").freeze
|
5
|
+
RESOURCE_DIR = File.join(ROOT_DIR,"resources").freeze
|
6
|
+
|
7
|
+
#
|
8
|
+
# Utility method to require all files ending in .rb in the directory
|
9
|
+
# with the same name as this file minus .rb
|
10
|
+
#
|
11
|
+
def require_all_libs_relative_to(fname)
|
12
|
+
prepend = File.basename(fname,".rb")
|
13
|
+
search_me = File.join(File.dirname(fname),prepend)
|
14
|
+
|
15
|
+
Dir.entries(search_me).each do |rb|
|
16
|
+
if File.extname(rb) == ".rb" then
|
17
|
+
require "#{prepend}/#{File.basename(rb,".rb")}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
module_function :require_all_libs_relative_to
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
Meimei.require_all_libs_relative_to(__FILE__)
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.2
|
3
|
+
specification_version: 1
|
4
|
+
name: meimei
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.1
|
7
|
+
date: 2008-02-06 00:00:00 -05:00
|
8
|
+
summary: A simple IRC bot framework.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: r888888888@gmail.com
|
12
|
+
homepage: http://meimei.rubyforge.org/
|
13
|
+
rubyforge_project: meimei
|
14
|
+
description: Meimei is a simple IRC bot framework. It is designed to be easily extensible. In the interest of simplicity it only implements a subset RFC 1459, mainly those dealing with PRIVMSGs.
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Albert Yi
|
31
|
+
files:
|
32
|
+
- CHANGES
|
33
|
+
- LICENSE
|
34
|
+
- Rakefile
|
35
|
+
- README
|
36
|
+
- lib/meimei/array_extensions.rb
|
37
|
+
- lib/meimei/client.rb
|
38
|
+
- lib/meimei/exception_dump.rb
|
39
|
+
- lib/meimei/gemspec.rb
|
40
|
+
- lib/meimei/persistent_hash.rb
|
41
|
+
- lib/meimei/server.rb
|
42
|
+
- lib/meimei/specification.rb
|
43
|
+
- lib/meimei/version.rb
|
44
|
+
- lib/meimei.rb
|
45
|
+
test_files: []
|
46
|
+
|
47
|
+
rdoc_options:
|
48
|
+
- --line-numbers
|
49
|
+
- --inline-source
|
50
|
+
- --main
|
51
|
+
- README
|
52
|
+
- --title
|
53
|
+
- "'meimei -- A simple IRC bot framework.'"
|
54
|
+
extra_rdoc_files:
|
55
|
+
- CHANGES
|
56
|
+
- LICENSE
|
57
|
+
- Rakefile
|
58
|
+
- README
|
59
|
+
executables: []
|
60
|
+
|
61
|
+
extensions: []
|
62
|
+
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
dependencies: []
|
66
|
+
|