commit d31869c4f952035431730eb91c9d3190be16ea8a
Author: Marc Dequenes <duck@Orfeo.duckcorp.org>
Date:   Sun Feb 15 21:40:18 2009 +0100

    [evol] split test file into libs

diff --git a/guard b/guard
new file mode 100755
index 0000000..0ef803f
--- /dev/null
+++ b/guard
@@ -0,0 +1,74 @@
+#!/usr/bin/ruby -Ku
+
+$: << "./lib"
+
+require 'cyborghood/cyborg'
+
+module CyborgHood
+  class Guard < Cyborg
+    ROOM_PARAMS = {
+      'muc#roomconfig_allowinvites'	=> 0,
+      'muc#roomconfig_changesubject'	=> 0,
+      'muc#roomconfig_membersonly'	=> 1,
+      'muc#roomconfig_publicroom'	=> 1,
+      'muc#roomconfig_persistentroom'	=> 1,
+      'muc#roomconfig_roomsecret'	=> 'moderators',
+    }
+
+    def initialize
+      super {
+        @in_room = true
+      }
+    end
+
+    def run
+      super do
+        logger.debug "COIN !"
+        p @muc.roster.collect{|k, v| [k, v.show] }
+        #sleep 3
+        Thread.stop
+        p "pompompom"
+        p @muc.roster.collect{|k, v| [k, v.show] }
+      end
+    end
+
+    private
+
+    def on_missing_room
+      logger.info "CyborgHood HeadQuarters meeting room does not exist yet; creating..."
+
+      logger.debug "Fetching and checking room config form"
+      room_config_form = @muc.get_room_configuration
+      missing_params = ROOM_PARAMS.keys - room_config_form
+      unless missing_params.empty?
+        logger.fatal "CyborgHood HeadQuarters do not provide the following meeting room facilities: " + missing_params.join(", ")
+        quit(true)
+      end
+
+      logger.debug "Setting room configuration"
+      @muc.submit_room_configuration(ROOM_PARAMS)
+    end
+
+    def on_existing_room
+      logger.debug "Checking ownership"
+      unless @muc.owner?
+        logger.fatal "CyborgHood HeadQuarters meeting room has been taken over !!!"
+        quit(true)
+      end
+    end
+  end
+end
+
+#Jabber::debug = true
+
+bot = CyborgHood::Guard.new
+
+trap('INT') do
+  bot.ask_to_stop
+end
+trap('TERM') do
+  bot.ask_to_stop
+end
+
+bot.run
+
diff --git a/lib/cyborghood/base.rb b/lib/cyborghood/base.rb
new file mode 100644
index 0000000..54a2f07
--- /dev/null
+++ b/lib/cyborghood/base.rb
@@ -0,0 +1,105 @@
+$KCODE = 'UTF8'
+require 'jcode'
+require 'singleton'
+require 'yaml'
+require 'log4r'
+require "cyborghood/lang_additions"
+
+module CyborgHood
+
+  PRODUCT = "CyborgHood"
+  VERSION = "0.0.1"
+
+  class Logger <  Log4r::Logger
+    include Singleton
+
+    LOG_FORMAT = "[%5l - %d] %m"
+
+    def output_level(level)
+      l = case level
+      when :verbose
+        Log4r::DEBUG
+      when :quiet
+        Log4r::FATAL
+      else # normal
+        Log4r::WARN
+      end
+      @main_outputter.level = l
+    end
+
+    def log_to_file(filename)
+      file_outputter = Log4r::FileOutputter.new(filename, :filename => filename)
+      file_outputter.formatter = @default_formatter
+      self.outputters << file_outputter
+    end
+
+    private
+
+    def initialize
+      super(PRODUCT)
+
+      self.level = Log4r::DEBUG
+      @default_formatter = Log4r::PatternFormatter.new(:pattern => LOG_FORMAT)
+      @main_outputter = Log4r::Outputter.stdout
+      @main_outputter.formatter = @default_formatter
+      @main_outputter.level = Log4r::WARN
+      self.outputters = [@main_outputter]
+    end
+  end
+
+  class Config
+    include Singleton
+
+    CFG_DIR = './config/'
+
+    CONFFILE_GLOBAL = "cyborghood.conf"
+    CONFFILE_BOT = "cyborghood_%s.conf"
+
+    def self.load(name = nil)
+      # load all config parts
+      config = fetch_config
+      config = merge_configs(config, fetch_config(name)) unless name.nil?
+
+      # create config object for easier access
+      @@config = config.to_ostruct.freeze
+    end
+
+    def method_missing(sym, *args, &block)
+      begin
+        @@config.send sym, *args, &block
+      rescue
+        nil
+      end
+    end
+
+    private
+
+    def self.fetch_config(name = nil)
+      begin
+        if name.nil?
+          filename = CONFFILE_GLOBAL
+        else
+          filename = sprintf(CONFFILE_BOT, name)
+        end
+        filepath = File.join(File.expand_path(CFG_DIR), filename)
+        str = File.read(filepath)
+        YAML.load(str)
+      rescue
+        logger.fatal "Problems fetching configuration file '" + filepath + "': " + $!
+        exit 1
+      end
+    end
+
+    def self.merge_configs(conf1, conf2)
+      new_conf = conf1.dup
+      conf2.each_pair do |k, v|
+        if conf1.has_key?(k) and conf1[k].is_a?(Hash) and v.is_a?(Hash)
+          new_conf[k] = merge_configs(conf1[k], v)
+        else
+          new_conf[k] = v
+        end
+      end
+      return new_conf
+    end
+  end
+end
diff --git a/lib/cyborghood/cyborg.rb b/lib/cyborghood/cyborg.rb
new file mode 100755
index 0000000..eee811a
--- /dev/null
+++ b/lib/cyborghood/cyborg.rb
@@ -0,0 +1,229 @@
+require 'cyborghood/base'
+require 'xmpp4r'
+require 'xmpp4r/presence'
+require 'xmpp4r/version/iq/version'
+require 'xmpp4r/version/helper/simpleresponder'
+require 'xmpp4r/caps'
+require 'xmpp4r/discovery'
+#require 'xmpp4r/vcard/helper/vcard'
+require 'xmpp4r/muc/helper/mucbrowser'
+require 'xmpp4r/muc/helper/simplemucclient'
+
+
+module CyborgHood
+  class Cyborg
+    attr_reader :jid, :room_jid, :chat_jid
+
+    FEATURE_NS = 'http://duckcorp.org/protocol/cyborghood'
+
+    def initialize()
+      # load config
+      Config.load(self.human_name.downcase)
+      @config = Config.instance
+
+      # setup logs
+      unless @config.log.nil?
+        logger.output_level(@config.log.console_level) unless @config.log.console_level.nil?
+        logger.log_to_file(@config.log.file) unless @config.log.file.nil?
+      end
+
+      # prepare
+      clear
+      @jid = Jabber::JID.new(@config.xmpp.jid)
+      @room_jid = Jabber::JID.new(@config.xmpp.muc).bare
+
+      yield if block_given?
+
+      logger.info "Bot '#{self.human_name}' loaded"
+    end
+
+    def run
+      logger.info "Initializing bot..."
+
+      begin
+        @cl = Jabber::Client.new(@jid)
+        @cl.allow_tls = true
+        @cl.ssl_capath = @config.xmpp.ssl_capath unless @config.xmpp.ssl_capath == nil
+        reconnect
+      rescue Jabber::ServerError => e
+        if e.error.type == :cancel and e.error.code == 409
+          if @nick_index < 100
+            retry
+          else
+            logger.fatal "Cannot connect to CyborgHood HeadQuarters because my Nickname range is full (maybe a security problem)"
+            quit(true)
+          end
+        else
+          logger.fatal "Cannot connect to CyborgHood HeadQuarters: " + e.error.text
+          quit(true)
+        end
+      end
+
+      @cl.on_exception { sleep 5; reconnect }
+
+      logger.info "Bot ready for work"
+
+      @run_thread = Thread.new { yield }
+      @run_thread.join
+
+      logger.info "Bot work is finished, going to sleep..."
+      quit
+    end
+
+    def ask_to_stop
+      @should_stop = true
+      @run_thread.wakeup if @run_thread.stop?
+    end
+
+    protected
+
+    def quit(fatal = false)
+      if @cl
+        @muc.exit if @muc and @muc.active?
+        @cl.close
+      end
+      clear
+      exit 1 if fatal
+    end
+
+    def advertise_presence(available)
+      show = (available ? :chat : :dnd)
+      @cl.send(Jabber::Presence.new.set_show(show))
+    end
+
+    def advertise_presence_in_room(available)
+      show = (available ? :chat : :dnd)
+      stanza = Jabber::Presence.new.set_show(show)
+      stanza.from = @jid.to_s
+      stanza.to = @chat_jid.to_s
+      @cl.send(stanza)
+    end
+
+    def discover_features(jid)
+      result = discover(jid)
+      result.nil? ? [] : result.features
+    end
+
+    def room_members
+      @muc.roster.keys.sort
+    end
+
+    def unnormalize_message(msg)
+      msg2 = msg.dup
+      REXML::DocType::DEFAULT_ENTITIES.each_value do |entity|
+        msg2.gsub!("&#{entity.name};", entity.value)
+      end
+      msg2.gsub!( '&amp;', '&' )
+      return msg2
+    end
+
+    private
+
+    def reconnect
+      logger.debug "Connecting to the XMPP server..."
+      @cl.connect
+      @cl.auth(@config.xmpp.passwd)
+
+      unless @cl.is_tls?
+        logger.fatal "Connection to CyborgHood HeadQuarters is not encrypted; please configure TLS support !"
+        quit(true)
+      end
+
+      # initial presence before being ready
+      logger.debug "Advertising unavailable status"
+      advertise_presence(false)
+
+      # version
+      logger.debug "Starting version responder"
+      Jabber::Version::SimpleResponder.new(@cl, PRODUCT, VERSION, "")
+
+      # advertise discovery info
+      logger.debug "Starting capabilities responder"
+      Jabber::Caps::Helper.new(@cl,
+        [Jabber::Discovery::Identity.new('client', self.human_name, 'bot')],
+        [Jabber::Discovery::Feature.new(FEATURE_NS)]
+      )
+
+      # enter MUC room
+      knock_at_room_door unless @in_room
+      enter_room
+
+      # presence
+      logger.debug "Advertising available status"
+      advertise_presence(true)
+    end
+
+    def knock_at_room_door
+    end
+
+    def enter_room
+      logger.debug "Discovering room component features"
+      unless discover_features(@room_jid).include?(Jabber::MUC::XMUC.new.namespace)
+        logger.fatal "CyborgHood HeadQuarters has no MUC support"
+        quit(true)
+      end
+
+      logger.debug "Fetching room list"
+      @muc_br = Jabber::MUC::MUCBrowser.new(@cl)
+      room_list = @muc_br.muc_rooms(@room_jid.domain)
+
+      @chat_jid = @room_jid.dup
+      @chat_jid.resource = generate_available_nick()
+
+      logger.debug "Joining room '#{@room_jid}' as '#{@chat_jid.resource}'..."
+      @muc = Jabber::MUC::SimpleMUCClient.new(@cl)
+      register_room_callbacks
+      @muc.join(@chat_jid)
+
+      if room_list.keys.include?(@room_jid.to_s)
+        on_existing_room()
+      else
+        on_missing_room()
+      end
+
+      # presence
+      logger.debug "Advertising available status in room"
+      advertise_presence_in_room(true)
+
+      @in_room = true
+    end
+
+    def on_missing_room
+      logger.fatal "CyborgHood HeadQuarters has no meeting room"
+      quit(true)
+    end
+
+    def on_existing_room
+    end
+
+    def register_room_callbacks
+    end
+
+    def discover(jid, node = nil)
+        iq = Jabber::Iq.new(:get, jid.domain)
+        iq.query = Jabber::Discovery::IqQueryDiscoInfo.new
+        iq.query.node = node unless node.nil?
+
+        result = nil
+        @cl.send_with_id(iq) {|reply|
+          result = reply.query
+        }
+        return result
+    end
+
+    def generate_available_nick
+      @nick_index += 1
+      self.human_name + "-" + @nick_index.to_s.rjust(2, "0")
+    end
+
+    def clear
+      @cl = nil
+      @muc_br = nil
+      @muc = nil
+      @chat_jid = nil
+      @nick_index = 0
+      @in_room = false
+      @should_stop = false
+    end
+  end
+end
diff --git a/lib/cyborghood/lang_additions.rb b/lib/cyborghood/lang_additions.rb
new file mode 100644
index 0000000..df82b18
--- /dev/null
+++ b/lib/cyborghood/lang_additions.rb
@@ -0,0 +1,24 @@
+require 'ostruct'
+
+class Hash
+  def to_ostruct
+    data = self.dup
+    data.each_pair do |k, v|
+      data[k] = v.to_ostruct if v.is_a?(Hash)
+    end
+    OpenStruct.new(data)
+  end
+end
+
+class Object
+  def logger
+    CyborgHood::Logger.instance
+  end
+
+  def self.human_name
+    self.name.split("::").last
+  end
+  def human_name
+    self.class.human_name
+  end
+end
diff --git a/test.rb b/test.rb
deleted file mode 100755
index 50eeb7c..0000000
--- a/test.rb
+++ /dev/null
@@ -1,423 +0,0 @@
-#!/usr/bin/ruby -Ku
-
-$KCODE = 'UTF8'
-require 'jcode'
-require 'singleton'
-require 'yaml'
-require 'ostruct'
-require 'log4r'
-require 'xmpp4r'
-require 'xmpp4r/presence'
-require 'xmpp4r/version/iq/version'
-require 'xmpp4r/version/helper/simpleresponder'
-require 'xmpp4r/caps'
-require 'xmpp4r/discovery'
-#require 'xmpp4r/vcard/helper/vcard'
-require 'xmpp4r/muc/helper/mucbrowser'
-require 'xmpp4r/muc/helper/simplemucclient'
-
-
-class Hash
-  def to_ostruct
-    data = self.dup
-    data.each_pair do |k, v|
-      data[k] = v.to_ostruct if v.is_a?(Hash)
-    end
-    OpenStruct.new(data)
-  end
-end
-
-class Object
-  def logger
-    CyborgHood::Logger.instance
-  end
-
-  def self.human_name
-    self.name.split("::").last
-  end
-  def human_name
-    self.class.human_name
-  end
-end
-
-module CyborgHood
-
-  PRODUCT = "CyborgHood"
-  VERSION = "0.0.1"
-
-  class Logger <  Log4r::Logger
-    include Singleton
-
-    LOG_FORMAT = "[%5l - %d] %m"
-
-    def output_level(level)
-      l = case level
-      when :verbose
-        Log4r::DEBUG
-      when :quiet
-        Log4r::FATAL
-      else # normal
-        Log4r::WARN
-      end
-      @main_outputter.level = l
-    end
-
-    def log_to_file(filename)
-      file_outputter = Log4r::FileOutputter.new(filename, :filename => filename)
-      file_outputter.formatter = @default_formatter
-      self.outputters << file_outputter
-    end
-
-    private
-
-    def initialize
-      super(PRODUCT)
-
-      self.level = Log4r::DEBUG
-      @default_formatter = Log4r::PatternFormatter.new(:pattern => LOG_FORMAT)
-      @main_outputter = Log4r::Outputter.stdout
-      @main_outputter.formatter = @default_formatter
-      @main_outputter.level = Log4r::WARN
-      self.outputters = [@main_outputter]
-    end
-  end
-
-  class Config
-    include Singleton
-
-    CFG_DIR = './config/'
-
-    CONFFILE_GLOBAL = "cyborghood.conf"
-    CONFFILE_BOT = "cyborghood_%s.conf"
-
-    def self.load(name = nil)
-      # load all config parts
-      config = fetch_config
-      config = merge_configs(config, fetch_config(name)) unless name.nil?
-
-      # create config object for easier access
-      @@config = config.to_ostruct.freeze
-    end
-
-    def method_missing(sym, *args, &block)
-      begin
-        @@config.send sym, *args, &block
-      rescue
-        nil
-      end
-    end
-
-    private
-
-    def self.fetch_config(name = nil)
-      begin
-        if name.nil?
-          filename = CONFFILE_GLOBAL
-        else
-          filename = sprintf(CONFFILE_BOT, name)
-        end
-        filepath = File.join(File.expand_path(CFG_DIR), filename)
-        str = File.read(filepath)
-        YAML.load(str)
-      rescue
-        logger.fatal "Problems fetching configuration file '" + filepath + "': " + $!
-        exit 1
-      end
-    end
-
-    def self.merge_configs(conf1, conf2)
-      new_conf = conf1.dup
-      conf2.each_pair do |k, v|
-        if conf1.has_key?(k) and conf1[k].is_a?(Hash) and v.is_a?(Hash)
-          new_conf[k] = merge_configs(conf1[k], v)
-        else
-          new_conf[k] = v
-        end
-      end
-      return new_conf
-    end
-  end
-
-  class Cyborg
-    attr_reader :jid, :room_jid, :chat_jid
-
-    FEATURE_NS = 'http://duckcorp.org/protocol/cyborghood'
-
-    def initialize()
-      # load config
-      Config.load(self.human_name.downcase)
-      @config = Config.instance
-
-      # setup logs
-      unless @config.log.nil?
-        logger.output_level(@config.log.console_level) unless @config.log.console_level.nil?
-        logger.log_to_file(@config.log.file) unless @config.log.file.nil?
-      end
-
-      # prepare
-      clear
-      @jid = Jabber::JID.new(@config.xmpp.jid)
-      @room_jid = Jabber::JID.new(@config.xmpp.muc).bare
-
-      yield if block_given?
-
-      logger.info "Bot '#{self.human_name}' loaded"
-    end
-
-    def run
-      logger.info "Initializing bot..."
-
-      begin
-        @cl = Jabber::Client.new(@jid)
-        @cl.allow_tls = true
-        @cl.ssl_capath = @config.xmpp.ssl_capath unless @config.xmpp.ssl_capath == nil
-        reconnect
-      rescue Jabber::ServerError => e
-        if e.error.type == :cancel and e.error.code == 409
-          if @nick_index < 100
-            retry
-          else
-            logger.fatal "Cannot connect to CyborgHood HeadQuarters because my Nickname range is full (maybe a security problem)"
-            quit(true)
-          end
-        else
-          logger.fatal "Cannot connect to CyborgHood HeadQuarters: " + e.error.text
-          quit(true)
-        end
-      end
-
-      @cl.on_exception { sleep 5; reconnect }
-
-      logger.info "Bot ready for work"
-
-      @run_thread = Thread.new { yield }
-      @run_thread.join
-
-      logger.info "Bot work is finished, going to sleep..."
-      quit
-    end
-
-    def ask_to_stop
-      @should_stop = true
-      @run_thread.wakeup if @run_thread.stop?
-    end
-
-    protected
-
-    def quit(fatal = false)
-      if @cl
-        @muc.exit if @muc and @muc.active?
-        @cl.close
-      end
-      clear
-      exit 1 if fatal
-    end
-
-    def advertise_presence(available)
-      show = (available ? :chat : :dnd)
-      @cl.send(Jabber::Presence.new.set_show(show))
-    end
-
-    def advertise_presence_in_room(available)
-      show = (available ? :chat : :dnd)
-      stanza = Jabber::Presence.new.set_show(show)
-      stanza.from = @jid.to_s
-      stanza.to = @chat_jid.to_s
-      @cl.send(stanza)
-    end
-
-    def discover_features(jid)
-      result = discover(jid)
-      result.nil? ? [] : result.features
-    end
-
-    def room_members
-      @muc.roster.keys.sort
-    end
-
-    def unnormalize_message(msg)
-      msg2 = msg.dup
-      REXML::DocType::DEFAULT_ENTITIES.each_value do |entity|
-        msg2.gsub!("&#{entity.name};", entity.value)
-      end
-      msg2.gsub!( '&amp;', '&' )
-      return msg2
-    end
-
-    private
-
-    def reconnect
-      logger.debug "Connecting to the XMPP server..."
-      @cl.connect
-      @cl.auth(@config.xmpp.passwd)
-
-      unless @cl.is_tls?
-        logger.fatal "Connection to CyborgHood HeadQuarters is not encrypted; please configure TLS support !"
-        quit(true)
-      end
-
-      # initial presence before being ready
-      logger.debug "Advertising unavailable status"
-      advertise_presence(false)
-
-      # version
-      logger.debug "Starting version responder"
-      Jabber::Version::SimpleResponder.new(@cl, PRODUCT, VERSION, "")
-
-      # advertise discovery info
-      logger.debug "Starting capabilities responder"
-      Jabber::Caps::Helper.new(@cl,
-        [Jabber::Discovery::Identity.new('client', self.human_name, 'bot')],
-        [Jabber::Discovery::Feature.new(FEATURE_NS)]
-      )
-
-      # enter MUC room
-      knock_at_room_door unless @in_room
-      enter_room
-
-      # presence
-      logger.debug "Advertising available status"
-      advertise_presence(true)
-    end
-
-    def knock_at_room_door
-    end
-
-    def enter_room
-      logger.debug "Discovering room component features"
-      unless discover_features(@room_jid).include?(Jabber::MUC::XMUC.new.namespace)
-        logger.fatal "CyborgHood HeadQuarters has no MUC support"
-        quit(true)
-      end
-
-      logger.debug "Fetching room list"
-      @muc_br = Jabber::MUC::MUCBrowser.new(@cl)
-      room_list = @muc_br.muc_rooms(@room_jid.domain)
-
-      @chat_jid = @room_jid.dup
-      @chat_jid.resource = generate_available_nick()
-
-      logger.debug "Joining room '#{@room_jid}' as '#{@chat_jid.resource}'..."
-      @muc = Jabber::MUC::SimpleMUCClient.new(@cl)
-      register_room_callbacks
-      @muc.join(@chat_jid)
-
-      if room_list.keys.include?(@room_jid.to_s)
-        on_existing_room()
-      else
-        on_missing_room()
-      end
-
-      # presence
-      logger.debug "Advertising available status in room"
-      advertise_presence_in_room(true)
-
-      @in_room = true
-    end
-
-    def on_missing_room
-      logger.fatal "CyborgHood HeadQuarters has no meeting room"
-      quit(true)
-    end
-
-    def on_existing_room
-    end
-
-    def register_room_callbacks
-    end
-
-    def discover(jid, node = nil)
-        iq = Jabber::Iq.new(:get, jid.domain)
-        iq.query = Jabber::Discovery::IqQueryDiscoInfo.new
-        iq.query.node = node unless node.nil?
-
-        result = nil
-        @cl.send_with_id(iq) {|reply|
-          result = reply.query
-        }
-        return result
-    end
-
-    def generate_available_nick
-      @nick_index += 1
-      self.human_name + "-" + @nick_index.to_s.rjust(2, "0")
-    end
-
-    def clear
-      @cl = nil
-      @muc_br = nil
-      @muc = nil
-      @chat_jid = nil
-      @nick_index = 0
-      @in_room = false
-      @should_stop = false
-    end
-  end
-
-  class Guard < Cyborg
-    ROOM_PARAMS = {
-      'muc#roomconfig_allowinvites'	=> 0,
-      'muc#roomconfig_changesubject'	=> 0,
-      'muc#roomconfig_membersonly'	=> 1,
-      'muc#roomconfig_publicroom'	=> 1,
-      'muc#roomconfig_persistentroom'	=> 1,
-      'muc#roomconfig_roomsecret'	=> 'moderators',
-    }
-
-    def initialize
-      super {
-        @in_room = true
-      }
-    end
-
-    def run
-      super do
-        logger.debug "COIN !"
-        p @muc.roster.collect{|k, v| [k, v.show] }
-        #sleep 3
-        Thread.stop
-        p "pompompom"
-        p @muc.roster.collect{|k, v| [k, v.show] }
-      end
-    end
-
-    private
-
-    def on_missing_room
-      logger.info "CyborgHood HeadQuarters meeting room does not exist yet; creating..."
-
-      logger.debug "Fetching and checking room config form"
-      room_config_form = @muc.get_room_configuration
-      missing_params = ROOM_PARAMS.keys - room_config_form
-      unless missing_params.empty?
-        logger.fatal "CyborgHood HeadQuarters do not provide the following meeting room facilities: " + missing_params.join(", ")
-        quit(true)
-      end
-
-      logger.debug "Setting room configuration"
-      @muc.submit_room_configuration(ROOM_PARAMS)
-    end
-
-    def on_existing_room
-      logger.debug "Checking ownership"
-      unless @muc.owner?
-        logger.fatal "CyborgHood HeadQuarters meeting room has been taken over !!!"
-        quit(true)
-      end
-    end
-  end
-end
-
-#Jabber::debug = true
-
-bot = CyborgHood::Guard.new
-
-trap('INT') do
-  bot.ask_to_stop
-end
-trap('TERM') do
-  bot.ask_to_stop
-end
-
-bot.run
-
