class Orocos::TaskConfigurations

Class handling multiple possible configuration for a single task

It can load configuration files that are structured as follows:

A configuration file is a YAML file that contains multiple sections. Each section starts with — and can contain options of the form option_name:value. The section header can be omitted for the very first section

For instance

--- name:default merge:true chain:default,test

The following options are possible:

name

it is optional for the first section and mandatory for further sections. It gives a name to the section, that can then be used to refer to the configuration information in #apply and #conf. If ommitted for the first section, the name 'default' is used

merge

If set to true, the section will be merged with previous configuration data previously stored under the same name. Otherwise, it replaces existing information. The default is false.

chain

If set, it has to be a comma-separated list of configuration names. It tells the configuration class that this configuration section should always be merged with the ones listed. The name of the current configuration section can be listed, in which case it will be merged in the specified order. Otherwise, it is added at the end.

Constants

ROUNDING_MODES
SCALES
UNITS

Attributes

model[R]

@return [OroGen::Spec::TaskContext] the task context model for which self holds

configurations
sections[R]

The known configuration sections for this task context model

Configuration sections are formatted as follows:

- compounds are represented by hashes
- arrays and containers are represented by arrays
- all other values are represented by the corresponding typelib value

This formatting allows to properly perform configuration merging, for instance when one selects the ('default', 'specific') configuration. Indeed, the compounds-represented-by-hashes only hold the values that are explicitly set in the input configuration hash. The nil entries in the arrays also allow to not override already set values.

The toplevel value (i.e. the value of e.g. sections) is always a hash whose keys are the task's property names.

@return [{String=>{String=>Object}}]

Public Class Methods

apply_conf_array_on_typelib_value(value, conf) click to toggle source

@api private

Helper method for {.to_typelib} when the value is an array

# File lib/orocos/configurations.rb, line 796
def self.apply_conf_array_on_typelib_value(value, conf)
    if value.kind_of?(Typelib::ArrayType)
        # This is a fixed-size array, verify that the size matches
        if conf.size > value.size
            raise ArgumentError, "Configuration object size is larger than field #{value}"
        end
    else
        element_t = value.class.deference
        while value.size < conf.size
            new_value = element_t.new
            new_value.zero!
            value.push(new_value)
        end
    end
    conf.each_with_index do |element, idx|
        value[idx] = apply_conf_on_typelib_value(value.raw_get(idx), element)
    end
    value
end
apply_conf_on_typelib_value(value, conf) click to toggle source

Applies a value coming from a YAML-compatible data structure to a typelib value

@param [Typelib::Type] value the value to be updated. Note that the

actually updated value is returned by the method (it might be
a different object)

@param [Object] conf a configuration object, as a mix of Hash, Array,

Numeric, String and Typelib values

@return [Typelib::Type] the updated value. It is not necessarily equal

to value
# File lib/orocos/configurations.rb, line 826
def self.apply_conf_on_typelib_value(value, conf)
    if conf.kind_of?(Hash)
        conf.each do |conf_key, conf_value|
            value.raw_set(conf_key,
                apply_conf_on_typelib_value(value.raw_get(conf_key), conf_value))
        end
        value
    elsif conf.respond_to?(:to_ary)
        apply_conf_array_on_typelib_value(value, conf)
    else
        Typelib.from_ruby(conf, value.class)
    end
end
convert_unit_to_SI(expr) click to toggle source
# File lib/orocos/configurations.rb, line 275
def self.convert_unit_to_SI(expr)
    unit, power = expr.split('^')
    power = Integer(power || '1')
    if unit_to_si = UNITS[unit]
        return unit_to_si ** power
    end

    SCALES.each do |prefix, scale|
        if unit.start_with?(prefix)
            if unit_to_si = UNITS[unit[prefix.size..-1]]
                return (unit_to_si*scale) ** power
            end
        end
    end
    raise ArgumentError, "does not know how to convert #{expr} to SI"
end
load_raw_sections_from_file(file) click to toggle source

Parses a configuration file to return the text of each section along with the section's options

@param [String] file the file @return [Array<(Hash,String)>] the list of sections. The hash is the

parsed representation of the option line (the line starting with
---) and the string is the raw section text
# File lib/orocos/configurations.rb, line 130
def self.load_raw_sections_from_file(file)
    document_lines = File.readlines(file)

    headers = document_lines.each_with_index.
        find_all { |line, _| line =~ /^---/ }
    if headers.empty? || headers.first[1] != 0
        headers.unshift ["--- name:default", -1]
    end

    options = headers.map do |line, line_number|
        line_options = Hash.new
        line = line.chomp
        line.split(/\s+/)[1..-1].each do |opt|
            if opt =~ /^(\w+):(.*)$/
                line_options[$1] = $2
            else
                raise ArgumentError, "#{file}:#{line_number}: wrong format #{opt}, expected option_name:value, where 'value' has no spaces"
            end
        end

        section_options = Hash[
            name: line_options.delete('name'),
            merge: (line_options.delete('merge') == 'true'),
            chain: (line_options.delete('chain') || '').split(',')]
        if !line_options.empty?
            ConfigurationManager.warn "unrecognized options #{line_options.keys.sort.join(", ")} in #{file}"
        end

        [section_options, line_number]
    end
    options[0][0][:name] ||= 'default'

    options.each do |line_options, line_number|
        if !line_options[:name]
            raise ArgumentError, "#{file}:#{line_number}: missing a 'name' option"
        end
    end

    sections = []
    options.each_cons(2) do |(_, line0), (_, line1)|
        sections << document_lines[line0 + 1, line1 - line0 - 1]
    end
    sections << document_lines[options[-1][1] + 1, document_lines.size - options[-1][1] - 1]

    options.map(&:first).zip(sections)
end
merge_conf(a, b, override) click to toggle source

Helper method that adds the configuration of b into the existing configuration hash a

See {#sections} for a description of how the configuration value formatting allows this to be done.

# File lib/orocos/configurations.rb, line 621
def self.merge_conf(a, b, override)
    result = if override
        a.recursive_merge(b) do |k, v1, v2|
            if v1.respond_to?(:to_ary) && v2.respond_to?(:to_ary)
                merge_conf_array(v1, v2, true)
            else
                v2
            end
        end
    else
        a.recursive_merge(b) do |k, v1, v2|
            if v1.respond_to?(:to_ary) && v2.respond_to?(:to_ary)
                merge_conf_array(v1, v2, false)
            elsif v1 != v2
                raise ArgumentError, "cannot merge configuration: conflict in field #{k} between v1=#{v1} and v2=#{v2}"
            else
                v1
            end
        end
    end
    result
end
merge_conf_array(a, b, override) click to toggle source
# File lib/orocos/configurations.rb, line 586
def self.merge_conf_array(a, b, override)
    result = []
    a.each_with_index do |v1, idx|
        v2 = b[idx]

        if !v2
            result << v1
            next
        elsif !v1
            result << v2
            next
        end

        if v1.kind_of?(Hash) && v2.kind_of?(Hash)
            result << merge_conf(v1, v2, override)
        elsif v1.respond_to?(:to_ary) && v2.respond_to?(:to_ary)
            result << merge_conf_array(v1, v2, override)
        elsif override || v1 == v2
            result << v2
        else
            raise ArgumentError, "cannot merge configuration: conflict in [#{idx}] between v1=#{v1} and v2=#{v2}"
        end
    end

    if b.size > a.size
        result.concat(b[a.size..-1])
    end
    result
end
new(task_model) click to toggle source
# File lib/orocos/configurations.rb, line 78
def initialize(task_model)
    @model = task_model
    @sections = Hash['default' => Hash.new]
    @merged_conf = Hash.new
    @context = Array.new
end
read_task_conf(task) click to toggle source

@api private

Reads the configuration of a task into a property-name-to-typelib value form

# File lib/orocos/configurations.rb, line 883
def self.read_task_conf(task)
    current_config = Hash.new
    task.each_property do |prop|
        current_config[prop.name] = prop.raw_read
    end
    current_config
end
save(config, file, name, task_model: nil, replace: false) click to toggle source

Saves a configuration section to a file

@overload save(conf, file, name)

@param [Hash] config the configuration section that should be saved,
  either as a hash of plain Ruby objects, or as a mapping from
  property names to typelib values
@param [String] file either a file or a directory. If it is a
  directory, the generated file will be named based on the task's
  model name
@param [String,nil] name the name of the new section
@param [TaskContext] task_model if given, the property's
  documentation stored in this model are added before each property
@return [Hash] the task configuration in YAML representation, as
  returned by {.config_as_hash}

@overload save(task, file, name)

@param [TaskContext] task the task whose configuration is to be saved
@param [String] file either a file or a directory. If it is a
  directory, the generated file will be named based on the task's
  model name
@param [String,nil] name the name of the new section. If nil is given,
  defaults to task.name
@return [Hash] the task configuration in YAML representation, as
  returned by {.config_as_hash}
# File lib/orocos/configurations.rb, line 965
def self.save(config, file, name, task_model: nil, replace: false)
    if config.respond_to?(:each_property)
        conf = TaskConfigurations.new(task_model || config.model)
        conf.extract(name, config)
        return conf.save(name, file)
    end

    task_model ||= OroGen::Spec::TaskContext.blank
    config = to_yaml(config)

    if File.directory?(file)
        if !task_model.name
            raise ArgumentError, "#{file} is a directory and the given model has no name"
        end
        file = File.join(file, "#{task_model.name}.yml")
    else
        FileUtils.mkdir_p(File.dirname(file))
    end

    parts = []
    config.keys.sort.each do |property_name|
        if (p = task_model.find_property(property_name)) && (doc = p.doc)
            parts << doc.split("\n").map { |s| "# #{s}" }.join("\n")
        else
            parts << "# no documentation available for this property"
        end

        property_hash = { property_name => config[property_name] }
        yaml = YAML.dump(property_hash)
        parts << yaml.split("\n")[1..-1].join("\n")
    end

    if !replace
        File.open(file, 'a') do |io|
            io.write("--- name:#{name}\n")
            io.write(parts.join("\n"))
            io.puts
        end
    else
        raw_sections =
            begin load_raw_sections_from_file(file)
            rescue Errno::ENOENT
                Array.new
            end

        raw_sections.delete_if { |options, _| options[:name] == name }
        raw_sections << [Hash[name: name], parts.map { |l| "#{l}\n" }]
        File.open(file, 'w') do |io|
            raw_sections.each do |options, doc|
                formatted_options = options.map do |k, v|
                    if v.respond_to?(:to_ary)
                        next if !v.empty?
                        v = v.join(",")
                    end
                    "#{k}:#{v}"
                end.compact
                io.puts "--- #{formatted_options.join(" ")}"
                io.write doc.join("")
                if doc.last != "\n"
                    io.puts
                end
            end
        end
    end
    config
end
to_yaml(value) click to toggle source

Converts a configuration structure into a representation suitable for marshalling into YAML

@param [Object] value the value to be converted, this is a mix of

Hash, Array, numeric and string Ruby objects, and Typelib values

@return [Object] a value that can be represented in YAML as-is

# File lib/orocos/configurations.rb, line 846
def self.to_yaml(value)
    case value
    when Typelib::CompoundType
        value.apply_changes_from_converted_types

        result = Hash.new
        value.raw_each_field do |field_name, field_value|
            result[field_name] = to_yaml(field_value)
        end
        result
    when Typelib::ArrayType, Typelib::ContainerType
        value.apply_changes_from_converted_types
        if value.respond_to?(:to_str)
            value.to_str
        else
            value.raw_each.map(&method(:to_yaml))
        end
    when Typelib::Type
        value.apply_changes_from_converted_types
        Typelib.to_ruby(value)
    when Array
        value.map(&method(:to_yaml))
    when Hash
        value.map_value do |_, v|
            to_yaml(v)
        end
    when Numeric, String, Symbol
        value
    else
        raise ArgumentError, "invalid object #{value} of type #{value.class} found while converting typelib values to their YAML representation"
    end
end

Public Instance Methods

[](section_name) click to toggle source

Retrieves the configuration for the given section name

@return [Object] see the description of {#sections} for the description

of formatting
# File lib/orocos/configurations.rb, line 96
def [](section_name)
    sections[section_name]
end
add(name, conf, normalize: true, merge: true) click to toggle source

Add a new configuration section to the configuration set

@param [String] name the configuration section name @param [{String=>Object}] conf the configuration data, as either a

mapping from property names to property values, or property names to
plain Ruby objects. It gets passed to {#normalize_conf}.

@param [Boolean] normalize if true, the configuration is normalized

to this class' expected internal representation first. Set to
false when the data has already been normalized.

@param [Boolean] merge if true, the configuration will be merged with

an existing section that has the same name (if there is one)

@return [Boolean] true if the configuration changed, and false

otherwise

@see extract

# File lib/orocos/configurations.rb, line 346
def add(name, conf, normalize: true, merge: true)
    if normalize
        conf = normalize_conf(conf)
    end

    changed = false
    if self.sections[name]
        if merge
            conf = TaskConfigurations.merge_conf(self.sections[name], conf, true)
        end
        changed = (self.sections[name] != conf)
        if changed
            # This happens rarely, be brutal about cache invalidation
            @merged_conf.clear
        end
    else
        changed = true
    end
    self.sections[name] = conf
    changed
end
apply(task, config, override = false) click to toggle source

Applies the specified configuration to the given task

@param [TaskContext] task the task on which the configuration should

be applied

@param [String,Array<String>,Hash] config either the name (or names) of

configuration section(s) as should be passed to {#conf}, or directly
a configuration value as a mapping from property names to
configuration object

@param [Boolean] override the override argument of {#conf} @return [void]

# File lib/orocos/configurations.rb, line 770
def apply(task, config, override = false)
    if !config.kind_of?(Hash)
        config = conf(config, override)
    end

    if !config
        if names == ['default']
            ConfigurationManager.info "required to apply configuration #{names.join(", ")} on #{task.name} of type #{task.model.name}, but this configuration is not registered or empty. Not changing anything."
            return
        else
            raise ArgumentError, "no configuration #{names.join(", ")} for #{task.model.name}"
        end
    end

    timestamp = Time.now
    config.each do |prop_name, conf|
        p = task.property(prop_name)
        result = p.raw_read
        result = TaskConfigurations.apply_conf_on_typelib_value(result, conf)
        p.write(result, timestamp)
    end
end
conf(names, override = false) click to toggle source

Returns the task configuration that is the combination of the named configuration sections

@param [Array<String>] names the list of sections that should be applied @param [Boolean] override if false, one of the sections listed in the

names parameter cannot override the value set by another. Otherwise,
the configurations are merged, with the sections appearing last
overriding the sections appearing first.

@raise ArgumentError if one of the listed sections does not exist, or

if the override option is false and two sections try to set the same
property

@return [Hash] a hash in which the keys are property names and the

values Typelib values that can be used to set these properties. See
{#apply} for a shortcut to apply a configuration on a task

For instance, let's assume that the following configurations are available

--- name:default
threshold: 20
--- name: fast
speed: 10
--- name: slow
speed: 1

Then

configuration(['default', 'fast'])

returns { 'threshold' => 20, 'speed' => 10 } regardless of the value of the override parameter, while

configuration(['default', 'fast', 'slow'])

will raise ArgumentError and

configuration(['default', 'fast', 'slow'], true)

returns { 'threshold' => 20, 'speed' => 1 }

@raises [SectionNotFound] if one of the required

configuration sections do not exist
# File lib/orocos/configurations.rb, line 698
def conf(names, override = false)
    names = Array(names)
    if names.empty?
        return Hash.new
    elsif cached = @merged_conf[[names, override]]
        return cached
    else
        config = names.inject(Hash.new) do |c, section_name|
            section = sections[section_name]
            if !section
                raise SectionNotFound.new(section_name), "#{section_name} is not a known configuration section for #{model.name}"
            end
            TaskConfigurations.merge_conf(c, section, override)
        end
        @merged_conf[[names, override]] = config
        return config
    end
end
conf_as_ruby(names, override: false) click to toggle source

Returns the required configuration in a property-to-ruby form

The objects are equivalent to the ruby objects one would get by enumerating a task's property

@param [String,Array<String>] names the configurations to apply. See

{#conf} for more details

@param [Boolean] override see {#conf}

@see conf conf_to_typelib

# File lib/orocos/configurations.rb, line 727
def conf_as_ruby(names, override: false)
    conf = conf_as_typelib(names, override: override)
    conf.map_value do |_, v|
        Typelib.to_ruby(v)
    end
end
conf_as_typelib(names, override: false) click to toggle source

Returns the required configuration in a property-to-typelib form

The typelib values are equivalent to the typelib objects one would get by enumerating a task's property

@param [String,Array<String>] names the configurations to apply. See

{#conf} for more details

@param [Boolean] override see {#conf}

@see conf conf_to_ruby

# File lib/orocos/configurations.rb, line 744
def conf_as_typelib(names, override: false)
    c = conf(names, override)
    return if !c

    result = Hash.new
    c.each do |property_name, ruby_value|
        orocos_type = model.find_property(property_name).type
        typelib_type = loader.typelib_type_for(orocos_type)

        typelib_value = typelib_type.new
        typelib_value.zero!
        result[property_name] = TaskConfigurations.apply_conf_on_typelib_value(typelib_value, ruby_value)
    end
    result
end
current_context() click to toggle source

Returns a string that describes in which context we are currently loading, for the benefit of warning and error messages

@see #in_context @return [String] the current context, or an empty string if none has

been specified with {#in_context}
# File lib/orocos/configurations.rb, line 912
def current_context
    @context.last || ''
end
each_resolved_conf() { |conf_name, conf([conf_name])| ... } click to toggle source
# File lib/orocos/configurations.rb, line 649
def each_resolved_conf
    return enum_for(__method__) if !block_given?
    sections.each_key do |conf_name|
        yield(conf_name, conf([conf_name]))
    end
end
evaluate_dynamic_content(filename, value) click to toggle source

@api private

Evaluate ruby content that has been embedded into the configuration file inbetween <%= … %>

# File lib/orocos/configurations.rb, line 104
def evaluate_dynamic_content(filename, value)
    ruby_content = ""
    begin
        # non greedy matching of dynamic code
        value.gsub!(/<%=((.|\n)*?)%>/) do |match|
            if match =~ /<%=((.|\n)*?)%>/
                ruby_content = $1.strip
                p = Proc.new {}
                eval(ruby_content, p.binding, filename)
            else
                match
            end
        end
    rescue Exception => e
        raise e, "error evaluating dynamic content '#{ruby_content}': #{e.message}", e.backtrace
    end
    value
end
evaluate_numeric_field(field, field_type) click to toggle source
# File lib/orocos/configurations.rb, line 294
def evaluate_numeric_field(field, field_type)
    rounding_mode = nil
    if field.respond_to?(:to_str)
        # Extract the value first
        if field =~ /^([+-]?\d+)$/
            # This is a plain integer, don't bother and don't annoy the
            # user with a float-to-integer rounding mode warning
            return Integer(field)
        elsif field =~ /^([+-]?\d+(?:\.\d+)?(?:e[+-]\d+)?)(.*)/
            value, unit = Float($1), $2
        else
            raise ArgumentError, "#{field} does not look like a numeric field"
        end

        unit = unit.scan(/\.\w+(?:\^-?\d+)?/).inject(1) do |u, unit_expr|
            unit_name = unit_expr[1..-1]
            if ROUNDING_MODES.include?(unit_name)
                rounding_mode = unit_name
                u
            else
                u * TaskConfigurations.convert_unit_to_SI(unit_name)
            end
        end
        value = value * unit
    else
        value = field
    end

    if value.kind_of?(Float) && field_type.integer?
        if !rounding_mode
            ConfigurationManager.warn "#{current_context} #{field} used for an integer field, but no rounding mode specified. Append one of .round, .floor or .ceil. This defaults to .floor"
            rounding_mode = :floor
        end
        value.send(rounding_mode)
    else value
    end
end
extract(name, task, merge: true) click to toggle source

Extract configuration from a task object and save it as a section in self

@param [#each_property] task the task. each_property must yield

objects which respond to #raw_read, this method returning a Typelib
value.

@param [String] section_name the section name. If one already exists

with that name it is overriden

@param [Boolean] merge if true, the configuration will be merged with

an existing section that has the same name (if there is one)

@return [Boolean] true if the configuration changed, and false

otherwise

@see add

# File lib/orocos/configurations.rb, line 388
def extract(name, task, merge: true)
    in_context("while saving section #{name} from task #{task.name}(#{task.model.name})") do
        add(name, TaskConfigurations.read_task_conf(task), merge: merge)
    end
end
has_section?(name) click to toggle source

Tests whether the given section exists

# File lib/orocos/configurations.rb, line 645
def has_section?(name)
    sections.has_key?(name)
end
in_context(msg) { || ... } click to toggle source

Specifies a string that describes in which context we are currently loading, for the benefit of warning and error messages.

@yield within this block, {#current_context} will return the message

string

@param [String] msg the context string @return [Object] the block's return value

# File lib/orocos/configurations.rb, line 899
def in_context(msg)
    @context << msg
    yield
ensure
    @context.pop
end
initialize_copy(source) click to toggle source
Calls superclass method
# File lib/orocos/configurations.rb, line 85
def initialize_copy(source)
    super
    @sections = sections.map_value { |k, v| v.dup }
    @merged_conf = Hash.new
    @context = Array.new
end
load_from_yaml(file, cache_dir: nil) click to toggle source

Loads the configurations from a YAML file

Multiple configurations can be saved in the file, in which case each configuration set must be separated by a line of the form

--- name:configuration_name

The first YAML document has, by default, the name 'default'. One can also be provided if needed.

@return [Array<String>] the names of the sections that have been modified

# File lib/orocos/configurations.rb, line 213
def load_from_yaml(file, cache_dir: nil)
    sections = self.class.load_raw_sections_from_file(file)

    changed_sections = []
    sections.each do |conf_options, doc|
        doc = doc.join("")
        doc = evaluate_dynamic_content(file, doc)

        if cache_dir
            cache_id, cached_yaml = read_yaml_from_cache(cache_dir, doc)
        end
        unless cached_yaml
            loaded_yaml = YAML.load(StringIO.new(doc)) || Hash.new
        end

        begin
            result = normalize_conf(cached_yaml || loaded_yaml || Hash.new)
        rescue ConversionFailed => e
            raise e, "while loading section #{conf_options[:name] || 'default'} #{e.message}", e.backtrace
        end

        if cache_id && !cached_yaml
            save_yaml_to_cache(cache_dir, cache_id, loaded_yaml)
        end

        name  = conf_options.delete(:name)
        chain = conf(conf_options.delete(:chain), true)
        result = Orocos::TaskConfigurations.merge_conf(result, chain, true)
        changed = in_context("while loading section #{name} of #{file}") do
            add(name, result, normalize: false, **conf_options)
        end

        if changed
            changed_sections << name
        end
    end
    if !changed_sections.empty?
       @merged_conf.clear
    end
    changed_sections
rescue Exception => e
    raise e, "error loading #{file}: #{e.message}", e.backtrace
end
loader() click to toggle source

@return [OroGen::Loaders::Base] a loader object that allows to access

the underlying oroGen models
# File lib/orocos/configurations.rb, line 74
def loader
    model.loader
end
normalize_conf(conf) click to toggle source

Converts a representation of a task configuration

@param [{String=>Object}] conf a mapping from property name to value.

See {#normalize_conf_value} for a description of the value's
formatting

@return [Object] a normalized configuration hash

# File lib/orocos/configurations.rb, line 420
def normalize_conf(conf)
    property_types = Hash.new
    conf.each do |k, v|
        if p = model.find_property(k)
            property_types[k] = model.loader.typelib_type_for(p.type)
        else
            raise ConversionFailed.new, "#{k} is not a property of #{model.name}"
        end
    end

    return normalize_conf_hash(conf, property_types)
end
normalize_conf_array(array, value_t) click to toggle source

@api private

Helper for {.}. See it for details

# File lib/orocos/configurations.rb, line 521
def normalize_conf_array(array, value_t)
    if value_t.respond_to?(:length) && value_t.length < array.size
        raise ConversionFailed.new, "array too big (got #{array.size} for a maximum of #{value_t.length}"
    end

    element_t = value_t.deference
    if element_t <= Typelib::NumericType
        # Try to pack the array. If it works, return it straight.
        # Otherwise, go through the slow path
        begin
            packed_array = array.pack("#{element_t.pack_code}*")
            if value_t.respond_to?(:length)
                if value_t.length == array.size
                    return value_t.from_buffer(packed_array)
                else
                    return array.map do |v|
                        normalize_conf_terminal_value(v, element_t)
                    end
                end
            else
                return value_t.from_buffer([array.size].pack("Q") + packed_array)
            end
        rescue TypeError
        end
    end

    array.each_with_index.map do |value, i|
        begin
            normalize_conf_value(value, element_t)
        rescue ConversionFailed => e
            e.full_path.unshift "[#{i}]"
            raise e, "failed to convert configuration value for #{e.full_path.join("")}", e.backtrace
        end
    end
end
normalize_conf_terminal_value(value, value_t) click to toggle source

@api private

Helper for {#normalize_conf_value} to normalize values that are terminal, i.e. that should be converted to a typelib value

# File lib/orocos/configurations.rb, line 495
def normalize_conf_terminal_value(value, value_t)
    if value_t <= Typelib::NumericType
        ruby_value = Typelib.to_ruby(value)
        if ruby_value.respond_to?(:to_str)
            converted_value = evaluate_numeric_field(ruby_value, value_t)
        else
            converted_value = value
        end
        typelib_value = Typelib.from_ruby(converted_value, value_t)
    else
        typelib_value = Typelib.from_ruby(value, value_t)
    end

    if typelib_value.class != value.class
        return normalize_conf_value(typelib_value, value_t)
    else
        typelib_value
    end

rescue ArgumentError => e
    raise ConversionFailed.new(e), e.message, e.backtrace
end
normalize_conf_value(value, value_t) click to toggle source

Converts a value into a normalized representation suitable to be stored in self

{TaskConfigurations} stores configuration as a combination of hashes (for structs), arrays (for arrays and containers) and typelib values.

Hashes and arrays are used to represent partial values. When applying the configuration, they are applied to the existing objects without erasing other existing data (e.g. with a hash, only the fields whose keys are present will be applied to the value).

Typelib values are final, i.e. they erase the complete part of the configuration they represent

This method iterates over the existing value, validates field names and types, and converts leaves (e.g. numeric fields) to their typelib representations once and for all.

@param [Object] value a value that is a mix of hash, arrays and

either nuermic/string values or typelib values. See description
above for more details.

@param [Typelib::Type] value_t the type we are validating against @return [Object] a normalized configuration value

# File lib/orocos/configurations.rb, line 457
def normalize_conf_value(value, value_t)
    if value_t.method_defined?(:to_str)
        return normalize_conf_terminal_value(value, value_t)
    elsif value.kind_of?(value_t)
        return value
    end

    case value
    when Typelib::ContainerType, Typelib::ArrayType
        element_t = value_t.deference
        value.raw_each.map { |v| normalize_conf_value(v, element_t) }
    when Typelib::CompoundType
        result = Hash.new
        value.raw_each_field do |field_name, field_value|
            result[field_name] = normalize_conf_value(field_value, value_t[field_name])
        end
        result
    when Hash
        if value_t <= Typelib::CompoundType
            normalize_conf_hash(value, value_t)
        else
            raise ConversionFailed.new, "cannot interpret a hash as a #{value_t.name}"
        end
    when Array
        if value_t <= Typelib::ArrayType || value_t <= Typelib::ContainerType
            normalize_conf_array(value, value_t)
        else
            raise ConversionFailed.new, "cannot interpret an array as #{value_t.name}"
        end
    else
        normalize_conf_terminal_value(value, value_t)
    end
end
read_yaml_from_cache(cache_dir, doc) click to toggle source

@api private

Read the YAML from the cache directory, if available

# File lib/orocos/configurations.rb, line 180
def read_yaml_from_cache(cache_dir, doc)
    cache_id = Digest::SHA256.hexdigest(doc)
    path = File.join(cache_dir, cache_id)
    return cache_id unless File.exist?(path)

    begin
        [cache_id, Marshal.load(File.read(path))]
    rescue Exception
        cache_id
    end
end
remove(name) click to toggle source

Remove a configuration section

@param [String] name the section name @return [Boolean] true if such as section existed, and false otherwise

# File lib/orocos/configurations.rb, line 372
def remove(name)
    !!sections.delete(name)
end
save(*args, task_model: self.model, replace: false) click to toggle source

Save a configuration section to disk

@overload save(section_name, file, replace: false, task_model: self.model)

@param [String] section_name the section name
@param [String] file either a file, or a directory. In the latter
  case, the file will be #{conf_dir}/#{model.name}.yml
@return [Hash] the configuration section that just got saved

@overload save(task, file, section_name)

@deprecated use {#extract} and {#save} instead
# File lib/orocos/configurations.rb, line 927
def save(*args, task_model: self.model, replace: false)
    if !args.first.respond_to?(:to_str)
        Orocos.warn "save(task, file, name) is deprecated, use a combination of #extract and #save(name, file) instead"
        task, file, name = *args
        extract(name, task)
        return save(name, file, task_model: task.model)
    end

    section_name, file = *args
    conf = conf(section_name)
    self.class.save(conf, file, section_name, task_model: task_model, replace: replace)
    conf
end
save_yaml_to_cache(cache_dir, cache_id, contents) click to toggle source

@api private

Write the YAML to the cache directory, if available

# File lib/orocos/configurations.rb, line 195
def save_yaml_to_cache(cache_dir, cache_id, contents)
    path = File.join(cache_dir, cache_id)
    File.open(path, 'w') do |io|
        io.write Marshal.dump(contents)
    end
end