# type.rb : a framework for type-to-string-to-type conversion # Copyright (C) 2006-2009 Vincent Fourmond # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 # USA require 'ctioga2/utils' module CTioga2 # The MetaBuilder module contains a framework to perform # string-to-type conversion and other stuff that can be useful for backends. module MetaBuilder # An exception that must be raised when # Type#string_to_type is given incorrect input. class IncorrectInput < Exception end # An exception raised when an invalid type is given to the class InvalidType < Exception end # A class that handles a parameter type. It has to be subclassed to # actually provide a parameter. The subclasses must provide the # following: # # * a #string_to_type function to convert from string to the type; # * a #type_to_string to convert back from type to string # * an instance #type_name that returns a really small description # of the type, to be used for instance to name command-line parameters. # * a #type_name statement that registers the current class to the # Type system. # # Moerover, it is a good idea to reimplement the # #qt4_create_input_widget method; the default implementation works, # but you probably wish it would look better. # # Types are implemented using hashes: this way, additionnal # parameters can easily be added. The hash *must* have a :type key # that will be interpreted by the children of Type. Examples: # # { :type => :integer} # { :type => :file, :filter => "Text Files (*.txt)} # # And so on. You definitely should document your type and it's # attributes properly, if you ever want that someone uses it. # # The list of currently recognised types is here: # # :integer :: Types::IntegerParameter # :float :: Types::FloatParameter # :string :: Types::StringParameter # :file :: Types::FileParameter # :boolean :: Types::BooleanParameter # :list :: Types::ListParameter # # Additionally to the parameters the given type is requiring, you can # pass some other kind of information using this hash, such as option # parser short argument, aliases, and so on. This has nothing to do # with type conversion, but it is the best place where to put this kind # of things, in my humble opinion. The currently recognized such additional # parameters are: # * :option_parser_short: a short option name for option_parser. # * :namespace: a ruby module that will be searched by #string_to_type # for a constant. If one of the given name is found, its value is # returned. # * :shortctus: a hash specifiying strings shortcuts for given values. # Elements of this hash that are regular expressions are taken class Type # A hash that makes the :type value of the _type_ argument correspond # to a Type child @@types = { } # The initial type specification that was given to the Type attr_accessor :type # A hash shortcut -> value. Can be _nil_ attr_accessor :shortcuts # A hash Regexp -> value. All elements will be looked for # matches for every single string conversion, so don't dump too # many of them here. attr_accessor :re_shortcuts # If the given string matches this regular expression, it is # passed through without further modification. attr_accessor :passthrough # An array of module whose constants can be used "as such" attr_accessor :namespace # When a :namespace option is provided, this hash provides a # lookup 'lowercase name' => constant value. attr_accessor :namespace_lookup # A default constructor. It should be safe to use it directly for # children, unless something more specific is needed. Any descendent # should *always* register _type_ as @type - or, even better, call # super. def initialize(type) if type.is_a?(Symbol) type = {:type => type} end @type = type if @type[:shortcuts] @shortcuts = @type[:shortcuts] @re_shortcuts = {} for k,v in @shortcuts if k.is_a? Regexp @re_shortcuts[k] = v end end end if @type[:passthrough] @passthrough = @type[:passthrough] end end # This class function actually registers the current type # to the Type ancestor. _name_ should be a symbol. # Moreover, if the second argument is provided, it automatically # creates a #type_name instance method returning this value. def self.type_name(name, public_name = nil, default_value = nil) if @@types.has_key?(name) warn { "Redefining type #{name} " + "from #{@@types[name]} to #{self}" } end @@types[name] = self self.send(:define_method,:type_name) do public_name end self.send(:define_method,:default_value) do default_value end end # This function converts a 'description' (see the Type) of the # type wanted into a Type child. As a special treat, a lone # symbol is converted into {:type => :symbol} def self.get_param_type(type) if type.is_a?(Symbol) type = {:type => type} end raise InvalidType,"The type argument must be a Hash" unless type.is_a?(Hash) begin return @@types.fetch(type[:type]) rescue raise InvalidType, "Type #{type[:type]} unknown to the type system" end end # Shortcut to convert directly a string to the given type specification. # Handy shortcut. def self.from_string(type, string) return get_type(type).string_to_type(string) end # Returns a Type child instance suitable for conversion # of the given type specification def self.get_type(type) if type.is_a? Type return type end return get_param_type(type).new(type) end # This function converts the given string to the appropriate # type. It is a wrapper around the #string_to_type_internal # function that can take advantage of a few general features. It # is recommanded to define a #string_to_type_internal function # rather to redefine #string_to_type def string_to_type(string) # First, passthrough if @passthrough && @passthrough === string return stt_run_hook(string) end # First, shortcuts: if @shortcuts and @shortcuts.key? string return stt_run_hook(@shortcuts[string]) end if @re_shortcuts for k, v in @re_shortcuts if string =~ k return stt_run_hook(v) end end end # Then, constants lookup. if @type.key?(:namespace) begin return stt_run_hook(lookup_const(string)) rescue IncorrectInput end end return stt_run_hook(string_to_type_internal(string)) end # This function does the exact opposite of the #string_to_type # one. It defaults to using the to_s methods of the # parameter. Be careful: it is absolutely important that for any # valid type, # # string_to_type(type_to_string(type)) == type def type_to_string(type) return type_to_string_internal(type) end # Returns a default value for the given type. This is # reimplemented systematically from children, with the # Type::type_name statement. def default_value end # Returns a type name suitable for displaying, for instance, in # an option parser, or inside a dialog box, and so on. Has to be # one word (not to confuse the option parser, for instance); it # is better if it is lowercase. def type_name return 'notype' end # Creates an option for the OptionParser _parser_. The block is # fed with the converted value. The default implementation # should be fine for most classes, but this still leaves the # room for reimplementation if necessary. The parameters are: # # * _parser_: the OptionParser; # * _name_: the name of the option; # * _desc_: it description, # * _block_: the block used to set the data. def option_parser_option(parser, name, desc, &block) args = [option_parser_long_option(name), desc] if @type.has_key?(:option_parser_short) args.unshift(@type[:option_parser_short]) end option_parser_raw(parser, *args, &block) end # Returns a value to be fed to OptionParser#on as a 'long' # option. It is separated from the rest to allow easy # redefinition (in special cases). _name_ is the name of the # option. def option_parser_long_option(name, param = nil) param ||= type_name param = param.gsub(/\s+/, '_') return "--#{name} #{param.upcase}" end # Whether the type is a boolean. Booleans are special cased for # their use in the command-line. def boolean? return false end ############################################################ # Part of the internal implementation of Types. This should be # used/redefined in children protected def build_namespace_lookup if @type[:namespace] @namespace = [@type[:namespace]].flatten @namespace_lookup = {} for m in @namespace for c in m.constants @namespace_lookup[c.to_s.downcase] = m.const_get(c) end end end end # Looks for the value as a constant specified in the :namespace # modules. Raises IncorrectInput if not found. def lookup_const(str) str = str.downcase if @type[:namespace] && (! @namespace_lookup) build_namespace_lookup end if @namespace_lookup.key? str return @namespace_lookup[str] else raise IncorrectInput, "Constant #{str} not found" end end # The internal function for converting type to a string. Used by # #type_to_string, children should only reimplement this # function and leave #type_to_string def type_to_string_internal(type) return type.to_s end # Runs the string_to_type conversion hook def stt_run_hook(val) if @type.key?(:stt_hook) return @type[:stt_hook].call(val) else val end end # Does the actual conversion from a _string_ to the # type. Redefine this in children. def string_to_type_internal(string) raise "The class #{self.class} should not be used by itself for conversion" end # Creates on option for the OptionParser _parser_. The _args_ # will be fed directly to OptionParser#on. The _block_ is called # with the value in the target type. def option_parser_raw(parser, *args, &block) b = block # For safe-keeping. c = proc do |str| b.call(string_to_type(str)) end parser.on(*args, &c) end end end end