bender-bot 0.2.4 → 0.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/VERSION +1 -1
- data/lib/bender/bot.rb +18 -143
- data/lib/bender/helpers.rb +136 -0
- data/lib/bender/main.rb +96 -10
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50e516ad8b6f747483302fc79ab811a8cccf2368
|
4
|
+
data.tar.gz: bd931995c92e814c96bbcea6f305534c2941cede
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b43003dffdf9a21bb11877116e287e04b80a84618a3d2743b7a83a030025d7c0303d6842a5d478c048c49680e9a0955576de7497ae0275e08baa26540701abe5
|
7
|
+
data.tar.gz: 23de30730b6a02d26c1e71603966bb6811ef7ef4a7037c978f4dc69e75e6d3afe4abccafdd8d4eae6c9afd0d40eae674d3a47f470e52a8cffc56d14e1d9c07e9
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/bender/bot.rb
CHANGED
@@ -8,6 +8,8 @@ require 'robut/storage/yaml_store'
|
|
8
8
|
require 'fuzzystringmatch'
|
9
9
|
require 'queryparams'
|
10
10
|
|
11
|
+
require_relative 'helpers'
|
12
|
+
|
11
13
|
Bot = Robut # alias
|
12
14
|
|
13
15
|
|
@@ -29,59 +31,10 @@ end
|
|
29
31
|
|
30
32
|
class BenderBot
|
31
33
|
include Bot::Plugin
|
34
|
+
include Helpers
|
32
35
|
|
33
36
|
JARO = FuzzyStringMatch::JaroWinkler.create :native
|
34
37
|
|
35
|
-
RESOLVED_TRANSITIONS = %w[ 51 ]
|
36
|
-
|
37
|
-
RESOLVED_STATE = /resolve/i
|
38
|
-
|
39
|
-
CLOSED_TRANSITIONS = %w[ 61 71 ]
|
40
|
-
|
41
|
-
CLOSED_STATE = /close/i
|
42
|
-
|
43
|
-
SEVERITIES = {
|
44
|
-
1 => '10480',
|
45
|
-
2 => '10481',
|
46
|
-
3 => '10482',
|
47
|
-
4 => '10483',
|
48
|
-
5 => '10484'
|
49
|
-
}
|
50
|
-
|
51
|
-
SHOW_FIELDS = {
|
52
|
-
'summary' => 'Summary',
|
53
|
-
'description' => 'Description',
|
54
|
-
'customfield_11250' => 'Severity',
|
55
|
-
'customfield_11251' => 'Impact Started',
|
56
|
-
'customfield_11252' => 'Impact Ended',
|
57
|
-
'customfield_11253' => 'Reported By',
|
58
|
-
'customfield_11254' => 'Services Affected',
|
59
|
-
'customfield_11255' => 'Cause',
|
60
|
-
'status' => 'Status',
|
61
|
-
'created' => 'Created',
|
62
|
-
'updated' => 'Updated'
|
63
|
-
}
|
64
|
-
|
65
|
-
|
66
|
-
QUOTES = [
|
67
|
-
'Bite my shiny metal ass!',
|
68
|
-
'This is the worst kind of discrimination there is: the kind against me!',
|
69
|
-
'I guess if you want children beaten, you have to do it yourself.',
|
70
|
-
"Hahahahaha! Oh wait you're serious. Let me laugh even harder.",
|
71
|
-
"You know what cheers me up? Other people's misfortune.",
|
72
|
-
'Anything less than immortality is a complete waste of time.',
|
73
|
-
"Blackmail is such an ugly word. I prefer extortion. The 'x' makes it sound cool.",
|
74
|
-
'Have you tried turning off the TV, sitting down with your children, and hitting them?',
|
75
|
-
"You're a pimple on society’s ass and you'll never amount to anything!",
|
76
|
-
'Shut up baby, I know it!',
|
77
|
-
"I'm so embarrassed. I wish everyone else was dead!",
|
78
|
-
"Afterlife? If I thought I had to live another life, I'd kill myself right now!",
|
79
|
-
"I'm back baby!",
|
80
|
-
"LET'S GO ALREADYYYYYY!",
|
81
|
-
"I don't have emotions and sometimes that makes me very sad",
|
82
|
-
"Life is hilariously cruel"
|
83
|
-
]
|
84
|
-
|
85
38
|
COMMANDS = {
|
86
39
|
help: {
|
87
40
|
desc: 'Display this help text',
|
@@ -134,7 +87,14 @@ class BenderBot
|
|
134
87
|
|
135
88
|
|
136
89
|
def handle room, sender, message
|
137
|
-
|
90
|
+
begin
|
91
|
+
@room_name = @@rooms.select { |r| r.xmpp_jid == room }.first.name
|
92
|
+
@@hipchat[@room_name].get_room # test
|
93
|
+
rescue
|
94
|
+
@@hipchat = HipChat::Client.new(@@options.hipchat_token)
|
95
|
+
@@rooms = @@hipchat.rooms
|
96
|
+
@room_name = @@rooms.select { |r| r.xmpp_jid == room }.first.name
|
97
|
+
end
|
138
98
|
@room = room
|
139
99
|
@sender = sender
|
140
100
|
@message = message
|
@@ -142,13 +102,6 @@ class BenderBot
|
|
142
102
|
severity_field = SHOW_FIELDS.key 'Severity'
|
143
103
|
severities = Hash.new { |h,k| h[k] = [] }
|
144
104
|
|
145
|
-
if message =~ /\/inc\s+(\w+)?\s*/
|
146
|
-
unless COMMANDS.include? $1.to_sym
|
147
|
-
reply_html 'Invalid usage', :red
|
148
|
-
reply_with_help
|
149
|
-
return
|
150
|
-
end
|
151
|
-
end
|
152
105
|
|
153
106
|
case message
|
154
107
|
|
@@ -175,7 +128,7 @@ class BenderBot
|
|
175
128
|
|
176
129
|
is = store['incidents'].reverse.map do |i|
|
177
130
|
status = normalize_value i['fields']['status']
|
178
|
-
unless status =~ /
|
131
|
+
unless status =~ /resolved|closed/i
|
179
132
|
'%s (%s - %s) [%s]: %s' % [
|
180
133
|
incident_link(i),
|
181
134
|
short_severity(i['fields'][severity_field]['value']),
|
@@ -230,7 +183,7 @@ class BenderBot
|
|
230
183
|
end
|
231
184
|
|
232
185
|
if severities.empty?
|
233
|
-
reply_html 'No
|
186
|
+
reply_html 'Good news everyone! No open incidents at the moment', :green
|
234
187
|
|
235
188
|
else
|
236
189
|
is = severities.keys.sort.map do |sev|
|
@@ -313,6 +266,11 @@ class BenderBot
|
|
313
266
|
else
|
314
267
|
reply_html 'Sorry, no such incident!', :red
|
315
268
|
end
|
269
|
+
|
270
|
+
|
271
|
+
when /^\s*\/inc/i
|
272
|
+
reply_html 'Invalid usage', :red
|
273
|
+
reply_with_help
|
316
274
|
end
|
317
275
|
|
318
276
|
|
@@ -346,31 +304,6 @@ private
|
|
346
304
|
|
347
305
|
|
348
306
|
|
349
|
-
def refresh_incidents
|
350
|
-
req_path = '/rest/api/2/search'
|
351
|
-
req_params = QueryParams.encode \
|
352
|
-
jql: "project = #{options.jira_project} ORDER BY created ASC, priority DESC",
|
353
|
-
fields: SHOW_FIELDS.keys.join(','),
|
354
|
-
startAt: 0,
|
355
|
-
maxResults: 1_000_000
|
356
|
-
|
357
|
-
uri = URI(options.jira_site + req_path + '?' + req_params)
|
358
|
-
http = Net::HTTP.new uri.hostname, uri.port
|
359
|
-
|
360
|
-
req = Net::HTTP::Get.new uri
|
361
|
-
req.basic_auth options.jira_user, options.jira_pass
|
362
|
-
req['Content-Type'] = 'application/json'
|
363
|
-
req['Accept'] = 'application/json'
|
364
|
-
|
365
|
-
resp = http.request req
|
366
|
-
issues = JSON.parse(resp.body)['issues']
|
367
|
-
|
368
|
-
store['incidents'] = issues.map! do |i|
|
369
|
-
i['num'] = i['key'].split('-', 2).last ; i
|
370
|
-
end
|
371
|
-
end
|
372
|
-
|
373
|
-
|
374
307
|
def file_incident data
|
375
308
|
req_path = '/rest/api/2/issue'
|
376
309
|
uri = URI(options.jira_site + req_path)
|
@@ -499,62 +432,4 @@ private
|
|
499
432
|
end
|
500
433
|
end
|
501
434
|
|
502
|
-
|
503
|
-
def normalize_value val
|
504
|
-
case val
|
505
|
-
when Hash
|
506
|
-
val['name'] || val['value'] || val
|
507
|
-
when Array
|
508
|
-
val.map { |v| v['value'] }.join(', ')
|
509
|
-
when /^\d{4}\-\d{2}\-\d{2}/
|
510
|
-
'%s (%s)' % [ val, normalize_date(val) ]
|
511
|
-
else
|
512
|
-
val
|
513
|
-
end
|
514
|
-
end
|
515
|
-
|
516
|
-
|
517
|
-
def normalize_date val
|
518
|
-
Time.parse(val).utc.iso8601(0).sub(/Z$/, 'UTC')
|
519
|
-
end
|
520
|
-
|
521
|
-
|
522
|
-
def friendly_date val
|
523
|
-
Time.parse(val).strftime('%Y-%m-%d %H:%M %Z')
|
524
|
-
end
|
525
|
-
|
526
|
-
|
527
|
-
def recent_incident? i
|
528
|
-
it = Time.parse(i['fields']['created'])
|
529
|
-
Time.now - it < one_day
|
530
|
-
end
|
531
|
-
|
532
|
-
|
533
|
-
def one_day
|
534
|
-
24 * 60 * 60 # seconds/day
|
535
|
-
end
|
536
|
-
|
537
|
-
|
538
|
-
def select_incident num, refresh=true
|
539
|
-
refresh_incidents if refresh
|
540
|
-
store['incidents'].select { |i| i['num'] == num }.first
|
541
|
-
end
|
542
|
-
|
543
|
-
|
544
|
-
def short_severity s
|
545
|
-
s.split(' - ', 2).first
|
546
|
-
end
|
547
|
-
|
548
|
-
|
549
|
-
def incident_url incident
|
550
|
-
options.jira_site + '/browse/' + incident['key']
|
551
|
-
end
|
552
|
-
|
553
|
-
def incident_link incident
|
554
|
-
'<a href="%s">%s</a>' % [
|
555
|
-
incident_url(incident),
|
556
|
-
incident['key']
|
557
|
-
]
|
558
|
-
end
|
559
|
-
|
560
435
|
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module Helpers
|
2
|
+
|
3
|
+
RESOLVED_TRANSITIONS = %w[ 51 ]
|
4
|
+
|
5
|
+
RESOLVED_STATE = /resolve/i
|
6
|
+
|
7
|
+
CLOSED_TRANSITIONS = %w[ 61 71 ]
|
8
|
+
|
9
|
+
CLOSED_STATE = /close/i
|
10
|
+
|
11
|
+
SEVERITIES = {
|
12
|
+
1 => '10480',
|
13
|
+
2 => '10481',
|
14
|
+
3 => '10482',
|
15
|
+
4 => '10483',
|
16
|
+
5 => '10484'
|
17
|
+
}
|
18
|
+
|
19
|
+
SHOW_FIELDS = {
|
20
|
+
'summary' => 'Summary',
|
21
|
+
'description' => 'Description',
|
22
|
+
'customfield_11250' => 'Severity',
|
23
|
+
'customfield_11251' => 'Impact Started',
|
24
|
+
'customfield_11252' => 'Impact Ended',
|
25
|
+
'customfield_11253' => 'Reported By',
|
26
|
+
'customfield_11254' => 'Services Affected',
|
27
|
+
'customfield_11255' => 'Cause',
|
28
|
+
'status' => 'Status',
|
29
|
+
'created' => 'Created',
|
30
|
+
'updated' => 'Updated'
|
31
|
+
}
|
32
|
+
|
33
|
+
|
34
|
+
QUOTES = [
|
35
|
+
'Bite my shiny metal ass!',
|
36
|
+
'This is the worst kind of discrimination there is: the kind against me!',
|
37
|
+
'I guess if you want children beaten, you have to do it yourself.',
|
38
|
+
"Hahahahaha! Oh wait you're serious. Let me laugh even harder.",
|
39
|
+
"You know what cheers me up? Other people's misfortune.",
|
40
|
+
'Anything less than immortality is a complete waste of time.',
|
41
|
+
"Blackmail is such an ugly word. I prefer extortion. The 'x' makes it sound cool.",
|
42
|
+
'Have you tried turning off the TV, sitting down with your children, and hitting them?',
|
43
|
+
"You're a pimple on society’s ass and you'll never amount to anything!",
|
44
|
+
'Shut up baby, I know it!',
|
45
|
+
"I'm so embarrassed. I wish everyone else was dead!",
|
46
|
+
"Afterlife? If I thought I had to live another life, I'd kill myself right now!",
|
47
|
+
"I'm back baby!",
|
48
|
+
"LET'S GO ALREADYYYYYY!",
|
49
|
+
"I don't have emotions and sometimes that makes me very sad",
|
50
|
+
"Life is hilariously cruel"
|
51
|
+
]
|
52
|
+
|
53
|
+
|
54
|
+
def refresh_incidents bot=self
|
55
|
+
req_path = '/rest/api/2/search'
|
56
|
+
req_params = QueryParams.encode \
|
57
|
+
jql: "project = #{options.jira_project} ORDER BY created ASC, priority DESC",
|
58
|
+
fields: SHOW_FIELDS.keys.join(','),
|
59
|
+
startAt: 0,
|
60
|
+
maxResults: 1_000_000
|
61
|
+
|
62
|
+
uri = URI(options.jira_site + req_path + '?' + req_params)
|
63
|
+
http = Net::HTTP.new uri.hostname, uri.port
|
64
|
+
|
65
|
+
req = Net::HTTP::Get.new uri
|
66
|
+
req.basic_auth options.jira_user, options.jira_pass
|
67
|
+
req['Content-Type'] = 'application/json'
|
68
|
+
req['Accept'] = 'application/json'
|
69
|
+
|
70
|
+
resp = http.request req
|
71
|
+
issues = JSON.parse(resp.body)['issues']
|
72
|
+
|
73
|
+
bot.store['incidents'] = issues.map! do |i|
|
74
|
+
i['num'] = i['key'].split('-', 2).last ; i
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def normalize_value val
|
80
|
+
case val
|
81
|
+
when Hash
|
82
|
+
val['name'] || val['value'] || val
|
83
|
+
when Array
|
84
|
+
val.map { |v| v['value'] }.join(', ')
|
85
|
+
when /^\d{4}\-\d{2}\-\d{2}/
|
86
|
+
'%s (%s)' % [ val, normalize_date(val) ]
|
87
|
+
else
|
88
|
+
val
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def normalize_date val
|
94
|
+
Time.parse(val).utc.iso8601(0).sub(/Z$/, 'UTC')
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def friendly_date val
|
99
|
+
Time.parse(val).strftime('%Y-%m-%d %H:%M %Z')
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
def recent_incident? i
|
104
|
+
it = Time.parse(i['fields']['created'])
|
105
|
+
Time.now - it < one_day
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def one_day
|
110
|
+
24 * 60 * 60 # seconds/day
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def select_incident num, refresh=true
|
115
|
+
refresh_incidents if refresh
|
116
|
+
store['incidents'].select { |i| i['num'] == num }.first
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
def short_severity s
|
121
|
+
s.split(' - ', 2).first
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
def incident_url incident
|
126
|
+
options.jira_site + '/browse/' + incident['key']
|
127
|
+
end
|
128
|
+
|
129
|
+
def incident_link incident
|
130
|
+
'<a href="%s">%s</a>' % [
|
131
|
+
incident_url(incident),
|
132
|
+
incident['key']
|
133
|
+
]
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
data/lib/bender/main.rb
CHANGED
@@ -6,12 +6,16 @@ require 'queryparams'
|
|
6
6
|
|
7
7
|
require_relative 'metadata'
|
8
8
|
require_relative 'mjolnir'
|
9
|
+
require_relative 'helpers'
|
9
10
|
require_relative 'web'
|
10
11
|
require_relative 'bot'
|
11
12
|
|
13
|
+
Thread.abort_on_exception = true
|
14
|
+
|
12
15
|
|
13
16
|
module Bender
|
14
17
|
class Main < Mjolnir
|
18
|
+
include Helpers
|
15
19
|
|
16
20
|
desc 'version', 'Echo the application version'
|
17
21
|
def version
|
@@ -47,6 +51,16 @@ module Bender
|
|
47
51
|
aliases: %w[ -t ],
|
48
52
|
desc: 'Set HipChat v1 API token',
|
49
53
|
required: true
|
54
|
+
option :hipchat_v2_token, \
|
55
|
+
type: :string,
|
56
|
+
aliases: %w[ -c ],
|
57
|
+
desc: 'Set HipChat v2 API token',
|
58
|
+
required: true
|
59
|
+
option :primary_room_id, \
|
60
|
+
type: :string,
|
61
|
+
aliases: %w[ -i ],
|
62
|
+
desc: 'Set HipChat primary room ID',
|
63
|
+
required: true
|
50
64
|
option :jid, \
|
51
65
|
type: :string,
|
52
66
|
aliases: %w[ -j ],
|
@@ -77,11 +91,6 @@ module Bender
|
|
77
91
|
aliases: %w[ -d ],
|
78
92
|
desc: 'Set path to application database',
|
79
93
|
required: true
|
80
|
-
option :rooms, \
|
81
|
-
type: :string,
|
82
|
-
aliases: %w[ -r ],
|
83
|
-
desc: 'Set HipChat rooms (comma-separated)',
|
84
|
-
required: true
|
85
94
|
option :jira_user, \
|
86
95
|
type: :string,
|
87
96
|
aliases: %w[ -U ],
|
@@ -107,15 +116,21 @@ module Bender
|
|
107
116
|
aliases: %w[ -T ],
|
108
117
|
desc: 'Set JIRA issue type',
|
109
118
|
required: true
|
110
|
-
option :
|
119
|
+
option :user_refresh, \
|
111
120
|
type: :numeric,
|
112
121
|
aliases: %w[ -R ],
|
113
|
-
desc: 'Set JIRA refresh rate',
|
122
|
+
desc: 'Set JIRA user refresh rate',
|
114
123
|
default: 300
|
124
|
+
option :issue_refresh, \
|
125
|
+
type: :numeric,
|
126
|
+
aliases: %w[ -S ],
|
127
|
+
desc: 'Set JIRA issue refresh rate',
|
128
|
+
default: 5
|
115
129
|
include_common_options
|
116
130
|
def start
|
117
131
|
bot = start_bot
|
118
|
-
|
132
|
+
periodically_refresh_users bot
|
133
|
+
periodically_refresh_incidents bot
|
119
134
|
serve_web bot
|
120
135
|
end
|
121
136
|
|
@@ -141,7 +156,78 @@ module Bender
|
|
141
156
|
end
|
142
157
|
|
143
158
|
|
144
|
-
def
|
159
|
+
def set_room_name_and_topic room_id, incidents, hipchat, bot
|
160
|
+
room = hipchat[room_id]
|
161
|
+
new_room = room.get_room
|
162
|
+
|
163
|
+
open_incidents = incidents.select do |i|
|
164
|
+
status = normalize_value i['fields']['status']
|
165
|
+
!(status =~ /resolved|closed/i)
|
166
|
+
end
|
167
|
+
|
168
|
+
@room_name ||= bot.store['primary_room_name'] || new_room['name']
|
169
|
+
@room_topic ||= bot.store['primary_room_topic'] || new_room['topic']
|
170
|
+
|
171
|
+
@open = false unless defined? @open
|
172
|
+
|
173
|
+
log.info \
|
174
|
+
primary_room_name: bot.store['primary_room_name'],
|
175
|
+
primary_room_topic: bot.store['primary_room_topic'],
|
176
|
+
new_room_name: new_room['name'],
|
177
|
+
new_room_topic: new_room['topic'],
|
178
|
+
room_name: @room_name,
|
179
|
+
room_topic: @room_topic,
|
180
|
+
open: @open
|
181
|
+
|
182
|
+
if open_incidents.empty?
|
183
|
+
if @open
|
184
|
+
new_room['name'] = @room_name
|
185
|
+
new_room['topic'] = @room_topic
|
186
|
+
room.update_room(new_room)
|
187
|
+
end
|
188
|
+
|
189
|
+
@open = false
|
190
|
+
|
191
|
+
else
|
192
|
+
unless @open
|
193
|
+
@room_name = new_room['name']
|
194
|
+
@room_topic = new_room['topic']
|
195
|
+
bot.store['primary_room_name'] = @room_name
|
196
|
+
bot.store['primary_room_topic'] = @room_topic
|
197
|
+
new_room['name'] = 'DANGER WILL ROBINSON'
|
198
|
+
new_room['topic'] = '%d open incicents!' % open_incidents.size
|
199
|
+
room.update_room(new_room)
|
200
|
+
end
|
201
|
+
|
202
|
+
@open = true
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
def periodically_refresh_incidents bot
|
208
|
+
Thread.new do
|
209
|
+
hipchat_v2 = HipChat::Client.new \
|
210
|
+
options.hipchat_v2_token, api_version: 'v2'
|
211
|
+
|
212
|
+
hipchat_v1 = HipChat::Client.new \
|
213
|
+
options.hipchat_token, api_version: 'v1'
|
214
|
+
|
215
|
+
room_id = options.primary_room_id
|
216
|
+
|
217
|
+
loop do
|
218
|
+
is = refresh_incidents bot
|
219
|
+
begin
|
220
|
+
set_room_name_and_topic room_id, is, hipchat_v2, bot
|
221
|
+
sleep options.issue_refresh
|
222
|
+
rescue NoMethodError
|
223
|
+
sleep 1
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
def periodically_refresh_users bot
|
145
231
|
req_path = '/rest/api/2/user/assignable/search'
|
146
232
|
req_params = QueryParams.encode \
|
147
233
|
project: options.jira_project,
|
@@ -169,7 +255,7 @@ module Bender
|
|
169
255
|
|
170
256
|
bot.store['users'] = users
|
171
257
|
|
172
|
-
sleep options.
|
258
|
+
sleep options.user_refresh
|
173
259
|
end
|
174
260
|
end
|
175
261
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bender-bot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Clemmer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-08-
|
11
|
+
date: 2015-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -177,6 +177,7 @@ files:
|
|
177
177
|
- bin/bender
|
178
178
|
- lib/bender.rb
|
179
179
|
- lib/bender/bot.rb
|
180
|
+
- lib/bender/helpers.rb
|
180
181
|
- lib/bender/main.rb
|
181
182
|
- lib/bender/metadata.rb
|
182
183
|
- lib/bender/mjolnir.rb
|