summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/ayk/options.rb182
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 &block; @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