diff options
| author | Jérémy Zurcher <jeremy@asynk.ch> | 2009-08-23 12:48:05 +0200 | 
|---|---|---|
| committer | Jérémy Zurcher <jeremy@asynk.ch> | 2011-06-30 08:07:29 +0200 | 
| commit | ca5e28805ad8ca2c1ee55c20ef3109601dce678f (patch) | |
| tree | 9bcbc2dc8b3d20a2ec9d71abb90662adba676a24 | |
| parent | c64ae508d158dec51f8de8ed468601b73daa61a9 (diff) | |
| download | ayk-ca5e28805ad8ca2c1ee55c20ef3109601dce678f.zip ayk-ca5e28805ad8ca2c1ee55c20ef3109601dce678f.tar.gz  | |
add option inspired from innate
| -rw-r--r-- | lib/ayk/options.rb | 182 | 
1 files changed, 182 insertions, 0 deletions
diff --git a/lib/ayk/options.rb b/lib/ayk/options.rb new file mode 100644 index 0000000..13b3e40 --- /dev/null +++ b/lib/ayk/options.rb @@ -0,0 +1,182 @@ +#! /usr/bin/env ruby +# -*- coding: UTF-8 -*- + +#---------------------------------------------------------------------------- +# +# File     : options.rb +# Author   : Jérémy Zurcher <jeremy@asynk.ch> +# Date     : 20/07/09  +# +#---------------------------------------------------------------------------- +# +module AYK +    # +    class OptionsError < Exception; end +    # +    # Provides a minimal DSL to describe options with defaults and metadata. +    # fork of Innate::Options +    class Options +        # +        @pedantic = true    # raise OptionsError in particular situation +        class << self +            attr_accessor :pedantic +        end +        def pedantic?; self.class.pedantic; end +        # +        # @param [#to_s]    name    unused at the moment +        # @param [Options]  parent  parent object inherited from Options +        def initialize name, parent = nil +            @name = name +            @parent = parent +            @hash = {} +            yield(self) if block_given? +        end +        # +        # Shortcut for instance_eval +        def dsl &block +            instance_eval(&block) if block +            self +        end +        # +        # Store an option in the Options instance. +        # +        # @param [#to_s]   doc   describing the purpose of this option +        # @param [#to_sym] key   used to access +        # @param [Object]  value may be anything +        # @param [Hash]    other optional Hash containing meta-data +        #                        :doc, :value keys will be ignored +        def option doc, key, value, other = {}, &block +            ks = key.to_sym +            raise OptionsError.new("Overriding existing option #{key}") if pedantic? and @hash.has_key? ks +            trigger = block || other[:trigger] +            convert = {:doc => doc.to_s, :value => value} +            convert[:trigger] = trigger if trigger +            @hash[ks] = other.merge(convert) +        end +        alias o option +        # +        # Create a new Options instance with +name+ and pass +block+ on to its #dsl. +        # Assigns the new instance to the +name+ Symbol on current instance. +        def sub name, &block +            ns = name.to_sym +            if @hash.has_key? ns +                found = @hash[ns] +                raise OptionsError.new("Calling sub on an existing option : #{name}") if not found.is_a? Options +                found.dsl(&block) +            else +                found = @hash[name] = Options.new(name, self).dsl(&block) +            end +            found +        end +        # +        # Add a block that will be called when a new value is set. +        # this block should  behave like Proc.new { |new_value,prev_value| ... } +        def trigger key, &block +            ks = key.to_sym +            raise OptionError.new("Setting trigger for unknown option : #{key}") if pedantic? and not @hash.has_key? ks +            @hash[ks][:trigger] = block +        end +        # +        # To avoid lookup on the parent, we can set a default to the internal Hash. +        # Parameters as in {Options#o}, but without the +key+. +        def default doc, value, other={} +            @hash.default = other.merge(:doc => doc, :value => value) +        end +        # +        # Try to retrieve the corresponding Hash for the passed keys, will try to +        # retrieve the key from a parent if no match is found on the current +        # instance. If multiple keys are passed it will try to find a matching +        # child and pass the request on to it. +        def get(key, *keys) +            if keys.empty? +                if value = @hash[key.to_sym] +                    value                                       # FIXME may be default value, OK ? +                elsif not @parent.nil? +                    @parent.get(key) +                else +                    nil +                end +            elsif sub_options = get(key) +                sub_options.get(*keys) +            end +        end +        private :get +        # +        # Retrieve only the :value from the value hash if found via +keys+. +        def [](*keys) +            value = get(*keys) +            value.is_a?(Hash) ? value[:value] : value +        end +        # +        # Assign new :value to the value hash on the current instance. +        def []=(key, value) +            ks = key.to_sym +            if @hash.has_key? ks +                ns = @hash[ks] +                ns[:trigger].call(value,ns[:value]) if ns[:trigger].respond_to?(:call) +                ns[:value] = value +            elsif existing = get(key) +                option(existing[:doc].to_s.dup, key, value) +            else +                option(":nodoc:", key, value) +            end +        end +        # +        # handle access to options hash +        def method_missing(meth, *args) +            raise NoMethodError.new "Trying to access private method #{meth} through method_missing" if private_methods.include? meth +            case meth.to_s +            when /^(.*)=$/ +                self[$1] = args.first +            else +                self[meth] +            end +        end +#        # +#        # @param [Array] keys +#        # @param [Object] value +#        def set_value(keys, value) +#            got = get(*keys) +#            return got[:value] = value if got +#            raise(IndexError, "There is no option available for %p" % [keys]) +#        end +#        def merge!(hash) +#            hash.each_pair do |key, value| +#                set_value(key.to_s.split('.'), value) +#            end +#        end +        # +        def to_hash; @hash end +        def each █ @hash.each(&block) end +        def each_pair +            @hash.each do |key, values| +                yield(key, self[key]) +            end +        end +        def inspect; @hash.inspect end +        def pretty_print q; q.pp_hash @hash end +    end +    # +#    # extend your class with this +#    module Optioned +#        def self.included(into) +#            into.extend(SingletonMethods) +# +#            snaked = into.name.split('::').last +#            snaked = snaked.gsub(/\B[A-Z][^A-Z]/, '_\&').downcase.gsub(' ', '_') +# +#            options = Innate.options.sub(snaked) +#            into.instance_variable_set(:@options, options) +#        end +# +#        module SingletonMethods +#            attr_reader :options +#        end +# +#        private +# +#        def options +#            self.class.options +#        end +#    end +end  | 
