lita-rally 0.1.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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +48 -0
- data/Rakefile +6 -0
- data/lib/lita/handlers/rally.rb +259 -0
- data/lib/lita-rally.rb +7 -0
- data/lita-rally.gemspec +25 -0
- data/locales/en.yml +4 -0
- data/spec/lita/handlers/rally_spec.rb +4 -0
- data/spec/spec_helper.rb +6 -0
- metadata +156 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 69fbe9f6b8d8878c919cfe737a9aa5424c8cfeaf
|
4
|
+
data.tar.gz: 5b5a75657b23de9bdaf7ca122057e500538d9b52
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e60d126c94f11b2993501ca3997e1d885fa5ddc6d725edbf454ece01429a03085aec9fb04b4cb413afd18c12492aa1fdc9378ae551c26d92c972f397fdc65a5a
|
7
|
+
data.tar.gz: a38d3ccbeab0af552bd044bdf54c5539498487dad7bc74faa9c11d6139fd5aa0a20dd2da9ed44fe6aa01a8fd75633050fc6faafb420252f50a744a3be718d008
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2015 Richard Li
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# lita-rally
|
2
|
+
|
3
|
+
A port of
|
4
|
+
[Hubot-Rally](https://github.com/github/hubot-scripts/blob/master/src/scripts/rally.coffee)
|
5
|
+
with some enhancements.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add lita-rally to your Lita instance's Gemfile:
|
10
|
+
|
11
|
+
``` ruby
|
12
|
+
gem "lita-rally"
|
13
|
+
```
|
14
|
+
|
15
|
+
|
16
|
+
## Configuration
|
17
|
+
|
18
|
+
**Required:**
|
19
|
+
|
20
|
+
```config.handlers.rally.username``` - Rally username
|
21
|
+
|
22
|
+
```config.handlers.rally.password``` - Rally password
|
23
|
+
|
24
|
+
**Optional:**
|
25
|
+
|
26
|
+
```config.handlers.rally.api_version``` - API version, default 'v2.0'
|
27
|
+
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
```
|
32
|
+
lita rally me <FormattedID>
|
33
|
+
```
|
34
|
+
Show information about Rally object identified by FormattedID.
|
35
|
+
|
36
|
+
```
|
37
|
+
lita rally me release stats for <release_name>
|
38
|
+
```
|
39
|
+
Show defect and user story count for the release <release_name>
|
40
|
+
|
41
|
+
```
|
42
|
+
lita rally me release info for <release_name>
|
43
|
+
```
|
44
|
+
Show object IDs (defects, user story, etc.) for release <release_name>.
|
45
|
+
|
46
|
+
## License
|
47
|
+
|
48
|
+
[MIT](http://opensource.org/licenses/MIT)
|
data/Rakefile
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Lita
|
5
|
+
module Handlers
|
6
|
+
class Rally < Handler
|
7
|
+
@@key_map = {
|
8
|
+
'de' => {
|
9
|
+
name: 'defect',
|
10
|
+
extra_output: [
|
11
|
+
'State',
|
12
|
+
'ScheduleState',
|
13
|
+
'Severity',
|
14
|
+
['Release', '_refObjectName'],
|
15
|
+
],
|
16
|
+
},
|
17
|
+
'ds' => {
|
18
|
+
name: 'defectsuite',
|
19
|
+
extra_output: [
|
20
|
+
'ScheduleState',
|
21
|
+
]
|
22
|
+
},
|
23
|
+
'f' => {
|
24
|
+
name: 'feature',
|
25
|
+
query_path: 'portfolioitem/feature',
|
26
|
+
extra_output: [
|
27
|
+
['State', '_refObjectName'],
|
28
|
+
['Parent', '_refObjectName'],
|
29
|
+
]
|
30
|
+
},
|
31
|
+
'i' => {
|
32
|
+
name: 'initiative',
|
33
|
+
query_path: 'portfolioitem/initiative',
|
34
|
+
extra_output: [
|
35
|
+
['State', '_refObjectName'],
|
36
|
+
['Parent', '_refObjectName'],
|
37
|
+
]
|
38
|
+
},
|
39
|
+
't' => {
|
40
|
+
name: 'theme',
|
41
|
+
query_path: 'portfolioitem/theme',
|
42
|
+
extra_output: [
|
43
|
+
['State', '_refObjectName'],
|
44
|
+
['Parent', '_refObjectName'],
|
45
|
+
]
|
46
|
+
},
|
47
|
+
'ta' => {
|
48
|
+
name: 'task',
|
49
|
+
extra_output: [
|
50
|
+
'State',
|
51
|
+
['WorkProduct', '_refObjectName'],
|
52
|
+
]
|
53
|
+
},
|
54
|
+
'tc' => {
|
55
|
+
name: 'testcase',
|
56
|
+
extra_output: [
|
57
|
+
['WorkProduct', '_refObjectName'],
|
58
|
+
'Type',
|
59
|
+
]
|
60
|
+
},
|
61
|
+
'us' => {
|
62
|
+
name: 'story',
|
63
|
+
query_path: 'hierarchicalrequirement',
|
64
|
+
link_path: 'userstory',
|
65
|
+
extra_output: [
|
66
|
+
'ScheduleState',
|
67
|
+
['Parent', '_refObjectName'],
|
68
|
+
['Feature', '_refObjectName'],
|
69
|
+
]
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
config :username
|
74
|
+
config :password
|
75
|
+
config :api_version
|
76
|
+
|
77
|
+
route(/^rally me ([[:alpha:]]+)(\d+)/, :rally_show, command: true, help: {
|
78
|
+
'rally me <identifier>' => 'Show me that rally object'
|
79
|
+
})
|
80
|
+
|
81
|
+
route(/^rally me release stats for (.+)/, :rally_release_stats,
|
82
|
+
command: true, help: {
|
83
|
+
'rally me release count for <release>' => 'Count US and DE for release'
|
84
|
+
})
|
85
|
+
|
86
|
+
route(/^rally me release info for (.+)/, :rally_release_info,
|
87
|
+
command: true, help: {
|
88
|
+
'rally me release info for <release>' => 'Show release info'
|
89
|
+
})
|
90
|
+
|
91
|
+
def rally_show(response)
|
92
|
+
if config_validate
|
93
|
+
get_rally_object(response)
|
94
|
+
else
|
95
|
+
response.reply('Configuration failed to validate, '\
|
96
|
+
'check your config')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def rally_release_stats(response)
|
101
|
+
if config_validate
|
102
|
+
get_rally_release_stat(response)
|
103
|
+
else
|
104
|
+
response.reply('Configuration failed to validate, '\
|
105
|
+
'check your config')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def rally_release_info(response)
|
110
|
+
if config_validate
|
111
|
+
get_rally_release_info(response)
|
112
|
+
else
|
113
|
+
response.reply('Configuration failed to validate, '\
|
114
|
+
'check your config')
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def config_validate
|
121
|
+
(config.username && config.password) ? true : false
|
122
|
+
end
|
123
|
+
|
124
|
+
def get_rest
|
125
|
+
api_version = config.api_version ? config.api_version : 'v2.0'
|
126
|
+
RestClient::Resource.new(
|
127
|
+
"https://rally1.rallydev.com/slm/webservice/#{api_version}/",
|
128
|
+
user: config.username,
|
129
|
+
password: config.password,
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
def validate_release(rest, release)
|
134
|
+
query = "(Name = \"#{release}\")"
|
135
|
+
JSON.parse(
|
136
|
+
rest['release'].get(params: {query: query})
|
137
|
+
)['QueryResult']['TotalResultCount'] == 0
|
138
|
+
end
|
139
|
+
|
140
|
+
def get_rally_release_info(response)
|
141
|
+
rest = get_rest
|
142
|
+
release = response.matches[0][0]
|
143
|
+
if validate_release(rest, release)
|
144
|
+
response.reply("I can find anything about release: '#{release}'!")
|
145
|
+
else
|
146
|
+
query = "(Release.Name = \"#{release}\")"
|
147
|
+
defects = JSON.parse(
|
148
|
+
rest['defect'].get(
|
149
|
+
params: {
|
150
|
+
query: query,
|
151
|
+
fetch: 'true',
|
152
|
+
pagesize: '200',
|
153
|
+
}
|
154
|
+
)
|
155
|
+
)['QueryResult']['Results'].map {|r| r['FormattedID']}.join(' ')
|
156
|
+
|
157
|
+
us = JSON.parse(
|
158
|
+
rest['hierarchicalrequirement'].get(
|
159
|
+
params: {
|
160
|
+
query: query,
|
161
|
+
fetch: 'true',
|
162
|
+
pagesize: '200',
|
163
|
+
}
|
164
|
+
)
|
165
|
+
)['QueryResult']['Results'].map {|r| r['FormattedID']}.join(' ')
|
166
|
+
|
167
|
+
output = "Release info for: #{release}\n" \
|
168
|
+
"Defects: #{defects}\n" \
|
169
|
+
"User Stories: #{us}\n"
|
170
|
+
response.reply(output)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def get_rally_release_stat(response)
|
175
|
+
rest = get_rest
|
176
|
+
release = response.matches[0][0]
|
177
|
+
if validate_release(rest, release)
|
178
|
+
response.reply("I can find anything about release: '#{release}'!")
|
179
|
+
else
|
180
|
+
query = "(Release.Name = \"#{release}\")"
|
181
|
+
de_result = JSON.parse(
|
182
|
+
rest['defect'].get(params: {query: query})
|
183
|
+
)['QueryResult']
|
184
|
+
de_count = de_result['TotalResultCount']
|
185
|
+
|
186
|
+
us_result = JSON.parse(
|
187
|
+
rest['hierarchicalrequirement'].get(params: {query: query})
|
188
|
+
)['QueryResult']
|
189
|
+
us_count = us_result['TotalResultCount']
|
190
|
+
|
191
|
+
output = "Release stats for: #{release}\n" \
|
192
|
+
"Defects count: #{de_count}\n" \
|
193
|
+
"User Story count: #{us_count}\n"
|
194
|
+
response.reply(output)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def get_rally_object(response)
|
199
|
+
rest = get_rest
|
200
|
+
type = response.matches[0][0].downcase
|
201
|
+
id = response.matches[0][1]
|
202
|
+
if @@key_map[type]
|
203
|
+
query_path = (@@key_map[type][:query_path] || @@key_map[type][:name])
|
204
|
+
link_path = (@@key_map[type][:link_path] || query_path)
|
205
|
+
query_result = JSON.parse(
|
206
|
+
rest[query_path].get(
|
207
|
+
params: {
|
208
|
+
query: "(FormattedId = #{id})",
|
209
|
+
fetch: 'true'
|
210
|
+
}
|
211
|
+
)
|
212
|
+
)['QueryResult']
|
213
|
+
if query_result['TotalResultCount'] == 0
|
214
|
+
response.reply("Can't find your so called #{type}#{id} in Rally!")
|
215
|
+
else
|
216
|
+
result = query_result['Results'][0]
|
217
|
+
link = link_to_item(result, link_path)
|
218
|
+
output = ''
|
219
|
+
output += link + "\n" if link
|
220
|
+
output += "#{result['FormattedID']} - #{result['Name']}\n" \
|
221
|
+
"Owner: #{result['Owner']['_refObjectName']}\n" \
|
222
|
+
"Project: #{result['Project']['_refObjectName']}\n"
|
223
|
+
@@key_map[type][:extra_output].each do |field|
|
224
|
+
output += "#{field}: #{result[field]}\n" if
|
225
|
+
field.is_a?(String) && result[field]
|
226
|
+
output += "#{field[0]}: #{result[field[0]][field[1]]}\n" if
|
227
|
+
field.is_a?(Array) && result[field[0]]
|
228
|
+
end
|
229
|
+
output += "Description: #{strip_html(result['Description'])}\n"
|
230
|
+
response.reply(output)
|
231
|
+
end
|
232
|
+
else
|
233
|
+
response.reply("I don't know the type #{type}")
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def link_to_item(result, type)
|
238
|
+
return false unless result['Project'] && result['ObjectID']
|
239
|
+
project_id = result['Project']['_ref'].split('/')[-1]
|
240
|
+
object_id = result['ObjectID']
|
241
|
+
"https://rally1.rallydev.com/#/#{project_id}/detail/#{type}/#{object_id}"
|
242
|
+
end
|
243
|
+
|
244
|
+
def strip_html(text)
|
245
|
+
text.gsub( '<br />', "\n\n"
|
246
|
+
).gsub(' ', ' '
|
247
|
+
).gsub('>', '>'
|
248
|
+
).gsub('<', '<'
|
249
|
+
).gsub('<div>', ''
|
250
|
+
).gsub('</div>', "\n"
|
251
|
+
).gsub(/<style.+\/style>/, ''
|
252
|
+
)
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
Lita.register_handler(Rally)
|
258
|
+
end
|
259
|
+
end
|
data/lib/lita-rally.rb
ADDED
data/lita-rally.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "lita-rally"
|
3
|
+
spec.version = "0.1.0"
|
4
|
+
spec.authors = ['Richard Li']
|
5
|
+
spec.email = ['evilcat@wisewolfsolutions.com']
|
6
|
+
spec.description = %q{lita bot Rally plugin}
|
7
|
+
spec.summary = %q{lita bot Rally plugin}
|
8
|
+
spec.homepage = "https://github.com/ecwws/lita-rally"
|
9
|
+
spec.license = "MIT"
|
10
|
+
spec.metadata = { "lita_plugin_type" => "handler" }
|
11
|
+
|
12
|
+
spec.files = `git ls-files`.split($/)
|
13
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
14
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
15
|
+
spec.require_paths = ["lib"]
|
16
|
+
|
17
|
+
spec.add_runtime_dependency "lita", ">= 4.1"
|
18
|
+
spec.add_runtime_dependency "rest-client"
|
19
|
+
spec.add_runtime_dependency "json"
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rack-test"
|
24
|
+
spec.add_development_dependency "rspec", ">= 3.0.0"
|
25
|
+
end
|
data/locales/en.yml
ADDED
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lita-rally
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Li
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: lita
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rest-client
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: json
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rack-test
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 3.0.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 3.0.0
|
111
|
+
description: lita bot Rally plugin
|
112
|
+
email:
|
113
|
+
- evilcat@wisewolfsolutions.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- Gemfile
|
120
|
+
- LICENSE
|
121
|
+
- README.md
|
122
|
+
- Rakefile
|
123
|
+
- lib/lita-rally.rb
|
124
|
+
- lib/lita/handlers/rally.rb
|
125
|
+
- lita-rally.gemspec
|
126
|
+
- locales/en.yml
|
127
|
+
- spec/lita/handlers/rally_spec.rb
|
128
|
+
- spec/spec_helper.rb
|
129
|
+
homepage: https://github.com/ecwws/lita-rally
|
130
|
+
licenses:
|
131
|
+
- MIT
|
132
|
+
metadata:
|
133
|
+
lita_plugin_type: handler
|
134
|
+
post_install_message:
|
135
|
+
rdoc_options: []
|
136
|
+
require_paths:
|
137
|
+
- lib
|
138
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
requirements: []
|
149
|
+
rubyforge_project:
|
150
|
+
rubygems_version: 2.2.2
|
151
|
+
signing_key:
|
152
|
+
specification_version: 4
|
153
|
+
summary: lita bot Rally plugin
|
154
|
+
test_files:
|
155
|
+
- spec/lita/handlers/rally_spec.rb
|
156
|
+
- spec/spec_helper.rb
|