Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure nested sections #298

Merged
merged 15 commits into from
Apr 18, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
145 changes: 145 additions & 0 deletions lib/fluent/config/configure_proxy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
module Fluent
module Config
class ConfigureProxy
attr_accessor :name, :param_name, :required, :multi, :argument, :params, :defaults, :sections
# config_param :desc, :string, :default => '....'
# config_set_default :buffer_type, :memory
#
# config_section :default, required: true, multi: false do
# config_argument :arg, :string
# config_param :required, :bool, default: false
# config_param :name, :string
# config_param :power, :integer
# end
#
# config_section :child, param_name: 'children', required: false, multi: true do
# config_param :name, :string
# config_param :power, :integer, default: nil
# config_section :item do
# config_param :name
# end
# end

def initialize(name, opts={})
@name = name.to_sym

@param_name = (opts[:param_name] || @name).to_sym
@required = opts[:required]
@multi = opts[:multi]

@argument = nil # nil: ignore argument
@params = {}
@defaults = {}
@sections = {}
end

def required?
@required.nil? ? false : @required
end

def multi?
@multi.nil? ? true : @multi
end

def merge(other) # self is base class, other is subclass
options = {
param_name: other.param_name,
required: ( other.required.nil? ? self.required : other.required ),
multi: ( other.multi.nil? ? self.multi : other.multi)
}
merged = self.class.new(other.name, options)

merged.argument = other.argument || self.argument
merged.params = self.params.merge(other.params)
merged.defaults = self.defaults.merge(other.defaults)
merged.sections = self.sections.merge(other.sections)

merged
end

def parameter_configuration(name, *args, &block)
name = name.to_sym

opts = {}
args.each { |a|
if a.is_a?(Symbol)
opts[:type] = a
elsif a.is_a?(Hash)
opts.merge!(a)
else
raise ArgumentError, "wrong number of arguments (#{1 + args.length} for #{block ? 2 : 3})"
end
}

type = opts[:type]
if block && type
raise ArgumentError, "both of block and type cannot be specified"
end

begin
type = :string if type.nil?
block ||= Configurable.lookup_type(type)
rescue ConfigError
# override error message
raise ArgumentError, "unknown config_argument type `#{type}'"
end

if opts.has_key?(:default)
config_set_default(name, opts[:default])
end

[name, block, opts]
end

def config_argument(name, *args, &block)
if @argument
raise ArgumentError, "config_argument called twice"
end
name, block, opts = parameter_configuration(name, *args, &block)

@argument = [name, block, opts]
name
end

def config_param(name, *args, &block)
name, block, opts = parameter_configuration(name, *args, &block)

@sections.delete(name)
@params[name] = [block, opts]
name
end

def config_set_default(name, defval)
name = name.to_sym

if @defaults.has_key?(name)
raise ArgumentError, "default value specified twice for #{name}"
end

@defaults[name] = defval
nil
end

def config_section(name, *args, &block)
unless block_given?
raise ArgumentError, "config_section requires block parameter"
end
name = name.to_sym

opts = {}
unless args.empty? || args.size == 1 && args.first.is_a?(Hash)
raise ArgumentError, "unknown config_section arguments: #{args.inspect}"
end

sub_proxy = ConfigureProxy.new(name, *args)
sub_proxy.instance_exec(&block)

@params.delete(name)
@sections[name] = sub_proxy

name
end
end
end
end

111 changes: 111 additions & 0 deletions lib/fluent/config/section.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
module Fluent
require 'fluent/config/error'

module Config
class Section < BasicObject
def self.name
'Fluent::Config::Section'
end

def initialize(params={})
@klass = 'Fluent::Config::Section'
@params = params
end

def to_h
@params
end

def +(other)
Section.new(self.to_h.merge(other.to_h))
end

def instance_of?(mod)
@klass == mod.name
end

def kind_of?(mod)
@klass == mod.name || BasicObject == mod
end
alias is_a? kind_of?

def [](key)
@params[key.to_sym]
end

def respond_to_missing?(symbol, include_private)
@params.has_key?(symbol)
end

def method_missing(name, *args)
if @params.has_key?(name)
@params[name]
else
super
end
end
end

module SectionGenerator
def self.generate(proxy, conf, logger, stack=[])
return nil if conf.nil?

section_stack = ""
unless stack.empty?
section_stack = ", in section " + stack.join(" > ")
end

section_params = {}

proxy.defaults.each_pair do |name, defval|
varname = name.to_sym
section_params[varname] = defval
end

if proxy.argument
unless conf.arg.empty?
key,block,opts = proxy.argument
section_params[key] = self.instance_exec(conf.arg, opts, name, &block)
end
unless section_params.has_key?(proxy.argument.first)
logger.error "config error in:\n#{conf}"
raise ConfigError, "'<#{proxy.name} ARG>' section requires argument" + section_stack
end
end

proxy.params.each_pair do |name, defval|
varname = name.to_sym
block,opts = defval
if val = conf[name.to_s]
section_params[varname] = self.instance_exec(val, opts, name, &block)
end
unless section_params.has_key?(varname)
logger.error "config error in:\n#{conf}"
raise ConfigError, "'#{name}' parameter is required" + section_stack
end
end

proxy.sections.each do |name, subproxy|
varname = subproxy.param_name.to_sym
elements = (conf.respond_to?(:elements) ? conf.elements : []).select{|e| e.name == subproxy.name.to_s }

if subproxy.required? && elements.size < 1
logger.error "config error in:\n#{conf}"
raise ConfigError, "'<#{subproxy.name}>' sections are required" + section_stack
end
if subproxy.multi?
section_params[varname] = elements.map{ |e| generate(subproxy, e, logger, stack + [subproxy.name]) }
else
if elements.size > 1
logger.error "config error in:\n#{conf}"
raise ConfigError, "'<#{subproxy.name}>' section cannot be written twice or more" + section_stack
end
section_params[varname] = generate(subproxy, elements.first, logger, stack + [subproxy.name])
end
end

Section.new(section_params)
end
end
end
end