guh 0.0.2
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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +17 -0
- data/guh.gemspec +27 -0
- data/lib/guh/action.rb +34 -0
- data/lib/guh/action_type.rb +27 -0
- data/lib/guh/base.rb +151 -0
- data/lib/guh/device.rb +104 -0
- data/lib/guh/device_class.rb +70 -0
- data/lib/guh/event.rb +9 -0
- data/lib/guh/event_type.rb +28 -0
- data/lib/guh/plugin.rb +44 -0
- data/lib/guh/rule.rb +58 -0
- data/lib/guh/vendor.rb +25 -0
- data/lib/guh/version.rb +3 -0
- data/lib/guh.rb +24 -0
- data/spec/helper.rb +51 -0
- data/spec/integration/action_spec.rb +42 -0
- data/spec/integration/action_type_spec.rb +11 -0
- data/spec/integration/base_spec.rb +18 -0
- data/spec/integration/device_class_spec.rb +50 -0
- data/spec/integration/device_spec.rb +64 -0
- data/spec/integration/event_type_spec.rb +17 -0
- data/spec/integration/plugin_spec.rb +15 -0
- data/spec/integration/rule_spec.rb +53 -0
- data/spec/integration/vendor_spec.rb +17 -0
- metadata +157 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7d9c96fa741e8caf95a21bd27791fe42948d38e9
|
4
|
+
data.tar.gz: 2b02f5eae38bd78c9c47b934d2480654843ddf3c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3d6e69e38dd6833f5a8f6df2bf9d1c2a9ce0513512655f67ea15fb91090df70eda1a75ed6bb34d1e3faf3bc25aedf6e4649babcc21f9fdf79e79ce0aca78700f
|
7
|
+
data.tar.gz: b7534ed8453175c5d4886cafa03322d307d22ab89335571d9a6eef3e9e8d917e1a443b246e10bfe65d380d6cf93bdcf53a78f2fad86d43a5ba2deed80db830bc
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 wukerplank
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Guh
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'guh'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install guh
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it ( http://github.com/HiveFive/guh/fork )
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new do |t|
|
6
|
+
t.rspec_opts = ["-c", "-f progress", "-r ./spec/helper.rb"]
|
7
|
+
t.pattern = 'spec/**/*_spec.rb'
|
8
|
+
end
|
9
|
+
|
10
|
+
task :console do
|
11
|
+
require 'irb'
|
12
|
+
require 'irb/completion'
|
13
|
+
require 'pp'
|
14
|
+
require 'guh'
|
15
|
+
ARGV.clear
|
16
|
+
IRB.start
|
17
|
+
end
|
data/guh.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'guh/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "guh"
|
8
|
+
spec.version = Guh::VERSION
|
9
|
+
spec.authors = ["Christoph Edthofer", "Thomas Esterer"]
|
10
|
+
spec.email = ["christoph.edthofer@guh.guru", "thomas.esterer@guh.guru"]
|
11
|
+
spec.summary = %q{The official Ruby wrapper for guh.}
|
12
|
+
spec.description = "The official Ruby wrapper for guh. guh is an open source home automation system."
|
13
|
+
spec.homepage = "http://guh.guru"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "json", "~> 1.8"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.3"
|
25
|
+
spec.add_development_dependency "rspec", "~> 2.14"
|
26
|
+
spec.add_development_dependency "yard", "~> 0.8"
|
27
|
+
end
|
data/lib/guh/action.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Guh
|
2
|
+
##
|
3
|
+
# This class wraps everything related to Actions.
|
4
|
+
#
|
5
|
+
class Action < Base
|
6
|
+
|
7
|
+
##
|
8
|
+
#
|
9
|
+
# Executes a specific Action on a specific Device.
|
10
|
+
#
|
11
|
+
# To activate an "Elro Power Switch":
|
12
|
+
#
|
13
|
+
# Guh::Action.execute("TODO find specific device id", "{31c9758e-6567-4f89-85bb-29e1a7c55d44}", {power: true})
|
14
|
+
#
|
15
|
+
def self.execute(device_id, action_type_id, params={})
|
16
|
+
response = get({
|
17
|
+
id: generate_request_id,
|
18
|
+
method: "Actions.ExecuteAction",
|
19
|
+
params: {
|
20
|
+
deviceId: device_id,
|
21
|
+
actionTypeId: action_type_id,
|
22
|
+
params: convert_map_to_list_of_maps(params)
|
23
|
+
}
|
24
|
+
})
|
25
|
+
|
26
|
+
if response['success']==true
|
27
|
+
return response
|
28
|
+
else
|
29
|
+
raise Guh::ArgumentError, response['errorMessage']
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Guh
|
2
|
+
##
|
3
|
+
# This class wraps everything related to ActionTypes.
|
4
|
+
#
|
5
|
+
class ActionType < Base
|
6
|
+
|
7
|
+
##
|
8
|
+
# Retrieves all known ActionTypes for a specific Device identified by its +device_id+.
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# Guh::ActionType.all("{308ae6e6-38b3-4b3a-a513-3199da2764f8}")
|
13
|
+
#
|
14
|
+
def self.all(device_class_id)
|
15
|
+
response = get({
|
16
|
+
id: generate_request_id,
|
17
|
+
method: "Devices.GetActionTypes",
|
18
|
+
params: {
|
19
|
+
deviceClassId: device_class_id
|
20
|
+
}
|
21
|
+
})
|
22
|
+
|
23
|
+
response['actionTypes']
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
data/lib/guh/base.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
module Guh
|
2
|
+
##
|
3
|
+
# The base class which all the specific wrapper classes inherit from. It provides some shared code to make development easier.
|
4
|
+
#
|
5
|
+
class Base
|
6
|
+
|
7
|
+
# Getter/Setter for the guh_ip_address attribute
|
8
|
+
def self.guh_ip_address #:nodoc:
|
9
|
+
@@guh_ip_address
|
10
|
+
end
|
11
|
+
def self.guh_ip_address=(hia) #:nodoc:
|
12
|
+
@@guh_ip_address=hia
|
13
|
+
end
|
14
|
+
@@guh_ip_address = "127.0.0.1"
|
15
|
+
|
16
|
+
# Getter/Setter for the guh_port attribute
|
17
|
+
def self.guh_port #:nodoc:
|
18
|
+
@@guh_port
|
19
|
+
end
|
20
|
+
def self.guh_port=(hia) #:nodoc:
|
21
|
+
@@guh_port=hia
|
22
|
+
end
|
23
|
+
@@guh_port = 1234
|
24
|
+
|
25
|
+
##
|
26
|
+
#
|
27
|
+
# Returns everything that is going on inside guh Core
|
28
|
+
#
|
29
|
+
# Example:
|
30
|
+
#
|
31
|
+
# Guh::Base.introspect
|
32
|
+
#
|
33
|
+
def self.introspect
|
34
|
+
get({
|
35
|
+
id: generate_request_id,
|
36
|
+
method: "JSONRPC.Introspect",
|
37
|
+
})
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
#
|
42
|
+
# <b>Don't use this unless you know what you are doing!</b>
|
43
|
+
#
|
44
|
+
# Send a request to guh Core and fetch the Response. This is a utility method used by the subclasses.
|
45
|
+
#
|
46
|
+
def self.get(request_hash)
|
47
|
+
request_string = hash_to_json(request_hash)
|
48
|
+
|
49
|
+
response = nil
|
50
|
+
client do |c|
|
51
|
+
c.puts(request_string)
|
52
|
+
|
53
|
+
response = fetch_message(c)
|
54
|
+
end
|
55
|
+
|
56
|
+
if response['status']=='success'
|
57
|
+
return response['params']
|
58
|
+
else
|
59
|
+
raise Guh::ResponseError, "The Request was not successful"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
#
|
65
|
+
# Configuration DSL helper method.
|
66
|
+
#
|
67
|
+
# The default values are:
|
68
|
+
# * +guh_ip_address+: 127.0.0.1
|
69
|
+
# * +guh_port+: 1234
|
70
|
+
#
|
71
|
+
# Example:
|
72
|
+
#
|
73
|
+
# Guh.configure do |config|
|
74
|
+
# config.guh_ip_address = 10.0.0.1
|
75
|
+
# config.guh_port = 6789
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
def self.configure(&block)
|
79
|
+
yield self
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def self.generate_request_id
|
85
|
+
999999
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.client(&block)
|
89
|
+
client = TCPSocket.open(@@guh_ip_address, @@guh_port)
|
90
|
+
|
91
|
+
connection_message = fetch_message(client)
|
92
|
+
|
93
|
+
# TODO check guh-core version and raise error if incompatible with this gem's version
|
94
|
+
# TODO look for timeout or connection refused errors
|
95
|
+
|
96
|
+
yield client
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.fetch_message(client)
|
100
|
+
end_of_message = false
|
101
|
+
|
102
|
+
message = ""
|
103
|
+
while (line = client.gets)
|
104
|
+
message << line
|
105
|
+
|
106
|
+
end_of_message = true if line.match(/^\}\n/)
|
107
|
+
|
108
|
+
break if end_of_message
|
109
|
+
end
|
110
|
+
|
111
|
+
return JSON::parse(message)
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
#
|
116
|
+
# Converts a regular hash into a list of hashes.
|
117
|
+
#
|
118
|
+
# Example:
|
119
|
+
#
|
120
|
+
# convert_map_to_list_of_maps({
|
121
|
+
# power: true,
|
122
|
+
# pin: 1,
|
123
|
+
# family_code: 'A'
|
124
|
+
# })
|
125
|
+
#
|
126
|
+
# Returns:
|
127
|
+
#
|
128
|
+
# [
|
129
|
+
# {power: true},
|
130
|
+
# {pin: 1},
|
131
|
+
# {family_code: 'A'}
|
132
|
+
# ]
|
133
|
+
#
|
134
|
+
def self.convert_map_to_list_of_maps(map)
|
135
|
+
# make sure we have an array to work with
|
136
|
+
map ||= []
|
137
|
+
|
138
|
+
# do the conversion work
|
139
|
+
list = []
|
140
|
+
map.each do |key, value|
|
141
|
+
list << {key => value}
|
142
|
+
end
|
143
|
+
|
144
|
+
return list
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.hash_to_json(hash)
|
148
|
+
JSON::dump(hash) + "\n"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/lib/guh/device.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
module Guh
|
2
|
+
##
|
3
|
+
# This class wraps everything related to configured Devices.
|
4
|
+
#
|
5
|
+
class Device < Base
|
6
|
+
|
7
|
+
|
8
|
+
##
|
9
|
+
#
|
10
|
+
def self.find(id)
|
11
|
+
device = self.all.detect{|d| d['id']==id}
|
12
|
+
|
13
|
+
return device
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
#
|
18
|
+
# Returns a list of all configured Devices.
|
19
|
+
#
|
20
|
+
# Example:
|
21
|
+
#
|
22
|
+
# Guh::Device.all
|
23
|
+
#
|
24
|
+
def self.all
|
25
|
+
response = get({
|
26
|
+
id: generate_request_id,
|
27
|
+
method: "Devices.GetConfiguredDevices"
|
28
|
+
})
|
29
|
+
|
30
|
+
return response['devices']
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
#
|
35
|
+
# Creates a configured device and returns it's ID.
|
36
|
+
#
|
37
|
+
# Example for the "Elro Power Switch":
|
38
|
+
#
|
39
|
+
# Guh::Device.add("{308ae6e6-38b3-4b3a-a513-3199da2764f8}", {
|
40
|
+
# channel1: true,
|
41
|
+
# channel2: false,
|
42
|
+
# channel3: false,
|
43
|
+
# channel4: false,
|
44
|
+
# channel5: false,
|
45
|
+
# channel6: true,
|
46
|
+
# channel7: false,
|
47
|
+
# channel8: false,
|
48
|
+
# channel9: false,
|
49
|
+
# channel10: false
|
50
|
+
# })
|
51
|
+
#
|
52
|
+
def self.add(device_class_id, params)
|
53
|
+
response = get({
|
54
|
+
id: generate_request_id,
|
55
|
+
method: "Devices.AddConfiguredDevice",
|
56
|
+
params: {
|
57
|
+
deviceClassId: device_class_id,
|
58
|
+
deviceParams: params
|
59
|
+
}
|
60
|
+
})
|
61
|
+
if response['success']==true
|
62
|
+
return response['deviceId']
|
63
|
+
else
|
64
|
+
raise Guh::ArgumentError, response['errorMessage']
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
#
|
70
|
+
# Removes a configured device.
|
71
|
+
#
|
72
|
+
# Example:
|
73
|
+
#
|
74
|
+
# device_id = Guh::Device.add("{ab73ad2f-6594-45a3-9063-8f72d365c5e5}", {familyCode: 'A'})
|
75
|
+
#
|
76
|
+
# Guh::Device.remove(device_id)
|
77
|
+
#
|
78
|
+
def self.remove(device_id)
|
79
|
+
response = get({
|
80
|
+
id: generate_request_id,
|
81
|
+
method: "Devices.RemoveConfiguredDevice",
|
82
|
+
params: {
|
83
|
+
deviceId: device_id
|
84
|
+
}
|
85
|
+
})
|
86
|
+
if response['success']==true
|
87
|
+
return true
|
88
|
+
else
|
89
|
+
raise Guh::ResponseError, response['errorMessage']
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
#
|
95
|
+
# Returns current number of configured devices
|
96
|
+
#
|
97
|
+
# Example: Guh::Device.count_configured
|
98
|
+
#
|
99
|
+
def self.count
|
100
|
+
self.all.length
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Guh
|
2
|
+
##
|
3
|
+
# This class wraps everything related to available Devices.
|
4
|
+
#
|
5
|
+
class DeviceClass < Base
|
6
|
+
|
7
|
+
##
|
8
|
+
#
|
9
|
+
# Returns a list of all supported Devices.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# Guh::DeviceClass.all
|
14
|
+
# # => a list of all supported devices
|
15
|
+
#
|
16
|
+
def self.all(options={})
|
17
|
+
params = {}
|
18
|
+
params['vendorId'] = options[:vendor_id] unless options[:vendor_id].nil?
|
19
|
+
|
20
|
+
response = get({
|
21
|
+
id: generate_request_id,
|
22
|
+
method: "Devices.GetSupportedDevices",
|
23
|
+
params: params
|
24
|
+
})
|
25
|
+
|
26
|
+
return response['deviceClasses']
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
#
|
31
|
+
# Finds a DeviceClass with a specific ID
|
32
|
+
#
|
33
|
+
# Example:
|
34
|
+
#
|
35
|
+
# Guh::DeviceClass.find("{2062d64d-3232-433c-88bc-0d33c0ba2ba6}")
|
36
|
+
# # => a list of all supported devices of a specific vendor
|
37
|
+
#
|
38
|
+
def self.find(id)
|
39
|
+
device_classes = self.all
|
40
|
+
|
41
|
+
device_class = device_classes.detect{|dc| dc['id']==id}
|
42
|
+
|
43
|
+
if device_class
|
44
|
+
return device_class
|
45
|
+
else
|
46
|
+
raise DeviceClassNotFound, "Could not find a DeviceClass with the id #{id}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
#
|
52
|
+
def self.discover(id, options={})
|
53
|
+
params = {deviceClassId: id}
|
54
|
+
params['discoveryParams'] = options
|
55
|
+
|
56
|
+
response = get({
|
57
|
+
id: generate_request_id,
|
58
|
+
method: "Devices.GetDiscoveredDevices",
|
59
|
+
params: params
|
60
|
+
})
|
61
|
+
|
62
|
+
if response['success']
|
63
|
+
return response['deviceDescriptors']
|
64
|
+
else
|
65
|
+
raise ResponseError, response['errorMessage']
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
data/lib/guh/event.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Guh
|
2
|
+
##
|
3
|
+
# This class wraps everything related to EventTypes.
|
4
|
+
#
|
5
|
+
class EventType < Base
|
6
|
+
|
7
|
+
##
|
8
|
+
#
|
9
|
+
# Returns a list of all available EventTypes.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# Guh::EventType.all(device_class_id)
|
14
|
+
#
|
15
|
+
def self.all(device_class_id)
|
16
|
+
response = get({
|
17
|
+
id: generate_request_id,
|
18
|
+
method: "Devices.GetEventTypes",
|
19
|
+
params: {
|
20
|
+
deviceClassId: device_class_id
|
21
|
+
}
|
22
|
+
})
|
23
|
+
|
24
|
+
response['eventTypes']
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/lib/guh/plugin.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Guh
|
2
|
+
##
|
3
|
+
# This class wraps everything related to Plugins.
|
4
|
+
#
|
5
|
+
class Plugin < Base
|
6
|
+
|
7
|
+
##
|
8
|
+
#
|
9
|
+
# Returns a list of all installed plugins.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# Guh::Plugin.all
|
14
|
+
#
|
15
|
+
def self.all
|
16
|
+
response = get({
|
17
|
+
id: generate_request_id,
|
18
|
+
method: "Devices.GetPlugins"
|
19
|
+
})
|
20
|
+
|
21
|
+
response['plugins']
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
#
|
26
|
+
# Set some params on a plugin.
|
27
|
+
#
|
28
|
+
# Example:
|
29
|
+
#
|
30
|
+
# HiveRpcWrapper::Plugin.set_params("TODO get proper plugin id", {foo: "bar"})
|
31
|
+
#
|
32
|
+
# def self.set_config(plugin_id, params)
|
33
|
+
# get({
|
34
|
+
# id: generate_request_id,
|
35
|
+
# method: "Devices.SetPluginConfig",
|
36
|
+
# params: {
|
37
|
+
# pluginId: plugin_id,
|
38
|
+
# params: params
|
39
|
+
# }
|
40
|
+
# })
|
41
|
+
# end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
data/lib/guh/rule.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Guh
|
2
|
+
##
|
3
|
+
# This class wraps everything related to Rules.
|
4
|
+
#
|
5
|
+
class Rule < Base
|
6
|
+
|
7
|
+
##
|
8
|
+
#
|
9
|
+
# Returns a list of all Rules.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# Guh::Rule.all
|
14
|
+
#
|
15
|
+
def self.all
|
16
|
+
response = get({
|
17
|
+
id: generate_request_id,
|
18
|
+
method: "Rules.GetRules"
|
19
|
+
})
|
20
|
+
|
21
|
+
response['rules']
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
#
|
26
|
+
# Creates a new Rule.
|
27
|
+
#
|
28
|
+
# Example:
|
29
|
+
#
|
30
|
+
# Guh::Rule.add(event, action)
|
31
|
+
#
|
32
|
+
def self.add(event, actions)
|
33
|
+
unless actions.is_a?(Array)
|
34
|
+
actions = [actions]
|
35
|
+
end
|
36
|
+
|
37
|
+
get({
|
38
|
+
id: generate_request_id,
|
39
|
+
method: "Rules.AddRule",
|
40
|
+
event: event,
|
41
|
+
actions: actions
|
42
|
+
})
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
#
|
47
|
+
# Removes a Rule.
|
48
|
+
#
|
49
|
+
# Example:
|
50
|
+
#
|
51
|
+
# Guh::Rule.remove(rule_id)
|
52
|
+
#
|
53
|
+
def self.remove(rule_id)
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
data/lib/guh/vendor.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Guh
|
2
|
+
##
|
3
|
+
# This class wraps everything related to Vendors.
|
4
|
+
#
|
5
|
+
class Vendor < Base
|
6
|
+
|
7
|
+
##
|
8
|
+
#
|
9
|
+
# Retrieves a list of all supported vendors
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# Guh::Vendor.all
|
14
|
+
#
|
15
|
+
def self.all
|
16
|
+
response = get({
|
17
|
+
id: generate_request_id,
|
18
|
+
method: "Devices.GetSupportedVendors"
|
19
|
+
})
|
20
|
+
|
21
|
+
return response['vendors']
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/guh/version.rb
ADDED
data/lib/guh.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "guh/version"
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
require "guh/base"
|
7
|
+
require "guh/action_type"
|
8
|
+
require "guh/action"
|
9
|
+
require "guh/device"
|
10
|
+
require "guh/device_class"
|
11
|
+
require "guh/event_type"
|
12
|
+
require "guh/event"
|
13
|
+
require "guh/plugin"
|
14
|
+
require "guh/rule"
|
15
|
+
require "guh/vendor"
|
16
|
+
|
17
|
+
module Guh
|
18
|
+
# Your code goes here...
|
19
|
+
|
20
|
+
class ConnectionError < Exception; end
|
21
|
+
class ResponseError < Exception; end
|
22
|
+
class ArgumentError < Exception; end
|
23
|
+
|
24
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec'
|
3
|
+
|
4
|
+
require 'guh'
|
5
|
+
|
6
|
+
puts "\n\n---------------------------------------------------"
|
7
|
+
puts "Please make sure that Guh is running"
|
8
|
+
puts "---------------------------------------------------\n\n"
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.before(:all) do
|
12
|
+
Guh::Base.configure do |c|
|
13
|
+
c.guh_ip_address = "127.0.0.1"
|
14
|
+
c.guh_port = 1234
|
15
|
+
end
|
16
|
+
|
17
|
+
# Remove all devices & rules from Guh Core
|
18
|
+
purge_configuration()
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def purge_devices
|
23
|
+
path = File.expand_path('~/.config/guh/guh.conf')
|
24
|
+
File.unlink(path) if File.exist?(path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def purge_rules
|
28
|
+
path = File.expand_path('~/.config/guh/rules.conf')
|
29
|
+
File.unlink(path) if File.exist?(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def purge_configuration
|
33
|
+
purge_devices()
|
34
|
+
purge_rules()
|
35
|
+
end
|
36
|
+
|
37
|
+
def pj(data)
|
38
|
+
puts ""
|
39
|
+
puts "-"*15
|
40
|
+
puts data.inspect
|
41
|
+
puts "-"*15
|
42
|
+
puts ""
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_configured_device(device_class_id, params)
|
46
|
+
# Create a device
|
47
|
+
device_id = Guh::Device.add(device_class_id, params)
|
48
|
+
|
49
|
+
# Get the newly configured device
|
50
|
+
return Guh::Device.all.detect{|d| d['id']==device_id}
|
51
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Guh::Action do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
# Elro Switch
|
7
|
+
device_class_id = "{308ae6e6-38b3-4b3a-a513-3199da2764f8}"
|
8
|
+
|
9
|
+
@device = create_configured_device(device_class_id, {
|
10
|
+
channel1: true,
|
11
|
+
channel2: false,
|
12
|
+
channel3: false,
|
13
|
+
channel4: false,
|
14
|
+
channel5: false,
|
15
|
+
channel6: false,
|
16
|
+
channel7: false,
|
17
|
+
channel8: false,
|
18
|
+
channel9: false,
|
19
|
+
channel10: false
|
20
|
+
})
|
21
|
+
|
22
|
+
# Get all the possible actions for the device
|
23
|
+
actions = Guh::ActionType.all(device_class_id)
|
24
|
+
|
25
|
+
# Just use the first action
|
26
|
+
@action = actions.first
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should execute a single action" do
|
30
|
+
-> {
|
31
|
+
response = Guh::Action.execute(@device['id'], @action['id'], {power: true})
|
32
|
+
}.should_not raise_error
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should fail if the wrong params are provided' do
|
36
|
+
|
37
|
+
-> {
|
38
|
+
response = Guh::Action.execute(@device['id'], @action['id'], {})
|
39
|
+
}.should raise_error
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Guh::Device do
|
4
|
+
|
5
|
+
it 'should raise an exception if Guh Core is not running' do
|
6
|
+
Guh::Base.configure do |c|
|
7
|
+
c.guh_ip_address = "0.0.0.0"
|
8
|
+
c.guh_port = 7890 # <---- wrong port to provoke excpetion
|
9
|
+
end
|
10
|
+
|
11
|
+
expect { Guh::Base.introspect }.to raise_error(Errno::ECONNREFUSED)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should raise an exception if Guh Core does not respond with success on connect' do
|
15
|
+
pending("TODO: Find a way to provoke this.")
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Guh::DeviceClass do
|
4
|
+
|
5
|
+
it "should get the supported devices" do
|
6
|
+
response = Guh::DeviceClass.all
|
7
|
+
|
8
|
+
response.should be_an_instance_of(Array)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should find a specific DeviceClass" do
|
12
|
+
device_class_id = "{ab73ad2f-6594-45a3-9063-8f72d365c5e5}"
|
13
|
+
|
14
|
+
-> {
|
15
|
+
Guh::DeviceClass.find("bogus")
|
16
|
+
}.should raise_error
|
17
|
+
|
18
|
+
-> {
|
19
|
+
device_class = Guh::DeviceClass.find(device_class_id)
|
20
|
+
device_class['id'].should eq(device_class_id)
|
21
|
+
}.should_not raise_error
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should get the supported devices of a specific vendor" do
|
25
|
+
response = Guh::Vendor.all
|
26
|
+
|
27
|
+
vendor_id = response.first['id']
|
28
|
+
|
29
|
+
devices = Guh::DeviceClass.all(vendor_id: vendor_id)
|
30
|
+
|
31
|
+
if devices.length > 0
|
32
|
+
devices.first['vendorId'].should eq(vendor_id)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should discover the openweathermap" do
|
37
|
+
|
38
|
+
device_class_id = "{985195aa-17ad-4530-88a4-cdd753d747d7}"
|
39
|
+
|
40
|
+
# pending "TODO wait until issue #13 is fixed"
|
41
|
+
|
42
|
+
device_descriptors = Guh::DeviceClass.discover(device_class_id, location: '')
|
43
|
+
|
44
|
+
puts "--"
|
45
|
+
puts device_descriptors.inspect
|
46
|
+
puts "--"
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Guh::Device do
|
4
|
+
|
5
|
+
it "should get the configured device" do
|
6
|
+
response = Guh::Device.all
|
7
|
+
|
8
|
+
response.should be_an_instance_of(Array)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should tell us the number of configured devices" do
|
12
|
+
count = Guh::Device.count
|
13
|
+
|
14
|
+
count.should be_a(Integer)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should let us configure a device" do
|
18
|
+
configured_count = Guh::Device.count
|
19
|
+
|
20
|
+
-> {
|
21
|
+
response = Guh::Device.add("{308ae6e6-38b3-4b3a-a513-3199da2764f8}", {
|
22
|
+
channel1: true,
|
23
|
+
channel2: false,
|
24
|
+
channel3: false,
|
25
|
+
channel4: false,
|
26
|
+
channel5: false,
|
27
|
+
channel6: false,
|
28
|
+
channel7: false,
|
29
|
+
channel8: false,
|
30
|
+
channel9: false,
|
31
|
+
channel10: false
|
32
|
+
})
|
33
|
+
}.should_not raise_error
|
34
|
+
|
35
|
+
Guh::Device.count.should eq(configured_count+1)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should fail if we omit the params" do
|
39
|
+
configured_count = Guh::Device.count
|
40
|
+
|
41
|
+
-> {
|
42
|
+
response = Guh::Device.add("{308ae6e6-38b3-4b3a-a513-3199da2764f8}", {})
|
43
|
+
}.should raise_error
|
44
|
+
|
45
|
+
Guh::Device.count.should eq(configured_count)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should create a new device, return its ID and remove it successfully" do
|
49
|
+
device_id = Guh::Device.add("{ab73ad2f-6594-45a3-9063-8f72d365c5e5}", {familyCode: 'A'})
|
50
|
+
|
51
|
+
device_id.should match(/^\{[a-z0-9\-]+\}$/i)
|
52
|
+
|
53
|
+
Guh::Device.remove(device_id).should be_true
|
54
|
+
|
55
|
+
Guh::Device.find(device_id).should be_nil
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should raise an error if we try to delete a non-existing device" do
|
59
|
+
-> {
|
60
|
+
Guh::Device.remove("abc").should
|
61
|
+
}.should raise_error
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Guh::EventType do
|
4
|
+
|
5
|
+
it "should get something back" do
|
6
|
+
# Elro Switch
|
7
|
+
device_class_id = "{308ae6e6-38b3-4b3a-a513-3199da2764f8}"
|
8
|
+
|
9
|
+
# Get all the possible events for the device
|
10
|
+
response = Guh::EventType.all(device_class_id)
|
11
|
+
|
12
|
+
response.should be_an_instance_of(Array)
|
13
|
+
|
14
|
+
pending "Right now we get empty responses for all events"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Guh::Plugin do
|
4
|
+
|
5
|
+
it "should return a list of all loaded plugins" do
|
6
|
+
response = Guh::Plugin.all
|
7
|
+
|
8
|
+
response.should be_an_instance_of(Array)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should add params to the Plugin config" do
|
12
|
+
pending("Implement after difference between PluginConfig & PluginParams is clear")
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Guh::Rule do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
# Intertechno Remote
|
7
|
+
@sender = create_configured_device("{ab73ad2f-6594-45a3-9063-8f72d365c5e5}", {
|
8
|
+
familyCode: 'A'
|
9
|
+
})
|
10
|
+
|
11
|
+
# Intertechno Switch
|
12
|
+
@receiver = create_configured_device("{324219e8-7c53-41b5-b314-c2900cd15252}", {
|
13
|
+
familyCode: 'A',
|
14
|
+
buttonCode: 1
|
15
|
+
})
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return a list of all rules" do
|
20
|
+
response = Guh::Rule.all
|
21
|
+
|
22
|
+
response.should be_an_instance_of(Array)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should create a new rule" do
|
26
|
+
senderDeviceClass = Guh::DeviceClass.all.detect{|d| d['id']==@sender['deviceClassId']}
|
27
|
+
eventId = senderDeviceClass['events'].first['id']
|
28
|
+
|
29
|
+
actionId = Guh::ActionType.all(@receiver['deviceClassId']).first['id']
|
30
|
+
|
31
|
+
event = {
|
32
|
+
eventTypeId: eventId,
|
33
|
+
deviceId: @sender['id'],
|
34
|
+
params: {inRange: true}
|
35
|
+
}
|
36
|
+
|
37
|
+
action = {
|
38
|
+
actionTypeId: actionId,
|
39
|
+
deviceId: @receiver['id'],
|
40
|
+
params: {
|
41
|
+
power: true
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
-> {
|
46
|
+
# response = Guh::Rule.add(event, action)
|
47
|
+
}.should_not raise_error
|
48
|
+
|
49
|
+
pending "TODO How do we know we were successful?"
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Guh::Vendor do
|
4
|
+
|
5
|
+
it "should return a list of available vendors" do
|
6
|
+
|
7
|
+
response = Guh::Vendor.all
|
8
|
+
|
9
|
+
response.length.should be > 0
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return information about a specific vendor" do
|
14
|
+
pending "TODO: Implement Guh::Vendor.find('{abc}')"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: guh
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christoph Edthofer
|
8
|
+
- Thomas Esterer
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-05-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ~>
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.8'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.8'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: bundler
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '1.5'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '1.5'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rake
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ~>
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '10.3'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '10.3'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rspec
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '2.14'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '2.14'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: yard
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.8'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ~>
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0.8'
|
84
|
+
description: The official Ruby wrapper for guh. guh is an open source home automation
|
85
|
+
system.
|
86
|
+
email:
|
87
|
+
- christoph.edthofer@guh.guru
|
88
|
+
- thomas.esterer@guh.guru
|
89
|
+
executables: []
|
90
|
+
extensions: []
|
91
|
+
extra_rdoc_files: []
|
92
|
+
files:
|
93
|
+
- .gitignore
|
94
|
+
- Gemfile
|
95
|
+
- Guardfile
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- guh.gemspec
|
100
|
+
- lib/guh.rb
|
101
|
+
- lib/guh/action.rb
|
102
|
+
- lib/guh/action_type.rb
|
103
|
+
- lib/guh/base.rb
|
104
|
+
- lib/guh/device.rb
|
105
|
+
- lib/guh/device_class.rb
|
106
|
+
- lib/guh/event.rb
|
107
|
+
- lib/guh/event_type.rb
|
108
|
+
- lib/guh/plugin.rb
|
109
|
+
- lib/guh/rule.rb
|
110
|
+
- lib/guh/vendor.rb
|
111
|
+
- lib/guh/version.rb
|
112
|
+
- spec/helper.rb
|
113
|
+
- spec/integration/action_spec.rb
|
114
|
+
- spec/integration/action_type_spec.rb
|
115
|
+
- spec/integration/base_spec.rb
|
116
|
+
- spec/integration/device_class_spec.rb
|
117
|
+
- spec/integration/device_spec.rb
|
118
|
+
- spec/integration/event_type_spec.rb
|
119
|
+
- spec/integration/plugin_spec.rb
|
120
|
+
- spec/integration/rule_spec.rb
|
121
|
+
- spec/integration/vendor_spec.rb
|
122
|
+
homepage: http://guh.guru
|
123
|
+
licenses:
|
124
|
+
- MIT
|
125
|
+
metadata: {}
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - '>='
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - '>='
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 2.2.2
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: The official Ruby wrapper for guh.
|
146
|
+
test_files:
|
147
|
+
- spec/helper.rb
|
148
|
+
- spec/integration/action_spec.rb
|
149
|
+
- spec/integration/action_type_spec.rb
|
150
|
+
- spec/integration/base_spec.rb
|
151
|
+
- spec/integration/device_class_spec.rb
|
152
|
+
- spec/integration/device_spec.rb
|
153
|
+
- spec/integration/event_type_spec.rb
|
154
|
+
- spec/integration/plugin_spec.rb
|
155
|
+
- spec/integration/rule_spec.rb
|
156
|
+
- spec/integration/vendor_spec.rb
|
157
|
+
has_rdoc:
|