+class Bot
+module Config
+ class Value
+ # allow the definition order to be preserved so that sorting by
+ # definition order is possible. The Wizard does this to allow
+ # the :wizard questions to be in a sensible order.
+ @@order = 0
+ attr_reader :type
+ attr_reader :desc
+ attr_reader :key
+ attr_reader :wizard
+ attr_reader :requires_restart
+ attr_reader :requires_rescan
+ attr_reader :order
+ attr_reader :manager
+ attr_reader :auth_path
+ def initialize(key, params)
+ @manager = Config.manager
+ # Keys must be in the form 'module.name'.
+ # They will be internally passed around as symbols,
+ # but we accept them both in string and symbol form.
+ unless key.to_s =~ /^.+\..+$/
+ raise ArgumentError,"key must be of the form 'module.name'"
+ end
+ @order = @@order
+ @@order += 1
+ @key = key.to_sym
+ if @manager.overrides.key?(@key)
+ @default = @manager.overrides[@key]
+ elsif params.has_key? :default
+ @default = params[:default]
+ else
+ @default = false
+ end
+ @desc = params[:desc]
+ @type = params[:type] || String
+ @on_change = params[:on_change]
+ @validate = params[:validate]
+ @wizard = params[:wizard]
+ @requires_restart = params[:requires_restart]
+ @requires_rescan = params[:requires_rescan]
+ @auth_path = "config::key::#{key.sub('.','::')}"
+ end
+ def default
+ if @default.instance_of?(Proc)
+ @default.call
+ else
+ @default
+ end
+ end
+ def get
+ return @manager.config[@key] if @manager.config.has_key?(@key)
+ return default
+ end
+ alias :value :get
+ def set(value, on_change = true)
+ @manager.config[@key] = value
+ @manager.changed = true
+ @on_change.call(@manager.bot, value) if on_change && @on_change
+ return self
+ end
+ def unset
+ @manager.config.delete(@key)
+ @manager.changed = true
+ @on_change.call(@manager.bot, value) if @on_change
+ return self
+ end
+
+ # set string will raise ArgumentErrors on failed parse/validate
+ def set_string(string, on_change = true)
+ value = parse string
+ if validate value
+ set value, on_change
+ else
+ raise ArgumentError, "invalid value: #{string}"
+ end
+ end
+
+ # override this. the default will work for strings only
+ def parse(string)
+ string
+ end
+
+ def to_s
+ get.to_s
+ end
+
+ protected
+ def validate(val, validator = @validate)
+ case validator
+ when false, nil
+ return true
+ when Proc
+ return validator.call(val)
+ when Regexp
+ raise ArgumentError, "validation via Regexp only supported for strings!" unless String === val
+ return validator.match(val)
+ else
+ raise ArgumentError, "validation type #{validator.class} not supported"
+ end
+ end
+ end
+
+ class StringValue < Value
+ end
+
+ class BooleanValue < Value
+ def parse(string)
+ return true if string == "true"
+ return false if string == "false"
+ if string =~ /^-?\d+$/
+ return string.to_i != 0
+ end
+ raise ArgumentError, "#{string} does not match either 'true' or 'false', and it's not an integer either"
+ end
+ def get
+ r = super
+ if r.kind_of?(Integer)
+ return r != 0
+ else
+ return r
+ end
+ end
+ end
+
+ class IntegerValue < Value
+ def parse(string)
+ return 1 if string == "true"
+ return 0 if string == "false"
+ raise ArgumentError, "not an integer: #{string}" unless string =~ /^-?\d+$/
+ string.to_i
+ end
+ def get
+ r = super
+ if r.kind_of?(Integer)
+ return r
+ else
+ return r ? 1 : 0
+ end
+ end
+ end
+
+ class FloatValue < Value
+ def parse(string)
+ raise ArgumentError, "not a float #{string}" unless string =~ /^-?[\d.]+$/
+ string.to_f
+ end
+ end
+
+ class ArrayValue < Value
+ def initialize(key, params)
+ super
+ @validate_item = params[:validate_item]
+ @validate ||= Proc.new do |v|
+ !v.find { |i| !validate_item(i) }
+ end
+ end
+
+ def validate_item(item)
+ validate(item, @validate_item)
+ end
+
+ def parse(string)
+ string.split(/,\s+/)
+ end
+ def to_s
+ get.join(", ")
+ end
+ def add(val)
+ newval = self.get.dup
+ unless newval.include? val
+ newval << val
+ validate_item(val) or raise ArgumentError, "invalid item: #{val}"
+ validate(newval) or raise ArgumentError, "invalid value: #{newval.inspect}"
+ set(newval)
+ end
+ end
+ def rm(val)
+ curval = self.get
+ raise ArgumentError, "value #{val} not present" unless curval.include?(val)
+ set(curval - [val])
+ end
+ end
+
+ class EnumValue < Value
+ def initialize(key, params)
+ super
+ @values = params[:values]
+ end
+ def values
+ if @values.instance_of?(Proc)
+ return @values.call(@manager.bot)
+ else
+ return @values
+ end
+ end
+ def parse(string)
+ unless values.include?(string)
+ raise ArgumentError, "invalid value #{string}, allowed values are: " + values.join(", ")
+ end
+ string
+ end
+ def desc
+ _("%{desc} [valid values are: %{values}]") % {:desc => @desc, :values => values.join(', ')}
+ end
+ end