On Wed, 13 Oct 2004, Bill wrote:

> Has anyone created a YAML.rb derived class for config files? That would pull
> those 'lonely' variables out into a separate file?

there's one in here (search for Config).  sorry for long post.

save as 'main.rb' and try runnning 'main.rb --template=conf' to generate a
config file for this main.  use with 'main.rb --config=conf -v4'


#!/usr/bin/env ruby
#
# builtin libs
#
   require 'optparse'
   require 'yaml'
   require 'pp'
   require 'pathname'
#
# 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 STDOUT
               @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
#
# utility methods
#
#
   require 'pathname'
   require 'socket'
   require 'tmpdir'
   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 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
#{{{
       opt_s = "#{ opt }"
       hash[opt] || hash[opt_s] || hash[opt_s.intern] 
#}}}
     end
     export 'getopt'
     def alive? pid
#{{{
       pid = Integer("#{ pid }")
       begin
         Process.kill 0, pid
         true
       rescue Errno::ESRCH
         false
       end
#}}}
     end
     export 'alive?'
     def maim(pid, opts = {})
#{{{
       sigs = getopt('signals', opts) || %w(SIGTERM SIGQUIT SIGKILL)
       suspend = getopt('suspend', opts) || 4
       pid = Integer("#{ pid }")
       sigs.each do |sig|
         begin
           Process.kill(sig, pid)
         rescue Errno::ESRCH
           return nil
         end
         sleep 0.2
         unless alive?(pid)
           break
         else
           sleep suspend
         end
       end
       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__ ||= Socket::gethostname.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
       ensure
         begin
           File::unlink refresh if refresh
         rescue Errno::ENOENT
         end
       end
#}}}
     end
     export 'uncache'
#}}}
   end # class Util
# main program class
#
   class Main
#{{{
     include Logging
     include Util

     VERSION = '0.0.0'
     PROGNAM = File::basename(Util::realpath($0))
     CONFIG_DEFAULT_PATH = "#{ PROGNAM }.conf"
     CONFIG_SEARCH_PATH = %w(. ~ /usr/local/etc /usr/etc /etc)

     USAGE = 
#{{{
<<-usage
NAME
   #{ PROGNAM } v#{ VERSION }

SYNOPSIS
   #{ PROGNAM } [options]+ [file]+

DESCRIPTTION

ENVIRONMENT

CONFIG
   default path => #{ CONFIG_DEFAULT_PATH }
   search path => #{ CONFIG_SEARCH_PATH.inspect }

DIAGNOSTICS
   success => $? == 0
   failure => $? != 0

AUTHOR
   ara.t.howard / noaa.gov

BUGS
   > 1

OPTIONS
usage
#}}}

     OPTSPEC = [
#{{{
       [
         '--help', '-h',
         'this message'
       ],
       [
         '--verbosity=verbostiy', '-v',
         '0|fatal < 1|error < 2|warn < 3|info < 4|debug - (default info)'
       ],
       [
         '--log=path','-l',
         'set log file - (default stderr)'
       ],
       [
         '--log_age=log_age',
         'daily | weekly | monthly - what age will cause log rolling (default nil)'
       ],
       [
         '--log_size=log_size',
         'size in bytes - what size will cause log rolling (default nil)'
       ],
       [
         '--config=path',
         'valid path - specify config file (default nil)'
       ],
       [
         '--template=[path]',
         'valid path - generate a template config file in path (default stdout)'
       ],
     ]
#}}}

     EXAMPLES =
#{{{
<<-examples
EXAMPLES

   0) #{ PROGNAM }
examples
#}}}

     EXIT_SUCCESS = 0
     EXIT_FAILURE = 1

     class Config < 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.flatten.each do |dirname|
             path = File::join dirname, basename
             if test ?e, path
               config = Config::new(path)
               break
             end
           end
           config || Config::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

     attr :logger
     attr :argv
     attr :env
     attr :options
     attr :op
     attr :logdev
     attr :verbosity
     attr :config

     def initialize argv = ARGV, env = ENV
#{{{
       begin
         @logger = Logger::new STDERR
         @argv = Util::mcp(argv.to_a)
         @env = Util::mcp(env.to_hash)
         parse_options
         if @options.has_key? 'help'
           usage STDOUT
           return EXIT_SUCCESS
         end
         if @options.has_key? 'template'
           gen_template @options['template']
           return EXIT_SUCCESS
         end
         parse_argv
         init_logging
         init_config
         status = run
         exit status
       rescue => e
         fatal{ e }
         exit EXIT_FAILURE
       end
#}}}
     end
     def parse_options
#{{{
       @op = OptionParser::new
       @options = {}
       OPTSPEC.each do |spec|
         k = spec.first.gsub(%r/(?:--)|(?:=.*$)|(?:\s+)/o,'')
         @op.def_option(*spec){|v| @options[k] = v}
       end
       #begin
         op.parse! @argv
       #rescue OptionParser::InvalidOption => e
         # preverve unknown options
         #e.recover(argv)
       #end
       @options
#}}}
     end
     def parse_argv
#{{{
       # arg0, arg1 = @argv
#}}}
     end
     def usage io = STDERR
#{{{
       io << USAGE if defined? USAGE
       if defined? OPTSPEC
         OPTSPEC.each do |os|
           a, b, c = os
           long, short, desc = nil
           [a,b,c].each do |word|
             next unless word
             word.strip!
             case word
               when %r/^--/o
                 long = word
               when %r/^-/o
                 short = word
               else
                 desc = word
             end
           end
           spec = ((long and short) ? [long, short] : [long])
           io << "  #{ spec.join(', ') }\n"
           io << "        #{ desc }\n" if desc
         end
         io << "\n"
       end
       io << EXAMPLES << "\n" if defined? EXAMPLES
       self
#}}}
     end
     def init_logging
#{{{
       log, log_age, log_size, verbosity =
         @options.values_at 'log', 'log_age', 'log_size', 'verbosity'
       log_age = atoi log_age rescue nil
       log_size = atoi log_size rescue nil
       $logger = @logger = Logger::new(log || STDERR, log_age, log_size)
     #
     # hack to fix Logger sync bug
     #
       class << @logger; attr :logdev; end
       @logdev = @logger.logdev.dev
       @logdev.sync = true
       level = nil
       verbosity ||= 'info'
       verbosity =
         case verbosity
           when /^\s*(?:4|d|debug)\s*$/io
             level = 'Logging::DEBUG'
             4
           when /^\s*(?:3|i|info)\s*$/io
             level = 'Logging::INFO'
             3
           when /^\s*(?:2|w|warn)\s*$/io
             level = 'Logging::WARN'
             2
           when /^\s*(?:1|e|error)\s*$/io
             level = 'Logging::ERROR'
             1
           when /^\s*(?:0|f|fatal)\s*$/io
             level = 'Logging::FATAL'
             0
           else
             abort "illegal verbosity setting <#{ verbosity }>"
         end
       @logger.level = 2 - ((verbosity % 5) - 2)
       debug {"logging level <#{ level }>"}
       @logger
#}}}
     end
     def init_config
#{{{
       @config =
         if @options['config']
           Config::new(@options['config'])
         else
           Config::any CONFIG_DEFAULT_PATH, CONFIG_SEARCH_PATH
         end
       debug { "config.path <#{ @config.path }>" }
       debug { "config\n#{ @config.to_hash.to_yaml }\n" }
#}}}
     end
     def gen_template template
#{{{
       Config::gen_template(template)
       self
#}}}
     end
     def run
#{{{
       warn{ "foobar" }
       p 42
       return EXIT_SUCCESS
#}}}
     end
#}}}
   end
#
# run main program unless included as lib
#
   Main::new ARGV, ENV if $0 == __FILE__
#
# the default config is stored here
#
__END__
#
# default config
#

key : value
k2 : v2





-a
--
===============================================================================
| EMAIL   :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
| PHONE   :: 303.497.6469
| When you do something, you should burn yourself completely, like a good
| bonfire, leaving no trace of yourself.  --Shunryu Suzuki
===============================================================================