huebot 1.1.0 → 1.3.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 +4 -4
- data/README.md +16 -9
- data/bin/huebot +39 -19
- data/lib/huebot/bot.rb +56 -27
- data/lib/huebot/bridge.rb +3 -6
- data/lib/huebot/{cli.rb → cli/cli.rb} +2 -1
- data/lib/huebot/cli/config.rb +47 -0
- data/lib/huebot/cli/helpers.rb +73 -39
- data/lib/huebot/cli/runner.rb +34 -26
- data/lib/huebot/client.rb +1 -1
- data/lib/huebot/compiler/api_v1.rb +69 -14
- data/lib/huebot/{compiler.rb → compiler/compiler.rb} +1 -1
- data/lib/huebot/device_mapper.rb +6 -8
- data/lib/huebot/group.rb +0 -1
- data/lib/huebot/light.rb +0 -1
- data/lib/huebot/logging/collecting_logger.rb +20 -0
- data/lib/huebot/logging/io_logger.rb +19 -0
- data/lib/huebot/logging/logging.rb +7 -0
- data/lib/huebot/logging/null_logger.rb +9 -0
- data/lib/huebot/program.rb +4 -3
- data/lib/huebot/version.rb +1 -1
- data/lib/huebot.rb +3 -3
- metadata +9 -5
- data/lib/huebot/config.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 643b636258939c0eb021d13d9369d845be85a5854ca1f6c38fa66c6556551c68
|
4
|
+
data.tar.gz: e5e1e74fded7ed9d456f8a7ce0c65f8f787a08187eb019413bddaff7bddaca74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '084232072462be01ec7867aadb43df7b476e4c0b6fe0fcc33b1d10cd23f568b8474d83ed450f0487123ce874c33a729d84bfa9e4cc683a59958e3b5fe7bd64e0'
|
7
|
+
data.tar.gz: 99c1ba63f4ff21161017fe42180ae76c5ee27721e32e7663eb5d09906953ef88886dd7e003f40e783e0ede9e3e5e123fab5635b5e0413085bf5882ebcb386bb8
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Huebot
|
2
2
|
|
3
|
-
Program your Hue lights!
|
3
|
+
Program your Hue lights using YAML or JSON!
|
4
4
|
|
5
|
-
$ huebot run dimmer.
|
5
|
+
$ huebot run dimmer.yaml --light="Office Desk"
|
6
6
|
|
7
7
|
A few examples are below. [See the Wiki](https://github.com/jhollinger/huebot/wiki) for full documentation.
|
8
8
|
|
@@ -34,13 +34,15 @@ serial:
|
|
34
34
|
state:
|
35
35
|
bri: 150
|
36
36
|
time: 10 # 10 second transition
|
37
|
-
pause:
|
37
|
+
pause:
|
38
|
+
after: 2 # 2 second pause before the next step
|
38
39
|
|
39
40
|
- transition:
|
40
41
|
state:
|
41
42
|
bri: 254
|
42
43
|
time: 10 # 10 second transition
|
43
|
-
pause:
|
44
|
+
pause:
|
45
|
+
after: 2 # 2 second pause before the next step
|
44
46
|
|
45
47
|
- transition:
|
46
48
|
state:
|
@@ -64,7 +66,8 @@ serial:
|
|
64
66
|
|
65
67
|
# Run these steps in parallel in an infinite loop
|
66
68
|
- parallel:
|
67
|
-
loop:
|
69
|
+
loop:
|
70
|
+
infinite: true
|
68
71
|
steps:
|
69
72
|
# Parallel branch 1: Fade inputs #1 and #3 up and down
|
70
73
|
- serial:
|
@@ -77,12 +80,14 @@ serial:
|
|
77
80
|
state:
|
78
81
|
bri: 254
|
79
82
|
time: 10 # transition over 10 seconds
|
80
|
-
pause:
|
83
|
+
pause:
|
84
|
+
after: 5 # pause an extra 5 sec after the transition
|
81
85
|
- transition:
|
82
86
|
state:
|
83
87
|
bri: 25
|
84
88
|
time: 10
|
85
|
-
pause:
|
89
|
+
pause:
|
90
|
+
after: 5
|
86
91
|
|
87
92
|
# Parallel branch 2: Fade inputs #2 and #4 down and up
|
88
93
|
- serial:
|
@@ -95,12 +100,14 @@ serial:
|
|
95
100
|
state:
|
96
101
|
bri: 25
|
97
102
|
time: 10
|
98
|
-
pause:
|
103
|
+
pause:
|
104
|
+
after: 5
|
99
105
|
- transition:
|
100
106
|
state:
|
101
107
|
bri: 254
|
102
108
|
time: 10
|
103
|
-
pause:
|
109
|
+
pause:
|
110
|
+
after: 5
|
104
111
|
```
|
105
112
|
|
106
113
|
[See the Wiki](https://github.com/jhollinger/huebot/wiki) for more documentation and examples.
|
data/bin/huebot
CHANGED
@@ -1,23 +1,27 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
# Used for local testing
|
4
|
-
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') if ENV["
|
4
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') if ENV["DEV"] == "1"
|
5
5
|
|
6
6
|
require 'huebot'
|
7
7
|
|
8
8
|
Huebot::CLI::Helpers.tap { |cli|
|
9
9
|
case cli.get_cmd
|
10
10
|
when :ls
|
11
|
+
cli.get_args!(num: 0)
|
12
|
+
opts = cli.get_opts!
|
11
13
|
bridge, error = Huebot::Bridge.connect
|
12
14
|
if error
|
13
15
|
$stderr.puts error
|
14
16
|
exit 1
|
15
17
|
end
|
16
|
-
|
18
|
+
|
19
|
+
retval = Huebot::CLI::Runner.ls(bridge.lights, bridge.groups, opts)
|
17
20
|
exit retval
|
18
21
|
|
19
22
|
when :run
|
20
|
-
opts
|
23
|
+
opts = cli.get_opts!
|
24
|
+
sources = cli.get_input! opts
|
21
25
|
if sources.empty?
|
22
26
|
cli.help!
|
23
27
|
exit 1
|
@@ -28,26 +32,35 @@ Huebot::CLI::Helpers.tap { |cli|
|
|
28
32
|
$stderr.puts error
|
29
33
|
exit 1
|
30
34
|
end
|
31
|
-
|
35
|
+
|
36
|
+
retval = Huebot::CLI::Runner.run(sources, bridge.lights, bridge.groups, opts)
|
32
37
|
exit retval
|
33
38
|
|
34
39
|
when :check
|
35
|
-
opts
|
40
|
+
opts = cli.get_opts!
|
41
|
+
sources = cli.get_input! opts
|
36
42
|
if sources.empty?
|
37
43
|
cli.help!
|
38
44
|
exit 1
|
39
45
|
end
|
40
46
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
+
lights, groups =
|
48
|
+
if opts.no_device_check
|
49
|
+
[[], []]
|
50
|
+
else
|
51
|
+
bridge, error = Huebot::Bridge.connect
|
52
|
+
if error
|
53
|
+
$stderr.puts error
|
54
|
+
exit 1
|
55
|
+
end
|
56
|
+
[bridge.lights, bridge.groups]
|
57
|
+
end
|
58
|
+
|
59
|
+
retval = Huebot::CLI::Runner.check(sources, lights, groups, opts)
|
47
60
|
exit retval
|
48
61
|
|
49
62
|
when :"get-state"
|
50
|
-
opts
|
63
|
+
opts = cli.get_opts!
|
51
64
|
if opts.inputs.empty?
|
52
65
|
cli.help!
|
53
66
|
exit 1
|
@@ -58,22 +71,29 @@ Huebot::CLI::Helpers.tap { |cli|
|
|
58
71
|
$stderr.puts error
|
59
72
|
exit 1
|
60
73
|
end
|
61
|
-
|
74
|
+
|
75
|
+
retval = Huebot::CLI::Runner.get_state(bridge.lights, bridge.groups, opts)
|
62
76
|
exit retval
|
63
77
|
|
64
78
|
when :"set-ip"
|
65
|
-
|
66
|
-
|
79
|
+
opts = cli.get_opts!
|
80
|
+
ip = cli.get_args!(num: 1).first
|
81
|
+
config = Huebot::CLI::Config.new
|
82
|
+
retval = Huebot::CLI::Runner.set_ip config, ip, opts
|
67
83
|
exit retval
|
68
84
|
|
69
85
|
when :"clear-ip"
|
70
|
-
cli.
|
71
|
-
|
86
|
+
opts = cli.get_opts!
|
87
|
+
cli.get_args!(num: 0)
|
88
|
+
config = Huebot::CLI::Config.new
|
89
|
+
retval = Huebot::CLI::Runner.clear_ip config, opts
|
72
90
|
exit retval
|
73
91
|
|
74
92
|
when :unregister
|
75
|
-
cli.
|
76
|
-
|
93
|
+
opts = cli.get_opts!
|
94
|
+
cli.get_args!(num: 0)
|
95
|
+
config = Huebot::CLI::Config.new
|
96
|
+
retval = Huebot::CLI::Runner.unregister config, opts
|
77
97
|
exit retval
|
78
98
|
|
79
99
|
else cli.help!
|
data/lib/huebot/bot.rb
CHANGED
@@ -1,56 +1,67 @@
|
|
1
1
|
module Huebot
|
2
|
+
# The Huebot runtime
|
2
3
|
class Bot
|
3
4
|
Error = Class.new(StandardError)
|
4
5
|
|
5
|
-
def initialize(device_mapper)
|
6
|
+
def initialize(device_mapper, waiter: nil, logger: nil)
|
6
7
|
@device_mapper = device_mapper
|
7
|
-
|
8
|
+
@logger = logger || Logging::NullLogger.new
|
9
|
+
@waiter = waiter || Waiter
|
8
10
|
end
|
9
11
|
|
10
12
|
def execute(program)
|
13
|
+
@logger.log :start, {program: program.name}
|
11
14
|
exec program.data
|
15
|
+
@logger.log :stop, {program: program.name}
|
12
16
|
end
|
13
17
|
|
14
18
|
private
|
15
19
|
|
16
20
|
def exec(node)
|
17
|
-
|
18
|
-
case i
|
21
|
+
case node.instruction
|
19
22
|
when Program::AST::Transition
|
20
|
-
transition
|
23
|
+
transition node.instruction
|
21
24
|
when Program::AST::SerialControl
|
22
|
-
serial node.children,
|
25
|
+
serial node.children, node.instruction
|
23
26
|
when Program::AST::ParallelControl
|
24
|
-
parallel node.children,
|
27
|
+
parallel node.children, node.instruction
|
25
28
|
else
|
26
|
-
raise Error, "Unexpected instruction '#{
|
29
|
+
raise Error, "Unexpected instruction '#{node.instruction.class.name}'"
|
27
30
|
end
|
28
31
|
end
|
29
32
|
|
30
|
-
def transition(
|
31
|
-
time = (state["transitiontime"] || 4).to_f / 10
|
32
|
-
devices = map_devices
|
33
|
+
def transition(i)
|
34
|
+
time = (i.state["transitiontime"] || 4).to_f / 10
|
35
|
+
devices = map_devices i.devices
|
36
|
+
@logger.log :transition, {devices: devices.map(&:name)}
|
37
|
+
|
38
|
+
wait i.pause.pre if i.pause&.pre
|
33
39
|
devices.map { |device|
|
34
40
|
Thread.new {
|
35
41
|
# TODO error handling
|
36
|
-
device.set_state state
|
37
|
-
|
42
|
+
_res = device.set_state i.state
|
43
|
+
@logger.log :set_state, {device: device.name, state: i.state, result: nil}
|
44
|
+
wait time if i.wait
|
38
45
|
}
|
39
46
|
}.map(&:join)
|
40
|
-
wait
|
47
|
+
wait i.pause.post if i.pause&.post
|
41
48
|
end
|
42
49
|
|
43
|
-
def serial(nodes,
|
44
|
-
|
45
|
-
|
50
|
+
def serial(nodes, i)
|
51
|
+
wait i.pause.pre if i.pause&.pre
|
52
|
+
control_loop(i.loop) { |loop_type|
|
53
|
+
@logger.log :serial, {loop: loop_type}
|
54
|
+
nodes.each { |node|
|
46
55
|
exec node
|
47
56
|
}
|
48
57
|
}
|
49
|
-
wait
|
58
|
+
wait i.pause.post if i.pause&.post
|
50
59
|
end
|
51
60
|
|
52
|
-
def parallel(nodes,
|
53
|
-
|
61
|
+
def parallel(nodes, i)
|
62
|
+
wait i.pause.pre if i.pause&.pre
|
63
|
+
control_loop(i.loop) { |loop_type|
|
64
|
+
@logger.log :parallel, {loop: loop_type}
|
54
65
|
nodes.map { |node|
|
55
66
|
Thread.new {
|
56
67
|
# TODO error handling
|
@@ -58,25 +69,37 @@ module Huebot
|
|
58
69
|
}
|
59
70
|
}.map(&:join)
|
60
71
|
}
|
61
|
-
wait
|
72
|
+
wait i.pause.post if i.pause&.post
|
62
73
|
end
|
63
74
|
|
64
75
|
def control_loop(lp)
|
65
76
|
case lp
|
66
77
|
when Program::AST::InfiniteLoop
|
67
|
-
loop {
|
78
|
+
loop {
|
79
|
+
wait lp.pause.pre if lp.pause&.pre
|
80
|
+
yield :infinite
|
81
|
+
wait lp.pause.post if lp.pause&.post
|
82
|
+
}
|
68
83
|
when Program::AST::CountedLoop
|
69
|
-
lp.n.times {
|
84
|
+
lp.n.times {
|
85
|
+
wait lp.pause.pre if lp.pause&.pre
|
86
|
+
yield :counted
|
87
|
+
wait lp.pause.post if lp.pause&.post
|
88
|
+
}
|
70
89
|
when Program::AST::DeadlineLoop
|
71
90
|
until Time.now >= lp.stop_time
|
72
|
-
|
91
|
+
wait lp.pause.pre if lp.pause&.pre
|
92
|
+
yield :deadline
|
93
|
+
wait lp.pause.post if lp.pause&.post
|
73
94
|
end
|
74
95
|
when Program::AST::TimerLoop
|
75
96
|
sec = ((lp.hours * 60) + lp.minutes) * 60
|
76
97
|
time = 0
|
77
98
|
until time >= sec
|
78
99
|
start = Time.now
|
79
|
-
|
100
|
+
wait lp.pause.pre if lp.pause&.pre
|
101
|
+
yield :timer
|
102
|
+
wait lp.pause.post if lp.pause&.post
|
80
103
|
time += (Time.now - start).round
|
81
104
|
end
|
82
105
|
else
|
@@ -102,8 +125,14 @@ module Huebot
|
|
102
125
|
end
|
103
126
|
|
104
127
|
def wait(seconds)
|
105
|
-
|
106
|
-
|
128
|
+
@logger.log :pause, {time: seconds}
|
129
|
+
@waiter.call seconds
|
130
|
+
end
|
131
|
+
|
132
|
+
module Waiter
|
133
|
+
def self.call(seconds)
|
134
|
+
sleep seconds
|
135
|
+
end
|
107
136
|
end
|
108
137
|
end
|
109
138
|
end
|
data/lib/huebot/bridge.rb
CHANGED
@@ -1,24 +1,21 @@
|
|
1
1
|
module Huebot
|
2
2
|
class Bridge
|
3
|
-
def self.connect(
|
4
|
-
client = Client.new(config)
|
3
|
+
def self.connect(client = Client.new)
|
5
4
|
error = client.connect
|
6
5
|
return nil, error if error
|
7
6
|
return new(client)
|
8
7
|
end
|
9
8
|
|
10
|
-
attr_reader :client
|
11
|
-
|
12
9
|
def initialize(client)
|
13
10
|
@client = client
|
14
11
|
end
|
15
12
|
|
16
13
|
def lights
|
17
|
-
client.get!("/lights").map { |(id, attrs)| Light.new(client, id, attrs) }
|
14
|
+
@client.get!("/lights").map { |(id, attrs)| Light.new(@client, id, attrs) }
|
18
15
|
end
|
19
16
|
|
20
17
|
def groups
|
21
|
-
client.get!("/groups").map { |(id, attrs)| Group.new(client, id, attrs) }
|
18
|
+
@client.get!("/groups").map { |(id, attrs)| Group.new(@client, id, attrs) }
|
22
19
|
end
|
23
20
|
end
|
24
21
|
end
|
@@ -8,8 +8,9 @@ module Huebot
|
|
8
8
|
#
|
9
9
|
# @attr inputs [Array<String>]
|
10
10
|
#
|
11
|
-
Options = Struct.new(:inputs, :read_stdin)
|
11
|
+
Options = Struct.new(:inputs, :read_stdin, :debug, :no_device_check, :stdin, :stdout, :stderr, :bot_waiter)
|
12
12
|
|
13
|
+
autoload :Config, 'huebot/cli/config'
|
13
14
|
autoload :Helpers, 'huebot/cli/helpers'
|
14
15
|
autoload :Runner, 'huebot/cli/runner'
|
15
16
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Huebot
|
5
|
+
module CLI
|
6
|
+
class Config
|
7
|
+
def initialize(path = "~/.config/huebot")
|
8
|
+
@path = File.expand_path(path)
|
9
|
+
@dir = File.dirname(@path)
|
10
|
+
@dir_exists = File.exist? @dir
|
11
|
+
reload
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](attr)
|
15
|
+
@config[attr.to_s]
|
16
|
+
end
|
17
|
+
|
18
|
+
def []=(attr, val)
|
19
|
+
if val.nil?
|
20
|
+
@config.delete(attr.to_s)
|
21
|
+
else
|
22
|
+
@config[attr.to_s] = val
|
23
|
+
end
|
24
|
+
write
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear
|
28
|
+
@config.clear
|
29
|
+
write
|
30
|
+
end
|
31
|
+
|
32
|
+
def reload
|
33
|
+
@config = File.exist?(@path) ? YAML.load_file(@path) : {}
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def write
|
39
|
+
unless @dir_exists
|
40
|
+
FileUtils.mkdir_p @dir
|
41
|
+
@dir_exists = true
|
42
|
+
end
|
43
|
+
File.write(@path, YAML.dump(@config))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/huebot/cli/helpers.rb
CHANGED
@@ -1,73 +1,91 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'yaml'
|
3
|
+
require 'json'
|
3
4
|
|
4
5
|
module Huebot
|
5
6
|
module CLI
|
6
7
|
module Helpers
|
8
|
+
DEFAULT_API_VERSION = 1.1
|
9
|
+
|
7
10
|
#
|
8
11
|
# Returns the command given to huebot.
|
9
12
|
#
|
10
13
|
# @return [Symbol]
|
11
14
|
#
|
12
|
-
def self.get_cmd
|
13
|
-
|
15
|
+
def self.get_cmd(argv = ARGV)
|
16
|
+
argv[0].to_s.to_sym
|
14
17
|
end
|
15
18
|
|
16
|
-
def self.get_args(min: nil, max: nil, num: nil)
|
17
|
-
args =
|
19
|
+
def self.get_args!(argv = ARGV, min: nil, max: nil, num: nil)
|
20
|
+
args, error = get_args(argv, min: min, max: max, num: num)
|
21
|
+
if error
|
22
|
+
$stderr.puts error
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
args
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.get_args(argv = ARGV, min: nil, max: nil, num: nil)
|
29
|
+
args = argv[1..]
|
18
30
|
if num
|
19
31
|
if num != args.size
|
20
|
-
|
21
|
-
exit 1
|
32
|
+
return nil, "Expected #{num} args, found #{args.size}"
|
22
33
|
end
|
23
34
|
elsif min and max
|
24
35
|
if args.size < min or args.size > max
|
25
|
-
|
36
|
+
return nil, "Expected #{min}-#{max} args, found #{args.size}"
|
26
37
|
end
|
27
38
|
elsif min
|
28
39
|
if args.size < min
|
29
|
-
|
30
|
-
exit 1
|
40
|
+
return nil, "Expected at least #{min} args, found #{args.size}"
|
31
41
|
end
|
32
42
|
elsif max
|
33
43
|
if args.size > max
|
34
|
-
|
35
|
-
exit 1
|
44
|
+
return nil, "Expected no more than #{max} args, found #{args.size}"
|
36
45
|
end
|
37
46
|
end
|
38
|
-
args
|
47
|
+
return args, nil
|
39
48
|
end
|
40
49
|
|
41
50
|
#
|
42
51
|
# Parses and returns input from the CLI. Serious errors might result in the program exiting.
|
43
52
|
#
|
44
|
-
# @
|
53
|
+
# @param opts [Huebot::CLI::Options] All given CLI options
|
45
54
|
# @return [Array<Huebot::Program::Src>] Array of given program sources
|
46
55
|
#
|
47
|
-
def self.get_input!
|
48
|
-
|
49
|
-
parser.parse!
|
50
|
-
|
51
|
-
files = ARGV[1..-1]
|
56
|
+
def self.get_input!(opts, argv = ARGV)
|
57
|
+
files = argv[1..-1]
|
52
58
|
if (bad_paths = files.select { |p| !File.exist? p }).any?
|
53
|
-
|
54
|
-
|
59
|
+
opts.stderr.puts "Cannot find #{bad_paths.join ', '}"
|
60
|
+
return []
|
55
61
|
end
|
56
62
|
|
57
63
|
sources = files.map { |path|
|
58
|
-
|
59
|
-
|
64
|
+
ext = File.extname path
|
65
|
+
src =
|
66
|
+
case ext
|
67
|
+
when ".yaml", ".yml"
|
68
|
+
YAML.safe_load(File.read path) || {}
|
69
|
+
when ".json"
|
70
|
+
JSON.load(File.read path) || {}
|
71
|
+
else
|
72
|
+
opts.stderr.puts "Unknown file extension '#{ext}'. Expected .yaml, .yml, or .json"
|
73
|
+
return []
|
74
|
+
end
|
75
|
+
version = (src.delete("version") || DEFAULT_API_VERSION).to_f
|
60
76
|
Program::Src.new(src, path, version)
|
61
77
|
}
|
62
78
|
|
63
|
-
if
|
64
|
-
puts "Please enter your YAML Huebot program below, followed by Ctrl+d:" if
|
65
|
-
|
66
|
-
|
67
|
-
|
79
|
+
if !opts.stdin.isatty or opts.read_stdin
|
80
|
+
opts.stdout.puts "Please enter your YAML or JSON Huebot program below, followed by Ctrl+d:" if opts.read_stdin
|
81
|
+
raw = opts.stdin.read.lstrip
|
82
|
+
src = raw[0] == "{" ? JSON.load(raw) : YAML.safe_load(raw)
|
83
|
+
|
84
|
+
opts.stdout.puts "Executing..." if opts.read_stdin
|
85
|
+
version = (src.delete("version") || DEFAULT_API_VERSION).to_f
|
68
86
|
sources << Program::Src.new(src, "STDIN", version)
|
69
87
|
end
|
70
|
-
|
88
|
+
sources
|
71
89
|
end
|
72
90
|
|
73
91
|
#
|
@@ -95,27 +113,35 @@ module Huebot
|
|
95
113
|
|
96
114
|
all_lights = programs.reduce([]) { |acc, p| acc + p.light_names }
|
97
115
|
if (missing_lights = device_mapper.missing_lights all_lights).any?
|
98
|
-
print_messages! io, "Unknown lights", missing_lights
|
116
|
+
print_messages! io, "Unknown lights", missing_lights unless quiet
|
99
117
|
end
|
100
118
|
|
101
119
|
all_groups = programs.reduce([]) { |acc, p| acc + p.group_names }
|
102
120
|
if (missing_groups = device_mapper.missing_groups all_groups).any?
|
103
|
-
print_messages! io, "Unknown groups", missing_groups
|
121
|
+
print_messages! io, "Unknown groups", missing_groups unless quiet
|
104
122
|
end
|
105
123
|
|
106
124
|
all_vars = programs.reduce([]) { |acc, p| acc + p.device_refs }
|
107
125
|
if (missing_vars = device_mapper.missing_vars all_vars).any?
|
108
|
-
print_messages! io, "Unknown device inputs", missing_vars.map { |d| "$#{d}" }
|
126
|
+
print_messages! io, "Unknown device inputs", missing_vars.map { |d| "$#{d}" } unless quiet
|
109
127
|
end
|
110
128
|
|
111
129
|
invalid_devices = missing_lights.size + missing_groups.size + missing_vars.size
|
112
130
|
return invalid_progs.any?, imperfect_progs.any?, invalid_devices > 0
|
113
131
|
end
|
114
132
|
|
133
|
+
def self.get_opts!
|
134
|
+
opts = default_options
|
135
|
+
parser = option_parser opts
|
136
|
+
parser.parse!
|
137
|
+
opts
|
138
|
+
end
|
139
|
+
|
115
140
|
# Print help and exit
|
116
141
|
def self.help!
|
117
|
-
|
118
|
-
|
142
|
+
opts = default_options
|
143
|
+
parser = option_parser opts
|
144
|
+
opts.stdout.puts parser.help
|
119
145
|
exit 1
|
120
146
|
end
|
121
147
|
|
@@ -135,15 +161,14 @@ module Huebot
|
|
135
161
|
}
|
136
162
|
end
|
137
163
|
|
138
|
-
def self.option_parser
|
139
|
-
|
140
|
-
parser = OptionParser.new { |opts|
|
164
|
+
def self.option_parser(options)
|
165
|
+
OptionParser.new { |opts|
|
141
166
|
opts.banner = %(
|
142
167
|
List all lights and groups:
|
143
168
|
huebot ls
|
144
169
|
|
145
170
|
Run program(s):
|
146
|
-
huebot run prog1.yaml [prog2.
|
171
|
+
huebot run prog1.yaml [prog2.yml [prog3.json ...]] [options]
|
147
172
|
|
148
173
|
Run program from STDIN:
|
149
174
|
cat prog1.yaml | huebot run [options]
|
@@ -168,9 +193,18 @@ module Huebot
|
|
168
193
|
opts.on("-lLIGHT", "--light=LIGHT", "Light ID or name") { |l| options.inputs << Light::Input.new(l) }
|
169
194
|
opts.on("-gGROUP", "--group=GROUP", "Group ID or name") { |g| options.inputs << Group::Input.new(g) }
|
170
195
|
opts.on("-i", "Read program from STDIN") { options.read_stdin = true }
|
171
|
-
opts.on("
|
196
|
+
opts.on("--debug", "Print debug info during run") { options.debug = true }
|
197
|
+
opts.on("--no-device-check", "Don't validate devices against the Bridge ('check' cmd only)") { options.no_device_check = true }
|
198
|
+
opts.on("-h", "--help", "Prints this help") { options.stdout.puts opts; exit }
|
172
199
|
}
|
173
|
-
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.default_options
|
203
|
+
options = Options.new([], false)
|
204
|
+
options.stdin = $stdin
|
205
|
+
options.stdout = $stdout
|
206
|
+
options.stderr = $stderr
|
207
|
+
options
|
174
208
|
end
|
175
209
|
end
|
176
210
|
end
|
data/lib/huebot/cli/runner.rb
CHANGED
@@ -1,76 +1,84 @@
|
|
1
1
|
module Huebot
|
2
2
|
module CLI
|
3
3
|
module Runner
|
4
|
-
def self.ls(
|
5
|
-
puts "Lights\n" +
|
6
|
-
"\nGroups\n" +
|
4
|
+
def self.ls(lights, groups, opts)
|
5
|
+
opts.stdout.puts "Lights\n" + lights.map { |l| " #{l.id}: #{l.name}" }.join("\n") + \
|
6
|
+
"\nGroups\n" + groups.map { |g| " #{g.id}: #{g.name}" }.join("\n")
|
7
7
|
return 0
|
8
8
|
rescue ::Huebot::Error => e
|
9
|
-
|
9
|
+
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
10
10
|
return 1
|
11
11
|
end
|
12
12
|
|
13
|
-
def self.run(
|
14
|
-
device_mapper = Huebot::DeviceMapper.new(bridge, opts.inputs)
|
13
|
+
def self.run(sources, lights, groups, opts)
|
15
14
|
programs = sources.map { |src|
|
16
15
|
Huebot::Compiler.build src
|
17
16
|
}
|
18
|
-
|
17
|
+
device_mapper = Huebot::DeviceMapper.new(lights: lights, groups: groups, inputs: opts.inputs)
|
18
|
+
found_errors, _found_warnings, missing_devices = Helpers.check! programs, device_mapper, opts.stderr
|
19
19
|
return 1 if found_errors || missing_devices
|
20
20
|
|
21
|
-
|
21
|
+
logger = opts.debug ? Logging::IOLogger.new(opts.stdout) : nil
|
22
|
+
bot = Huebot::Bot.new(device_mapper, logger: logger, waiter: opts.bot_waiter)
|
22
23
|
programs.each { |prog| bot.execute prog }
|
23
24
|
return 0
|
24
25
|
rescue ::Huebot::Error => e
|
25
|
-
|
26
|
+
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
26
27
|
return 1
|
27
28
|
end
|
28
29
|
|
29
|
-
def self.check(
|
30
|
-
device_mapper = Huebot::DeviceMapper.new(bridge, opts.inputs)
|
30
|
+
def self.check(sources, lights, groups, opts)
|
31
31
|
programs = sources.map { |src|
|
32
32
|
Huebot::Compiler.build src
|
33
33
|
}
|
34
|
-
|
34
|
+
|
35
|
+
# Assume all devices and inputs are correct
|
36
|
+
if opts.no_device_check
|
37
|
+
light_input_names = opts.inputs.select { |i| i.is_a? Light::Input }.map(&:val)
|
38
|
+
lights = programs.reduce(light_input_names) { |acc, p| acc + p.light_names }.uniq.each_with_index.map { |name, i| Light.new(nil, i+1, {"name" => name}) }
|
39
|
+
|
40
|
+
group_input_names = opts.inputs.select { |i| i.is_a? Group::Input }.map(&:val)
|
41
|
+
groups = programs.reduce(group_input_names) { |acc, p| acc + p.group_names }.uniq.each_with_index.map { |name, i| Group.new(nil, i+1, {"name" => name}) }
|
42
|
+
end
|
43
|
+
|
44
|
+
device_mapper = Huebot::DeviceMapper.new(lights: lights, groups: groups, inputs: opts.inputs)
|
45
|
+
found_errors, found_warnings, missing_devices = Helpers.check! programs, device_mapper, opts.stderr
|
35
46
|
return (found_errors || found_warnings || missing_devices) ? 1 : 0
|
36
47
|
rescue ::Huebot::Error => e
|
37
|
-
|
48
|
+
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
38
49
|
return 1
|
39
50
|
end
|
40
51
|
|
41
|
-
def self.get_state(
|
42
|
-
device_mapper = Huebot::DeviceMapper.new(
|
52
|
+
def self.get_state(lights, groups, opts)
|
53
|
+
device_mapper = Huebot::DeviceMapper.new(lights: lights, groups: groups, inputs: opts.inputs)
|
43
54
|
device_mapper.each do |device|
|
44
|
-
puts device.name
|
45
|
-
puts " #{device.get_state}"
|
55
|
+
opts.stdout.puts device.name
|
56
|
+
opts.stdout.puts " #{device.get_state}"
|
46
57
|
end
|
47
58
|
0
|
48
59
|
end
|
49
60
|
|
50
|
-
def self.set_ip
|
51
|
-
config = Huebot::Config.new
|
61
|
+
def self.set_ip(config, ip, opts)
|
52
62
|
config["ip"] = ip
|
53
63
|
0
|
54
64
|
rescue ::Huebot::Error => e
|
55
|
-
|
65
|
+
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
56
66
|
return 1
|
57
67
|
end
|
58
68
|
|
59
|
-
def self.clear_ip
|
60
|
-
config = Huebot::Config.new
|
69
|
+
def self.clear_ip(config, opts)
|
61
70
|
config["ip"] = nil
|
62
71
|
0
|
63
72
|
rescue ::Huebot::Error => e
|
64
|
-
|
73
|
+
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
65
74
|
return 1
|
66
75
|
end
|
67
76
|
|
68
|
-
def self.unregister
|
69
|
-
config = Huebot::Config.new
|
77
|
+
def self.unregister(config, opts)
|
70
78
|
config.clear
|
71
79
|
0
|
72
80
|
rescue ::Huebot::Error => e
|
73
|
-
|
81
|
+
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
74
82
|
return 1
|
75
83
|
end
|
76
84
|
end
|
data/lib/huebot/client.rb
CHANGED
@@ -51,45 +51,61 @@ module Huebot
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def build_transition(t, errors, warnings, inherited_devices = nil)
|
54
|
+
if t.nil?
|
55
|
+
errors << "'transition' may not be blank"
|
56
|
+
t = {}
|
57
|
+
end
|
58
|
+
|
54
59
|
state = build_state(t, errors, warnings)
|
55
60
|
devices = build_devices(t, errors, warnings, inherited_devices)
|
56
|
-
|
61
|
+
pause = build_pause(t, errors, warnings)
|
62
|
+
wait = @api_version >= 1.1 ? build_wait(t, errors, warnings) : true
|
57
63
|
|
58
64
|
errors << "'transition' requires devices" if devices.empty?
|
59
65
|
errors << "Unknown keys in 'transition': #{t.keys.join ", "}" if t.keys.any?
|
60
66
|
|
61
|
-
instruction = Program::AST::Transition.new(state, devices,
|
67
|
+
instruction = Program::AST::Transition.new(state, devices, wait, pause)
|
62
68
|
return instruction, []
|
63
69
|
end
|
64
70
|
|
65
71
|
def build_serial(t, errors, warnings, inherited_devices = nil)
|
72
|
+
if t.nil?
|
73
|
+
errors << "'serial' may not be blank"
|
74
|
+
t = {}
|
75
|
+
end
|
76
|
+
|
66
77
|
lp = build_loop(t, errors, warnings)
|
67
|
-
|
78
|
+
pause = build_pause(t, errors, warnings)
|
68
79
|
devices = build_devices(t, errors, warnings, inherited_devices)
|
69
80
|
children = build_steps(t, errors, warnings, devices)
|
70
81
|
|
71
82
|
errors << "'serial' requires steps" if children.empty?
|
72
83
|
errors << "Unknown keys in 'serial': #{t.keys.join ", "}" if t.keys.any?
|
73
84
|
|
74
|
-
instruction = Program::AST::SerialControl.new(lp,
|
85
|
+
instruction = Program::AST::SerialControl.new(lp, pause)
|
75
86
|
return instruction, children
|
76
87
|
end
|
77
88
|
|
78
89
|
def build_parallel(t, errors, warnings, inherited_devices = nil)
|
90
|
+
if t.nil?
|
91
|
+
errors << "'parallel' may not be blank"
|
92
|
+
t = {}
|
93
|
+
end
|
94
|
+
|
79
95
|
lp = build_loop(t, errors, warnings)
|
80
|
-
|
96
|
+
pause = build_pause(t, errors, warnings)
|
81
97
|
devices = build_devices(t, errors, warnings, inherited_devices)
|
82
98
|
children = build_steps(t, errors, warnings, devices)
|
83
99
|
|
84
100
|
errors << "'parallel' requires steps" if children.empty?
|
85
101
|
errors << "Unknown keys in 'parallel': #{t.keys.join ", "}" if t.keys.any?
|
86
102
|
|
87
|
-
instruction = Program::AST::ParallelControl.new(lp,
|
103
|
+
instruction = Program::AST::ParallelControl.new(lp, pause)
|
88
104
|
return instruction, children
|
89
105
|
end
|
90
106
|
|
91
107
|
def map_state_keys(state, errors, warnings)
|
92
|
-
# bugfix to YAML
|
108
|
+
# bugfix to YAML - it parses the "on" key as a Boolean
|
93
109
|
case state.delete true
|
94
110
|
when true
|
95
111
|
state["on"] = true
|
@@ -167,9 +183,7 @@ module Huebot
|
|
167
183
|
loop_val = t.delete "loop"
|
168
184
|
case loop_val
|
169
185
|
when Hash
|
170
|
-
pause = loop_val
|
171
|
-
errors << "'loop.pause' must be an integer. Found '#{pause.class.name}'" if pause and !pause.is_a? Integer
|
172
|
-
|
186
|
+
pause = build_pause(loop_val, errors, warnings)
|
173
187
|
lp =
|
174
188
|
case loop_val.keys
|
175
189
|
when INFINITE_KEYS
|
@@ -216,11 +230,19 @@ module Huebot
|
|
216
230
|
Program::AST::DeadlineLoop.new(stop_time)
|
217
231
|
end
|
218
232
|
|
219
|
-
def
|
220
|
-
|
221
|
-
|
233
|
+
def build_pause(t, errors, warnings)
|
234
|
+
case @api_version
|
235
|
+
when 1.0 then build_pause_1_0(t, errors, warnings)
|
236
|
+
when 1.1 then build_pause_1_1(t, errors, warnings)
|
237
|
+
else raise Error, "Unknown api version '#{@api_version}'"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def build_pause_1_0(t, errors, warnings)
|
242
|
+
pause_val = t.delete "pause"
|
243
|
+
case pause_val
|
222
244
|
when Integer, Float
|
223
|
-
|
245
|
+
Program::AST::Pause.new(nil, pause_val)
|
224
246
|
when nil
|
225
247
|
nil
|
226
248
|
else
|
@@ -229,6 +251,39 @@ module Huebot
|
|
229
251
|
end
|
230
252
|
end
|
231
253
|
|
254
|
+
def build_pause_1_1(t, errors, warnings)
|
255
|
+
pause_val = t.delete "pause"
|
256
|
+
case pause_val
|
257
|
+
when Integer, Float
|
258
|
+
Program::AST::Pause.new(nil, pause_val)
|
259
|
+
when Hash
|
260
|
+
pre = pause_val.delete "before"
|
261
|
+
post = pause_val.delete "after"
|
262
|
+
errors << "'pause.before' must be an integer or float" unless pre.nil? or pre.is_a? Integer or pre.is_a? Float
|
263
|
+
errors << "'pause.after' must be an integer or float" unless post.nil? or post.is_a? Integer or post.is_a? Float
|
264
|
+
errors << "Unknown keys in 'pause': #{pause_val.keys.join ", "}" if pause_val.keys.any?
|
265
|
+
Program::AST::Pause.new(pre, post)
|
266
|
+
when nil
|
267
|
+
nil
|
268
|
+
else
|
269
|
+
errors << "'pause' must be an integer or float"
|
270
|
+
nil
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def build_wait(t, errors, warnings)
|
275
|
+
wait = t.delete "wait"
|
276
|
+
case wait
|
277
|
+
when true, false
|
278
|
+
wait
|
279
|
+
when nil
|
280
|
+
true
|
281
|
+
else
|
282
|
+
errors << "'transition.wait' must be true or false"
|
283
|
+
true
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
232
287
|
def build_devices(t, errors, warnings, inherited_devices = nil)
|
233
288
|
devices_ref = t.delete("devices") || {}
|
234
289
|
return inherited_devices if devices_ref.empty? and inherited_devices
|
data/lib/huebot/device_mapper.rb
CHANGED
@@ -2,20 +2,18 @@ module Huebot
|
|
2
2
|
class DeviceMapper
|
3
3
|
Unmapped = Class.new(Error)
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
|
7
|
-
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@groups_by_id = all_groups.reduce({}) { |a, g| a[g.id] = g; a }
|
11
|
-
@groups_by_name = all_groups.reduce({}) { |a, g| a[g.name] = g; a }
|
5
|
+
def initialize(lights: [], groups:[], inputs: [])
|
6
|
+
@lights_by_id = lights.each_with_object({}) { |l, a| a[l.id] = l }
|
7
|
+
@lights_by_name = lights.each_with_object({}) { |l, a| a[l.name] = l }
|
8
|
+
@groups_by_id = groups.each_with_object({}) { |g, a| a[g.id] = g }
|
9
|
+
@groups_by_name = groups.each_with_object({}) { |g, a| a[g.name] = g }
|
12
10
|
@devices_by_var = inputs.each_with_index.each_with_object({}) { |(x, idx), obj|
|
13
11
|
obj[idx + 1] =
|
14
12
|
case x
|
15
13
|
when Light::Input then @lights_by_id[x.val.to_i] || @lights_by_name[x.val]
|
16
14
|
when Group::Input then @groups_by_id[x.val.to_i] || @groups_by_name[x.val]
|
17
15
|
else raise Error, "Invalid input: #{x}"
|
18
|
-
end || raise(Unmapped, "Could not find #{x.class.name[8..-
|
16
|
+
end || raise(Unmapped, "Could not find #{x.class.name[8..-8].downcase} with id or name '#{x.val}'")
|
19
17
|
}
|
20
18
|
@all = @devices_by_var.values
|
21
19
|
end
|
data/lib/huebot/group.rb
CHANGED
data/lib/huebot/light.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Huebot
|
2
|
+
module Logging
|
3
|
+
class CollectingLogger
|
4
|
+
attr_reader :events
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@events = []
|
8
|
+
@mut = Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def log(event_type, data = {})
|
12
|
+
now = Time.now
|
13
|
+
@mut.synchronize {
|
14
|
+
@events << [now, event_type, data]
|
15
|
+
}
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Huebot
|
4
|
+
module Logging
|
5
|
+
class IOLogger
|
6
|
+
def initialize(io)
|
7
|
+
@io = io
|
8
|
+
@mut = Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def log(event_type, data = {})
|
12
|
+
ts = Time.now.iso8601
|
13
|
+
@mut.synchronize {
|
14
|
+
@io.puts "#{ts} #{event_type} #{data.to_json}"
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/huebot/program.rb
CHANGED
@@ -16,15 +16,16 @@ module Huebot
|
|
16
16
|
module AST
|
17
17
|
Node = Struct.new(:instruction, :children, :errors, :warnings)
|
18
18
|
|
19
|
-
Transition = Struct.new(:state, :devices, :
|
20
|
-
SerialControl = Struct.new(:loop, :
|
21
|
-
ParallelControl = Struct.new(:loop, :
|
19
|
+
Transition = Struct.new(:state, :devices, :wait, :pause)
|
20
|
+
SerialControl = Struct.new(:loop, :pause)
|
21
|
+
ParallelControl = Struct.new(:loop, :pause)
|
22
22
|
|
23
23
|
InfiniteLoop = Struct.new(:pause)
|
24
24
|
CountedLoop = Struct.new(:n, :pause)
|
25
25
|
TimerLoop = Struct.new(:hours, :minutes, :pause)
|
26
26
|
DeadlineLoop = Struct.new(:stop_time, :pause)
|
27
27
|
|
28
|
+
Pause = Struct.new(:pre, :post)
|
28
29
|
DeviceRef = Struct.new(:ref)
|
29
30
|
Light = Struct.new(:name)
|
30
31
|
Group = Struct.new(:name)
|
data/lib/huebot/version.rb
CHANGED
data/lib/huebot.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
module Huebot
|
2
2
|
Error = Class.new(StandardError)
|
3
3
|
|
4
|
-
autoload :Config, 'huebot/config'
|
5
4
|
autoload :Client, 'huebot/client'
|
6
|
-
autoload :CLI, 'huebot/cli'
|
5
|
+
autoload :CLI, 'huebot/cli/cli'
|
7
6
|
autoload :Bridge, 'huebot/bridge'
|
8
7
|
autoload :DeviceState, 'huebot/device_state'
|
9
8
|
autoload :Light, 'huebot/light'
|
10
9
|
autoload :Group, 'huebot/group'
|
11
10
|
autoload :DeviceMapper, 'huebot/device_mapper'
|
12
11
|
autoload :Program, 'huebot/program'
|
13
|
-
autoload :Compiler, 'huebot/compiler'
|
12
|
+
autoload :Compiler, 'huebot/compiler/compiler'
|
14
13
|
autoload :Bot, 'huebot/bot'
|
14
|
+
autoload :Logging, 'huebot/logging/logging'
|
15
15
|
autoload :VERSION, 'huebot/version'
|
16
16
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: huebot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.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-
|
11
|
+
date: 2023-12-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -50,17 +50,21 @@ files:
|
|
50
50
|
- lib/huebot.rb
|
51
51
|
- lib/huebot/bot.rb
|
52
52
|
- lib/huebot/bridge.rb
|
53
|
-
- lib/huebot/cli.rb
|
53
|
+
- lib/huebot/cli/cli.rb
|
54
|
+
- lib/huebot/cli/config.rb
|
54
55
|
- lib/huebot/cli/helpers.rb
|
55
56
|
- lib/huebot/cli/runner.rb
|
56
57
|
- lib/huebot/client.rb
|
57
|
-
- lib/huebot/compiler.rb
|
58
58
|
- lib/huebot/compiler/api_v1.rb
|
59
|
-
- lib/huebot/
|
59
|
+
- lib/huebot/compiler/compiler.rb
|
60
60
|
- lib/huebot/device_mapper.rb
|
61
61
|
- lib/huebot/device_state.rb
|
62
62
|
- lib/huebot/group.rb
|
63
63
|
- lib/huebot/light.rb
|
64
|
+
- lib/huebot/logging/collecting_logger.rb
|
65
|
+
- lib/huebot/logging/io_logger.rb
|
66
|
+
- lib/huebot/logging/logging.rb
|
67
|
+
- lib/huebot/logging/null_logger.rb
|
64
68
|
- lib/huebot/program.rb
|
65
69
|
- lib/huebot/version.rb
|
66
70
|
homepage: https://github.com/jhollinger/huebot
|
data/lib/huebot/config.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
require 'yaml'
|
3
|
-
|
4
|
-
module Huebot
|
5
|
-
class Config
|
6
|
-
def initialize(path = "~/.config/huebot")
|
7
|
-
@path = File.expand_path(path)
|
8
|
-
@dir = File.dirname(@path)
|
9
|
-
@dir_exists = File.exist? @dir
|
10
|
-
@config = File.exist?(@path) ? YAML.load_file(@path) : {}
|
11
|
-
end
|
12
|
-
|
13
|
-
def [](attr)
|
14
|
-
@config[attr.to_s]
|
15
|
-
end
|
16
|
-
|
17
|
-
def []=(attr, val)
|
18
|
-
if val.nil?
|
19
|
-
@config.delete(attr.to_s)
|
20
|
-
else
|
21
|
-
@config[attr.to_s] = val
|
22
|
-
end
|
23
|
-
write
|
24
|
-
end
|
25
|
-
|
26
|
-
def clear
|
27
|
-
@config.clear
|
28
|
-
write
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def write
|
34
|
-
unless @dir_exists
|
35
|
-
FileUtils.mkdir_p @dir
|
36
|
-
@dir_exists = true
|
37
|
-
end
|
38
|
-
File.write(@path, YAML.dump(@config))
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|