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 ===============================================================================