ghedsh 1.1.40 → 2.3.8

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.
data/lib/actions/orgs.rb CHANGED
@@ -1,955 +1,749 @@
1
- require 'readline'
2
- require 'octokit'
3
1
  require 'json'
4
2
  require 'csv'
3
+ require 'fileutils'
5
4
  require 'require_all'
6
- require_rel '.'
7
- require 'readline'
8
-
9
- GITHUB_LIST=['githubid','github','idgithub','github_id','id_github','githubuser','github_user']
10
- MAIL_LIST=['email','mail','e-mail']
11
-
12
- class Organizations
13
-
14
- attr_accessor :orgslist
15
- attr_accessor :assiglist
16
- attr_accessor :peoplelist
17
-
18
-
19
- def load_assig()
20
- @assiglist=Hash.new()
21
- @assiglist=Sys.new.load_assig_db("#{ENV['HOME']}/.ghedsh")
22
- return @assiglist
5
+ require 'rainbow'
6
+ require 'tty-spinner'
7
+ require 'tty-prompt'
8
+ require_relative '../helpers'
9
+
10
+ # Class containing actions available inside an organization
11
+ #
12
+ # @see http://octokit.github.io/octokit.rb/Octokit/Client/Organizations.html
13
+ class Organization
14
+ # CLI prompt, info about the current (CLI) scope
15
+ # @param config [Hash] user configuration tracking current org, repo, etc.
16
+ def self.shell_prompt(config)
17
+ if config['Repo'].nil?
18
+ Rainbow("#{config['User']}> ").aqua << Rainbow("#{config['Org']}> ").magenta
19
+ else
20
+ Rainbow("#{config['User']}> ").aqua << Rainbow("#{config['Org']}> ").magenta << Rainbow("#{config['Repo']}> ").color(236, 151, 21)
21
+ end
23
22
  end
24
23
 
25
- def load_people()
26
- @peoplelist=Hash.new()
27
- @peoplelist=Sys.new.load_people_db("#{ENV['HOME']}/.ghedsh")
28
- return @peoplelist
24
+ # Builds final cd syntax. Transforms user's CLI input to valid Ruby expression doing some parsing.
25
+ # @param type [String] scope of cd command
26
+ # @param name [String, Regexp] name of repo, org, team etc. inside current organization
27
+ # @return syntax_map [String] Valid Ruby expression to perform eval.
28
+ def build_cd_syntax(type, name)
29
+ syntax_map = { 'repo' => "Organization.new.cd('repo', #{name}, client, env)",
30
+ 'team' => "Organization.new.cd('team', #{name}, client, env)" }
31
+ unless syntax_map.key?(type)
32
+ raise Rainbow("cd #{type} currently not supported.").color(ERROR_CODE)
33
+ end
34
+ syntax_map[type]
29
35
  end
30
36
 
31
- def show_assignments(client, config) #client,orgs
32
- list=self.load_assig()
33
-
34
- assig=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
35
- if assig!=nil
36
- if assig["assigs"].empty?
37
- puts "No assignments are available yet"
38
- else
39
- assig["assigs"].each do |i|
40
- puts "\n"
41
- puts i["name_assig"]
42
- i.each{|key, value| if key.include?("repo") then puts "Repository #{key.delete("repo")}: #{value}" end}
43
- if i["groups"]!=[] and i["groups"]!=nil
44
- puts "\tGroups: "
45
- i["groups"].each do |y|
46
- puts "\t\t#{y}"
47
- end
48
- end
49
- if i["people"]!=[] and i["people"]!=nil
50
- puts "\tStudents: "
51
- i["people"].each do |y|
52
- puts "\t\t#{y}"
53
- end
54
- puts "\n"
55
- end
56
- print "\n"
57
- end
58
- end
37
+ # Open info on default browser.
38
+ #
39
+ # If 'params' is String or Regexp user selects referred organization members
40
+ #
41
+ # @param config [Hash] user configuration tracking current org, repo, etc.
42
+ # @params [String, Regexp]
43
+ # @param client [Object] Octokit client object
44
+ # @example open user profile URL with String provided
45
+ # User > Org > open 'some_user'
46
+ # @example open user URL with Regexp provided
47
+ # User > Org > open /pattern/
48
+ def open_info(config, params, client)
49
+ unless params.nil?
50
+ # looking for org member by regexp
51
+ pattern = build_regexp_from_string(params)
52
+ member_url = select_member(config, pattern, client)
53
+ open_url(member_url.to_s) unless member_url.nil?
54
+ return
55
+ end
56
+
57
+ if config['Repo'].nil?
58
+ open_url(config['org_url'].to_s)
59
59
  else
60
- puts "No assignments are available yet"
61
- list["orgs"].push({"name"=>config["Org"],"assigs"=>[]})
62
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
60
+ open_url(config['repo_url'].to_s)
63
61
  end
62
+ rescue StandardError => exception
63
+ puts Rainbow(exception.message.to_s).color(ERROR_CODE)
64
+ puts
64
65
  end
65
66
 
66
- def assignment_repository(client,config,name)
67
- ex=false
68
- until ex==true
69
- puts "\n"
70
- puts "Assignment: #{name}"
71
- puts "1) Add a repository already created "
72
- puts "2) Create a new empty repository"
73
- puts "3) Don't assign a repository yet"
74
- print "option => "
75
- op=gets.chomp
76
- puts "\n"
77
- if op=="1" or op=="2" or op=="3"
78
- ex=true
79
- end
67
+ # Display organization repos
68
+ #
69
+ # @param client [Object] Octokit client object
70
+ # @param config [Hash] user configuration tracking current org, repo, etc.
71
+ # @param params [Regexp] regexp to check repo name
72
+ def show_repos(client, config, params)
73
+ spinner = custom_spinner("Fetching #{config['Org']} repositories :spinner ...")
74
+ spinner.auto_spin
75
+ org_repos = []
76
+ client.organization_repositories(config['Org'].to_s).each do |repo|
77
+ org_repos << repo[:name]
78
+ end
79
+ org_repos.sort_by!(&:downcase)
80
+ spinner.stop(Rainbow('done!').color(4, 255, 0))
81
+ if params.nil?
82
+ item_counter = 0
83
+ org_repos.each do |repo_name|
84
+ puts repo_name
85
+ item_counter += 1
86
+ end
87
+ puts "\n#{item_counter} #{config['Org']} repositories listed."
88
+ else
89
+ pattern = build_regexp_from_string(params)
90
+ occurrences = show_matching_items(org_repos, pattern)
91
+ puts Rainbow("No repository inside #{config['Org']} matched \/#{pattern.source}\/").color(INFO_CODE) if occurrences.zero?
92
+ puts "\n#{occurrences} #{config['Org']} repositories listed."
80
93
  end
81
- case
82
- when op=="1" || op=="2"
94
+ end
83
95
 
84
- if op=="1"
85
- ex2=false
86
- until ex2==true
87
- exname=false
88
- until exname==true
89
- puts "Name of the repository -> Owner/Repository or Organization/Repository :"
90
- reponame=gets.chomp
91
- if reponame.split("/").size!=2 and reponame!=""
92
- puts "Please introduce a valid format."
93
- else
94
- exname=true
95
- end
96
- if reponame==""
97
- exname=true
98
- ex2=true
99
- end
100
- end
101
- if reponame!=""
102
- if client.repository?("#{reponame}")==false
103
- puts "\e[31m The repository #{reponame} doesn't exist\n \e[0m"
104
- puts "\nName of the repository (To skip and add the repository later, only press enter): "
105
- if reponame==""
106
- ex2=true
107
- end
108
- else
109
- ex2=true
110
- end
111
- end
112
- end
113
- end
114
- if op=="2"
115
- ex2=false
116
- until ex2==true
117
- puts "Name of the repository (To skip and add the repository later, press enter): "
118
- reponame=gets.chomp
119
- if reponame==""
120
- ex2=true
121
- else
122
- # ex2=Repositories.new().create_repository(client,config,reponame,false,ORGS)
123
- if client.repository?("#{config["Org"]}/#{reponame}")
124
- puts "\e[31m Already exists a repository with that name in #{config["Org"]}\e[0m"
125
- else
126
- reponame="#{config["Org"]}/#{reponame}"
127
- ex2=true
128
- end
129
- end
130
- end
131
- end
132
- when op=="3" then reponame=""
133
- end
134
- return reponame
96
+ # Set organization repos privacy to private. You need a paid plan to set private repos inside an organization
97
+ #
98
+ # @param client [Object] Octokit client object
99
+ # @param config [Hash] user configuration tracking current org, repo, etc.
100
+ # @param params [Regexp] regexp to change privacy of matching repos.
101
+ def change_to_private_repo(client, config, params)
102
+ pattern = build_regexp_from_string(params)
103
+ spinner = custom_spinner('Setting private repos :spinner ...')
104
+ spinner.auto_spin
105
+ repos = []
106
+ client.organization_repositories(config['Org'].to_s).each do |repo|
107
+ repos.push(repo[:name]) if pattern.match(repo[:name])
108
+ end
109
+ repos.each do |i|
110
+ client.set_private("#{config['Org']}/#{i}")
111
+ end
112
+ spinner.stop(Rainbow('done!').color(4, 255, 0))
113
+ rescue StandardError => exception
114
+ puts Rainbow(exception.message.to_s).color(ERROR_CODE)
115
+ end
116
+
117
+ # Set organization repos privacy to public.
118
+ #
119
+ # @param client [Object] Octokit client object
120
+ # @param config [Hash] user configuration tracking current org, repo, etc.
121
+ # @param params [Regexp] regexp to change privacy of matching repos.
122
+ def change_to_public_repo(client, config, params)
123
+ pattern = build_regexp_from_string(params)
124
+ spinner = custom_spinner('Setting public repos :spinner ...')
125
+ spinner.auto_spin
126
+ repos = []
127
+ client.organization_repositories(config['Org'].to_s).each do |repo|
128
+ repos.push(repo[:name]) if pattern.match(repo[:name])
129
+ end
130
+ repos.each do |i|
131
+ client.set_public("#{config['Org']}/#{i}")
132
+ end
133
+ spinner.stop(Rainbow('done!').color(4, 255, 0))
134
+ rescue StandardError => exception
135
+ puts Rainbow(exception.message.to_s).color(ERROR_CODE)
135
136
  end
136
137
 
137
- def assignment_groups(client,config)
138
- team=Teams.new()
139
- sys=Sys.new()
140
- groupslist=team.get_groupslist(config)
141
- groupsadd=[]
142
- teamlist=team.read_teamlist(client,config)
138
+ # Create a 'super repo' containing assignment repositories as submodules
139
+ #
140
+ # @param [Object] client Octokit client object
141
+ # @param [Hash] config user configuration tracking current org, repo, etc.
142
+ # @param [Array<String>] params 'super repo's' name and Regexp to match submodules
143
+ # params[0] is the evaluation repository's name
144
+ # params[1] Regexp of submodules
145
+ def new_eval(client, config, params)
146
+ options = { private: true, organization: config['Org'].to_s, description: 'Evaluation repository created using ghedsh.' }
147
+ repo_name = params[0]
148
+ submodules = []
149
+ evaluation_repo = client.create_repository(repo_name, options)
150
+ pattern = build_regexp_from_string(params[1])
151
+ client.organization_repositories(config['Org'].to_s).each do |repo|
152
+ submodules << repo[:ssh_url] if pattern.match(repo[:name]) && (repo[:name] != repo_name)
153
+ end
154
+ unless submodules.empty?
155
+ local_repo_path = "#{Dir.pwd}/#{repo_name}"
156
+ FileUtils.mkdir_p(local_repo_path)
157
+ FileUtils.cd(local_repo_path) do
158
+ system('git init')
159
+ submodules.each do |i|
160
+ system("git submodule add #{i}")
161
+ end
162
+ system('git add .')
163
+ system('git commit -m "First commit"')
164
+ system("git remote add origin #{evaluation_repo[:ssh_url]}")
165
+ system('git push -u origin master')
166
+ end
167
+ end
168
+ puts Rainbow("No submodule found with /#{pattern.source}/") if submodules.empty?
169
+ rescue StandardError => e
170
+ puts Rainbow(e.message.to_s).color(ERROR_CODE)
171
+ end
143
172
 
144
- puts "\n"
145
- puts "Groups currently available:\n\n"
146
- groupslist.each do |aux|
147
- puts "\t#{aux}"
173
+ def repo_has_submodules?(client, config)
174
+ repo_content = []
175
+ client.contents("#{config['Org']}/#{config['Repo']}").each do |i|
176
+ repo_content << i[:name]
148
177
  end
178
+ unless repo_content.include?('.gitmodules')
179
+ puts Rainbow('Current repo does not include .gitmodules and command will not work.').color(WARNING_CODE)
180
+ return false
181
+ end
182
+ true
183
+ end
149
184
 
150
- puts "\nAdd groups to your assignment (Press enter to skip): "
151
- op=gets.chomp
152
- if op==""
153
- puts "Do you want to create a new group? (Press any key and enter to proceed, or only enter to skip)"
154
- an=gets.chomp
155
-
156
- if an!=""
157
- time=Time.new
158
- puts "Put the name of the group (If you skip with enter, the group's name will be \"Group-#{time.ctime}\")"
159
- name=gets.chomp
160
- if name==""
161
- name="Group-#{time.ctime}"
162
- name=name.split(" ").join("-")
163
- end
164
- puts "\nTeams currently available:\n\n"
165
- teamlist.each do |aux|
166
- puts "\t#{aux[0]}"
167
- end
168
- begin
169
- puts "\n1)Put a list of Teams"
170
- puts "2)Take all the Teams available"
171
- puts "3)Take the teams from a file\n"
172
- puts "4)Add the teams to the group later"
173
- print "option => "
174
- op2=gets.chomp
175
- end while (op2!="1" && op2!="2" && op2!="3" && op2!="4")
176
- refuse=false #para no añadirlo cuando se niege en la repeticion de la busqueda de fichero
177
- if op2=="1"
178
- puts "\nPut a list of Teams (Separeted with space): "
179
- list=gets.chomp
180
- list=list.split(" ")
181
- end
182
- if op2=="2"
183
- puts "All the teams have been taken"
184
- list=teamlist.keys
185
- end
186
- if op2=="3"
187
- begin
188
- ex=2
189
- puts "Put the name of the file: "
190
- file=gets.chomp
191
- list=sys.loadfile(file)
192
- if list==nil
193
- puts "The file doesn't exist or It's empty. Do you like to try again? (y/n):"
194
- op3=gets.chomp
195
- if op3 == "n" or op3 == "N"
196
- ex=1
197
- refuse=true
198
- end
199
- else
200
- ex=1
201
- end
202
- end while ex!=1
203
- end
204
- if op!="4" and refuse==false
205
- team.new_group(client,config,name,list)
206
- end
207
- groupsadd.push(name)
208
- else
209
- groupsadd=[]
185
+ # Evaluate a bash command in each submodule
186
+ #
187
+ # @param [Object] client Octokit client object
188
+ # @param [Hash] config user configuration tracking current org, repo, etc.
189
+ # @param [Array<String>] params bash command
190
+ def foreach_eval(client, config, params)
191
+ if config['Repo']
192
+ return unless repo_has_submodules?(client, config)
193
+ command = params.join(' ')
194
+ FileUtils.cd(Dir.pwd) do
195
+ system("git submodule foreach '#{command} || :'")
210
196
  end
211
-
212
197
  else
213
- groupsadd=op.split(" ")
214
- groupsadd.each do |item|
215
- if groupslist.detect{|aux| aux==item}==nil
216
- groupsadd.delete(item)
217
- end
218
- end
198
+ puts Rainbow('Please change to an organization repository to run this command.').color(INFO_CODE)
199
+ return
219
200
  end
220
- return groupsadd
201
+ rescue StandardError => e
202
+ puts Rainbow(e.message.to_s).color(ERROR_CODE)
221
203
  end
222
204
 
223
- def assignment_people(client,config)
224
- refuse=false
225
- members=self.get_organization_members(client,config)
226
- sys=Sys.new()
227
- begin
228
- puts "\n1)Put a list of students"
229
- puts "2)Take all the list"
230
- puts "3)Take the students from a file\n"
231
- puts "4)Add the students later"
232
- print "option => "
233
- op2=gets.chomp
234
- end while (op2!="1" && op2!="2" && op2!="3" && op2!="4")
235
- if op2=="1"
236
- puts members
237
- puts "\nPut a list of students (Separeted with space): "
238
- list=gets.chomp
239
- list=list.split(" ")
240
- end
241
- if op2=="2"
242
- puts "All the students have been taken"
243
- list=members
244
- end
245
- if op2=="3"
246
- begin
247
- ex=2
248
- puts "Put the name of the file: "
249
- file=gets.chomp
250
- list=sys.loadfile(file)
251
- if list==nil
252
- puts "The file doesn't exist or It's empty. Do you like to try again? (y/n):"
253
- op3=gets.chomp
254
- if op3 == "n" or op3 == "N"
255
- ex=1
256
- refuse=true
257
- end
258
- else
259
- ex=1
260
- end
261
- end while ex!=1
262
- end
263
- if op2!=4 and refuse==false
264
- if op2!=2
265
- if list!=nil
266
- list.each do |i|
267
- if !members.include?(i)
268
- list.delete(i)
269
- end
270
- end
271
- end
205
+ def foreach_try(client, config, params)
206
+ if config['Repo']
207
+ return unless repo_has_submodules?(client, config)
208
+ command = params.join(' ')
209
+ FileUtils.cd(Dir.pwd) do
210
+ system("git submodule foreach '#{command}'")
272
211
  end
273
- return list
274
212
  else
275
- return []
213
+ puts Rainbow('Please change to an organization repository to run this command.').color(INFO_CODE)
214
+ return
276
215
  end
216
+ rescue StandardError => e
217
+ puts Rainbow(e.message.to_s).color(ERROR_CODE)
277
218
  end
278
219
 
279
- def create_assig(client,config,name)
280
- list=self.load_assig()
281
- assigs=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
282
-
283
- if assigs==nil
284
- list["orgs"].push({"name"=>config["Org"],"assigs"=>[]})
285
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
286
- end
287
-
288
- assig_exist=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux| aux["name_assig"]==name}
289
- if assig_exist==nil
290
- begin
291
- puts "\n1)Individual assignment"
292
- puts "2)Group assignment"
293
- puts "3)Discard assignment"
294
- print "option => "
295
- op=gets.chomp
296
- end while op!="1" && op!="2" && op!="3"
297
-
298
- if op!="3"
299
- reponame=self.assignment_repository(client,config,name)
300
- if op=="2"
301
- groupsadd=self.assignment_groups(client,config)
302
- peopleadd=[]
303
- end
304
- if op=="1"
305
- peopleadd=self.assignment_people(client,config)
306
- groupsadd=[]
307
- end
308
- begin
309
- list["orgs"][list["orgs"].index{|aux| aux["name"]==config["Org"]}]["assigs"].push({"name_assig"=>name,"teams"=>[],"people"=>peopleadd,"groups"=>groupsadd,"repo"=>reponame})
310
- rescue Exception => e
311
- puts e
312
- end
313
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
314
- end
220
+ # Display files and directories within a repository
221
+ #
222
+ # @param client [Object] Octokit client object
223
+ # @param config [Hash] user configuration tracking current org, repo, etc.
224
+ # @params params [String] Subdirectory within a repo, if not provided shows root directory
225
+ def show_files(client, config, params)
226
+ if config['Repo']
227
+ options = { path: '' }
228
+ options[:path] = params[0] unless params.empty?
229
+ file_names_and_types = []
230
+ client.contents("#{config['Org']}/#{config['Repo']}", options).each do |i|
231
+ file_names_and_types << "#{i[:name]} (#{i[:type]})"
232
+ end
233
+ file_names_and_types.sort_by!(&:downcase)
234
+ puts file_names_and_types
315
235
  else
316
- puts "Already exists an Assignment with that name"
236
+ puts Rainbow('Please change to organization repository to see its files.').color(INFO_CODE)
317
237
  end
238
+ rescue StandardError => e
239
+ puts Rainbow(e.message.to_s).color(ERROR_CODE)
318
240
  end
319
241
 
320
- def get_assigs(client,config,show)
321
- list=self.load_assig()
322
- assiglist=[]
323
- assig=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
324
- if assig!=nil
325
- if assig["assigs"].empty?
326
- if show==true
327
- puts "\e[31m No assignments are available yet\e[0m"
328
- end
329
- else
330
- assig["assigs"].each do |i|
331
- assiglist.push(i["name_assig"])
332
- end
242
+ # Display organization teams
243
+ #
244
+ # @param client [Object] Octokit client object
245
+ # @param config [Hash] user configuration tracking current org, repo, etc.
246
+ # @param params [Regexp] regexp to check team name
247
+ def show_teams(client, config, params)
248
+ org_teams = []
249
+ spinner = custom_spinner("Fetching #{config['Org']} teams :spinner ...")
250
+ spinner.auto_spin
251
+ client.organization_teams(config['Org'].to_s).each do |team|
252
+ org_teams << team[:name]
253
+ end
254
+ org_teams.sort_by!(&:downcase)
255
+ spinner.stop(Rainbow('done!').color(4, 255, 0))
256
+ if params.nil?
257
+ org_teams.each do |name|
258
+ puts name
333
259
  end
334
260
  else
335
- list["orgs"].push({"name"=>config["Org"],"assigs"=>[]})
336
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
261
+ pattern = build_regexp_from_string(params)
262
+ occurrences = show_matching_items(org_teams, pattern)
263
+ puts Rainbow("No team inside #{config['Org']} matched \/#{pattern.source}\/").color(INFO_CODE) if occurrences.zero?
337
264
  end
338
- return assiglist
339
- end
340
-
341
- def get_single_assig(config,wanted)
342
- list=self.load_assig()
343
- as=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
344
- as=as["assigs"].detect{|aux| aux["name_assig"]==wanted}
345
- return as
346
265
  end
347
266
 
348
- def show_assig_info(config,assig)
349
- assig=self.get_single_assig(config,assig)
350
- team=Teams.new()
351
- puts "\n"
352
- puts assig["name_assig"]
353
- assig=assig.sort.to_h
354
- assig.each{|key, value| if key.include?("repo") then puts "Repository #{key.delete("repo")}: #{value}" end}
355
- puts
356
- assig.each{|key, value| if key.include?("sufix") then puts "Sufix of Repository #{key.delete("sufix")} : #{value}" end}
357
- print "\n"
358
- if assig["groups"]!=[]
359
- puts "\tGroups: "
360
- assig["groups"].each do |y|
361
- puts "\t\t#{y}"
362
- t=team.get_single_group(config,y)
363
- if t!=nil
364
- t.each do |z|
365
- puts "\t\t\t#{z}"
366
- end
367
- end
368
- end
267
+ # Clone a repository, if repo_name is Regexp it will clone all org repositories matching pattern
268
+ # if repo_name is String searches for that repo and clones it
269
+ #
270
+ # @param enviroment [ShellContext] contains the shell context, including Octokit client and user config
271
+ # @param repo_name [Regexp, String] pattern or name of repo
272
+ # @param custom_path [String] if is not provided default path is HOME/ghedsh-cloned else is some
273
+ # path under HOME (already existing or not)
274
+ def clone_repository(enviroment, repo_name, custom_path)
275
+ client = enviroment.client
276
+ config = enviroment.config
277
+ repos_to_clone = []
278
+ if repo_name.include?('/')
279
+ pattern = build_regexp_from_string(repo_name)
280
+ client.organization_repositories(config['Org'].to_s).each do |repo|
281
+ repos_to_clone << { name: repo[:name], ssh_url: repo[:clone_url] } if pattern.match(repo[:name])
282
+ end
283
+ puts Rainbow("No repository matched \/#{pattern.source}\/").color(INFO_CODE) if repos_to_clone.empty?
284
+ else
285
+ repo = client.repository("#{config['Org']}/#{repo_name}")
286
+ repos_to_clone << { name: repo[:name], ssh_url: repo[:clone_url] }
369
287
  end
370
- if assig["people"]!=[] and assig["people"]!=nil
371
- puts "\tStudents: "
372
- assig["people"].each do |y|
373
- puts "\t\t#{y}"
288
+ unless repos_to_clone.empty?
289
+ perform_git_clone(repos_to_clone, custom_path)
290
+ if custom_path.nil?
291
+ puts Rainbow("Cloned into #{Dir.pwd}").color(INFO_CODE).underline
292
+ else
293
+ puts Rainbow("Cloned into #{Dir.home}#{custom_path}").color(INFO_CODE).underline
374
294
  end
375
- puts "\n"
295
+ puts
376
296
  end
297
+ rescue StandardError => exception
298
+ puts Rainbow(exception.message.to_s).color(ERROR_CODE)
299
+ puts
377
300
  end
378
301
 
379
- def make_assig(client,config,assig)
380
- web="https://github.com/"
381
- web2="git@github.com:"
382
- r=Repositories.new()
383
- team=Teams.new()
384
- sys=Sys.new()
385
- teamlist=team.read_teamlist(client,config)
386
- assig=self.get_single_assig(config,assig)
387
- repolist=[]
388
- point=1
389
- assig.each{|key, value| if key.include?("repo") then repolist.push(value) end}
390
-
391
- if repolist!=[]
392
- repolist.each do |repo|
393
- sys.create_temp("#{ENV['HOME']}/.ghedsh/temp")
394
- puts repo
395
- if repolist.size>1
396
- if point>1 || assig.has_key?("sufix1")
397
- sufix="sufix#{point}"
398
- sufix="-#{assig["#{sufix}"]}"
399
- else
400
- sufix=""
401
- end
402
- else
403
- sufix=""
404
- end
405
- point=point+1
406
- if !client.repository?(repo)
407
- aux=repo.split("/")
408
- aux=aux[1]
409
- r.create_repository(client,config,aux,false,ORGS)
410
- end
411
- system("git clone #{web2}#{repo}.git #{ENV['HOME']}/.ghedsh/temp/#{repo}")
412
- if assig["groups"]!=[]
413
- assig["groups"].each do |i|
414
-
415
- teamsforgroup=team.get_single_group(config,i)
416
- if teamsforgroup!=nil
417
- teamsforgroup.each do |y|
418
- config["TeamID"]=teamlist["#{y}"]
419
- config["Team"]=y
420
- puts y
421
- puts sufix
422
- r.create_repository(client,config,"#{assig["name_assig"]}#{sufix}-#{y}",true,TEAM)
423
- system("git -C #{ENV['HOME']}/.ghedsh/temp/#{repo} remote rm origin")
424
- system("git -C #{ENV['HOME']}/.ghedsh/temp/#{repo} remote add origin #{web2}#{config["Org"]}/#{assig["name_assig"]}#{sufix}-#{y}.git")
425
-
426
- system("git -C #{ENV['HOME']}/.ghedsh/temp/#{repo} push origin --all")
427
- end
428
- end
429
- end
430
- end
431
- if assig["people"]!=[] and assig["people"]!=nil
432
- assig["people"].each do |i|
433
- r.create_repository(client,config,"#{assig["name_assig"]}#{sufix}-#{i}",true,ORGS)
434
- system("git -C #{ENV['HOME']}/.ghedsh/temp/#{repo} remote rm origin")
435
- system("git -C #{ENV['HOME']}/.ghedsh/temp/#{repo} remote add origin #{web2}#{config["Org"]}/#{assig["name_assig"]}#{sufix}-#{i}.git")
436
- system("git -C #{ENV['HOME']}/.ghedsh/temp/#{repo} push origin --all")
437
- r.add_collaborator(client,"#{config["Org"]}/#{assig["name_assig"]}#{sufix}-#{i}",i)
438
- end
439
- end
440
- end
302
+ # Open default browser ready to create an issue with GitHub form.
303
+ #
304
+ # @param config [Hash] user configuration tracking current org, repo, etc.
305
+ # @example create issue
306
+ # User > Org > Repo > new_issue
307
+ def create_issue(config)
308
+ if config['Repo']
309
+ issue_creation_url = "https://github.com/#{config['Org']}/#{config['Repo']}/issues/new"
310
+ open_url(issue_creation_url)
441
311
  else
442
- puts "\e[31m No repository is given for this assignment\e[0m"
312
+ puts Rainbow('Change to repo in order to create an issue.').color(INFO_CODE)
443
313
  end
444
314
  end
445
315
 
446
- def add_team_to_assig(client,config,assig,data)
447
- assig=self.get_single_assig(config,assig)
448
- end
449
-
450
- def add_people_to_assig(client,config,assig)
451
- list=self.load_assig()
452
- people=self.assignment_people(client,config)
453
- if list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"]==nil
454
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"]=[]
455
- end
456
-
457
- if people!=""
316
+ # Add members typing them separately
317
+ #
318
+ # @param client [Object] Octokit client object
319
+ # @param config [Hash] user configuration tracking current org, repo, etc.
320
+ # @example add two members to current org (separated with commas)
321
+ # User > Org > invite_member member1, member2
322
+ # @example add two members to current org (separated with blanks)
323
+ # User > Org > invite_member member1 member2
324
+ # @example add members to current org (commas and blanks combined)
325
+ # User > Org > invite_member member1, member2 member4, member5
326
+ def add_members(client, config, members)
327
+ if members.empty?
328
+ puts Rainbow('Please type each member you would like to add.').color(INFO_CODE)
329
+ else
330
+ people = split_members(members)
331
+ spinner = custom_spinner('Adding member(s) :spinner ...')
458
332
  people.each do |i|
459
- if !list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"].include?(i)
460
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"].push(i)
461
- else
462
- puts "#{i} is already in this assignment."
463
- end
333
+ options = { role: 'member', user: i.to_s }
334
+ client.update_organization_membership(config['Org'].to_s, options)
464
335
  end
465
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
336
+ spinner.stop(Rainbow('done!').color(4, 255, 0))
466
337
  end
338
+ rescue StandardError => exception
339
+ puts Rainbow(exception.message.to_s).color(ERROR_CODE)
467
340
  end
468
341
 
469
- def add_group_to_assig(client,config,assig)
470
- list=self.load_assig()
471
-
472
- groups=self.assignment_groups(client,config)
473
- if groups!=""
474
- groups.each do |i|
475
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["groups"].push(i)
476
- end
477
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
478
- end
342
+ # Add members from file (JSON). File must be located somewhere in HOME.
343
+ #
344
+ # @param client [Object] Octokit client object
345
+ # @param config [Hash] user configuration tracking current org, repo, etc.
346
+ def add_members_from_file(client, config, path)
347
+ file_path = "#{Dir.home}#{path}"
348
+ members_json = File.read(file_path)
349
+ members_file = JSON.parse(members_json)
350
+ spinner = custom_spinner('Adding members from file :spinner ...')
351
+ members_file['members'].each do |member|
352
+ options = { role: 'member', user: member['id'].to_s }
353
+ client.update_organization_membership(config['Org'].to_s, options)
354
+ end
355
+ spinner.stop(Rainbow('done!').color(4, 255, 0))
356
+ rescue Errno::ENOENT
357
+ puts Rainbow('Could not open file, check file location.').color(ERROR_CODE)
358
+ rescue StandardError => exception
359
+ puts Rainbow(exception.message.to_s).color(ERROR_CODE)
479
360
  end
480
361
 
481
- def open_assig(config,assig)
482
- Sys.new.open_url("https://github.com/search?q=org%3A#{config["Org"]}+#{assig}")
483
- end
484
-
485
- #Takes people info froma a csv file and gets into ghedsh people information
486
- def add_people_info(client,config,file,relation)
487
- list=self.load_people()
488
- csvoptions={:quote_char => "|",:headers=>true,:skip_blanks=>true}
489
- members=self.get_organization_members(client,config) #members of the organization
490
- change=false
491
- indexname=""
492
-
493
- inpeople=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
494
- if inpeople==nil
495
- list["orgs"].push({"name"=>config["Org"],"users"=>[]})
496
- Sys.new.save_people("#{ENV['HOME']}/.ghedsh",list)
497
- end
498
-
499
- if file.end_with?(".csv")==false
500
- file=file+".csv"
501
- end
502
- if File.exist?(file)
503
- begin
504
- mem = CSV.read(file,csvoptions)
505
- rescue
506
- print "Invalid csv format."
507
- end
508
-
509
- fields=mem.headers
510
- users=Hash.new;
511
- users=[]
512
- puts "\nFields found: "
513
- puts fields
514
- puts
515
- mem.each do |i|
516
- aux=Hash.new
517
- fields.each do |j|
518
- if i[j]!=nil
519
- if GITHUB_LIST.include?(j.gsub("\"", "").downcase.strip)
520
- data=i[j]
521
- data=data.gsub("\"", "")
522
- aux["github"]=data
523
- j="github"
524
- else
525
- if MAIL_LIST.include?(j.gsub("\"", "").downcase.strip)
526
- aux["email"]=i[j].gsub("\"", "").strip
527
- indexname=j
528
- j="email"
529
- change=true
530
- else
531
- data=i[j].gsub("\"", "")
532
- aux[j.gsub("\"", "").downcase.strip]=data.strip
533
- end
534
- end
535
- else
536
- data=i[j].gsub("\"", "")
537
- aux[j.gsub("\"", "").downcase.strip]=data.strip
538
- end
539
- end
540
- users.push(aux)
541
- end
542
- ## Aqui empiezan las diferenciaa
543
- if relation==true
544
- if change==true
545
- fields[fields.index(indexname)]="email"
546
- end
547
- fields=users[0].keys
548
- # if users.keys.include?("github") and users.keys.include?("email") and users.keys.size==2
549
- if fields.include?("github") and fields.include?("email") and fields.size==2
550
- users.each do |i|
551
- if members.include?(i["github"].delete("\""))
552
- here=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].detect{|aux2| aux2["github"]==i["github"]} #miro si ya esta registrado
553
- if here==nil
554
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"]<<i
555
- puts "#{i["github"]} information correctly added"
556
- else #si ya esta registrado...
557
- puts "#{i["github"]} is already registered in this organization"
558
- end
559
- else
560
- puts "#{i["github"]} is not registered in this organization"
561
- end
562
- end
563
- else
564
- puts "No relationship found between github users and emails."
565
- return nil
566
- end
567
- else #insercion normal, relacion ya hecha
568
- users.each do |i|
569
- here=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].detect{|aux2| aux2["email"]==i["email"]}
570
- if here!=nil
571
- i.each do |j|
572
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].detect{|aux2| aux2["email"]==i["email"]}["#{j[0]}"]=j[1]
573
- end
574
- else
575
- puts "No relation found of #{i["email"]} in #{config["Org"]}"
576
- end
362
+ # Removes a group of members form current organization. It is possible to specify them
363
+ # in a file or with Regexp to match GitHub IDs.
364
+ #
365
+ # @param client [Object] Octokit client object
366
+ # @param config [Hash] user configuration tracking current org, repo, etc.
367
+ # @param param [String, Regexp] if string must be file path to remove memebers from file
368
+ # if Regexp will remove matching GitHub IDs from organization
369
+ def delete_member(client, config, param)
370
+ permissions = client.organization_membership(config['Org'], opts = { user: client.login })
371
+ unless permissions[:role] == 'admin'
372
+ puts Rainbow("You must have Admin permissions on #{config['Org']} to run this command.").underline.color(WARNING_CODE)
373
+ return
374
+ end
375
+ if is_file?("#{Dir.home}#{param}")
376
+ puts 'Removing member/members from file.'
377
+ file_path = "#{Dir.home}#{param}"
378
+ remove_json = File.read(file_path)
379
+ remove_member_file = JSON.parse(remove_json)
380
+ remove_member_file['remove'].each do |member|
381
+ client.remove_organization_member(congif['Org'], member['id'].to_s)
382
+ end
383
+ elsif eval(param).is_a?(Regexp)
384
+ members_to_remove = []
385
+ pattern = build_regexp_from_string(param)
386
+ client.organization_members(config['Org'].to_s).each do |member|
387
+ members_to_remove.push(member[:login]) if pattern.match(member[:login])
388
+ end
389
+ if members_to_remove.empty?
390
+ puts Rainbow("No members to remove matched with \/#{pattern.source}\/").color(WARNING_CODE)
391
+ else
392
+ members_to_remove.each do |i|
393
+ client.remove_organization_member(congif['Org'], i.to_s)
577
394
  end
578
395
  end
579
- #tocho
580
- Sys.new.save_people("#{ENV['HOME']}/.ghedsh",list)
581
- else
582
- print "\n#{file} file not found.\n\n"
583
396
  end
397
+ rescue SyntaxError => e
398
+ puts Rainbow('Parameter is not a file and there was a Syntax Error building Regexp.').color(ERROR_CODE)
399
+ rescue StandardError => e
400
+ puts Rainbow(e.message.to_s).color(ERROR_CODE)
584
401
  end
585
402
 
586
- def rm_people_info(client,config)
587
- list=self.load_people()
588
- inpeople=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
589
- if inpeople==nil
590
- puts "Extended information has not been added yet"
403
+ # Invite as members all outside collaborators from organization.
404
+ # This action needs admin permissions on the organization.
405
+ # This method checks first if parameter is an existing file, then checks Regexp, else invites all
406
+ # outside collaborators of current organization.
407
+ #
408
+ # @param client [Object] Octokit client object
409
+ # @param config [Hash] user configuration tracking current org, repo, etc.
410
+ # @param param [String, Regexp] file path or Regexp to invite outside collabs
411
+ def invite_all_outside_collaborators(client, config, param)
412
+ permissions = client.organization_membership(config['Org'], opts = { user: client.login })
413
+ unless permissions[:role] == 'admin'
414
+ puts Rainbow("You must have Admin permissions on #{config['Org']} to run this command.").underline.color(WARNING_CODE)
415
+ return
416
+ end
417
+ outside_collaborators = []
418
+ spinner = custom_spinner('Sending invitations :spinner ...')
419
+ if is_file?("#{Dir.home}#{param}")
420
+ puts 'Adding outside collaborators from file'
421
+ file_path = "#{Dir.home}#{param}"
422
+ collab_json = File.read(file_path)
423
+ collab_file = JSON.parse(collab_json)
424
+ collab_file['collabs'].each do |collab|
425
+ outside_collaborators.push(collab['id'])
426
+ end
427
+ elsif eval(param).is_a?(Regexp)
428
+ pattern = build_regexp_from_string(param)
429
+ client.outside_collaborators(config['Org']).each do |i|
430
+ outside_collaborators.push(i[:login]) if pattern.match(i[:login])
431
+ end
591
432
  else
592
- if inpeople["users"].empty?
593
- puts "Extended information has not been added yet"
594
- else
595
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"]=[]
596
- Sys.new.save_people("#{ENV['HOME']}/.ghedsh",list)
597
- puts "The aditional information of #{config["Org"]} has been removed"
433
+ begin
434
+ client.outside_collaborators(config['Org']).each do |i|
435
+ outside_collaborators.push(i[:login])
436
+ end
437
+ rescue StandardError => exception
438
+ puts Rainbow('If you entered file path, please ensure that is the correct path.').color(ERROR_CODE)
598
439
  end
599
440
  end
441
+ outside_collaborators.each do |j|
442
+ options = { role: 'member', user: j.to_s }
443
+ client.update_organization_membership(config['Org'], options)
444
+ end
445
+ spinner.stop(Rainbow('done!').color(4, 255, 0))
446
+ rescue SyntaxError => e
447
+ puts Rainbow('Parameter is not a file and there was a Syntax Error building Regexp.').color(ERROR_CODE)
448
+ rescue StandardError => exception
449
+ puts Rainbow(exception.message.to_s).color(ERROR_CODE)
600
450
  end
601
451
 
602
- def search_rexp_people_info(client,config,exp)
603
- list=self.load_people()
604
- if list!=nil
605
- if list["users"]!=[]
606
- list=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
607
- if exp.match(/^\//)
608
- sp=exp.split('/')
609
- exp=Regexp.new(sp[1],sp[2])
610
- end
611
- list=Sys.new.search_rexp_peoplehash(list["users"],exp)
612
-
613
- if list!=[]
614
- fields=list[0].keys
615
- list.each do |i|
616
- puts "\n\e[31m#{i["github"]}\e[0m"
617
- fields.each do |j|
618
- puts "#{j.capitalize}:\t #{i[j]}"
619
- end
620
- puts
621
- end
622
- end
623
- else
624
- puts "Extended information has not been added yet"
625
- end
452
+ # Open default browser and shows open issues
453
+ #
454
+ # @param config [Hash] user configuration tracking current org, repo, etc.
455
+ # @example open issue's list
456
+ # User > Org > Repo > issues
457
+ def show_issues(config)
458
+ if config['Repo']
459
+ issues_url = "https://github.com/#{config['Org']}/#{config['Repo']}/issues"
460
+ open_url(issues_url)
626
461
  else
627
- list["orgs"].push({"name"=>config["Org"],"users"=>[]})
628
- Sys.new.save_people("#{ENV['HOME']}/.ghedsh",list)
629
- puts "Extended information has not been added yet"
462
+ puts Rainbow('Change to repo in order to view all issues').color(INFO_CODE)
630
463
  end
631
464
  end
632
465
 
633
- def show_people_info(client,config,user)
634
- list=self.load_people()
635
-
636
- inpeople=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
637
- peopleinfolist=[]
638
-
639
- if inpeople==nil
640
- list["orgs"].push({"name"=>config["Org"],"users"=>[]})
641
- Sys.new.save_people("#{ENV['HOME']}/.ghedsh",list)
642
- puts "Extended information has not been added yet"
466
+ # Display organization people. It shows all members and if the authenticated user is org admin
467
+ # also displays outside collaborators.
468
+ #
469
+ # @param client [Object] Octokit client object
470
+ # @param config [Hash] user configuration tracking current org, repo, etc.
471
+ # @param params [Regexp] if provided, it must be a Regexp
472
+ # @example Display organization people
473
+ # User > Org > people
474
+ # @example Display people matching Regexp
475
+ # User > Org > people /alu/
476
+ def show_people(client, config, params)
477
+ spinner = custom_spinner("Fetching #{config['Org']} people :spinner ...")
478
+ spinner.auto_spin
479
+ org_members = []
480
+ client.organization_members(config['Org'].to_s).each do |member|
481
+ org_members << [member[:login], 'member']
482
+ end
483
+ membership = {}
484
+ client.organization_membership(config['Org'].to_s).each do |key, value|
485
+ membership[key] = value
486
+ end
487
+ if membership[:role] == 'admin'
488
+ client.outside_collaborators(config['Org'].to_s).each do |collab|
489
+ org_members << [collab[:login], 'outside collaborator']
490
+ end
491
+ end
492
+ spinner.stop(Rainbow('done!').color(4, 255, 0))
493
+ if params.nil?
494
+ table = Terminal::Table.new headings: ['Github ID', 'Role'], rows: org_members
495
+ puts table
643
496
  else
644
- if inpeople["users"]!=[]
645
- if user==nil
646
- fields=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"][0].keys
647
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].each do |i|
648
- puts "\n\e[31m#{i["github"]}\e[0m"
649
- fields.each do |j|
650
- puts "#{j.capitalize}:\t #{i[j]}"
651
- end
652
- peopleinfolist<<i["github"]
653
- end
654
- return peopleinfolist
655
- else
656
- if user.include?("@")
657
- inuser=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].detect{|aux2| aux2["email"]==user}
658
- else
659
- inuser=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].detect{|aux2| aux2["github"]==user}
660
- end
661
- if inuser==nil
662
- puts "Not extended information has been added of that user."
663
- else
664
- fields=inuser.keys
665
- puts "\n\e[31m#{inuser["github"]}\e[0m"
666
- fields.each do |j|
667
- puts "#{j.capitalize}:\t #{inuser[j]}"
668
- end
669
- puts
670
- end
671
- end
672
- else
673
- puts "Extended information has not been added yet"
497
+ unless params.include?('/')
498
+ raise Rainbow('Parameter must be a Regexp. Example: /pattern/').color(ERROR_CODE)
499
+ return
674
500
  end
501
+ pattern = build_regexp_from_string(params)
502
+ occurrences = build_item_table(org_members, pattern)
503
+ puts Rainbow("No member inside #{config['Org']} matched \/#{pattern.source}\/").color(INFO_CODE) if occurrences.zero?
675
504
  end
676
505
  end
677
506
 
678
- def add_repo_to_assig(client,config,assig,change)
679
- list=self.load_assig()
680
- notexist=false
681
-
682
- if change!=nil #change an specific repository
683
- reponumber=change
684
- fields=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}.keys
685
- if reponumber=="1"
686
- if !fields.include?("repo")
687
- notexist=true
688
- end
507
+ # perform cd to repo scope, if 'name' is a Regexp, matches are stored and then we let the user select one
508
+ # if is not a Regexp, check that the string provided is a vale repository name
509
+ #
510
+ # @param name [String, Regexp] repo name
511
+ # @param client [Object] Octokit client object
512
+ # @param enviroment [ShellContext] contains the shell context, including Octokit client and user config
513
+ # @return [ShellContext] changed context is returned if there is not an error during process.
514
+ def cd_repo(name, client, enviroment)
515
+ if name.class == Regexp
516
+ pattern = Regexp.new(name.source, name.options)
517
+ org_repos = []
518
+ org_repos_url = {}
519
+ spinner = custom_spinner("Matching #{enviroment.config['Org']} repositories :spinner ...")
520
+ spinner.auto_spin
521
+ client.organization_repositories(enviroment.config['Org'].to_s).each do |org_repo|
522
+ if pattern.match(org_repo[:name].to_s)
523
+ org_repos << org_repo[:name]
524
+ org_repos_url[org_repo[:name].to_s] = org_repo[:html_url]
525
+ end
526
+ end
527
+ spinner.stop(Rainbow('done!').color(4, 255, 0))
528
+ if org_repos.empty?
529
+ puts Rainbow("No repository matched with #{name.source} inside organization #{enviroment.config['Org']}").color(WARNING_CODE)
530
+ puts
531
+ return
689
532
  else
690
- if !fields.include?("repo"+change.to_s)
691
- notexist=true
692
- end
693
- end
694
- else #adding new repository
695
- fields=list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}.keys
696
- reponumber=1
697
- ex=0
698
- while ex==0
699
- if reponumber==1
700
- if fields.include?("repo")
701
- reponumber=reponumber+1
702
- else
703
- ex=1
704
- end
705
- else
706
- if fields.include?("repo#{reponumber}")
707
- reponumber=reponumber+1
708
- else
709
- ex=1
710
- end
711
- end
533
+ prompt = TTY::Prompt.new
534
+ answer = prompt.select('Select desired organization repository', org_repos)
535
+ enviroment.config['Repo'] = answer
536
+ enviroment.config['repo_url'] = org_repos_url[answer]
537
+ enviroment.deep = Organization
712
538
  end
713
- end
714
-
715
- if notexist==true
716
- puts "Doesn't exist that repository"
717
539
  else
718
- reponame=self.assignment_repository(client,config,assig["name_assig"])
719
- if reponumber.to_i>1
720
- sufix="sufix#{reponumber}"
721
- sufixname=self.assignment_repo_sufix(reponumber,1)
722
- end
723
- end
724
-
725
- if reponame!="" and notexist==false
726
- if reponumber.to_i==1
727
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["repo"]=reponame
728
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
540
+ if client.repository?("#{enviroment.config['Org']}/#{name}")
541
+ org_repo_url = 'https://github.com/' << enviroment.config['Org'].to_s << '/' << name.to_s
542
+ enviroment.config['Repo'] = name
543
+ enviroment.config['repo_url'] = org_repo_url
544
+ enviroment.deep = Organization
729
545
  else
730
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["repo#{reponumber}"]=reponame
731
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["#{sufix}"]=sufixname
732
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
546
+ puts Rainbow("Maybe #{name} is not an organizaton or currently does not exist.").color(WARNING_CODE)
547
+ return
733
548
  end
734
549
  end
550
+ enviroment
735
551
  end
736
552
 
737
- def rm_assigment_repo(config,assig,reponumber)
738
- list=self.load_assig()
739
-
740
- if reponumber==1
741
- if list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["repo"]!=nil
742
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}.delete("repo")
743
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}.delete("sufix1")
744
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
553
+ # perform cd to team scope, first we retrieve all user's orgs, then we check 'name'
554
+ # if its a Regexp then matches are displayed on screen and user selects one (if no match warning
555
+ # is shown and we return nil)
556
+ # if 'name' is not a Regexp then it must be the full team's name so we check that is inside org_teams
557
+ # Last option is showing a warning and return nil (so we dont push to the stack_context)
558
+ #
559
+ # @param name [String, Regexp] team name
560
+ # @param client [Object] Octokit client object
561
+ # @param enviroment [ShellContext] contains the shell context, including Octokit client and user config
562
+ # @return [ShellContext] changed context is returned if there is not an error during process.
563
+ def cd_team(name, client, enviroment)
564
+ org_teams = []
565
+ org_teams_id = {}
566
+ spinner = custom_spinner("Fetching #{enviroment.config['Org']} teams :spinner ...")
567
+ spinner.auto_spin
568
+ client.organization_teams(enviroment.config['Org'].to_s).each do |team|
569
+ org_teams << team[:name]
570
+ org_teams_id[team[:name].to_s] = team[:id]
571
+ end
572
+ spinner.stop(Rainbow('done!').color(4, 255, 0))
573
+ if name.class == Regexp
574
+ pattern = Regexp.new(name.source, name.options)
575
+ name_matches = []
576
+ org_teams.each do |team_name|
577
+ name_matches << team_name if pattern.match(team_name.to_s)
578
+ end
579
+ if name_matches.empty?
580
+ puts Rainbow("No team matched with \/#{name.source}\/ inside organization #{enviroment.config['Org']}").color(WARNING_CODE)
581
+ puts
582
+ return
745
583
  else
746
- puts "Doesn't exist that repository"
584
+ prompt = TTY::Prompt.new
585
+ answer = prompt.select('Select desired organization', name_matches)
586
+ enviroment.config['Team'] = answer
587
+ enviroment.config['TeamID'] = org_teams_id[answer]
588
+ enviroment.config['team_url'] = 'https://github.com/orgs/' << enviroment.config['Org'] << '/teams/' << enviroment.config['Team']
589
+ enviroment.deep = Team
747
590
  end
748
591
  else
749
- if list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["repo#{reponumber}"]!=nil
750
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}.delete("repo#{reponumber}")
751
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}.delete("sufix#{reponumber}")
752
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
592
+ if org_teams.include?(name)
593
+ enviroment.config['Team'] = name
594
+ enviroment.config['TeamID'] = org_teams_id[name]
595
+ enviroment.config['team_url'] = 'https://github.com/orgs/' << enviroment.config['Org'] << '/teams/' << enviroment.config['Team']
596
+ enviroment.deep = Team
753
597
  else
754
- puts "Doesn't exist that repository"
598
+ puts Rainbow("Maybe #{name} is not a #{enviroment.config['Org']} team or currently does not exist.").color(WARNING_CODE)
599
+ puts
600
+ return
755
601
  end
756
602
  end
603
+ enviroment
757
604
  end
758
605
 
759
- def rm_assigment_student(config,assig,student,mode)
760
- list=self.load_assig()
761
- if mode==1
762
- if list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"].include?(student)
763
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"].delete(student)
764
- puts "Student #{student} correctly deleted from the assignment"
765
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
766
- else
767
- puts "Student not found"
768
- end
769
- else
770
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["people"].clear()
771
- puts "All the students has been deleted from the assignment"
772
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
773
- end
606
+ # cd method contains a hash representing where user can navigate depending on context. In this case
607
+ # inside an organization user is allowed to change to a org repo and org team.
608
+ # In order to add new 'cd types' see example below.
609
+ #
610
+ # @param type [String] name of the navigating option (repo, org, team, etc.)
611
+ # @param name [String, Regexp] String or Regexp to find the repository.
612
+ # @example add 'chuchu' cd scope
613
+ # add to cd_scopes = {'chuchu => method(:cd_chuchu)}
614
+ # call it with (name, client, enviroment) parameters
615
+ def cd(type, name, client, enviroment)
616
+ cd_scopes = { 'repo' => method(:cd_repo), 'team' => method(:cd_team) }
617
+ cd_scopes[type].call(name, client, enviroment)
774
618
  end
775
619
 
776
- def rm_assigment_group(config,assig,group,mode)
777
- list=self.load_assig()
778
- if mode==1
779
- if list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["groups"].include?(group)
780
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["groups"].delete(group)
781
- puts "Group #{group} correctly deleted from the assignment"
782
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
783
- else
784
- puts "Group not found"
785
- end
620
+ # Show commits of current repo. If user is not in a repo, repository name for commit showing must be provided.
621
+ #
622
+ # @param enviroment [ShellContext] contains the shell context, including Octokit client and user config
623
+ # @param params [Array<String>] if user is not on a repo then params[0] is repo name. Optionally,
624
+ # branch can be specified (value is in params[1]), if not provided default branch is master
625
+ def show_commits(enviroment, params)
626
+ options = {}
627
+ if !enviroment.config['Repo'].nil?
628
+ repo = enviroment.config['Repo']
629
+ options[:sha] = if params.empty?
630
+ 'master'
631
+ else
632
+ params[0]
633
+ end
786
634
  else
787
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["groups"].clear()
788
- puts "All the groups has been deleted from the assignment"
789
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
635
+ repo = params[0]
636
+ options[:sha] = if params[1].nil?
637
+ 'master'
638
+ else
639
+ params[1]
640
+ end
790
641
  end
791
- end
792
-
793
- def assignment_repo_sufix(reponumber,order)
794
- op=""
795
- print "\n"
796
- if order==1
797
- while op=="" or op=="\n"
798
- puts "Add the suffix of the repository \"#{reponumber}\", in order to differentiate it from the other repositories: "
799
- op=gets.chomp
800
- end
801
- else
802
- while op=="" or op=="\n"
803
- puts "Add the suffix of the first repository, in order to differentiate it from the other repositories: "
804
- op=gets.chomp
642
+ begin
643
+ enviroment.client.commits("#{enviroment.config['Org']}/#{repo}", options).each do |i|
644
+ puts "\tSHA: #{i[:sha]}"
645
+ puts "\t\t Commit date: #{i[:commit][:author][:date]}"
646
+ puts "\t\t Commit author: #{i[:commit][:author][:name]}"
647
+ puts "\t\t\t Commit message: #{i[:commit][:message]}"
648
+ puts
805
649
  end
650
+ rescue StandardError => exception
651
+ puts exception
652
+ puts Rainbow("If you are not currently on a repo, USAGE TIP: `commits <repo_name> [branch_name]` (default: 'master')").color(INFO_CODE)
806
653
  end
807
- return op
808
654
  end
809
655
 
810
- def change_repo_sufix(config,assig,reponumber)
811
- list=self.load_assig()
812
- if list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["sufix#{reponumber}"]!=nil
813
- if reponumber==1
814
- sufixname=self.assignment_repo_sufix(reponumber,2)
815
- sufix="sufix1"
816
- else
817
- sufixname=self.assignment_repo_sufix(reponumber,1)
818
- sufix="sufix#{reponumber}"
819
- end
820
-
821
- list["orgs"].detect{|aux| aux["name"]==config["Org"]}["assigs"].detect{|aux2| aux2["name_assig"]==assig}["#{sufix}"]=sufixname
822
- Sys.new.save_assigs("#{ENV['HOME']}/.ghedsh",list)
823
- else
824
- puts "Doesn't exist a repository with that identifier"
825
- end
656
+ # Repo creation: creates new repo inside current org
657
+ #
658
+ # @param enviroment [ShellContext] contains the shell context, including Octokit client and user config
659
+ # @param repo_name [String] repository name
660
+ # @param options [Hash] repository options (repo organization, visibility, etc)
661
+ def create_repo(enviroment, repo_name, options)
662
+ client = enviroment.client
663
+ options[:organization] = enviroment.config['Org'].to_s
664
+ client.create_repository(repo_name, options)
665
+ puts Rainbow('Repository created correctly!').color(79, 138, 16)
666
+ rescue StandardError => exception
667
+ puts Rainbow(exception.message.to_s).color(ERROR_CODE)
668
+ puts
826
669
  end
827
670
 
828
- def show_organization_members_bs(client,config)
829
- orgslist=[]
830
- print "\n"
831
- mem=client.organization_members(config["Org"])
832
- mem.each do |i|
833
- m=eval(i.inspect)
834
- orgslist.push(m[:login])
835
- puts m[:login]
836
- end
671
+ # Removes repository by name
672
+ #
673
+ # @param enviroment [ShellContext] contains the shell context, including Octokit client and user config
674
+ # @param repo_name [String] name of the repo to be removed
675
+ def remove_repo(enviroment, repo_name)
676
+ client = enviroment.client
677
+ client.delete_repository("#{enviroment.config['Org']}/#{repo_name}")
678
+ puts Rainbow('Repository deleted.').color(INFO_CODE)
679
+ rescue StandardError => exception
680
+ puts Rainbow(exception.message.to_s).color(ERROR_CODE)
837
681
  puts
838
- return orgslist
839
682
  end
840
683
 
841
- def get_organization_members(client,config)
842
- mem=client.organization_members(config["Org"])
843
- list=[]
844
- if mem!=nil
845
- mem.each do |i|
846
- list<<i[:login]
684
+ # Removes a group of GitHub users from an organization
685
+ #
686
+ #
687
+ def remove_org_member; end
688
+
689
+ # Team creation: opens team creation page on default browser when calling new_team
690
+ # with no options. If option provided, it must be the directory and name of the JSON file
691
+ # somewhere in your HOME directory for bulk creation of teams and members.
692
+ #
693
+ # @param client [Object] Octokit client object
694
+ # @param config [Hash] user configuration tracking current org, repo, etc.
695
+ # @param params [Array<String>] user specified parameters, if not nil must be path to teams JSON file.
696
+ # @example Open current org URL and create new team
697
+ # User > Org > new_team
698
+ # @example Create multiple teams with its members inside current org
699
+ # User > Org > new_team (HOME)/path/to/file/creation.json
700
+ def create_team(client, config, params)
701
+ if params.nil?
702
+ team_creation_url = "https://github.com/orgs/#{config['Org']}/new-team"
703
+ open_url(team_creation_url)
704
+ else
705
+ members_not_added = []
706
+ begin
707
+ file_path = "#{Dir.home}#{params}"
708
+ teams_json = File.read(file_path)
709
+ teams_file = JSON.parse(teams_json)
710
+ spinner = custom_spinner("Creating teams in #{config['Orgs']} :spinner ...")
711
+ teams_file['teams'].each do |team|
712
+ # assigned to 'created_team' to grab the ID (and use it for adding members)
713
+ # of the newly created team
714
+ created_team = client.create_team(config['Org'].to_s,
715
+ name: team['name'].to_s,
716
+ privacy: team['privacy'])
717
+ team['members'].each do |member|
718
+ member_addition = client.add_team_member(created_team[:id], member)
719
+ members_not_added.push(member) if member_addition == false # if !!member_adition
720
+ end
721
+ end
722
+ spinner.stop(Rainbow('done!').color(4, 255, 0))
723
+ puts Rainbow('Teams created correctly!').color(79, 138, 16)
724
+ puts Rainbow("Could not add following members: #{members_not_added}").color(WARNING_CODE) unless members_not_added.empty?
725
+ rescue StandardError => e
726
+ puts Rainbow(e.message.to_s).color(ERROR_CODE)
847
727
  end
848
728
  end
849
- return list
850
729
  end
851
730
 
852
- def show_orgs(client,config)
853
- orgslist=[]
854
- print "\n"
855
- org=client.organizations
856
- org.each do |i|
857
- o=eval(i.inspect)
858
- puts o[:login]
859
- orgslist.push(o[:login])
860
- end
861
- print "\n"
862
- return orgslist
731
+ # Open org's team list and delete manually (faster than checking teams IDs and delete it)
732
+ #
733
+ # @param client [Object] Octokit client object
734
+ # @param config [Hash] user configuration tracking current org, repo, etc.
735
+ def remove_team(config)
736
+ teams_url = "https://github.com/orgs/#{config['Org']}/teams"
737
+ open_url(teams_url)
863
738
  end
864
739
 
865
740
  def read_orgs(client)
866
- orgslist=[]
867
- org=client.organizations
741
+ orgslist = []
742
+ org = client.organizations
868
743
  org.each do |i|
869
- o=eval(i.inspect)
744
+ o = eval(i.inspect)
870
745
  orgslist.push(o[:login])
871
746
  end
872
- return orgslist
873
- end
874
-
875
- def open_org(client,config)
876
- mem=client.organization(config["Org"])
877
- Sys.new.open_url(mem[:html_url])
878
- end
879
-
880
- def open_user_url(client,config,user,field)
881
- list=self.load_people()
882
- inpeople=list["orgs"].detect{|aux| aux["name"]==config["Org"]}
883
- found=0
884
-
885
- if inpeople==nil
886
- list["orgs"].push({"name"=>config["Org"],"users"=>[]})
887
- Sys.new.save_people("#{ENV['HOME']}/.ghedsh",list)
888
- puts "Extended information has not been added yet"
889
- else
890
- if user.downcase.start_with?("/") and user.downcase.count("/")==2
891
- sp=user.split('/')
892
- exp=Regexp.new(sp[1],sp[2])
893
- inuser=Sys.new.search_rexp_peoplehash(list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"],exp)
894
- user.slice!(0); user=user.chop
895
- else
896
- inuser=[]
897
- inuser.push(list["orgs"].detect{|aux| aux["name"]==config["Org"]}["users"].detect{|aux2| aux2["github"]==user})
898
- end
899
- if inuser==nil
900
- puts "Not extended information has been added of that user."
901
- else
902
- if field==nil
903
- inuser.each do |i|
904
- i.each_value do |j|
905
- if j.include?("github.com")
906
- if !j.include?("https://") && !j.include?("http://")
907
- Sys.new.open_url("https://"+j)
908
- else
909
- Sys.new.open_url(j)
910
- end
911
- found=1
912
- end
913
- end
914
- end
915
- if found==0
916
- puts "No github web profile in the aditional information"
917
- end
918
- else
919
- if inuser!=[]
920
- if field.downcase.start_with?("/") and field.downcase.end_with?("/") ##regexp
921
- field=field.delete("/")
922
- inuser.each do |i|
923
- if i!=nil
924
- i.each_value do |j|
925
- if j.include?(field)
926
- if j.include?("https://") || j.include?("http://")
927
- Sys.new.open_url(j)
928
- end
929
- end
930
- end
931
- end
932
- end
933
- else
934
- inuser.each do |i|
935
- if inuser.keys.include?(field.downcase)
936
- if inuser[field.downcase].include?("https://") or inuser[field.downcase].include?("http://")
937
- url=inuser["#{field.downcase}"]
938
- else
939
- url="http://"+inuser["#{field.downcase}"]
940
- end
941
- Sys.new.open_url(url)
942
- else
943
- puts "No field found with that name"
944
- end
945
- end
946
- end
947
- else
948
- puts "No field found with that name"
949
- end
950
- end
951
- end
952
- end
747
+ orgslist
953
748
  end
954
-
955
749
  end