require 'yaml' require 'singleton' require_relative 'error' require_relative 'utilities' module Cuboid # @author Tasos "Zapotek" Laskos # @see OptionGroups class Options include Singleton def self.attr_accessor(*vars) @attr_accessors ||= [] @attr_accessors |= vars super( *vars ) end def self.attr_accessors @attr_accessors end def attr_accessors self.class.attr_accessors end # {Options} error namespace. # # All {Options} errors inherit from and live under it. # # @author Tasos "Zapotek" Laskos class Error < Cuboid::Error end class <] # {OptionGroups Option group} classes by name. def group_classes @group_classes ||= {} end # Should be called by {OptionGroup.inherited}. # @private def register_group( group ) name = Utilities.caller_name # Prepare an attribute reader for this group... attr_reader name # ... and initialize it. instance_variable_set "@#{name}".to_sym, group.new group_classes[name.to_sym] = group end end # Load all {OptionGroups}. require_relative 'option_groups' TO_RPC_IGNORE = Set.new([ :instance, :rpc, :dispatcher, :queue, :paths, :snapshot, :report, :output, :system ]) TO_HASH_IGNORE = Set.new([ :instance ]) # @return [String] # E-mail address of the person that authorized the run. # # @see HTTP::Client#headers attr_accessor :authorized_by attr_accessor :application def initialize reset end # Restores everything to their default values. # # @return [Options] `self` def reset # nil everything out. instance_variables.each { |var| instance_variable_set( var.to_s, nil ) } # Set fresh option groups. group_classes.each do |name, klass| instance_variable_set "@#{name}".to_sym, klass.new end @authorized_by = nil self end # Configures options via a Hash object. # # @param [Hash] options # If the key refers to a class attribute, the attribute will be assigned # the given value, if it refers to one of the {OptionGroups} the value # should be a hash with data to update that {OptionGroup group} using # {OptionGroup#update}. # # @return [Options] # # @see OptionGroups def update( options ) options.each do |k, v| k = k.to_sym if group_classes.include? k send( k ).update v else send( "#{k.to_s}=", v ) end end self end alias :set :update # @return [Hash] # Hash of errors with the name of the invalid options/groups as the keys. def validate errors = {} group_classes.keys.each do |name| next if (group_errors = send(name).validate).empty? errors[name] = group_errors end errors end # @param [String] file # Saves `self` to `file` using YAML. def save( file ) File.open( file, 'w' ) do |f| f.write to_save_data f.path end end def to_save_data to_rpc_data.to_yaml end def to_save_data_without_defaults to_rpc_data_without_defaults.to_yaml end # Loads a file created by {#save}. # # @param [String] filepath # Path to the file created by {#save}. # # @return [Cuboid::Options] def load( filepath ) update( YAML.load_file( filepath ) ) end # @return [Hash] # `self` converted to a Hash suitable for RPC transmission. def to_rpc_data hash = {} instance_variables.each do |var| val = instance_variable_get( var ) var = normalize_name( var ) next if TO_RPC_IGNORE.include?( var ) hash[var.to_s] = (val.is_a? OptionGroup) ? val.to_rpc_data : val end hash.deep_clone end def to_rpc_data_without_defaults defaults = self.class.allocate.reset.to_rpc_data to_rpc_data.reject { |k, v| defaults[k] == v } end # @return [Hash] # `self` converted to a Hash. def to_hash hash = {} instance_variables.each do |var| val = instance_variable_get( var ) var = normalize_name( var ) next if TO_HASH_IGNORE.include?( var ) hash[var] = (val.is_a? OptionGroup) ? val.to_h : val end hash.deep_clone end alias :to_h :to_hash # @param [Hash] hash # Hash to convert into {#to_hash} format. # # @return [Hash] # `hash` in {#to_hash} format. def rpc_data_to_hash( hash ) self.class.allocate.reset.update( hash ).to_hash. reject { |k| TO_RPC_IGNORE.include? k } end # @param [Hash] hash # Hash to convert into {#to_rpc_data} format. # # @return [Hash] # `hash` in {#to_rpc_data} format. def hash_to_rpc_data( hash ) self.class.allocate.reset.update( hash ).to_rpc_data end def hash_to_save_data( hash ) self.class.allocate.reset.update( hash ).to_save_data end def dup self.class.allocate.reset.update( self.to_h ) end private def group_classes self.class.group_classes end def normalize_name( name ) name.to_s.gsub( '@', '' ).to_sym end end end