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.
Files changed (48) hide show
  1. data/README.textile +8 -0
  2. data/VERSION.yml +2 -2
  3. data/bin/marvin +88 -55
  4. data/config/settings.yml.sample +6 -1
  5. data/config/setup.rb +19 -4
  6. data/handlers/debug_handler.rb +2 -2
  7. data/handlers/hello_world.rb +1 -1
  8. data/lib/marvin.rb +14 -9
  9. data/lib/marvin/abstract_client.rb +10 -2
  10. data/lib/marvin/console.rb +54 -0
  11. data/lib/marvin/daemon.rb +71 -0
  12. data/lib/marvin/distributed.rb +14 -0
  13. data/lib/marvin/distributed/dispatch_handler.rb +82 -0
  14. data/lib/marvin/distributed/drb_client.rb +78 -0
  15. data/lib/marvin/distributed/ring_server.rb +41 -0
  16. data/lib/marvin/exception_tracker.rb +1 -1
  17. data/lib/marvin/irc.rb +4 -5
  18. data/lib/marvin/irc/client.rb +13 -2
  19. data/lib/marvin/irc/server.rb +57 -0
  20. data/lib/marvin/irc/server/abstract_connection.rb +79 -0
  21. data/lib/marvin/irc/server/base_connection.rb +66 -0
  22. data/lib/marvin/irc/server/channel.rb +111 -0
  23. data/lib/marvin/irc/server/named_store.rb +14 -0
  24. data/lib/marvin/irc/server/user.rb +5 -0
  25. data/lib/marvin/irc/server/user/handle_mixin.rb +138 -0
  26. data/lib/marvin/irc/server/user_connection.rb +127 -0
  27. data/lib/marvin/loader.rb +111 -36
  28. data/lib/marvin/logger.rb +2 -2
  29. data/lib/marvin/options.rb +11 -2
  30. data/lib/marvin/parsers/command.rb +43 -11
  31. data/lib/marvin/settings.rb +7 -6
  32. data/lib/marvin/status.rb +72 -0
  33. data/lib/marvin/test_client.rb +5 -1
  34. data/lib/marvin/util.rb +18 -1
  35. data/script/client +2 -18
  36. data/script/console +9 -0
  37. data/script/distributed_client +9 -0
  38. data/script/ring_server +12 -0
  39. data/script/server +12 -0
  40. data/script/status +11 -0
  41. data/test/parser_comparison.rb +27 -1
  42. data/test/parser_test.rb +254 -10
  43. data/test/test_helper.rb +1 -1
  44. metadata +62 -8
  45. data/lib/marvin/drb_handler.rb +0 -12
  46. data/lib/marvin/irc/abstract_server.rb +0 -4
  47. data/lib/marvin/irc/base_server.rb +0 -11
  48. 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
@@ -1,4 +1,4 @@
1
1
  ---
2
- patch: 20081120
2
+ patch: 0
3
3
  major: 0
4
- minor: 1
4
+ minor: 2
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
- LOCATION_ROOT = File.join(File.dirname(__FILE__), "..")
6
- DEST = ARGV[1] || "./marvin"
7
-
8
- def j(*args); File.join(*args); end
9
-
10
- def copy(f, t = nil)
11
- t = f if t.nil?
12
- File.open(j(DEST, t), "w+") do |file|
13
- file.puts File.read(j(LOCATION_ROOT, f))
14
- end
15
- end
16
-
17
- puts "Marvin - A Ruby IRC Library / Framework"
18
- if ARGV.include?("-h") || ARGV.include?("--help")
19
- puts "Usage: marvin create <name> - Creates a marvin directory at name or ./marvin"
20
- puts " marvin (in a Marvin dir) - Starts it, equiv. to script/marvin"
21
- exit
22
- end
23
-
24
- if ARGV.length >= 1 && !["start", "stop", "run", "restart"].include?(ARGV[0])
25
- if ARGV[0].to_s.downcase != "create"
26
- puts "'#{ARGV[0]}' isn't a valid command. - Please use #{__FILE__} --help"
27
- exit(1)
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
- if File.exist?(DEST) && File.directory?(DEST)
30
- puts "The folder '#{DEST}' already exists."
31
- exit(1)
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
- # Generate it.
34
- FileUtils.mkdir(DEST)
35
- ["log", "tmp", "config", "handlers", "script"].each do |folder|
36
- FileUtils.mkdir(j(DEST, folder))
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
- puts "Writing Settings file"
40
- copy "config/settings.yml.sample", "config/settings.yml"
69
+ private
41
70
 
42
- puts "Writing Connections file"
43
- copy "config/connections.yml.sample", "config/connections.yml"
71
+ def source(*args)
72
+ File.expand_path(File.join(*args))
73
+ end
44
74
 
45
- puts "Writing setup.rb"
46
- copy "config/setup.rb"
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
- puts "Copying start scripts"
49
- copy "script/client"
50
- copy "script/daemon-runner"
51
- FileUtils.chmod 0755, j(DEST, "script/client")
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
- puts "Copying example handlers"
55
- copy "handlers/hello_world.rb"
56
- copy "handlers/debug_handler.rb"
86
+ def mkdir_p(path)
87
+ say " --> mkdir #{path.gsub(" ", "\\ ")}" if @verbose
88
+ FileUtils.mkdir_p path
89
+ end
57
90
 
58
- puts "Done!"
59
- elsif ARGV.length >= 1
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
- exec "script/daemon-runner #{ARGV.map {|a| a.include?(" ") ? "\"#{a}\"" : a }.join(" ")}"
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
- if !File.exist?("script/client")
67
- puts "Woops! This isn't a marvin directory."
68
- exit(1)
69
- end
70
- exec "script/client"
103
+ Marvin.start
71
104
  end
@@ -2,7 +2,12 @@ default:
2
2
  name: Marvin
3
3
  use_logging: false
4
4
  datastore_location: tmp/datastore.json
5
+ nicks:
6
+ - MarvinBot1000
7
+ - MarvinBot2000
8
+ - MarvinBot3000
9
+ - MarvinusBoticus
5
10
  development:
6
11
  user: MarvinBot
7
12
  name: MarvinBot
8
- nick: Marvin
13
+ nick: MarvinBot
data/config/setup.rb CHANGED
@@ -1,14 +1,29 @@
1
- # Register all of the handlers you wish to use
2
- # when the clien connects.
3
- Marvin::Loader.before_connecting do
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
- # Example Handler use.
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
 
@@ -1,5 +1,5 @@
1
- # Used for debugging stuff - trying
2
- # adding misc stuff to play around with it
1
+ # Use this class to debug stuff as you
2
+ # go along - e.g. dump events etc.
3
3
  class DebugHandler < Marvin::Base
4
4
 
5
5
  end
@@ -3,7 +3,7 @@ class HelloWorld < Marvin::CommandHandler
3
3
  exposes :hello
4
4
 
5
5
  def hello(data)
6
- reply "Hola!"
6
+ reply "Hola from process with pid #{Process.pid}!"
7
7
  end
8
8
 
9
9
  end
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
- end
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
- Marvin::Loader.stop! if self.connections.blank?
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