huebot 0.5.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  module Huebot
2
2
  class DeviceMapper
3
- Unmapped = Class.new(StandardError)
3
+ Unmapped = Class.new(Error)
4
4
 
5
5
  def initialize(bridge, inputs = [])
6
6
  all_lights, all_groups = bridge.lights, bridge.groups
@@ -9,43 +9,57 @@ module Huebot
9
9
  @lights_by_name = all_lights.reduce({}) { |a, l| a[l.name] = l; a }
10
10
  @groups_by_id = all_groups.reduce({}) { |a, g| a[g.id] = g; a }
11
11
  @groups_by_name = all_groups.reduce({}) { |a, g| a[g.name] = g; a }
12
- @devices_by_var = inputs.each_with_index.reduce({}) { |a, (x, idx)|
13
- dev = case x
14
- when LightInput then @lights_by_id[x.val] || @lights_by_name[x.val]
15
- when GroupInput then @groups_by_id[x.val] || @groups_by_name[x.val]
16
- else raise "Invalid input: #{x}"
17
- end || raise(Unmapped, "Could not find #{x.class.name[8..-6].downcase} with id or name '#{x.val}'")
18
- a["$#{idx + 1}"] = dev
19
- a
12
+ @devices_by_var = inputs.each_with_index.each_with_object({}) { |(x, idx), obj|
13
+ obj[idx + 1] =
14
+ case x
15
+ when Light::Input then @lights_by_id[x.val.to_i] || @lights_by_name[x.val]
16
+ when Group::Input then @groups_by_id[x.val.to_i] || @groups_by_name[x.val]
17
+ else raise Error, "Invalid input: #{x}"
18
+ end || raise(Unmapped, "Could not find #{x.class.name[8..-6].downcase} with id or name '#{x.val}'")
20
19
  }
21
20
  @all = @devices_by_var.values
22
21
  end
23
22
 
23
+ def each
24
+ if block_given?
25
+ @all.each { |device| yield device }
26
+ else
27
+ @all.each
28
+ end
29
+ end
30
+
24
31
  def light!(id)
25
- case id
26
- when Integer
27
- @lights_by_id[id]
28
- when String
29
- @lights_by_name[id]
30
- end || (raise Unmapped, "Unmapped light '#{id}'")
32
+ @lights_by_id[id] || @lights_by_name[id] || (raise Unmapped, "Unmapped light '#{id}'")
31
33
  end
32
34
 
33
35
  def group!(id)
34
- case id
35
- when Integer
36
- @groups_by_id[id]
37
- when String
38
- @groups_by_name[id]
39
- end || (raise Unmapped, "Unmapped group '#{id}'")
36
+ @groups_by_id[id] || @groups_by_name[id] || (raise Unmapped, "Unmapped group '#{id}'")
40
37
  end
41
38
 
42
39
  def var!(id)
43
40
  case id
44
- when "$all"
41
+ when :all
45
42
  @all
46
43
  else
47
- @devices_by_var[id]
48
- end || (raise Unmapped, "Unmapped device '#{id}'")
44
+ @devices_by_var[id] || (raise Unmapped, "Unmapped device '#{id}'")
45
+ end
46
+ end
47
+
48
+ def missing_lights(names)
49
+ names - @lights_by_name.keys
50
+ end
51
+
52
+ def missing_groups(names)
53
+ names - @groups_by_name.keys
54
+ end
55
+
56
+ def missing_vars(vars)
57
+ missing = vars - @devices_by_var.keys
58
+ if @all.any?
59
+ missing - [:all]
60
+ else
61
+ missing
62
+ end
49
63
  end
50
64
  end
51
65
  end
@@ -1,7 +1,11 @@
1
1
  module Huebot
2
2
  module DeviceState
3
3
  def set_state(state)
4
- client.put!(state_url, state)
4
+ client.put!(state_change_url, state)
5
+ end
6
+
7
+ def get_state
8
+ client.get!(url).fetch("state")
5
9
  end
6
10
  end
7
11
  end
data/lib/huebot/group.rb CHANGED
@@ -1,22 +1,29 @@
1
1
  module Huebot
2
2
  class Group
3
+ #
4
+ # Struct for specifying a Group input (id or name)
5
+ #
6
+ # @attr val [Integer|String] id or name
7
+ #
8
+ Input = Struct.new(:val)
9
+
3
10
  include DeviceState
4
11
  attr_reader :client, :id, :name
5
12
 
6
13
  def initialize(client, id, attrs)
7
14
  @client = client
8
- @id = id
15
+ @id = id.to_i
9
16
  @name = attrs.fetch("name")
10
17
  @attrs = attrs
11
18
  end
12
19
 
13
20
  private
14
21
 
15
- def state_url
22
+ def state_change_url
16
23
  url "/action"
17
24
  end
18
25
 
19
- def url(path)
26
+ def url(path = "")
20
27
  "/groups/#{id}#{path}"
21
28
  end
22
29
  end
data/lib/huebot/light.rb CHANGED
@@ -1,22 +1,29 @@
1
1
  module Huebot
2
2
  class Light
3
+ #
4
+ # Struct for specifying a Light input (id or name)
5
+ #
6
+ # @attr val [Integer|String] id or name
7
+ #
8
+ Input = Struct.new(:val)
9
+
3
10
  include DeviceState
4
11
  attr_reader :client, :id, :name
5
12
 
6
13
  def initialize(client, id, attrs)
7
14
  @client = client
8
- @id = id
15
+ @id = id.to_i
9
16
  @name = attrs.fetch("name")
10
17
  @attrs = attrs
11
18
  end
12
19
 
13
20
  private
14
21
 
15
- def state_url
22
+ def state_change_url
16
23
  url "/state"
17
24
  end
18
25
 
19
- def url(path)
26
+ def url(path = "")
20
27
  "/lights/#{id}#{path}"
21
28
  end
22
29
  end
@@ -1,32 +1,82 @@
1
1
  module Huebot
2
2
  class Program
3
- Transition = Struct.new(:wait, :state, :devices)
4
- ParallelTransition = Struct.new(:wait, :children)
3
+ #
4
+ # Struct for storing a program's Intermediate Representation and source filepath.
5
+ #
6
+ # @attr tokens [Hash]
7
+ # @attr filepath [String]
8
+ # @attr api_version [Float] API version
9
+ #
10
+ Src = Struct.new(:tokens, :filepath, :api_version) do
11
+ def default_name
12
+ File.basename(filepath, ".*")
13
+ end
14
+ end
5
15
 
6
- attr_accessor :name
7
- attr_accessor :initial_state
8
- attr_accessor :transitions
9
- attr_accessor :final_state
10
- attr_accessor :loop
11
- attr_accessor :loops
12
- attr_accessor :errors
13
- attr_accessor :warnings
14
-
15
- def initialize
16
- @name = nil
17
- @initial_state = nil
18
- @transitions = []
19
- @final_state = nil
20
- @loop = false
21
- @loops = 0
22
- @errors = []
23
- @warnings = []
16
+ module AST
17
+ Node = Struct.new(:instruction, :children, :errors, :warnings)
18
+
19
+ Transition = Struct.new(:state, :devices, :sleep)
20
+ SerialControl = Struct.new(:loop, :sleep)
21
+ ParallelControl = Struct.new(:loop, :sleep)
22
+
23
+ InfiniteLoop = Struct.new(:pause)
24
+ CountedLoop = Struct.new(:n, :pause)
25
+ TimerLoop = Struct.new(:hours, :minutes, :pause)
26
+ DeadlineLoop = Struct.new(:stop_time, :pause)
27
+
28
+ DeviceRef = Struct.new(:ref)
29
+ Light = Struct.new(:name)
30
+ Group = Struct.new(:name)
31
+ NoOp = Struct.new(:x)
24
32
  end
25
33
 
34
+ attr_accessor :name
35
+ attr_accessor :api_version
36
+ attr_accessor :data
37
+
26
38
  def valid?
27
39
  errors.empty?
28
40
  end
29
41
 
30
- alias_method :loop?, :loop
42
+ # Returns all light names hard-coded into the program
43
+ def light_names(node = data)
44
+ devices(AST::Light).uniq.map(&:name)
45
+ end
46
+
47
+ # Returns all group names hard-coded into the program
48
+ def group_names(node = data)
49
+ devices(AST::Group).uniq.map(&:name)
50
+ end
51
+
52
+ # Returns all device refs (e.g. $all, $1, $2) in the program
53
+ def device_refs(node = data)
54
+ devices(AST::DeviceRef).uniq.map(&:ref)
55
+ end
56
+
57
+ def errors(node = data)
58
+ node.children.reduce(node.errors) { |errors, child|
59
+ errors + child.errors
60
+ }
61
+ end
62
+
63
+ def warnings(node = data)
64
+ node.children.reduce(node.warnings) { |warnings, child|
65
+ warnings + child.warnings
66
+ }
67
+ end
68
+
69
+ private
70
+
71
+ def devices(type, node = data)
72
+ case node.instruction
73
+ when AST::Transition
74
+ node.instruction.devices.select { |d| d.is_a? type }
75
+ when AST::SerialControl, AST::ParallelControl
76
+ node.children.map { |n| devices type, n }.flatten
77
+ else
78
+ []
79
+ end
80
+ end
31
81
  end
32
82
  end
@@ -1,4 +1,4 @@
1
1
  module Huebot
2
2
  # Gem version
3
- VERSION = '0.5.0'
3
+ VERSION = '1.1.0'
4
4
  end
data/lib/huebot.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  module Huebot
2
+ Error = Class.new(StandardError)
3
+
2
4
  autoload :Config, 'huebot/config'
3
5
  autoload :Client, 'huebot/client'
6
+ autoload :CLI, 'huebot/cli'
4
7
  autoload :Bridge, 'huebot/bridge'
5
8
  autoload :DeviceState, 'huebot/device_state'
6
9
  autoload :Light, 'huebot/light'
@@ -10,26 +13,4 @@ module Huebot
10
13
  autoload :Compiler, 'huebot/compiler'
11
14
  autoload :Bot, 'huebot/bot'
12
15
  autoload :VERSION, 'huebot/version'
13
-
14
- #
15
- # Struct for storing a program's Intermediate Representation and source filepath.
16
- #
17
- # @attr ir [Hash]
18
- # @attr filepath [String]
19
- #
20
- ProgramSrc = Struct.new(:ir, :filepath)
21
-
22
- #
23
- # Struct for specifying a Light input (id or name)
24
- #
25
- # @attr val [Integer|String] id or name
26
- #
27
- LightInput = Struct.new(:val)
28
-
29
- #
30
- # Struct for specifying a Group input (id or name)
31
- #
32
- # @attr val [Integer|String] id or name
33
- #
34
- GroupInput = Struct.new(:val)
35
16
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: huebot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Hollinger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-08 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2023-12-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
13
41
  description: Declare and run YAML programs for Philips Hue devices
14
42
  email: jordan.hollinger@gmail.com
15
43
  executables:
@@ -23,8 +51,11 @@ files:
23
51
  - lib/huebot/bot.rb
24
52
  - lib/huebot/bridge.rb
25
53
  - lib/huebot/cli.rb
54
+ - lib/huebot/cli/helpers.rb
55
+ - lib/huebot/cli/runner.rb
26
56
  - lib/huebot/client.rb
27
57
  - lib/huebot/compiler.rb
58
+ - lib/huebot/compiler/api_v1.rb
28
59
  - lib/huebot/config.rb
29
60
  - lib/huebot/device_mapper.rb
30
61
  - lib/huebot/device_state.rb