meimei 0.1.1
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/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
|
+
|