Sutto-marvin 0.1.20081120 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +8 -0
- data/VERSION.yml +2 -2
- data/bin/marvin +88 -55
- data/config/settings.yml.sample +6 -1
- data/config/setup.rb +19 -4
- data/handlers/debug_handler.rb +2 -2
- data/handlers/hello_world.rb +1 -1
- data/lib/marvin.rb +14 -9
- data/lib/marvin/abstract_client.rb +10 -2
- data/lib/marvin/console.rb +54 -0
- data/lib/marvin/daemon.rb +71 -0
- data/lib/marvin/distributed.rb +14 -0
- data/lib/marvin/distributed/dispatch_handler.rb +82 -0
- data/lib/marvin/distributed/drb_client.rb +78 -0
- data/lib/marvin/distributed/ring_server.rb +41 -0
- data/lib/marvin/exception_tracker.rb +1 -1
- data/lib/marvin/irc.rb +4 -5
- data/lib/marvin/irc/client.rb +13 -2
- data/lib/marvin/irc/server.rb +57 -0
- data/lib/marvin/irc/server/abstract_connection.rb +79 -0
- data/lib/marvin/irc/server/base_connection.rb +66 -0
- data/lib/marvin/irc/server/channel.rb +111 -0
- data/lib/marvin/irc/server/named_store.rb +14 -0
- data/lib/marvin/irc/server/user.rb +5 -0
- data/lib/marvin/irc/server/user/handle_mixin.rb +138 -0
- data/lib/marvin/irc/server/user_connection.rb +127 -0
- data/lib/marvin/loader.rb +111 -36
- data/lib/marvin/logger.rb +2 -2
- data/lib/marvin/options.rb +11 -2
- data/lib/marvin/parsers/command.rb +43 -11
- data/lib/marvin/settings.rb +7 -6
- data/lib/marvin/status.rb +72 -0
- data/lib/marvin/test_client.rb +5 -1
- data/lib/marvin/util.rb +18 -1
- data/script/client +2 -18
- data/script/console +9 -0
- data/script/distributed_client +9 -0
- data/script/ring_server +12 -0
- data/script/server +12 -0
- data/script/status +11 -0
- data/test/parser_comparison.rb +27 -1
- data/test/parser_test.rb +254 -10
- data/test/test_helper.rb +1 -1
- metadata +62 -8
- data/lib/marvin/drb_handler.rb +0 -12
- data/lib/marvin/irc/abstract_server.rb +0 -4
- data/lib/marvin/irc/base_server.rb +0 -11
- data/script/daemon-runner +0 -12
data/README.textile
CHANGED
@@ -79,6 +79,14 @@ commands by default:
|
|
79
79
|
|
80
80
|
As defined in handlers/hello_world.rb
|
81
81
|
|
82
|
+
h2. Thanks
|
83
|
+
|
84
|
+
Thanks goes out to the following people / projects:
|
85
|
+
|
86
|
+
* Jeff Rafter - contributed code and doc changes, now one of the co-developers.
|
87
|
+
* epitron / halogrium - For the ragel state machine used in Marvin::Parsers::RagelParser
|
88
|
+
* The creator of Ruby-IRCD - the server component is heavily influenced by / part derivative of said work.
|
89
|
+
|
82
90
|
h2. Marvin::Base - A handler starting point
|
83
91
|
|
84
92
|
The first, Marvin::Base provides a base set of methods (e.g. say,
|
data/VERSION.yml
CHANGED
data/bin/marvin
CHANGED
@@ -1,71 +1,104 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'rubygems'
|
3
3
|
require 'fileutils'
|
4
|
+
require "thor"
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
6
|
+
class Marvin < Thor
|
7
|
+
|
8
|
+
GEM_ROOT = File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
9
|
+
|
10
|
+
attr_accessor :dest
|
11
|
+
|
12
|
+
# Map default help tasks.
|
13
|
+
map ["-h", "-?", "--help", "-D"] => :help
|
14
|
+
|
15
|
+
desc "create [PATH]", "creates a new marvin app at the given path"
|
16
|
+
method_options :verbose => :boolean
|
17
|
+
def create(path)
|
18
|
+
@dest = File.expand_path(path)
|
19
|
+
@verbose = options[:verbose]
|
20
|
+
if File.directory?(@dest)
|
21
|
+
STDOUT.puts "The given directory, \"#{path}\", already exists."
|
22
|
+
exit! 1
|
23
|
+
else
|
24
|
+
say "Creating Marvin app"
|
25
|
+
say " => Making directories"
|
26
|
+
mkdir @dest
|
27
|
+
mkdir source(@dest, "script")
|
28
|
+
mkdir source(@dest, "config")
|
29
|
+
mkdir source(@dest, "handlers")
|
30
|
+
mkdir_p source(@dest, "tmp/pids")
|
31
|
+
mkdir source(@dest, "log")
|
32
|
+
mkdir source(@dest, "lib")
|
33
|
+
say " => Copying files..."
|
34
|
+
copy "config/setup.rb"
|
35
|
+
copy "config/connections.yml.sample", "config/connections.yml"
|
36
|
+
copy "config/settings.yml.sample", "config/settings.yml"
|
37
|
+
copy "handlers/hello_world.rb"
|
38
|
+
copy "handlers/debug_handler.rb"
|
39
|
+
%w(client console distributed_client ring_server status).each do |c|
|
40
|
+
copy "script/#{c}"
|
41
|
+
FileUtils.chmod 0755, source(@dest, "script/#{c}")
|
42
|
+
end
|
43
|
+
say "Done!"
|
44
|
+
end
|
28
45
|
end
|
29
|
-
|
30
|
-
|
31
|
-
|
46
|
+
|
47
|
+
desc "start [PATH]", "starts client at the given path"
|
48
|
+
def start(path = ".")
|
49
|
+
@dest = File.expand_path(path)
|
50
|
+
if File.exist?(source(@dest, "script/client"))
|
51
|
+
Dir.chdir(@dest) { exec "script/client" }
|
52
|
+
else
|
53
|
+
STDOUT.puts "Woops! #{@dest.gsub(" ", "\\ ")} doesn't look to be a marvin directory."
|
54
|
+
exit!
|
55
|
+
end
|
32
56
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
57
|
+
|
58
|
+
desc "status [PATH]", "shows status of marvin app at a given location"
|
59
|
+
def status(path = ".")
|
60
|
+
@dest = File.expand_path(path)
|
61
|
+
if File.exist?(source(@dest, "script/status"))
|
62
|
+
Dir.chdir(@dest) { exec "script/status" }
|
63
|
+
else
|
64
|
+
STDOUT.puts "Woops! #{@dest.gsub(" ", "\\ ")} doesn't look to be a marvin directory."
|
65
|
+
exit!
|
66
|
+
end
|
37
67
|
end
|
38
68
|
|
39
|
-
|
40
|
-
copy "config/settings.yml.sample", "config/settings.yml"
|
69
|
+
private
|
41
70
|
|
42
|
-
|
43
|
-
|
71
|
+
def source(*args)
|
72
|
+
File.expand_path(File.join(*args))
|
73
|
+
end
|
44
74
|
|
45
|
-
|
46
|
-
|
75
|
+
def copy(from, to = from)
|
76
|
+
s, d = source(GEM_ROOT, from), source(@dest, to)
|
77
|
+
say " --> cp #{s.gsub(" ", "\\ ")} #{d.gsub(" ", "\\ ")}" if @verbose
|
78
|
+
FileUtils.cp_r(s, d)
|
79
|
+
end
|
47
80
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
FileUtils.chmod 0755, j(DEST, "script/daemon-runner")
|
81
|
+
def mkdir(path)
|
82
|
+
say " --> mkdir #{path.gsub(" ", "\\ ")}" if @verbose
|
83
|
+
FileUtils.mkdir path
|
84
|
+
end
|
53
85
|
|
54
|
-
|
55
|
-
|
56
|
-
|
86
|
+
def mkdir_p(path)
|
87
|
+
say " --> mkdir #{path.gsub(" ", "\\ ")}" if @verbose
|
88
|
+
FileUtils.mkdir_p path
|
89
|
+
end
|
57
90
|
|
58
|
-
|
59
|
-
|
60
|
-
if !File.exist?("script/daemon-runner")
|
61
|
-
puts "Woops! This isn't a marvin directory."
|
62
|
-
exit(1)
|
91
|
+
def say(text)
|
92
|
+
STDOUT.puts text
|
63
93
|
end
|
64
|
-
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
# Check if we have arguments, we run the normal
|
98
|
+
# thor task otherwise we just print the help
|
99
|
+
# message.
|
100
|
+
if ARGV.empty?
|
101
|
+
Marvin.new.help
|
65
102
|
else
|
66
|
-
|
67
|
-
puts "Woops! This isn't a marvin directory."
|
68
|
-
exit(1)
|
69
|
-
end
|
70
|
-
exec "script/client"
|
103
|
+
Marvin.start
|
71
104
|
end
|
data/config/settings.yml.sample
CHANGED
data/config/setup.rb
CHANGED
@@ -1,14 +1,29 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
|
1
|
+
# Is loaded on setup / when handlers need to be
|
2
|
+
# registered. Use it to register handlers / do
|
3
|
+
# any repeatable setup that will happen before
|
4
|
+
# any connections are created
|
5
|
+
Marvin::Loader.before_run do
|
4
6
|
|
5
7
|
# E.G.
|
6
8
|
# MyHandler.register! (Marvin::Base subclass) or
|
7
9
|
# Marvin::Settings.default_client.register_handler my_handler (a handler instance)
|
8
10
|
|
9
|
-
#
|
11
|
+
# Register in ruby
|
12
|
+
#
|
10
13
|
# LoggingHandler.register! if Marvin::Settings.use_logging
|
11
14
|
|
15
|
+
# Conditional registration - load the distributed dispatcher
|
16
|
+
# if an actual client, otherwise use the normal handlers.
|
17
|
+
#
|
18
|
+
# if Marvin::Loader.distributed_client?
|
19
|
+
# HelloWorld.register!
|
20
|
+
# DebugHandler.register!
|
21
|
+
# else
|
22
|
+
# Marvin::Distributed::DispatchHandler.register!
|
23
|
+
# end
|
24
|
+
|
25
|
+
# And any other code here that will be run before the client
|
26
|
+
|
12
27
|
HelloWorld.register!
|
13
28
|
DebugHandler.register!
|
14
29
|
|
data/handlers/debug_handler.rb
CHANGED
data/handlers/hello_world.rb
CHANGED
data/lib/marvin.rb
CHANGED
@@ -8,8 +8,17 @@ require 'marvin/core_ext'
|
|
8
8
|
require 'marvin/exceptions'
|
9
9
|
|
10
10
|
module Marvin
|
11
|
+
module VERSION
|
12
|
+
MAJOR = 0
|
13
|
+
MINOR = 1
|
14
|
+
PATCH = 20081120
|
15
|
+
|
16
|
+
STRING = [MAJOR, MINOR, PATCH].join(".")
|
17
|
+
end
|
18
|
+
|
11
19
|
autoload :Util, 'marvin/util'
|
12
20
|
autoload :Dispatchable, 'marvin/dispatchable'
|
21
|
+
autoload :Distributed, 'marvin/distributed'
|
13
22
|
autoload :AbstractClient, 'marvin/abstract_client'
|
14
23
|
autoload :Base, 'marvin/base'
|
15
24
|
autoload :ClientMixin, 'marvin/client_mixin'
|
@@ -23,6 +32,8 @@ module Marvin
|
|
23
32
|
autoload :DataStore, 'marvin/data_store'
|
24
33
|
autoload :ExceptionTracker, 'marvin/exception_tracker'
|
25
34
|
autoload :Options, 'marvin/options'
|
35
|
+
autoload :Daemon, 'marvin/daemon'
|
36
|
+
autoload :Status, 'marvin/status'
|
26
37
|
# Parsers
|
27
38
|
autoload :AbstractParser, 'marvin/abstract_parser'
|
28
39
|
autoload :Parsers, 'marvin/parsers.rb'
|
@@ -32,14 +43,8 @@ module Marvin
|
|
32
43
|
|
33
44
|
Settings.setup # Load Settings etc.
|
34
45
|
|
35
|
-
|
36
|
-
|
37
|
-
def p(text)
|
38
|
-
res = Marvin::Parsers::SimpleParser.parse(text)
|
39
|
-
if res.blank?
|
40
|
-
puts "Unrecognized Result"
|
41
|
-
else
|
42
|
-
STDOUT.puts "Event: #{res.to_incoming_event_name}"
|
43
|
-
STDOUT.puts "Args: #{res.to_hash.inspect}"
|
46
|
+
def self.version
|
47
|
+
VERSION::STRING
|
44
48
|
end
|
49
|
+
|
45
50
|
end
|
@@ -8,6 +8,7 @@ module Marvin
|
|
8
8
|
include Marvin::Dispatchable
|
9
9
|
|
10
10
|
def initialize(opts = {})
|
11
|
+
self.original_opts = opts.dup # Copy the options so we can use them to reconnect.
|
11
12
|
self.server = opts[:server]
|
12
13
|
self.port = opts[:port]
|
13
14
|
self.default_channels = opts[:channels]
|
@@ -16,7 +17,7 @@ module Marvin
|
|
16
17
|
end
|
17
18
|
|
18
19
|
cattr_accessor :events, :configuration, :logger, :is_setup, :connections
|
19
|
-
attr_accessor :channels, :nickname, :server, :port, :nicks, :pass
|
20
|
+
attr_accessor :channels, :nickname, :server, :port, :nicks, :pass, :disconnect_expected, :original_opts
|
20
21
|
|
21
22
|
# Set the default values for the variables
|
22
23
|
self.events = []
|
@@ -43,7 +44,12 @@ module Marvin
|
|
43
44
|
logger.info "Handling disconnect for #{self.server}:#{self.port}"
|
44
45
|
self.connections.delete(self) if self.connections.include?(self)
|
45
46
|
dispatch :client_disconnected
|
46
|
-
|
47
|
+
unless self.disconnect_expected
|
48
|
+
logger.warn "Lost connection to server - adding reconnect"
|
49
|
+
self.class.add_reconnect self.original_opts
|
50
|
+
else
|
51
|
+
Marvin::Loader.stop! if self.connections.blank?
|
52
|
+
end
|
47
53
|
end
|
48
54
|
|
49
55
|
# Sets the current class-wide settings of this IRC Client
|
@@ -185,6 +191,7 @@ module Marvin
|
|
185
191
|
def join(channel)
|
186
192
|
channel = Marvin::Util.channel_name(channel)
|
187
193
|
# Record the fact we're entering the room.
|
194
|
+
# TODO: Refactor to only add the channel when we receive confirmation we've joined.
|
188
195
|
self.channels << channel
|
189
196
|
command :JOIN, channel
|
190
197
|
logger.info "Joined channel #{channel}"
|
@@ -203,6 +210,7 @@ module Marvin
|
|
203
210
|
end
|
204
211
|
|
205
212
|
def quit(reason = nil)
|
213
|
+
self.disconnect_expected = true
|
206
214
|
logger.info "Preparing to part from #{self.channels.size} channels"
|
207
215
|
self.channels.to_a.each do |chan|
|
208
216
|
logger.info "Parting from #{chan}"
|
@@ -0,0 +1,54 @@
|
|
1
|
+
MARVIN_ROOT = File.join(File.dirname(__FILE__), "../..")
|
2
|
+
Marvin::Settings.verbose = true
|
3
|
+
Marvin::Settings.log_level = :debug
|
4
|
+
Marvin::Settings.default_client = Marvin::TestClient
|
5
|
+
Marvin::Loader.run! :console
|
6
|
+
|
7
|
+
def parse(line)
|
8
|
+
Marvin::Settings.default_parser.parse(line)
|
9
|
+
end
|
10
|
+
|
11
|
+
def logger
|
12
|
+
Marvin::Logger.logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def client
|
16
|
+
$client ||= Marvin::Settings.default_client.new(:port => 6667, :server => "irc.freenode.net")
|
17
|
+
end
|
18
|
+
|
19
|
+
class ServerMock < Marvin::IRC::Server::BaseConnection
|
20
|
+
def send_line(line)
|
21
|
+
puts ">> #{line}"
|
22
|
+
end
|
23
|
+
def kill_connection!
|
24
|
+
puts "Killing connection"
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_peername
|
28
|
+
# Localhost, HTTP
|
29
|
+
"\034\036\000P\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\000"
|
30
|
+
end
|
31
|
+
|
32
|
+
def host
|
33
|
+
"localhost"
|
34
|
+
end
|
35
|
+
|
36
|
+
def port
|
37
|
+
6667
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def server(reset = false)
|
43
|
+
$server = ServerMock.new(:port => 6667, :host => "localhost") if $server.blank? || reset
|
44
|
+
return $server
|
45
|
+
end
|
46
|
+
|
47
|
+
def user(reset = false)
|
48
|
+
unless @user_created || reset
|
49
|
+
server.receive_line "NICK SuttoL"
|
50
|
+
server.receive_line "USER SuttoL 0 * :SuttoL"
|
51
|
+
@user_created = true
|
52
|
+
end
|
53
|
+
return server.connection_implementation
|
54
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Marvin
|
2
|
+
class Daemon
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def alive?(pid)
|
6
|
+
return Process.getpgid(pid) != -1
|
7
|
+
rescue Errno::ESRCH
|
8
|
+
return false
|
9
|
+
end
|
10
|
+
|
11
|
+
def kill_all(type = :all)
|
12
|
+
if type == :all
|
13
|
+
files = Dir[Marvin::Settings.root / "tmp/pids/*.pid"]
|
14
|
+
files.each { |f| kill_all_from f }
|
15
|
+
elsif type.is_a?(Symbol)
|
16
|
+
kill_all_from(pid_file_for(type))
|
17
|
+
end
|
18
|
+
return nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def daemonize!
|
22
|
+
exit if fork
|
23
|
+
Process.setsid
|
24
|
+
exit if fork
|
25
|
+
self.write_pid
|
26
|
+
File.umask 0000
|
27
|
+
STDIN.reopen "/dev/null"
|
28
|
+
STDOUT.reopen "/dev/null", "a"
|
29
|
+
STDERR.reopen STDOUT
|
30
|
+
Marvin::Settings.verbose = false
|
31
|
+
end
|
32
|
+
|
33
|
+
def cleanup!
|
34
|
+
f = Marvin::Settings.root / "tmp/pids/marvin-#{Marvin::Loader.type.to_s.underscore}.pid"
|
35
|
+
FileUtils.rm_f(f) if (pids_from(f) - Process.pid).blank?
|
36
|
+
end
|
37
|
+
|
38
|
+
def pids_for_type(type)
|
39
|
+
pids_from(pid_file_for(type))
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def kill_all_from(file)
|
45
|
+
pids = pids_from(file)
|
46
|
+
pids.each { |p| Process.kill("TERM", p) unless p == Process.pid }
|
47
|
+
FileUtils.rm_f(file)
|
48
|
+
rescue => e
|
49
|
+
STDOUT.puts e.inspect
|
50
|
+
end
|
51
|
+
|
52
|
+
def pid_file_for(type)
|
53
|
+
Marvin::Settings.root / "tmp/pids/marvin-#{type.to_s.underscore}.pid"
|
54
|
+
end
|
55
|
+
|
56
|
+
def pids_from(file)
|
57
|
+
return [] unless File.exist?(file)
|
58
|
+
pids = File.read(file)
|
59
|
+
pids = pids.split("\n").map { |l| l.strip.to_i(10) }.select { |p| alive?(p) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def write_pid
|
63
|
+
f = pid_file_for(Marvin::Loader.type)
|
64
|
+
pids = pids_from(f)
|
65
|
+
pids << Process.pid unless pids.include?(Process.pid)
|
66
|
+
File.open(f, "w+") { |f| f.puts pids.join("\n") }
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|