require 'tengine/support/config/definition' module Tengine::Support::Config::Definition::HasManyChildren def children @children ||= [] end def child_by_name(name) name = name.to_sym if name.respond_to?(:to_sym) children.detect{|child| child.__name__ == name} end def find(name_array) name_array = Array(name_array) head = name_array.shift if child = child_by_name(head) name_array.empty? ? child : child.find(name_array) else nil end end def add(name, klass, options = {}, &block) result = klass.new result.__parent__ = self result.__name__ = name result.instantiate_children dependencies = options[:dependencies] || {} klass.definition_reference_names.each do |res_name| name_array = dependencies[res_name] raise "missing dependency of #{name.inspect} in :dependencies options to add(#{name.inspect}, #{klass.name}...)" unless name_array obj = root.find(Array(name_array)) raise "#{name_array.inspect} not found" unless obj result.send("#{res_name}=", obj) end (options[:parameters] || {}).each do |param, value| result.send("#{param}=", value.respond_to?(:to_proc) ? result.instance_eval(&value) : value) end defaults = options[:defaults] || {} defaults.each do |key, value| child = result.child_by_name(key) raise "child not found for #{key.inspct} in #{result.__name__}" unless child child.default = value if value end children << result (class << self; self; end).class_eval{ define_method(name){ result } } result.instance_eval(&block) if block result end def group(name, options = {}, &block) result = Tengine::Support::Config::Definition::Group.new(name, options) result.__parent__ = self (class << self; self; end).class_eval{ define_method(name){ result } } children << result result.instance_eval(&block) if block result end def field(name, *args, &block) attrs = args.last.is_a?(Hash) ? args.pop : {} attrs[:description] = args.first unless args.empty? attrs.update({ :__name__ => name, :__parent__ => self, :__type__ => attrs[:__type__] || :field, :convertor => block, }) if field = children.detect{|child| child.__name__ == name} new_field = field.dup new_field.update(attrs) idx = self.children.index(field) self.children[idx] = new_field field = new_field else field = Tengine::Support::Config::Definition::Field.new(attrs) self.children << field end (class << self; self; end).module_eval do define_method(field.__name__) do instance_variable_get("@#{field.__name__}") || (field.default.respond_to?(:to_proc) ? self.instance_eval(&field.deault) : field.default) end define_method("#{field.__name__}=") do |value| instance_variable_set("@#{field.__name__}", field.convert(value, self)) end end end def ignore(*names) @ignoreds ||= [] names = names.flatten.map(&:to_sym) @ignoreds.concat(names) end def action(name, *args, &block) attrs = args.last.is_a?(Hash) ? args.pop : {} attrs.update({ :__name__ => name, :__parent__ => self, :__block__ => block, :__type__ => :action, }) attrs[:description] = args.first unless args.empty? field = Tengine::Support::Config::Definition::Field.new(attrs) self.children << field end alias_method :__action__, :action def action?; false; end def separator(description) attrs = { :description => description, :__name__ => :"separator#{children.count + 1}", :__parent__ => self, :__type__ => :separator, } field = Tengine::Support::Config::Definition::Field.new(attrs) self.children << field end def separator?; false; end def load_config(name, *args) options = args.last.is_a?(Hash) ? args.pop : {} options[:type] = :load_config args << options field(name, *args) end def to_hash children.inject({}) do |dest, child| unless child.action? || child.separator? value = child.to_hash unless value.is_a?(Hash) && value.empty? dest[child.__name__] = child.to_hash end end dest end end def load(hash) hash.each do |name, value| name = name.to_sym next if @ignoreds && @ignoreds.include?(name) child = child_by_name(name) unless child where = respond_to?(:__name__) ? " on " + __name__.inspect : "" raise "child not found for #{name.to_s.inspect}#{where}" end if child.is_a?(Tengine::Support::Config::Definition::Field) self.send("#{name}=", value) else child.load(value) end end end def accept_visitor(visitor) visitor.visit(self) end def name_array (__parent__ ? __parent__.name_array : []) + [__name__] end def get_value(obj) obj.is_a?(Proc) ? self.instance_eval(&obj) : obj end def [](child_name) child = child_by_name(child_name) if child.is_a?(Tengine::Support::Config::Definition::Field) self.send(child_name) else child end end def []=(child_name, value) child = child_by_name(child_name) if child.is_a?(Tengine::Support::Config::Definition::Field) self.send("#{child_name}=", value) else raise ArgumentError, "can't replace #{child_name.inspect}" end end end