huebot 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 643b636258939c0eb021d13d9369d845be85a5854ca1f6c38fa66c6556551c68
4
- data.tar.gz: e5e1e74fded7ed9d456f8a7ce0c65f8f787a08187eb019413bddaff7bddaca74
3
+ metadata.gz: 2fd718fb625c5f417b31619579459bdfcaffac98276d9981882df7d9b1e192b9
4
+ data.tar.gz: 213fb99651e6826283d4c0be21c26e9442f146ea2df1734a2acb5c53f0e6eafa
5
5
  SHA512:
6
- metadata.gz: '084232072462be01ec7867aadb43df7b476e4c0b6fe0fcc33b1d10cd23f568b8474d83ed450f0487123ce874c33a729d84bfa9e4cc683a59958e3b5fe7bd64e0'
7
- data.tar.gz: 99c1ba63f4ff21161017fe42180ae76c5ee27721e32e7663eb5d09906953ef88886dd7e003f40e783e0ede9e3e5e123fab5635b5e0413085bf5882ebcb386bb8
6
+ metadata.gz: caa7f46fb1d7ac5954f7b1e8f8b48c81b69c3cea59c7ce0ad246eb6ecafda318bd161eec1aff2a41e7e8f6a84af4d2a18a555f56dab808e97b39ff3396b91d9c
7
+ data.tar.gz: 2221b64eb67ab257232358e52657446cb3b8c95d7f102220954ee71e9a85bc1447ec1566712b651bd1a0d5c4db8be2eff82097551302186cd092688ad0df5548
data/lib/huebot/bot.rb CHANGED
@@ -41,7 +41,7 @@ module Huebot
41
41
  # TODO error handling
42
42
  _res = device.set_state i.state
43
43
  @logger.log :set_state, {device: device.name, state: i.state, result: nil}
44
- wait time if i.wait
44
+ wait Program::AST::Num.new(time) if i.wait
45
45
  }
46
46
  }.map(&:join)
47
47
  wait i.pause.post if i.pause&.post
@@ -81,7 +81,7 @@ module Huebot
81
81
  wait lp.pause.post if lp.pause&.post
82
82
  }
83
83
  when Program::AST::CountedLoop
84
- lp.n.times {
84
+ number(lp.n).round.times {
85
85
  wait lp.pause.pre if lp.pause&.pre
86
86
  yield :counted
87
87
  wait lp.pause.post if lp.pause&.post
@@ -124,11 +124,23 @@ module Huebot
124
124
  }
125
125
  end
126
126
 
127
- def wait(seconds)
127
+ def wait(n)
128
+ seconds = number n
128
129
  @logger.log :pause, {time: seconds}
129
130
  @waiter.call seconds
130
131
  end
131
132
 
133
+ def number(n)
134
+ case n
135
+ when Program::AST::Num
136
+ n.n
137
+ when Program::AST::RandomNum
138
+ rand(n.min..n.max)
139
+ else
140
+ raise Error, "Unknown numeric type. Expected Program::AST::Num, Program::AST::NRandomNum, found: #{n.class.name}"
141
+ end
142
+ end
143
+
132
144
  module Waiter
133
145
  def self.call(seconds)
134
146
  sleep seconds
@@ -5,7 +5,7 @@ require 'json'
5
5
  module Huebot
6
6
  module CLI
7
7
  module Helpers
8
- DEFAULT_API_VERSION = 1.1
8
+ DEFAULT_API_VERSION = 1.2
9
9
 
10
10
  #
11
11
  # Returns the command given to huebot.
@@ -8,6 +8,8 @@ module Huebot
8
8
  rescue ::Huebot::Error => e
9
9
  opts.stderr.puts "#{e.class.name}: #{e.message}"
10
10
  return 1
11
+ rescue Interrupt
12
+ return 130
11
13
  end
12
14
 
13
15
  def self.run(sources, lights, groups, opts)
@@ -25,6 +27,8 @@ module Huebot
25
27
  rescue ::Huebot::Error => e
26
28
  opts.stderr.puts "#{e.class.name}: #{e.message}"
27
29
  return 1
30
+ rescue Interrupt
31
+ return 130
28
32
  end
29
33
 
30
34
  def self.check(sources, lights, groups, opts)
@@ -47,6 +51,8 @@ module Huebot
47
51
  rescue ::Huebot::Error => e
48
52
  opts.stderr.puts "#{e.class.name}: #{e.message}"
49
53
  return 1
54
+ rescue Interrupt
55
+ return 130
50
56
  end
51
57
 
52
58
  def self.get_state(lights, groups, opts)
@@ -56,6 +62,8 @@ module Huebot
56
62
  opts.stdout.puts " #{device.get_state}"
57
63
  end
58
64
  0
65
+ rescue Interrupt
66
+ return 130
59
67
  end
60
68
 
61
69
  def self.set_ip(config, ip, opts)
@@ -64,6 +72,8 @@ module Huebot
64
72
  rescue ::Huebot::Error => e
65
73
  opts.stderr.puts "#{e.class.name}: #{e.message}"
66
74
  return 1
75
+ rescue Interrupt
76
+ return 130
67
77
  end
68
78
 
69
79
  def self.clear_ip(config, opts)
@@ -72,6 +82,8 @@ module Huebot
72
82
  rescue ::Huebot::Error => e
73
83
  opts.stderr.puts "#{e.class.name}: #{e.message}"
74
84
  return 1
85
+ rescue Interrupt
86
+ return 130
75
87
  end
76
88
 
77
89
  def self.unregister(config, opts)
@@ -80,6 +92,8 @@ module Huebot
80
92
  rescue ::Huebot::Error => e
81
93
  opts.stderr.puts "#{e.class.name}: #{e.message}"
82
94
  return 1
95
+ rescue Interrupt
96
+ return 130
83
97
  end
84
98
  end
85
99
  end
@@ -10,6 +10,8 @@ module Huebot
10
10
  PARALLEL_KEYS = ["parallel"].freeze
11
11
  INFINITE_KEYS = ["infinite"].freeze
12
12
  COUNT_KEYS = ["count"].freeze
13
+ RANDOM_KEYS = ["random"].freeze
14
+ MIN_MAX = ["min", "max"].freeze
13
15
  TIMER_KEYS = ["timer"].freeze
14
16
  DEADLINE_KEYS = ["until"].freeze
15
17
  HHMM = /\A[0-9]{2}:[0-9]{2}\Z/.freeze
@@ -187,26 +189,54 @@ module Huebot
187
189
  lp =
188
190
  case loop_val.keys
189
191
  when INFINITE_KEYS
190
- loop_val["infinite"] == true ? Program::AST::InfiniteLoop.new : Program::AST::CountedLoop.new(1)
192
+ loop_val.fetch("infinite") == true ? Program::AST::InfiniteLoop.new : Program::AST::CountedLoop.new(Program::AST::Num.new(1))
191
193
  when COUNT_KEYS
192
- num = loop_val["count"]
194
+ num = loop_val.fetch("count")
193
195
  errors << "'loop.count' must be an integer. Found '#{num.class.name}'" unless num.is_a? Integer
194
- Program::AST::CountedLoop.new(num)
196
+ Program::AST::CountedLoop.new(Program::AST::Num.new(num))
197
+ when RANDOM_KEYS
198
+ n = build_random loop_val, errors, warnings
199
+ Program::AST::CountedLoop.new(n)
195
200
  when TIMER_KEYS
196
- build_timer_loop loop_val["timer"], errors, warnings
201
+ build_timer_loop loop_val.fetch("timer"), errors, warnings
197
202
  when DEADLINE_KEYS
198
- build_deadline_loop loop_val["until"], errors, warnings
203
+ build_deadline_loop loop_val.fetch("until"), errors, warnings
199
204
  else
200
205
  errors << "'loop' must contain exactly one of: 'infinite', 'count', 'timer', or 'until', and optionally 'pause'. Found: #{loop_val.keys.join ", "}"
201
- Program::AST::CountedLoop.new(1)
206
+ Program::AST::CountedLoop.new(Program::AST::Num.new(1))
202
207
  end
203
208
  lp.pause = pause
204
209
  lp
205
210
  when nil
206
- Program::AST::CountedLoop.new(1)
211
+ Program::AST::CountedLoop.new(Program::AST::Num.new(1))
207
212
  else
208
213
  errors << "'loop' must be an object. Found '#{loop_val.class.name}'"
209
- Program::AST::CountedLoop.new(1)
214
+ Program::AST::CountedLoop.new(Program::AST::Num.new(1))
215
+ end
216
+ end
217
+
218
+ def build_random(t, errors, warnings)
219
+ random = t.delete("random") || {}
220
+ min = build_random_n(random, "min", errors, warnings)
221
+ max = build_random_n(random, "max", errors, warnings)
222
+ errors << "'random.max' must be greater than 'random.min'" unless max > min
223
+ errors << "Unknown keys in 'random': #{random.keys.join ", "}" if random.keys.any?
224
+ Program::AST::RandomNum.new(min, max)
225
+ end
226
+
227
+ def build_random_n(t, name, errors, warnings)
228
+ n = t.delete name
229
+ case n
230
+ when Integer, Float
231
+ if n >= 0
232
+ n
233
+ else
234
+ errors << "'random.#{name}' must be >= 0, found #{n}"
235
+ 0
236
+ end
237
+ else
238
+ errors << "'random.#{name}' must be an integer or float > 0, found #{n.class.name}"
239
+ 0
210
240
  end
211
241
  end
212
242
 
@@ -234,6 +264,7 @@ module Huebot
234
264
  case @api_version
235
265
  when 1.0 then build_pause_1_0(t, errors, warnings)
236
266
  when 1.1 then build_pause_1_1(t, errors, warnings)
267
+ when 1.2 then build_pause_1_2(t, errors, warnings)
237
268
  else raise Error, "Unknown api version '#{@api_version}'"
238
269
  end
239
270
  end
@@ -242,7 +273,7 @@ module Huebot
242
273
  pause_val = t.delete "pause"
243
274
  case pause_val
244
275
  when Integer, Float
245
- Program::AST::Pause.new(nil, pause_val)
276
+ Program::AST::Pause.new(nil, Program::AST::Num.new(pause_val))
246
277
  when nil
247
278
  nil
248
279
  else
@@ -255,22 +286,69 @@ module Huebot
255
286
  pause_val = t.delete "pause"
256
287
  case pause_val
257
288
  when Integer, Float
258
- Program::AST::Pause.new(nil, pause_val)
289
+ Program::AST::Pause.new(nil, Program::AST::Num.new(pause_val))
259
290
  when Hash
260
291
  pre = pause_val.delete "before"
261
292
  post = pause_val.delete "after"
262
293
  errors << "'pause.before' must be an integer or float" unless pre.nil? or pre.is_a? Integer or pre.is_a? Float
263
294
  errors << "'pause.after' must be an integer or float" unless post.nil? or post.is_a? Integer or post.is_a? Float
264
295
  errors << "Unknown keys in 'pause': #{pause_val.keys.join ", "}" if pause_val.keys.any?
296
+ pre = Program::AST::Num.new(pre) if pre
297
+ post = Program::AST::Num.new(post) if post
265
298
  Program::AST::Pause.new(pre, post)
266
299
  when nil
267
300
  nil
268
301
  else
269
- errors << "'pause' must be an integer or float"
302
+ errors << "'pause' must be an integer or float, or an object with 'before' and/or 'after'"
270
303
  nil
271
304
  end
272
305
  end
273
306
 
307
+ def build_pause_1_2(t, errors, warnings)
308
+ pause_val = t.delete "pause"
309
+ case pause_val
310
+ when Integer, Float
311
+ Program::AST::Pause.new(nil, Program::AST::Num.new(pause_val))
312
+ when Hash
313
+ pre = build_pause_part(pause_val, "before", errors, warnings)
314
+ post = build_pause_part(pause_val, "after", errors, warnings)
315
+ errors << "'pause' requires one or both of 'before' or 'after'" if pre.nil? and post.nil?
316
+ errors << "Unknown keys in 'pause': #{pause_val.keys.join ", "}" if pause_val.keys.any?
317
+ Program::AST::Pause.new(pre, post)
318
+ when nil
319
+ nil
320
+ else
321
+ errors << "'pause' must be an integer or float, or an object with 'before' and/or 'after'"
322
+ nil
323
+ end
324
+ end
325
+
326
+ def build_pause_part(t, part, errors, warnings)
327
+ val = t.delete part
328
+ case val
329
+ when nil
330
+ nil
331
+ when Integer, Float
332
+ Program::AST::Num.new(val)
333
+ when Hash
334
+ if @api_version < 1.2
335
+ errors << "Unknown 'pause.#{part}' type (#{val.class.name})"
336
+ return Program::AST::Num.new(1)
337
+ end
338
+
339
+ case val.keys
340
+ when RANDOM_KEYS
341
+ build_random val, errors, warnings
342
+ else
343
+ errors << "Expected 'pause.#{part}' to contain 'random', found #{val.keys.join ", "}"
344
+ Program::AST::Num.new(1)
345
+ end
346
+ else
347
+ errors << "Unknown 'pause.#{part}' type (#{val.class.name})"
348
+ Program::AST::Num.new(1)
349
+ end
350
+ end
351
+
274
352
  def build_wait(t, errors, warnings)
275
353
  wait = t.delete "wait"
276
354
  case wait
@@ -13,7 +13,7 @@ module Huebot
13
13
  def self.build(src)
14
14
  compiler_class =
15
15
  case src.api_version
16
- when 1.0, 1.1 then ApiV1
16
+ when 1.0, 1.1, 1.2 then ApiV1
17
17
  else raise Error, "Unknown API version '#{src.api_version}'"
18
18
  end
19
19
  compiler = compiler_class.new(src.api_version)
@@ -26,6 +26,8 @@ module Huebot
26
26
  DeadlineLoop = Struct.new(:stop_time, :pause)
27
27
 
28
28
  Pause = Struct.new(:pre, :post)
29
+ Num = Struct.new(:n)
30
+ RandomNum = Struct.new(:min, :max)
29
31
  DeviceRef = Struct.new(:ref)
30
32
  Light = Struct.new(:name)
31
33
  Group = Struct.new(:name)
@@ -56,14 +58,14 @@ module Huebot
56
58
  end
57
59
 
58
60
  def errors(node = data)
59
- node.children.reduce(node.errors) { |errors, child|
60
- errors + child.errors
61
+ node.children.reduce(node.errors) { |acc, child|
62
+ acc + errors(child)
61
63
  }
62
64
  end
63
65
 
64
66
  def warnings(node = data)
65
- node.children.reduce(node.warnings) { |warnings, child|
66
- warnings + child.warnings
67
+ node.children.reduce(node.warnings) { |acc, child|
68
+ acc + warnings(child)
67
69
  }
68
70
  end
69
71
 
@@ -1,4 +1,4 @@
1
1
  module Huebot
2
2
  # Gem version
3
- VERSION = '1.3.0'
3
+ VERSION = '1.4.0'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: huebot
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Hollinger