module ALib
#{{{
  VERSION = '0.0.1'
#
# utility methods
#
  require 'pathname'
  require 'socket'
  require 'tmpdir'
  require 'rbconfig'
  module  Util
#{{{
    class << self
      def export sym
#{{{
        sym = "#{ sym }".intern
        module_function sym 
        public sym 
#}}}
      end
      def append_features c
#{{{
        super
        c.extend Util 
#}}}  
      end
    end
    def require_version version
#{{{
      major, minor, teeny = "#{ version }".split(%r/\./o).map{|n| Integer(n)}
      required = "#{ major }.#{ minor }.#{ teeny }"
      _major, _minor, _teeny = 
        %w( MAJOR MINOR TEENY ).map{|k| Integer(::Config::CONFIG[k])}
      actual = "#{ _major }.#{ _minor }.#{ _teeny }"
      unless _major > major  or _major == major and _minor >= minor 
        STDERR.puts("=" * 79)
        STDERR.puts "<#{ Util::prognam }> requires a ruby version >= <#{ required }>"
        STDERR.puts 
        STDERR.puts "you are currenlty running ruby version <#{ actual }>"
        STDERR.puts 
        STDERR.puts "possible problems which could cause this are:"
        STDERR.puts "  - improper PATH environment variable setting"
        STDERR.puts "  - ruby > <#{ required }> has not been installed"
        STDERR.puts("=" * 79)
        exit 1
      end
#}}}
    end
    export 'require_version'
    def prognam
#{{{
      File::basename $0
#}}}
    end
    export 'prognam'
    def mcp obj
#{{{
      Marshal.load(Marshal.dump(obj))
#}}}
    end
    export 'mcp'
    def klass
#{{{
      self.class
#}}}
    end
    export 'klass'
    def realpath path
#{{{
      path = File.expand_path "#{ path }"
      begin
        Pathname.new(path).realpath.to_s
      rescue Errno::ENOENT, Errno::ENOTDIR
        path
      end
#}}}
    end
    export 'realpath'
    def hashify(*hashes)
#{{{
      hashes.inject(accum={}){|accum,hash| accum.update hash}
#}}}
    end
    export 'hashify'
    def getopt opt, hash, default = nil
#{{{
      key = opt
      return hash[key] if hash.has_key? key
      key = "#{ key }"
      return hash[key] if hash.has_key? key
      key = key.intern
      return hash[key] if hash.has_key? key
      return default
#}}}
    end
    export 'getopt'
    def alive pid
#{{{
      pid = Integer("#{ pid }")
      begin
        Process.kill 0, pid
        true
      rescue Errno::ESRCH
        false
      end
#}}}
    end
    alias alive? alive
    export 'alive'
    export 'alive?'
    def maim(pid, opts = {})
#{{{
      sigs = getopt('signals', opts) || %w(SIGTERM SIGQUIT SIGKILL) 
      suspend = getopt('suspend', opts) || 4
      pid = Integer("#{ pid }")
      existed = false
      sigs.each do |sig|
        begin
          Process.kill(sig, pid)
          existed = true 
        rescue Errno::ESRCH
          unless existed
            return nil 
          else
            return true 
          end
        end
        return true unless alive?(pid)
        sleep suspend
        return true unless alive?(pid)
      end
      return(not alive?(pid)) 
#}}}
    end
    export 'maim'
    def timestamp time = Time.now
#{{{
      usec = "#{ time.usec }"
      usec << ('0' * (6 - usec.size)) if usec.size < 6 
      time.strftime('%Y-%m-%d %H:%M:%S.') << usec
#}}}
    end
    export 'timestamp'
    def stamptime string, local = true 
#{{{
      string = "#{ string }"
      pat = %r/^\s*(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d).(\d\d\d\d\d\d)\s*$/o
      match = pat.match string
      raise ArgumentError, "<#{ string.inspect }>" unless match
      yyyy,mm,dd,h,m,s,u = match.to_a[1..-1].map{|m| m.to_i}
      if local
        Time.local yyyy,mm,dd,h,m,s,u
      else
        Time.gm yyyy,mm,dd,h,m,s,u
      end
#}}}
    end
    export 'stamptime'
    def escape! s, char, esc
#{{{
      re = %r/([#{0x5c.chr << esc}]*)#{char}/
      s.gsub!(re) do
        (($1.size % 2 == 0) ? ($1 << esc) : $1) + char 
      end
#}}}
    end
    export 'escape!'
    def escape s, char, esc
#{{{
      ss = "#{ s }"
      escape! ss, char, esc
      ss
#}}}
    end
    export 'escape'
    def fork(*args, &block)
#{{{
      begin
        verbose = $VERBOSE
        $VERBOSE = nil
        Process::fork(*args, &block)
      ensure
        $VERBOSE = verbose
      end
#}}}
    end
    export 'fork'
    def exec(*args, &block)
#{{{
      begin
        verbose = $VERBOSE
        $VERBOSE = nil
        Kernel::exec(*args, &block)
      ensure
        $VERBOSE = verbose
      end
#}}}
    end
    export 'exec'
    def hostname
#{{{
      @__hostname__ ||= Socket::gethostname
#}}}
    end
    export 'hostname'
    def host
#{{{
      @__host__ ||= hostname.gsub(%r/\..*$/o,'')
#}}}
    end
    export 'host'
    def emsg e
#{{{
      "#{ e.message } - (#{ e.class })"
#}}}
    end
    export 'emsg'
    def btrace e
#{{{
      (e.backtrace or []).join("\n")
#}}}
    end
    export 'btrace'
    def errmsg e
#{{{
      emsg(e) << "\n" << btrace(e)
#}}}
    end
    export 'errmsg'
    def erreq a, b
#{{{
      a.class == b.class and
      a.message == b.message and
      a.backtrace == b.backtrace
#}}}
    end
    export 'erreq'
    def tmpnam dir = Dir.tmpdir, seed = File.basename($0)
#{{{
      pid = Process.pid
      path = "%s_%s_%s_%s_%d" % 
        [Util::hostname, seed, pid, Util::timestamp.gsub(/\s+/o,'_'), rand(101010)]
      File.join(dir, path)
#}}}
    end
    export 'tmpnam'
    def uncache file 
#{{{
      refresh = nil
      begin
        is_a_file = File === file
        path = (is_a_file ? file.path : file.to_s) 
        stat = (is_a_file ? file.stat : File::stat(file.to_s)) 
        refresh = tmpnam(File::dirname(path))
        File::link path, refresh rescue File::symlink path, refresh
        File::chmod stat.mode, path
        File::utime stat.atime, stat.mtime, path
        open(File::dirname(path)){|d| d.fsync rescue nil}
      ensure 
        begin
          File::unlink refresh if refresh
        rescue Errno::ENOENT
        end
      end
#}}}
    end
    export 'uncache'
    def system(*args, &block)
#{{{
      begin
        verbose = $VERBOSE
        $VERBOSE = nil
        Kernel::system(*args, &block)
      ensure
        $VERBOSE = verbose
      end
#}}}
    end
    export 'system'
    def columnize buf, width = 80, indent = 0
#{{{
      column = []
      words = buf.split %r/\s+/o
      row = ' ' * indent
      while((word = words.shift))
        if((row.size + word.size) < (width - 1))
          row << word
        else
          column << row
          row = ' ' * indent
          row << word
        end
        row << ' ' unless row.size == (width - 1)
      end
      column << row unless row.strip.empty?
      column.join "\n"
#}}}
    end
    export 'columnize'
    def defval var, default = nil
#{{{  
      v = "#{ var }"
      c = "DEFAULT_#{ v }".upcase
      begin 
        klass.send(v) || klass.const_get(c)
      rescue NameError
        default
      end
#}}}
    end
    export 'defval'

#}}}
  end # class Util
#
# logging methods
#
  require 'logger'
  module Logging
#{{{
  #
  # a module that adds an accessor to Logging objects in ored to fix a bug where
  # not all logging devices are put into sync mode, resulting in improper log
  # rolling.  this is a hack.
  #
    module LoggerExt
#{{{
      attr :logdev
#}}}
    end # module LoggerExt
  #
  # implementations of the methods shared by both classes and objects of classes
  # which include Logging
  #
    module LogMethods
#{{{
      def logger
#{{{
        if defined?(@logger) and @logger
          @logger
        else
          if Class === self
            @logger = self.default_logger
          else
            @logger = self::class::logger
          end
          raise "@logger is undefined!" unless defined?(@logger) and @logger
          @logger
        end
#}}}
      end
      def logger= log
#{{{
        @logger = log
        @logger.extend LoggerExt
        @logger.logdev.dev.sync = true
        @logger
#}}}
      end
      def debug(*args, &block); logger.debug(*args, &block); end
      def info(*args, &block);  logger.info(*args, &block) ; end
      def warn(*args, &block);  logger.warn(*args, &block) ; end
      def error(*args, &block); logger.error(*args, &block); end
      def fatal(*args, &block); logger.fatal(*args, &block); end
      def log_err e
#{{{
        if logger.debug?
          error{ errmsg e } 
        else
          error{ emsg e } 
        end
#}}}
      end
      def emsg e
#{{{
        "#{ e.message } - (#{ e.class })"
#}}}
      end
      def btrace e
#{{{
        e.backtrace.join("\n")
#}}}
      end
      def errmsg e
#{{{
        emsg(e) << "\n" << btrace(e)
#}}}
      end
#}}}
    end # module LogMethods
    EOL    = "\n"
    DIV0   = ("." * 79) << EOL 
    DIV1   = ("-" * 79) << EOL 
    DIV2   = ("=" * 79) << EOL 
    DIV3   = ("#" * 79) << EOL 
    SEC0   = ("." * 16) << EOL 
    SEC1   = ("-" * 16) << EOL 
    SEC2   = ("=" * 16) << EOL 
    SEC3   = ("#" * 16) << EOL 
    class << self
#{{{
      def append_features c
#{{{
        ret = super
        c.extend LogMethods
        class << c
          def default_logger
#{{{
            if defined?(@default_logger) and @default_logger
              @default_logger
            else
              self.default_logger = Logger.new STDERR
              @default_logger.debug{ "<#{ self }> using default logger"}
              @default_logger
            end
#}}}
          end
          def default_logger= log
#{{{
            @default_logger = log
            @default_logger.extend LoggerExt
            @default_logger.logdev.dev.sync = true
            @default_logger
#}}}
          end
        end
        ret
#}}}
      end
#}}}
    end
    include LogMethods
#}}}
  end # module Logging
#
# simple config file class
#
  require 'yaml'
  class ConfigFile < Hash
#{{{
    class << self
      def gen_template(arg = nil)
#{{{
        @data ||= DATA.read
        case arg 
          when IO 
            arg.write @data
          when String
            open(arg, 'w'){|f| f.write @data}
          else
            STDOUT.write @data 
        end
        self
#}}}
      end
      def load_default
#{{{
        @data ||= DATA.read
        @default ||= YAML::load(munge(@data)) || {}
#}}}
      end
      def any(basename, *dirnames)
#{{{
        config = nil
        dirnames.each do |dirname|
          path = File::join dirname, basename 
          if test ?e, path
            config = self::new(path)
            break
          end
        end
        config || self::new('default') 
#}}}
      end
      def munge buf
#{{{
        buf.gsub(%r/\t/o,'  ')
#}}}
      end
    end
    attr :path
    def initialize path
#{{{
      @path = nil 
      yaml = nil
      if path.nil? or path and path =~ /^\s*default/io
        yaml = self.class.load_default 
        @path = 'DEFAULT' 
      else path
        yaml = YAML::load(self.class.munge(open(path).read))
        @path = path
      end
      self.update yaml
#}}}
    end
    def to_hash
#{{{
      {}.update self
#}}}
    end
#}}}
  end
#
# simple list file class (list of commands)
#
  class ListFile < Array
#{{{
    attr :path
    def initialize path
#{{{
      @path = path
      open(@path) do |f|
        while((line = f.gets))
          line.gsub!(%r/#.*/o,'')
          next if line =~ %r/^[^\S]+$/o
          line.gsub!(%r/^[^\S]+|[^\S]+$/o,'')
          self << line
        end
      end
#}}}
    end
#}}}
  end
#}}}
end
