github_labeler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/bin/github_labeler +288 -0
  3. data/lib/github_labeler.rb +298 -0
  4. metadata +88 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 21e8c495727bbb1dedeb2c52ece44b3def6c0290
4
+ data.tar.gz: d18b4d9bf033db40d62841716d26515e7a147655
5
+ SHA512:
6
+ metadata.gz: e60f2a3d8177b3ff85229decb3ddab4f72dd681991fb6b38235cc267390974576b47ce014ebf58c8466c2627618df03d022c823dc18f8b44dec3fe0aa2123ef7
7
+ data.tar.gz: 7b8a60e750fc95e5b233898c54e4baf2d674511c3d2c5ce1e5ea652dedccc5014ebfb8f42756ab9001342361df5eb4c026e8161bb5e23c0125cb572427cf7d2b
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "commander"
4
+ require "json"
5
+ require "github_labeler"
6
+
7
+ ARGV.push('-h') if ARGV.empty?
8
+
9
+ cli = HighLine.new($stdin, $stderr)
10
+
11
+ Commander.configure do
12
+ program :name, 'GitHub Labeler'
13
+ program :version, '0.1.0'
14
+ program :description, 'Simple program for managing issue labels across GitHub repositories.'
15
+
16
+ global_option '--verbose', "Enable output of detailed debugging information to STDERR"
17
+ global_option '--execute', "Execute changes immediately, without asking for confirmation"
18
+ global_option '--token STRING', String, "GitHub OAuth token for making API calls. If not specified, the GITHUB_OAUTH_TOKEN environment variable is used. Create a token here: https://github.com/settings/token"
19
+
20
+ command :duplicate do |c|
21
+ c.syntax = 'github_labeler duplicate [options]'
22
+ c.description = 'Duplicate labels from one repository to another'
23
+
24
+ c.option '-s', '--source STRING', String, '(required) Source repository to copy labels from (USER/REPO)'
25
+ c.option '-d', '--destination STRING', String, '(required) Destination repository to copy labels to (USER/REPO), or name of JSON file with list of repositories'
26
+
27
+ c.example "Destination is repository name", "github_labeler duplicate -s github/linguist -d foo/bar"
28
+ c.example "Destinations are specified in JSON file", "github_labeler duplicate -s github/linguist -d dest.json"
29
+
30
+ c.action do |args, options|
31
+ options.default \
32
+ :verbose => false,
33
+ :execute => false,
34
+ :token => ENV["GITHUB_OAUTH_TOKEN"]
35
+
36
+ raise ArgumentError.new("Token is required") unless options.token
37
+ raise ArgumentError.new("Source is required") unless options.source
38
+ raise ArgumentError.new("Destination is required") unless options.destination
39
+
40
+ labeler = GitHubLabeler.new(options.token, options.verbose)
41
+ destination = File.file?(options.destination) ? JSON.parse(IO.read(options.destination), {:symbolize_names => true}) : [options.destination]
42
+ changes = labeler.duplicate_labels_from_repo(options.source, destination)
43
+
44
+ puts JSON.pretty_generate(changes)
45
+
46
+ if changes.size == 0
47
+ cli.say("No changes to execute.")
48
+ elsif options.execute or cli.agree("Execute #{changes.size} changes? [y/n]")
49
+ labeler.execute_changes(changes)
50
+ end
51
+
52
+ cli.say("Done!")
53
+ end
54
+ end
55
+
56
+ command :export do |c|
57
+ c.syntax = 'github_labeler export [options]'
58
+ c.description = 'Export a list of labels from a repository or list of repositories'
59
+
60
+ c.option '-r', '--repo STRING', String, '(required) Repository from which to extract labels (USER/REPO)'
61
+
62
+ c.example "Repository is repository name", "github_labeler export -r foo/bar"
63
+
64
+ c.action do |args, options|
65
+ options.default \
66
+ :verbose => false,
67
+ :execute => false,
68
+ :token => ENV["GITHUB_OAUTH_TOKEN"]
69
+
70
+ raise ArgumentError.new("Token is required") unless options.token
71
+ raise ArgumentError.new("Repo is required") unless options.repo
72
+ raise ArgumentError.new("Token is required") unless options.token
73
+
74
+ labeler = GitHubLabeler.new(options.token, options.verbose)
75
+ labels = labeler.export_labels_from_repo(options.repo)
76
+
77
+ puts JSON.pretty_generate(labels)
78
+
79
+ cli.say("Done!")
80
+ end
81
+ end
82
+
83
+ command :execute do |c|
84
+ c.syntax = 'github_labeler execute [options]'
85
+ c.description = 'Execute changes previously created by the program'
86
+
87
+ c.option '-c', '--changes STRING', String, '(required) Name of JSON file with list of changes'
88
+
89
+ c.example "Changes are specified in JSON file", "github_labeler export -c changes.json"
90
+
91
+ c.action do |args, options|
92
+ options.default \
93
+ :verbose => false,
94
+ :execute => false,
95
+ :token => ENV["GITHUB_OAUTH_TOKEN"]
96
+
97
+ raise ArgumentError.new("Token is required") unless options.token
98
+ raise ArgumentError.new("Changes are required") unless options.changes
99
+
100
+ labeler = GitHubLabeler.new(options.token, options.verbose)
101
+ changes = JSON.parse(IO.read(options.changes), {:symbolize_names => true})
102
+ result = labeler.execute_changes(changes)
103
+
104
+ puts JSON.pretty_generate(result)
105
+
106
+ cli.say("Done!")
107
+ end
108
+ end
109
+
110
+ command :add do |c|
111
+ c.syntax = 'github_labeler add [options]'
112
+ c.description = 'Add one or more label to one or more repositories'
113
+
114
+ c.option '-l', '--labels STRING', String, '(required) Label (NAME#COLOR) or name of JSON file with list of labels'
115
+ c.option '-r', '--repos STRING', String, '(required) Destination repository to add labels to (USER/REPO) or name of JSON file with list of repositories'
116
+
117
+ c.example "Label is a string and destination repo is repository name", "github_labeler add -r github/linguist -l api#c7def8"
118
+ c.example "Labels and repositories are specified in JSON files", "github_labeler add -r repos.json -l labels.json"
119
+
120
+ c.action do |args, options|
121
+ options.default \
122
+ :verbose => false,
123
+ :execute => false,
124
+ :token => ENV["GITHUB_OAUTH_TOKEN"]
125
+
126
+ raise ArgumentError.new("Token is required") unless options.token
127
+ raise ArgumentError.new("Labels are required") unless options.labels
128
+ raise ArgumentError.new("Repos are required") unless options.repos
129
+
130
+ labeler = GitHubLabeler.new(options.token, options.verbose)
131
+ repos = File.file?(options.repos) ? JSON.parse(IO.read(options.repos), {:symbolize_names => true}) : [options.repos]
132
+ labels = options.labels
133
+
134
+ if File.file?(options.labels)
135
+ labels = JSON.parse(IO.read(options.labels), {:symbolize_names => true})
136
+ else
137
+ label_name, label_color = labels.split("#")
138
+ labels = [{:name => label_name, :color => label_color}]
139
+ end
140
+
141
+ changes = labeler.add_labels_to_repos(repos, labels)
142
+
143
+ puts JSON.pretty_generate(changes)
144
+
145
+ if changes.size == 0
146
+ cli.say("No changes to execute.")
147
+ elsif options.execute or cli.agree("Execute #{changes.size} changes? [y/n]")
148
+ labeler.execute_changes(changes)
149
+ end
150
+
151
+ cli.say("Done!")
152
+ end
153
+ end
154
+
155
+ command :delete do |c|
156
+ c.syntax = 'github_labeler delete [options]'
157
+ c.description = 'Delete one or more label from one or more repositories'
158
+
159
+ c.option '-l', '--labels STRING', String, '(required) Label (NAME) or name of JSON file with list of labels'
160
+ c.option '-r', '--repos STRING', String, '(required) Destination repository to delete labels from (USER/REPO) or name of JSON file with list of repositories'
161
+
162
+ c.example "Label is a string and destination repo is repository name", "github_labeler delete -r github/linguist -l api"
163
+ c.example "Labels and repositories are specified in JSON files", "github_labeler delete -r repos.json -l labels.json"
164
+
165
+ c.action do |args, options|
166
+ options.default \
167
+ :verbose => false,
168
+ :execute => false,
169
+ :token => ENV["GITHUB_OAUTH_TOKEN"]
170
+
171
+ raise ArgumentError.new("Token is required") unless options.token
172
+ raise ArgumentError.new("Labels are required") unless options.labels
173
+ raise ArgumentError.new("Repos are required") unless options.repos
174
+
175
+ labeler = GitHubLabeler.new(options.token, options.verbose)
176
+ repos = File.file?(options.repos) ? JSON.parse(IO.read(options.repos), {:symbolize_names => true}) : [options.repos]
177
+ labels = options.labels
178
+
179
+ if File.file?(options.labels)
180
+ labels = JSON.parse(IO.read(options.labels), {:symbolize_names => true})
181
+ else
182
+ labels = [{:name => labels, :color => nil}]
183
+ end
184
+
185
+ changes = labeler.delete_labels_from_repos(repos, labels)
186
+
187
+ puts JSON.pretty_generate(changes)
188
+
189
+ if changes.size == 0
190
+ cli.say("No changes to execute.")
191
+ elsif options.execute or cli.agree("Execute #{changes.size} changes? [y/n]")
192
+ labeler.execute_changes(changes)
193
+ end
194
+
195
+ cli.say("Done!")
196
+ end
197
+ end
198
+
199
+ command :rename do |c|
200
+ c.syntax = 'github_labeler rename [options]'
201
+ c.description = 'Rename one or more labels in one or more repositories'
202
+
203
+ c.option '-l', '--labels STRING', String, '(required) Label (OLDNAME/NEWNAME) or name of JSON file with list of labels'
204
+ c.option '-r', '--repos STRING', String, '(required) Destination repository to rename labels in (USER/REPO) or name of JSON file with list of repositories'
205
+
206
+ c.example "Label is a string and destination repo is repository name", "github_labeler rename -r github/linguist -l design/ui"
207
+ c.example "Labels and repositories are specified in JSON files", "github_labeler rename -r repos.json -l labels.json"
208
+
209
+ c.action do |args, options|
210
+ options.default \
211
+ :verbose => false,
212
+ :execute => false,
213
+ :token => ENV["GITHUB_OAUTH_TOKEN"]
214
+
215
+ raise ArgumentError.new("Token is required") unless options.token
216
+ raise ArgumentError.new("Labels are required") unless options.labels
217
+ raise ArgumentError.new("Repos are required") unless options.repos
218
+
219
+ labeler = GitHubLabeler.new(options.token, options.verbose)
220
+ repos = File.file?(options.repos) ? JSON.parse(IO.read(options.repos), {:symbolize_names => true}) : [options.repos]
221
+ labels = options.labels
222
+
223
+ if File.file?(options.labels)
224
+ labels = JSON.parse(IO.read(options.labels), {:symbolize_names => true})
225
+ else
226
+ old_name, new_name = labels.split("/")
227
+ labels = [{:name => old_name, :new_name => new_name}]
228
+ end
229
+
230
+ changes = labeler.rename_labels_in_repos(repos, labels)
231
+
232
+ puts JSON.pretty_generate(changes)
233
+
234
+ if changes.size == 0
235
+ cli.say("No changes to execute.")
236
+ elsif options.execute or cli.agree("Execute #{changes.size} changes? [y/n]")
237
+ labeler.execute_changes(changes)
238
+ end
239
+
240
+ cli.say("Done!")
241
+ end
242
+ end
243
+
244
+ command :recolor do |c|
245
+ c.syntax = 'github_labeler recolor [options]'
246
+ c.description = 'Recolor one or more labels in one or more repositories'
247
+
248
+ c.option '-l', '--labels STRING', String, '(required) Label (NAME#COLOR) or name of JSON file with list of labels'
249
+ c.option '-r', '--repos STRING', String, '(required) Destination repository to recolor labels in (USER/REPO) or name of JSON file with list of repositories'
250
+
251
+ c.example "Label is a string and destination repo is repository name", "github_labeler recolor -r github/linguist -l api#c7def8"
252
+ c.example "Labels and repositories are specified in JSON files", "github_labeler recolor -r repos.json -l labels.json"
253
+
254
+ c.action do |args, options|
255
+ options.default \
256
+ :verbose => false,
257
+ :execute => false,
258
+ :token => ENV["GITHUB_OAUTH_TOKEN"]
259
+
260
+ raise ArgumentError.new("Token is required") unless options.token
261
+ raise ArgumentError.new("Labels are required") unless options.labels
262
+ raise ArgumentError.new("Repos are required") unless options.repos
263
+
264
+ labeler = GitHubLabeler.new(options.token, options.verbose)
265
+ repos = File.file?(options.repos) ? JSON.parse(IO.read(options.repos), {:symbolize_names => true}) : [options.repos]
266
+ labels = options.labels
267
+
268
+ if File.file?(options.labels)
269
+ labels = JSON.parse(IO.read(options.labels), {:symbolize_names => true})
270
+ else
271
+ label_name, label_color = labels.split("#")
272
+ labels = [{:name => label_name, :color => label_color}]
273
+ end
274
+
275
+ changes = labeler.recolor_labels_in_repos(repos, labels)
276
+
277
+ puts JSON.pretty_generate(changes)
278
+
279
+ if changes.size == 0
280
+ cli.say("No changes to execute.")
281
+ elsif options.execute or cli.agree("Execute #{changes.size} changes? [y/n]")
282
+ labeler.execute_changes(changes)
283
+ end
284
+
285
+ cli.say("Done!")
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,298 @@
1
+ require "json"
2
+ require "logger"
3
+ require "Octokit"
4
+
5
+ Octokit.auto_paginate = true
6
+
7
+ class GitHubLabeler
8
+ attr_accessor :client, # Octokit client for acesing the API
9
+ :repo_labels, # Labels cache for repositories
10
+ :logger # Logger for writing debugging info
11
+
12
+ def initialize(token, verbose=false, labels=nil)
13
+ @logger = Logger.new(STDERR)
14
+ @logger.sev_threshold = verbose ? Logger::DEBUG : Logger::WARN
15
+
16
+ @logger.debug "Creating new GitHubLabeler instance."
17
+
18
+ @logger.debug "Creating a new Octokit client with token #{token[0..5]}"
19
+
20
+ begin
21
+ @client = Octokit::Client.new(:access_token => token)
22
+ @client.rate_limit
23
+ rescue Octokit::Unauthorized => exception
24
+ @logger.error "Token #{token[0..5]} is not valid"
25
+ raise ArgumentError.new("Token #{token[0..5]} is not valid")
26
+ end
27
+
28
+ @logger.debug "Token #{token[0..5]} is valid"
29
+
30
+ @repo_labels = !labels.nil? ? labels : {}
31
+ end
32
+
33
+ #
34
+ # Checks if the remaining rate limit allows us to make the specified number of
35
+ # changes
36
+ #
37
+ def is_remaining_rate_limit_sufficient?(expected_number_of_calls)
38
+ remaining_limit = @client.rate_limit.remaining
39
+
40
+ @logger.debug "There are #{expected_number_of_calls} API calls to be made, and the remaining API rate limit quota is #{remaining_limit}."
41
+
42
+ return expected_number_of_calls <= @client.rate_limit.remaining
43
+ end
44
+
45
+ #
46
+ # Executes a list of label changes. Each change has the following format:
47
+ #
48
+ # {
49
+ # :type => "update/create/delete",
50
+ # :repo => "testrename/testing",
51
+ # :label => {:color => "fc2929", :name => "design", :new_name => "ui"}
52
+ # }
53
+ #
54
+ def execute_changes(changes, options = {})
55
+ @logger.debug "Executing changes"
56
+
57
+ if !is_remaining_rate_limit_sufficient?(changes.size)
58
+ @logger.error "Remaining rate limit is not enough to make all changes. Wait for the limit to refresh and try again."
59
+ return []
60
+ end
61
+
62
+ changes = validate_changes(changes, options)
63
+
64
+ changes.each do |change|
65
+ @logger.debug "Executing change: #{change_string(change)}"
66
+
67
+ if change[:type] == "add"
68
+ success = @client.add_label(change[:repo], change[:label][:name], change[:label][:color])
69
+
70
+ if !@repo_labels[change[:repo]].nil?
71
+ @repo_labels[change[:repo]][success[:name].downcase] = {:name => success[:name], :color => success[:color]}
72
+ end
73
+
74
+ @logger.debug "Change succeded"
75
+ next
76
+ end
77
+
78
+ if change[:type] == "update"
79
+ new_label = {:name => change[:label][:new_name] || change[:label][:name]}
80
+ if !change[:label][:color].nil?
81
+ new_label[:color] = change[:label][:color]
82
+ end
83
+
84
+ success = @client.update_label(change[:repo], change[:label][:name], new_label)
85
+
86
+ if !@repo_labels[change[:repo]].nil?
87
+ @repo_labels[change[:repo]][success[:name].downcase] = {:name => success[:name], :color => success[:color]}
88
+ end
89
+
90
+ @logger.debug "Change succeded"
91
+ next
92
+ end
93
+
94
+ if change[:type] == "delete"
95
+ success = @client.delete_label!(change[:repo], change[:label][:name])
96
+
97
+ if !@repo_labels[change[:repo]].nil?
98
+ @repo_labels[change[:repo]][change[:label][:name].downcase] = nil
99
+ end
100
+
101
+ @logger.debug "Change succeded"
102
+ next
103
+ end
104
+ end
105
+
106
+ @logger.debug "Done executing changes"
107
+
108
+ return changes
109
+ end
110
+
111
+ #
112
+ # Updates repo labels cache for a specific label
113
+ #
114
+ def update_repos_labels_cache(repo, label)
115
+ @repo_labels[change[:repo]][change[:label][:name].downcase] = success
116
+ end
117
+
118
+ #
119
+ # Validates changes by merging multiple updates into one, removing
120
+ # duplicates, detecting conflicts, etc.
121
+ #
122
+ def validate_changes(changes, options = {})
123
+ return changes.sort_by { |hsh| [hsh["repo"], hsh["type"]] }
124
+ end
125
+
126
+ #
127
+ # Create changes for adding a list of labels to a list of repos
128
+ #
129
+ def add_labels_to_repos(repos, labels, options = {})
130
+ @logger.debug "Adding labels to repositories"
131
+ return process_labels_for_repos(repos, labels, method(:add_label_to_repo), options)
132
+ end
133
+
134
+ #
135
+ # Create changes for deleting a list of labels from a list of repos
136
+ #
137
+ def delete_labels_from_repos(repos, labels, options = {})
138
+ @logger.debug "Deleting labels from repositories"
139
+ return process_labels_for_repos(repos, labels, method(:delete_label_from_repo), options)
140
+ end
141
+
142
+ #
143
+ # Create changes for renaming a list of labels in a list of repos
144
+ #
145
+ def rename_labels_in_repos(repos, labels, options = {})
146
+ @logger.debug "Renaming labels in repositories"
147
+ return process_labels_for_repos(repos, labels, method(:rename_label_in_repo), options)
148
+ end
149
+
150
+ #
151
+ # Create changes for recoloring a list of labels in a list of repos
152
+ #
153
+ def recolor_labels_in_repos(repos, labels, options = {})
154
+ @logger.debug "Recoloring labels in repositories"
155
+ return process_labels_for_repos(repos, labels, method(:recolor_label_in_repo), options)
156
+ end
157
+
158
+ #
159
+ # Generic creation of changes for list of labels and list of repos
160
+ #
161
+ def process_labels_for_repos(repos, labels, change_creator, options = {})
162
+ if !is_remaining_rate_limit_sufficient?(repos.size)
163
+ @logger.error "Rate limit is not enough to process all labels in repositories"
164
+ return nil
165
+ end
166
+
167
+ changes = []
168
+
169
+ for repo in repos
170
+ repo_name = repo if repo.instance_of?(String)
171
+ repo_name = repo[:full_name] if repo.instance_of?(Hash)
172
+
173
+ @logger.debug "Processing labels for repository #{repo_name}"
174
+
175
+ refresh_repo_labels(repo_name, options)
176
+
177
+ for label in labels
178
+ @logger.debug "Processing label #{label[:name]}"
179
+ change = change_creator.call(repo_name, label, options)
180
+ changes << change if !change.nil?
181
+ end
182
+ end
183
+
184
+ return changes
185
+ end
186
+
187
+ #
188
+ # Fetches the list of labels in a repository and stores them
189
+ # in the labels cache
190
+ #
191
+ def refresh_repo_labels(repo, options = {})
192
+ @logger.debug "Fetching label information for #{repo}"
193
+
194
+ @repo_labels[repo] = {}
195
+ @client.labels(repo).each do |label|
196
+ @repo_labels[repo][label[:name].downcase] = label
197
+ end
198
+ end
199
+
200
+ #
201
+ # Create a single change for adding a label to a repo
202
+ #
203
+ def add_label_to_repo(repo, label, options = {})
204
+ existing_label = @repo_labels[repo][label[:name].downcase]
205
+
206
+ if existing_label
207
+ if existing_label[:color] != label[:color] or existing_label[:name] != label[:name]
208
+ @logger.warn "Label #{label[:name]} already exist, creating an update"
209
+ return { :type => "update", :repo => repo, :label => label }
210
+ end
211
+ else
212
+ return { :type => "add", :repo => repo, :label => label }
213
+ end
214
+
215
+ @logger.warn "Label #{label[:name]} already exist and is the same. No change created"
216
+ return nil
217
+ end
218
+
219
+ #
220
+ # Create a single change for deleting a label from a repo
221
+ #
222
+ def delete_label_from_repo(repo, label, options = {})
223
+ existing_label = @repo_labels[repo][label[:name].downcase]
224
+
225
+ if existing_label
226
+ return { :type => "delete", :repo => repo, :label => label }
227
+ end
228
+
229
+ @logger.warn "Label #{label[:name]} doesn't exist. No change created"
230
+ return nil
231
+ end
232
+
233
+ #
234
+ # Create a single change for renaming a label in a repo
235
+ #
236
+ def rename_label_in_repo(repo, label, options = {})
237
+ existing_label = @repo_labels[repo][label[:name].downcase]
238
+
239
+ if existing_label
240
+ if label[:new_name] and label[:new_name] != label[:name]
241
+ return { :type => "update", :repo => repo, :label => label }
242
+ end
243
+ else
244
+ @logger.warn "Label #{label[:name]} doesn't exist. Creating a create change"
245
+ newLabel = {:name => label[:new_name], :color => label[:color]}
246
+ return { :type => "add", :repo => repo, :label => newLabel }
247
+ end
248
+
249
+ @logger.warn "Label #{label[:name]} exist and is the same. No change created"
250
+ return nil
251
+ end
252
+
253
+ #
254
+ # Create a single change for recoloring a label in a repo
255
+ #
256
+ def recolor_label_in_repo(repo, label, options = {})
257
+ existing_label = @repo_labels[repo][label[:name].downcase]
258
+
259
+ if existing_label
260
+ if existing_label[:color] != label[:color]
261
+ return { :type => "update", :repo => repo, :label => label }
262
+ end
263
+ else
264
+ @logger.warn "Label #{label[:name]} doesn't exist. Creating a create change"
265
+ return { :type => "add", :repo => repo, :label => label }
266
+ end
267
+
268
+ @logger.warn "Label #{label[:name]} exist and is the same. No change created"
269
+ return nil
270
+ end
271
+
272
+ #
273
+ # Creates changes for duplicating labels from one repo to other repos
274
+ #
275
+ def duplicate_labels_from_repo(repo_source, repos_end, options = {})
276
+ @logger.debug "Duplicating labels from repo #{repo_source}"
277
+ source_repo_labels = export_labels_from_repo(repo_source, options)
278
+ return add_labels_to_repos(repos_end, source_repo_labels, options)
279
+ end
280
+
281
+ #
282
+ # Fetches a list of labels for a repository
283
+ #
284
+ def export_labels_from_repo(repo, options = {})
285
+ @logger.debug "Exporting labels from repo #{repo}"
286
+
287
+ @client.labels(repo).map do |label|
288
+ { :name => label[:name], :color => label[:color] }
289
+ end
290
+ end
291
+
292
+ #
293
+ # Get a human readable string representation of a change
294
+ #
295
+ def change_string(change, options = {})
296
+ return "#{change[:repo]} - #{change[:type]} - #{change[:label][:name]} - color: #{change[:label][:color]} - new_name: #{change[:label][:new_name]}"
297
+ end
298
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: github_labeler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ivan Zuzak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: commander
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: octokit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.3'
55
+ description:
56
+ email: izuzak@gmail.com
57
+ executables:
58
+ - github_labeler
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - bin/github_labeler
63
+ - lib/github_labeler.rb
64
+ homepage: https://github.com/izuzak/github_labeler
65
+ licenses:
66
+ - MIT
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 2.2.3
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Utility for running actions on issue labels in groups of GitHub repositories
88
+ test_files: []