lib/fwissr.rb
require 'rubygems'
require 'optparse'
require 'yaml'
FWISSR_USE_YAJL = true
if FWISSR_USE_YAJL
require 'yajl'
else
require 'json'
end
require 'fwissr/version'
require 'fwissr/source'
require 'fwissr/registry'
#
# Fwissr loads all conf files in directories: +/etc/fwissr/+ and +~/.fwissr/+
#
# Two conf files are handled differently: +/etc/fwissr/fwissr.json+ and +~/.fwissr/fwissr.json+
#
# These two main conf files are 'top_level' ones and so their settings are added to registry root. They can
# too contain a +fwissr_sources+ setting that is used to setup additional sources.
#
# Registry is accessed with Fwissr#[] method
#
# @example +/etc/fwissr/fwissr.json+ file:
#
# {
# 'fwissr_sources': [
# { 'filepath': '/mnt/my_app/conf/' },
# { 'filepath': '/etc/my_app.json' },
# { 'mongodb': 'mongodb://db1.example.net/my_app', 'collection': 'config', 'refresh': true },
# ],
# 'fwissr_refresh_period': 30
# }
#
module Fwissr
# default path where main conf file is located
DEFAULT_MAIN_CONF_PATH = "/etc/fwissr"
# default directory (relative to current user's home) where user's main conf file is located
DEFAULT_MAIN_USER_CONF_DIR = ".fwissr"
# main conf file
MAIN_CONF_FILE = "fwissr.json"
class << self
attr_writer :main_conf_path, :main_user_conf_path
# Main config files directory
#
# @api private
#
# @return [String] Directory path
def main_conf_path
@main_conf_path ||= DEFAULT_MAIN_CONF_PATH
end
# User's specific config files directory
#
# @api private
#
# @return [String] Directory path
def main_user_conf_path
@main_user_conf_path ||= File.join(Fwissr.find_home, DEFAULT_MAIN_USER_CONF_DIR)
end
# Finds the user's home directory
#
# @note Borrowed from rubygems
# @api private
#
# @return [String] Directory path
def find_home
['HOME', 'USERPROFILE'].each do |homekey|
return ENV[homekey] if ENV[homekey]
end
if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] then
return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}"
end
begin
File.expand_path("~")
rescue
if File::ALT_SEPARATOR then
"C:/"
else
"/"
end
end
end
# Parse command line arguments
#
# @api private
def parse_args!(argv)
args = {
:inspect => false,
:json => false,
:dump => false,
:pretty => false,
}
# define parser
opt_parser = OptionParser.new do |opts|
opts.banner = "Usage: fwissr [-ijph] <key>\nWith key:\n\t#{Fwissr.keys.sort.join("\n\t")}\n\n"
opts.define_head "The configuration registry."
opts.on("-i", "--inspect", "Returns 'inspected' result") do
args[:inspect] = true
end
opts.on("-j", "--json", "Returns result in json") do
args[:json] = true
end
opts.on("--dump", "Dump all keys and values") do
args[:dump] = true
end
opts.on("-p", "--pretty", "Pretty output") do
args[:pretty] = true
end
opts.on("-?", "-h", "--help", "Show this help message") do
puts opts
exit
end
opts.on_tail("--version", "Show version") do
puts Fwissr::VERSION
exit
end
end
# parse what we have on the command line
opt_parser.parse!(argv)
# get key
if argv.empty? && !args[:dump]
puts "Please specify the key, e.g. 'fwissr /fqdn'"
puts opt_parser
exit
end
args[:key] = argv.first unless argv.empty?
args
end
# Load registry
#
# @api private
#
# @return [Hash] Registry
def global_registry
@global_registry ||= begin
result = Fwissr::Registry.new('refresh_period' => self.main_conf['fwissr_refresh_period'])
# check main conf files
if File.exists?(self.main_conf_path) || File.exists?(self.main_user_conf_path)
# setup main conf files sources
if File.exists?(self.main_conf_path)
result.add_source(Fwissr::Source.from_settings({ 'filepath' => self.main_conf_path }))
end
if File.exists?(self.main_user_conf_path)
result.add_source(Fwissr::Source.from_settings({ 'filepath' => self.main_user_conf_path }))
end
# setup additional sources
if !self.main_conf['fwissr_sources'].nil?
self.main_conf['fwissr_sources'].each do |source_setting|
result.add_source(Fwissr::Source.from_settings(source_setting))
end
end
end
result
end
end
# Main config
#
# @api private
#
# @return [Hash] Configuration from main +fwissr.json+ files
def main_conf
@main_conf ||= begin
result = { }
if File.exists?(self.main_conf_file)
result = self.merge_conf!(result, self.parse_conf_file(self.main_conf_file))
end
if File.exists?(self.main_user_conf_file)
result = self.merge_conf!(result, self.parse_conf_file(self.main_user_conf_file))
end
result
end
end
# Main config file
#
# @api private
#
# @return [String] File path
def main_conf_file
@main_conf_file ||= File.join(self.main_conf_path, MAIN_CONF_FILE)
end
# Main user's config file
#
# @api private
#
# @return [String] File path
def main_user_conf_file
@main_user_conf_file ||= File.join(self.main_user_conf_path, MAIN_CONF_FILE)
end
# Registry accessor
#
# @param key [String] Setting key
# @return [Object] Setting value
def [](key)
self.global_registry[key]
end
alias :get :[]
# Dumps registry keys
#
# @return [Array] Keys list
def keys
self.global_registry.keys
end
# Dumps registry
#
# @return [Hash] The entire registry
def dump
self.global_registry.dump
end
#
# Utils
#
# Parse a configuration file
#
# @api private
#
# @param conf_file_path [String] Configuration file path
# @return [Hash] Parsed configuration
def parse_conf_file(conf_file_path)
conf_file_ext = File.extname(conf_file_path)
case conf_file_ext
when ".json"
# json file
if FWISSR_USE_YAJL
Yajl::Parser.parse(File.read(conf_file_path), :check_utf8 => false)
else
JSON.parse(File.read(conf_file_path))
end
when ".yaml", ".yml"
# yaml file
YAML.load_file(conf_file_path)
else
raise "Unsupported conf file kind: #{conf_file_path}"
end
end
# Merge two conf files
#
# @note Borrowed from rails
# @api private
#
# @param to_hash [Hash] Merge to this conf
# @param other_hash [Hash] Merge this conf
# @return [Hash] Merged configuration
def merge_conf(to_hash, other_hash)
self.merge_conf!(to_hash.dup, other_hash)
end
# Merge two conf files (in place)
#
# @note Borrowed from rails
# @api private
#
# @param to_hash [Hash] Merge to this conf
# @param other_hash [Hash] Merge this conf
# @return [Hash] Merged configuration
def merge_conf!(to_hash, other_hash)
other_hash.each_pair do |k,v|
tv = to_hash[k]
to_hash[k] = (tv.is_a?(Hash) && v.is_a?(Hash)) ? self.merge_conf(tv, v) : v
end
to_hash
end
# Simple deep freezer
#
# @api private
#
# @param obj [Object] Object to freeze
# @return [Object] Deep frozen object
def deep_freeze(obj)
if obj.is_a?(Hash)
obj.each do |k, v|
self.deep_freeze(v)
end
elsif obj.is_a?(Array)
obj.each do |v|
self.deep_freeze(v)
end
end
obj.freeze
end
end # class << self
end # module Fwissr