lib/fluent/config/types.rb
#
# Fluentd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'json'
require 'fluent/config/error'
module Fluent
module Config
def self.reformatted_value(type, val, opts = {}, name = nil)
REFORMAT_VALUE.call(type, val, opts, name)
end
def self.size_value(str, opts = {}, name = nil)
return nil if str.nil?
case str.to_s
when /([0-9]+)k/i
$~[1].to_i * 1024
when /([0-9]+)m/i
$~[1].to_i * (1024 ** 2)
when /([0-9]+)g/i
$~[1].to_i * (1024 ** 3)
when /([0-9]+)t/i
$~[1].to_i * (1024 ** 4)
else
INTEGER_TYPE.call(str, opts, name)
end
end
def self.time_value(str, opts = {}, name = nil)
return nil if str.nil?
case str.to_s
when /([0-9]+)s/
$~[1].to_i
when /([0-9]+)m/
$~[1].to_i * 60
when /([0-9]+)h/
$~[1].to_i * 60 * 60
when /([0-9]+)d/
$~[1].to_i * 24 * 60 * 60
else
FLOAT_TYPE.call(str, opts, name)
end
end
def self.bool_value(str, opts = {}, name = nil)
return nil if str.nil?
case str.to_s
when 'true', 'yes'
true
when 'false', 'no'
false
when ''
true
else
# Current parser passes comment without actual values, e.g. "param #foo".
# parser should pass empty string in this case but changing behaviour may break existing environment so keep parser behaviour. Just ignore comment value in boolean handling for now.
if str.respond_to?('start_with?') && str.start_with?('#')
true
elsif opts[:strict]
raise Fluent::ConfigError, "#{name}: invalid bool value: #{str}"
else
nil
end
end
end
def self.regexp_value(str, opts = {}, name = nil)
return nil unless str
return Regexp.compile(str) unless str.start_with?("/")
right_slash_position = str.rindex("/")
if right_slash_position < str.size - 3
raise Fluent::ConfigError, "invalid regexp: missing right slash: #{str}"
end
options = str[(right_slash_position + 1)..-1]
option = 0
option |= Regexp::IGNORECASE if options.include?("i")
option |= Regexp::MULTILINE if options.include?("m")
Regexp.compile(str[1...right_slash_position], option)
end
def self.string_value(val, opts = {}, name = nil)
return nil if val.nil?
v = val.to_s
v = v.frozen? ? v.dup : v # config_param can't assume incoming string is mutable
v.force_encoding(Encoding::UTF_8)
end
STRING_TYPE = Proc.new { |val, opts = {}, name = nil|
Config.string_value(val, opts, name)
}
def self.symbol_value(val, opts = {}, name = nil)
return nil if val.nil? || val.empty?
val.delete_prefix(":").to_sym
end
SYMBOL_TYPE = Proc.new { |val, opts = {}, name = nil|
Config.symbol_value(val, opts, name)
}
def self.enum_value(val, opts = {}, name = nil)
return nil if val.nil?
s = val.to_sym
list = opts[:list]
raise "Plugin BUG: config type 'enum' requires :list of symbols" unless list.is_a?(Array) && list.all?(Symbol)
unless list.include?(s)
raise ConfigError, "valid options are #{list.join(',')} but got #{val}"
end
s
end
ENUM_TYPE = Proc.new { |val, opts = {}, name = nil|
Config.enum_value(val, opts, name)
}
INTEGER_TYPE = Proc.new { |val, opts = {}, name = nil|
if val.nil?
nil
elsif opts[:strict]
begin
Integer(val)
rescue ArgumentError, TypeError => e
raise ConfigError, "#{name}: #{e.message}"
end
else
val.to_i
end
}
FLOAT_TYPE = Proc.new { |val, opts = {}, name = nil|
if val.nil?
nil
elsif opts[:strict]
begin
Float(val)
rescue ArgumentError, TypeError => e
raise ConfigError, "#{name}: #{e.message}"
end
else
val.to_f
end
}
SIZE_TYPE = Proc.new { |val, opts = {}, name = nil|
Config.size_value(val, opts, name)
}
BOOL_TYPE = Proc.new { |val, opts = {}, name = nil|
Config.bool_value(val, opts, name)
}
TIME_TYPE = Proc.new { |val, opts = {}, name = nil|
Config.time_value(val, opts, name)
}
REGEXP_TYPE = Proc.new { |val, opts = {}, name = nil|
Config.regexp_value(val, opts, name)
}
REFORMAT_VALUE = ->(type, value, opts = {}, name = nil) {
if value.nil?
value
else
case type
when :string then value.to_s.force_encoding(Encoding::UTF_8)
when :integer then INTEGER_TYPE.call(value, opts, name)
when :float then FLOAT_TYPE.call(value, opts, name)
when :size then Config.size_value(value, opts, name)
when :bool then Config.bool_value(value, opts, name)
when :time then Config.time_value(value, opts, name)
when :regexp then Config.regexp_value(value, opts, name)
when :symbol then Config.symbol_value(value, opts, name)
else
raise "unknown type in REFORMAT: #{type}"
end
end
}
def self.hash_value(val, opts = {}, name = nil)
return nil if val.nil?
param = if val.is_a?(String)
val.start_with?('{') ? JSON.parse(val) : Hash[val.strip.split(/\s*,\s*/).map{|v| v.split(':', 2)}]
else
val
end
if param.class != Hash
raise ConfigError, "hash required but got #{val.inspect}"
end
if opts.empty?
param
else
newparam = {}
param.each_pair do |key, value|
new_key = opts[:symbolize_keys] ? key.to_sym : key
newparam[new_key] = opts[:value_type] ? REFORMAT_VALUE.call(opts[:value_type], value, opts, new_key) : value
end
newparam
end
end
HASH_TYPE = Proc.new { |val, opts = {}, name = nil|
Config.hash_value(val, opts, name)
}
def self.array_value(val, opts = {}, name = nil)
return nil if val.nil?
param = if val.is_a?(String)
val.start_with?('[') ? JSON.parse(val) : val.strip.split(/\s*,\s*/)
else
val
end
if param.class != Array
raise ConfigError, "array required but got #{val.inspect}"
end
if opts[:value_type]
param.map{|v| REFORMAT_VALUE.call(opts[:value_type], v, opts, nil) }
else
param
end
end
ARRAY_TYPE = Proc.new { |val, opts = {}, name = nil|
Config.array_value(val, opts, name)
}
end
end