class Builderator::Config::Attributes

Shared Attribute Mixin

Attributes

attributes[R]
extends[R]
nodes[R]
parent[R]

Public Class Methods

attribute(attribute_name, default = nil, **options) click to toggle source
# File lib/builderator/config/attributes.rb, line 17
def attribute(attribute_name, default = nil, **options)
  ##
  # Helpers for Array-type attributes
  ##
  if options[:type] == :list
    define_method(attribute_name) do |*arg, **run_options|
      ## Instantiate List if it doesn't exist yet. `||=` will always return a new Rash.
      @attributes[attribute_name] = Config::List.new(run_options) unless @attributes.has?(attribute_name, Config::List)

      unless arg.empty?
        @attributes[attribute_name].set(*arg.flatten)
        @attributes[attribute_name].set(*arg) if options[:flatten] == false
      end
      @attributes[attribute_name]
    end

    define_method(options[:singular]) do |*arg, **run_options|
      send(attribute_name, run_options).push(*arg.flatten)
    end if options.include?(:singular)

    return
  end

  ##
  # Helpers for Hash-type attributes
  ##
  if options[:type] == :hash
    define_method(attribute_name) do |arg = nil|
      ## Instantiate List if it doesn't exist yet. `||=` will always return a new Rash.
      @attributes[attribute_name] = Config::Rash.new unless @attributes.has?(attribute_name, Config::Rash)

      dirty(@attributes[attribute_name].merge!(Config::Rash.coerce(arg)).any?) unless arg.nil?
      @attributes[attribute_name]
    end

    return
  end

  ## Getter/Setter
  define_method(attribute_name) do |*arg|
    set_or_return(attribute_name, arg.first, default, options)
  end

  ## Setter
  define_method("#{attribute_name}=") do |arg|
    set_if_valid(attribute_name, arg, options)
  end
end
collection(collection_name, &definition) click to toggle source

A Collection is a named-set of items in a sub-node of the attribute-set.

Like Namespaces, Collections map to a top-level key, but they also have multiple second-order keys:

e.g. `collection :vagrant …` adds a DSL method `vagrant(name = :default, &block)` which maps to `attributes[<name>]`

Multiple entities can be added to the collection by calling the DSL method with unique `name` arguments. Multiple calls to the DSL method with the same name argument will update the existing entity in place

An entry can be defined as an extension of another node by passing a hash as the instance name: `name => Config.node(:name)`. This will use the values defined in `Config.node(:name)` as defaults for the new entry

# File lib/builderator/config/attributes.rb, line 111
def collection(collection_name, &definition)
  collection_class = Collection.create(collection_name, &definition)

  define_method(collection_name) do |instance_name = nil, &block|
    extension_base = nil

    ## Allow extension to be defined as a key-value
    if instance_name.is_a?(Hash)
      extension_base = instance_name.first.last
      instance_name = instance_name.first.first
    end

    nodes[collection_name] ||= collection_class.new(
      @attributes[collection_name],
      :parent => self)

    return nodes[collection_name] if instance_name.nil?
    nodes[collection_name].fetch(instance_name, :extends => extension_base, &block)
  end
end
namespace(namespace_name, &definition) click to toggle source

A Namespace is a singleton sub-node of the attribute-set

e.g. `namespace :chef …` maps to `attributes` and adds a method `chef(&block)` to the DSL which is used as follows:

“` chef do

run_list 'foo', 'bar'
...

end “`

Multiple calls to the DSL method are safe and will update the same sub-node.

# File lib/builderator/config/attributes.rb, line 82
def namespace(namespace_name, &definition)
  namespace_class = Namespace.create(namespace_name, &definition)

  define_method(namespace_name) do |&block|
    nodes[namespace_name] ||= namespace_class.new(
      @attributes[namespace_name],
      :name => namespace_name,
      :parent => self, &block)
  end
end
new(attributes = {}, options = {}, &block) click to toggle source
# File lib/builderator/config/attributes.rb, line 181
def initialize(attributes = {}, options = {}, &block)
  @attributes = Rash.coerce(attributes)
  @nodes = {}
  @block = block

  ## Track change status for consumers
  @parent = options.fetch(:parent, self)
  @extends = options[:extends]
  @dirty = false
end

Public Instance Methods

==(other) click to toggle source
# File lib/builderator/config/attributes.rb, line 172
def ==(other)
  attributes == other.attributes
end
clean() click to toggle source

Clear dirty state flag

# File lib/builderator/config/attributes.rb, line 193
def clean
  @dirty = false
end
compile(evaluate = true) click to toggle source
# File lib/builderator/config/attributes.rb, line 203
def compile(evaluate = true)
  ## Underlay base values if present
  if extends.is_a?(Attributes)
    previous_state = attributes
    dirty_state = dirty

    attributes.merge!(extends.attributes)

    @block.call(self) if @block && evaluate
    nodes.each { |_, node| node.compile }

    root.dirty!(dirty_state || previous_state.diff(attributes).any?)

    return self
  end

  ## Compile this node and its children
  @block.call(self) if @block && evaluate
  nodes.each { |_, node| node.compile }

  self
end
dirty(update = false) click to toggle source

All dirty state should aggregate at the root node

# File lib/builderator/config/attributes.rb, line 163
def dirty(update = false)
  return @dirty ||= update if root?
  root.dirty(update)
end
dirty!(set) click to toggle source
# File lib/builderator/config/attributes.rb, line 168
def dirty!(set)
  @dirty = set
end
merge(other) click to toggle source
# File lib/builderator/config/attributes.rb, line 226
def merge(other)
  dirty(attributes.merge!(other.attributes).any?)
  self
end
reset!() click to toggle source
# File lib/builderator/config/attributes.rb, line 197
def reset!
  @attributes = Config::Rash.new
  @nodes = {}
  @dirty = false
end
root() click to toggle source

Get the root Attributes object

# File lib/builderator/config/attributes.rb, line 152
def root
  return self if root?

  parent.root
end
root?() click to toggle source
# File lib/builderator/config/attributes.rb, line 158
def root?
  parent == self
end
seal() click to toggle source
# File lib/builderator/config/attributes.rb, line 141
def seal
  attributes.seal
  self
end
to_json(*_) click to toggle source
# File lib/builderator/config/attributes.rb, line 231
def to_json(*_)
  JSON.pretty_generate(to_hash)
end
unseal() click to toggle source
# File lib/builderator/config/attributes.rb, line 146
def unseal
  attributes.unseal
  self
end

Protected Instance Methods

set_if_valid(key, arg, options = {}) click to toggle source
# File lib/builderator/config/attributes.rb, line 237
def set_if_valid(key, arg, options = {})
  ## TODO: define validation interface

  ## Mutation helpers

  # Input is a path relative to the working directory
  arg = Util.relative_path(arg).to_s if options[:relative]

  # Input is a path relative to the workspace
  arg = Util.workspace(arg).to_s if options[:workspace]

  ## Unchanged
  return if @attributes[key] == arg

  dirty(true) ## A mutation has occured
  @attributes[key] = arg
end
set_or_return(key, arg = nil, default = nil, **options) click to toggle source
# File lib/builderator/config/attributes.rb, line 255
def set_or_return(key, arg = nil, default = nil, **options)
  if arg.nil?
    return @attributes[key] if @attributes.has?(key)

    ## Default
    return if default.is_a?(NilClass) ## No default

    ## Allow a default to be a static value, or instantiated
    ## at call-time from a class (e.g. Array or Hash)
    default_value = default.is_a?(Class) ? default.new : default
    return default_value if @attributes.sealed

    return set_if_valid(key, default_value, options)
  end

  ## Set value
  set_if_valid(key, arg, options)
end