class Huebot::Compiler

Public Class Methods

new(device_mapper) click to toggle source
# File lib/huebot/compiler.rb, line 3
def initialize(device_mapper)
  @device_mapper = device_mapper
end

Public Instance Methods

build(ir, default_name = nil) click to toggle source

Build a huebot program from an intermediate representation (a Hash).

@param ir [Hash] @param default_name [String] A name to use if one isn't specified @return [Huebot::Program]

# File lib/huebot/compiler.rb, line 14
def build(ir, default_name = nil)
  ir = ir.clone
  prog = Huebot::Program.new
  prog.name = ir.delete("name") || default_name

  # loop/loops
  val_loop = ir.delete("loop") || ir.delete(:loop)
  prog.errors << "'loop' must be 'true' or 'false'." if !val_loop.nil? and ![true, false].include?(val_loop)
  prog.loop = val_loop == true

  val_loops = ir.delete("loops") || ir.delete(:loops)
  prog.errors << "'loops' must be a positive integer." if !val_loops.nil? and val_loops.to_i < 0
  prog.loops = val_loops.to_i

  prog.errors << "'loop' and 'loops' are mutually exclusive." if prog.loop? and prog.loops > 0

  # initial state
  if (val_init = ir.delete("initial") || ir.delete(:initial))
    errors, warnings, state = build_transition val_init
    prog.initial_state = state
    prog.errors += errors
    prog.warnings += warnings
  end

  # transitions
  if (val_trns = ir.delete("transitions") || ir.delete(:transitions))
    val_trns.each do |val_trn|
      errors, warnings, state = if val_trn["parallel"] || val_trn[:parallel]
                                  build_parallel_transition val_trn
                                else
                                  build_transition val_trn
                                end
      prog.transitions << state
      prog.errors += errors
      prog.warnings += warnings
    end
  end

  # final state
  if (val_fnl = ir.delete("final") || ir.delete(:final))
    errors, warnings, state = build_transition val_fnl
    prog.final_state = state
    prog.errors += errors
    prog.warnings += warnings
  end

  # be strict about extra crap
  if (unknown = ir.keys.map(&:to_s)).any?
    prog.errors << "Unrecognized values: #{unknown.join ', '}."
  end

  # Add any warnings
  prog.warnings << "'final' is defined but will never be reached because 'loop' is 'true'." if prog.final_state and prog.loop?

  prog
end

Private Instance Methods

build_parallel_transition(t) click to toggle source
# File lib/huebot/compiler.rb, line 73
def build_parallel_transition(t)
  errors, warnings = [], []
  transition = Huebot::Program::ParallelTransition.new(0, [])

  transition.wait = t.delete("wait") || t.delete(:wait)
  errors << "'wait' must be a positive integer." if transition.wait and transition.wait.to_i <= 0

  parallel = t.delete("parallel") || t.delete(:parallel)
  if !parallel.is_a? Array
    errors << "'parallel' must be an array of transitions"
  else
    parallel.each do |sub_t|
      sub_errors, sub_warnings, sub_transition = build_transition(sub_t)
      errors += sub_errors
      warnings += sub_warnings
      transition.children << sub_transition
    end
  end

  return errors, warnings, transition
end
build_transition(t) click to toggle source
# File lib/huebot/compiler.rb, line 95
def build_transition(t)
  errors, warnings = [], []
  transition = Huebot::Program::Transition.new
  transition.devices = []

  map_devices(t, :light, :lights, :light!) { |map_errors, devices|
    errors += map_errors
    transition.devices += devices
  }

  map_devices(t, :group, :groups, :group!) { |map_errors, devices|
    errors += map_errors
    transition.devices += devices
  }

  map_devices(t, :device, :devices, :var!) { |map_errors, devices|
    errors += map_errors
    transition.devices += devices
  }
  errors << "Missing light/lights, group/groups, or device/devices" if transition.devices.empty?

  transition.wait = t.delete("wait") || t.delete(:wait)
  errors << "'wait' must be a positive integer." if transition.wait and transition.wait.to_i <= 0

  state = {}
  switch = t.delete("switch")
  switch = t.delete(:switch) if switch.nil?
  if !switch.nil?
    state[:on] = case switch
                 when true, :on then true
                 when false, :off then false
                 else
                   errors << "Unrecognized 'switch' value '#{switch}'."
                   nil
                 end
  end
  state[:transitiontime] = t.delete("time") || t.delete(:time) || t.delete("transitiontime") || t.delete(:transitiontime) || 4

  transition.state = t.merge(state).reduce({}) { |a, (key, val)|
    a[key.to_sym] = val
    a
  }
  return errors, warnings, transition
end
map_devices(t, singular_key, plural_key, ref_type) { |errors, devices| ... } click to toggle source
# File lib/huebot/compiler.rb, line 142
def map_devices(t, singular_key, plural_key, ref_type)
  errors, devices = [], []

  key = t[singular_key.to_s] || t[singular_key]
  keys = t[plural_key.to_s] || t[plural_key]

  (Array(key) + Array(keys)).each { |x|
    begin
      devices += Array(@device_mapper.send(ref_type, x))
    rescue Huebot::DeviceMapper::Unmapped => e
      errors << e.message
    end
  }

  yield errors, devices
end