teachers_pet 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CONTRIBUTING.md +19 -0
- data/Gemfile +3 -0
- data/Guardfile +5 -0
- data/LICENSE +20 -0
- data/README.md +76 -0
- data/Rakefile +6 -0
- data/bin/clone_repos +6 -0
- data/bin/create_repos +6 -0
- data/bin/create_teams +6 -0
- data/bin/fork_collab +6 -0
- data/bin/push_files +6 -0
- data/lib/teachers_pet/actions/base.rb +160 -0
- data/lib/teachers_pet/actions/clone_repos.rb +89 -0
- data/lib/teachers_pet/actions/create_repos.rb +82 -0
- data/lib/teachers_pet/actions/create_teams.rb +102 -0
- data/lib/teachers_pet/actions/fork_collab.rb +45 -0
- data/lib/teachers_pet/actions/push_files.rb +75 -0
- data/lib/teachers_pet/configuration.rb +67 -0
- data/lib/teachers_pet/version.rb +1 -1
- data/lib/teachers_pet.rb +2 -0
- data/spec/actions/clone_repos_spec.rb +44 -0
- data/spec/actions/create_teams_spec.rb +40 -0
- data/spec/fixtures/instructors +3 -0
- data/spec/fixtures/students +3 -0
- data/spec/spec_helper.rb +56 -0
- data/teachers_pet.gemspec +17 -8
- metadata +133 -15
- data/bin/forkcollab +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34f31b6d378cbb283cead5e47e9a115b04fc48dd
|
4
|
+
data.tar.gz: c67ba2a5cb528af7e6a956b00162bbfe4b6d789b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8abd43bf0409d38576a414321598658e8cc04edf95b3e1e3b287887b437e6e5da38ab548c52e544e89e78eb0e63cd3b531bd2f1534495f30777a4b2dead499d4
|
7
|
+
data.tar.gz: 3e3137a1d275ea9381356aaa39bf3e784ecbdb11c88ecb213e96e2f2681350d46cc8240f21752ca2d2730831b4355d0058150a5a3a6488c6644d11c1a02fe649
|
data/.gitignore
CHANGED
@@ -1,8 +1,14 @@
|
|
1
|
+
# project specific
|
2
|
+
/instructors
|
3
|
+
/students
|
4
|
+
/teams
|
5
|
+
|
1
6
|
*.gem
|
2
7
|
*.rbc
|
3
8
|
.bundle
|
4
9
|
.config
|
5
10
|
coverage
|
11
|
+
Gemfile.lock
|
6
12
|
InstalledFiles
|
7
13
|
lib/bundler/man
|
8
14
|
pkg
|
@@ -16,3 +22,13 @@ tmp
|
|
16
22
|
.yardoc
|
17
23
|
_yardoc
|
18
24
|
doc/
|
25
|
+
|
26
|
+
# OS generated files #
|
27
|
+
######################
|
28
|
+
.DS_Store
|
29
|
+
.DS_Store?
|
30
|
+
._*
|
31
|
+
.Spotlight-V100
|
32
|
+
.Trashes
|
33
|
+
ehthumbs.db
|
34
|
+
Thumbs.db
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
## Development
|
2
|
+
|
3
|
+
```bash
|
4
|
+
git clone https://github.com/education/teachers_pet.git
|
5
|
+
cd teachers_pet
|
6
|
+
bundle install
|
7
|
+
# then run actions using
|
8
|
+
bundle exec ./bin/COMMAND
|
9
|
+
```
|
10
|
+
|
11
|
+
## Running tests
|
12
|
+
|
13
|
+
```bash
|
14
|
+
bundle
|
15
|
+
# then
|
16
|
+
bundle exec rspec
|
17
|
+
# or
|
18
|
+
bundle exec guard
|
19
|
+
```
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 GitHub Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# teachers_pet [![Build Status](https://travis-ci.org/education/teachers_pet.svg?branch=master)](https://travis-ci.org/education/teachers_pet)
|
2
|
+
|
3
|
+
Command line tools to help teachers use GitHub in their classrooms.
|
4
|
+
|
5
|
+
## Philosophy
|
6
|
+
|
7
|
+
Each class is an 'organization' on GitHub. This allows the instructors (GitHub organization Owners) to create, push, pull, and administer all repositories. This achieves two goals:
|
8
|
+
|
9
|
+
* Instructors can push code starter code to all students
|
10
|
+
* Instructors can easily browse/pull student code at any time during the assignment to assist in questions, check on progress
|
11
|
+
|
12
|
+
Each student is given a team in the organization. The team name is the same as the student's GitHub username. The course instructors are also added as team members for each team (see the goals above).
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
[Install Ruby 1.9.3+](https://www.ruby-lang.org/en/installation/), then run
|
17
|
+
|
18
|
+
```bash
|
19
|
+
gem install teachers_pet
|
20
|
+
```
|
21
|
+
|
22
|
+
To use the latest-and-greatest code from this repository, see the instructions in [CONTRIBUTING.md](CONTRIBUTING.md).
|
23
|
+
|
24
|
+
## Basic Setup
|
25
|
+
|
26
|
+
* Create an organization (you will be an owner by default). The organization should reflect the name of your course.
|
27
|
+
* Create a `students` file (you will be prompted for the path later)
|
28
|
+
* Individual assignments: one username per line
|
29
|
+
* Group assignments: one team per line in the format `teamName username username username`
|
30
|
+
* Add the GitHub username of all instructors to an 'instructors' file (one per line)
|
31
|
+
* Run `create_teams`
|
32
|
+
|
33
|
+
## Passwords / OAuth
|
34
|
+
|
35
|
+
The scripts will ask for your GitHub password in order to run. If you have [two factor authentication](https://help.github.com/articles/about-two-factor-authentication) enabled, [create a personal access token](https://help.github.com/articles/creating-an-access-token-for-command-line-use) (replace `github.com` with your host for GitHub Enterprise):
|
36
|
+
|
37
|
+
https://github.com/settings/tokens/new?description=teachers_pet&scopes=repo%2Cpublic_repo%2Cwrite%3Aorg%2Crepo%3Astatus%2Cread%3Aorg%2Cuser%2Cadmin%3Aorg
|
38
|
+
|
39
|
+
Then, add the following line to your `.bash_profile`:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
export ghe_oauth="YOUR_TOKEN_HERE"
|
43
|
+
```
|
44
|
+
|
45
|
+
## Actions
|
46
|
+
|
47
|
+
### Creating assignments
|
48
|
+
|
49
|
+
For each assignment, run `create_repos` to create a repository for each student. The repositories are technically created per team, but if you use `create_teams` first, then there will be one team per student.
|
50
|
+
|
51
|
+
### Collaborator access
|
52
|
+
|
53
|
+
Give collaborator access to everyone who has forked your repository.
|
54
|
+
|
55
|
+
```bash
|
56
|
+
fork_collab
|
57
|
+
```
|
58
|
+
|
59
|
+
### Pushing starter files
|
60
|
+
|
61
|
+
This is the workflow that we use. Create a private repository on GitHub. Clone it to your machine and place in all the necessary starter files (.gitignore and build files, like Makefile are highly recommended). Commit and push this repository to the origin.
|
62
|
+
|
63
|
+
While in the directory for the starter file repository, run the `push_repos` script.
|
64
|
+
|
65
|
+
This works by creating a git remote for each repository and thing doing a push to that repository.
|
66
|
+
|
67
|
+
### Pulling repositories for grading
|
68
|
+
|
69
|
+
When grading, use the `clone_repos` script to clone all the repositories in the organization that match the username-repository naming scheme that is generated when `create_repos` is run.
|
70
|
+
|
71
|
+
## Related projects
|
72
|
+
|
73
|
+
* https://education.github.com/guide
|
74
|
+
* https://github.com/hogbait/6170_repo_management
|
75
|
+
* https://github.com/mikehelmick/edugit-scripts
|
76
|
+
* https://github.com/UCSB-CS-Using-GitHub-In-Courses/github-acad-scripts
|
data/Rakefile
ADDED
data/bin/clone_repos
ADDED
data/bin/create_repos
ADDED
data/bin/create_teams
ADDED
data/bin/fork_collab
ADDED
data/bin/push_files
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..')
|
2
|
+
|
3
|
+
require 'configuration'
|
4
|
+
|
5
|
+
## Common code for the edugit scripts.
|
6
|
+
module TeachersPet
|
7
|
+
module Actions
|
8
|
+
class Base
|
9
|
+
def get_auth_method
|
10
|
+
auth_method = nil
|
11
|
+
|
12
|
+
choose do |menu|
|
13
|
+
menu.prompt = "Login via oAuth or Password? "
|
14
|
+
menu.choice :oAuth do
|
15
|
+
auth_method = 'oauth'
|
16
|
+
end
|
17
|
+
menu.choice :Password do
|
18
|
+
auth_method = 'password'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
auth_method
|
23
|
+
end
|
24
|
+
|
25
|
+
def config_github
|
26
|
+
return unless @username.nil?
|
27
|
+
@api_endpoint = ask('What is the API endpoint?') { |q| q.default = Configuration.apiEndpoint }
|
28
|
+
@web_endpoint = ask("What is the Web endpoint?") { |q| q.default = Configuration.webEndpoint }
|
29
|
+
|
30
|
+
@username = ask('What is your username? (You must be an owner for the organization)?') { |q| q.default = ENV['USER'] }
|
31
|
+
|
32
|
+
@authmethod = self.get_auth_method
|
33
|
+
|
34
|
+
if @authmethod == 'oauth'
|
35
|
+
@oauthtoken = ask('What is your oAuth token?') { |q| q.default = ENV['ghe_oauth'] }
|
36
|
+
end
|
37
|
+
if @authmethod == 'password'
|
38
|
+
@password = ask('What is your password?') { |q| q.echo = false }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def init_client
|
43
|
+
self.config_github
|
44
|
+
puts "=" * 50
|
45
|
+
puts "Authenticating to GitHub..."
|
46
|
+
Octokit.configure do |c|
|
47
|
+
c.api_endpoint = @api_endpoint
|
48
|
+
c.web_endpoint = @web_endpoint
|
49
|
+
# Organizations can get big, pull in all pages
|
50
|
+
c.auto_paginate = true
|
51
|
+
end
|
52
|
+
|
53
|
+
if @authmethod == 'password'
|
54
|
+
@client = Octokit::Client.new(:login => @username, :password => @password)
|
55
|
+
end
|
56
|
+
if @authmethod == 'oauth'
|
57
|
+
@client = Octokit::Client.new(:login => @username, :access_token => @oauthtoken)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def read_organization(organization)
|
62
|
+
abort("GitHub client not initialized") if @client.nil?
|
63
|
+
@client.organization(organization)
|
64
|
+
end
|
65
|
+
|
66
|
+
def execute(command)
|
67
|
+
`#{command}`
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
def repository?(organization, repo_name)
|
72
|
+
begin
|
73
|
+
@client.repository("#{organization}/#{repo_name}")
|
74
|
+
rescue
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_students_file_path
|
80
|
+
ask("What is the filename of the list of students?") { |q| q.default = TeachersPet::Configuration.studentsFile }
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_instructors_file_path
|
84
|
+
ask("What is the filename of the list of instructors?") { |q| q.default = TeachersPet::Configuration.instructorsFile }
|
85
|
+
end
|
86
|
+
|
87
|
+
def get_existing_repos_by_names(organization)
|
88
|
+
repos = Hash.new
|
89
|
+
response = @client.organization_repositories(organization)
|
90
|
+
print " Org repo names"
|
91
|
+
response.each do |repo|
|
92
|
+
repos[repo[:name]] = repo
|
93
|
+
print " '#{repo[:name]}'"
|
94
|
+
end
|
95
|
+
print "\n";
|
96
|
+
return repos
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_teams_by_name(organization)
|
100
|
+
org_teams = @client.organization_teams(organization)
|
101
|
+
teams = Hash.new
|
102
|
+
org_teams.each do |team|
|
103
|
+
teams[team[:name]] = team
|
104
|
+
end
|
105
|
+
return teams
|
106
|
+
end
|
107
|
+
|
108
|
+
def get_team_member_logins(team_id)
|
109
|
+
@client.team_members(team_id).map do |member|
|
110
|
+
member[:login]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def read_file(filename, type)
|
115
|
+
map = Hash.new
|
116
|
+
puts "Loading #{type}:"
|
117
|
+
File.open(filename).each_line do |team|
|
118
|
+
# Team can be a single user, or a team name and multiple users
|
119
|
+
# Trim whitespace, otherwise issues occur
|
120
|
+
team.strip!
|
121
|
+
items = team.split(' ')
|
122
|
+
items.each do |item|
|
123
|
+
abort("No users can be named 'owners' (in any case)") if 'owners'.eql?(item.downcase)
|
124
|
+
end
|
125
|
+
|
126
|
+
if map[items[0]].nil?
|
127
|
+
map[items[0]] = Array.new
|
128
|
+
puts " -> #{items[0]}"
|
129
|
+
if (items.size > 1)
|
130
|
+
print " \\-> members: "
|
131
|
+
1.upto(items.size - 1) do |i|
|
132
|
+
print "#{items[i]} "
|
133
|
+
map[items[0]] << items[i]
|
134
|
+
end
|
135
|
+
print "\n"
|
136
|
+
else
|
137
|
+
map[items[0]] << items[0]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
return map
|
142
|
+
end
|
143
|
+
|
144
|
+
def confirm(message, abort_on_no = true)
|
145
|
+
# confirm
|
146
|
+
confirmed = false
|
147
|
+
choose do |menu|
|
148
|
+
menu.prompt = message
|
149
|
+
|
150
|
+
menu.choice :yes do confirmed = true end
|
151
|
+
menu.choice :no do confirmed = false end
|
152
|
+
end
|
153
|
+
if abort_on_no && !confirmed
|
154
|
+
abort("Creation canceled by user")
|
155
|
+
end
|
156
|
+
return confirmed
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Author: Mike Helmick
|
2
|
+
# Clones all student repositories for a particular assignment
|
3
|
+
#
|
4
|
+
# Currently this will clone all student repositories into the current
|
5
|
+
|
6
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..')
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'highline/question'
|
10
|
+
require 'highline/import'
|
11
|
+
require 'highline/compatibility'
|
12
|
+
require 'octokit'
|
13
|
+
require 'teachers_pet/actions/base'
|
14
|
+
|
15
|
+
module TeachersPet
|
16
|
+
module Actions
|
17
|
+
class CloneRepos < Base
|
18
|
+
def read_info
|
19
|
+
@repository = ask('What repository name should be cloned for each student?') { |q| q.validate = /\w+/ }
|
20
|
+
@organization = ask("What is the organization name?") { |q| q.default = TeachersPet::Configuration.organization }
|
21
|
+
@student_file = self.get_students_file_path
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_files
|
25
|
+
@students = read_file(@student_file, 'Students')
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_clone_method
|
29
|
+
cloneMethod = 'https'
|
30
|
+
choose do |menu|
|
31
|
+
menu.prompt = "Clone via? "
|
32
|
+
menu.choice :ssh do
|
33
|
+
cloneMethod = 'ssh'
|
34
|
+
end
|
35
|
+
menu.choice :https do
|
36
|
+
cloneMethod = 'https'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
cloneMethod
|
41
|
+
end
|
42
|
+
|
43
|
+
def create
|
44
|
+
cloneMethod = self.get_clone_method
|
45
|
+
|
46
|
+
confirm("Clone all repositories?")
|
47
|
+
|
48
|
+
# create a repo for each student
|
49
|
+
self.init_client
|
50
|
+
|
51
|
+
org_hash = read_organization(@organization)
|
52
|
+
abort('Organization could not be found') if org_hash.nil?
|
53
|
+
puts "Found organization at: #{org_hash[:url]}"
|
54
|
+
|
55
|
+
# Load the teams - there should be one team per student.
|
56
|
+
org_teams = get_teams_by_name(@organization)
|
57
|
+
# For each student - pull the repository if it exists
|
58
|
+
puts "\nCloning assignment repositories for students..."
|
59
|
+
@students.keys.each do |student|
|
60
|
+
unless org_teams.key?(student)
|
61
|
+
puts(" ** ERROR ** - no team for #{student}")
|
62
|
+
next
|
63
|
+
end
|
64
|
+
repo_name = "#{student}-#{@repository}"
|
65
|
+
|
66
|
+
unless repository?(@organization, repo_name)
|
67
|
+
puts " ** ERROR ** - Can't find expected repository '#{repo_name}'"
|
68
|
+
next
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
sshEndpoint = @web_endpoint.gsub("https://","git@").gsub("/",":")
|
73
|
+
command = "git clone #{sshEndpoint}#{@organization}/#{repo_name}.git"
|
74
|
+
if cloneMethod.eql?('https')
|
75
|
+
command = "git clone #{@web_endpoint}#{@organization}/#{repo_name}.git"
|
76
|
+
end
|
77
|
+
puts " --> Cloning: '#{command}'"
|
78
|
+
self.execute(command)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def run
|
83
|
+
self.read_info
|
84
|
+
self.load_files
|
85
|
+
self.create
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Author: Mike Helmick
|
2
|
+
# Script to create assignment repositories for students under the appropriate organization
|
3
|
+
|
4
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..')
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'highline/question'
|
8
|
+
require 'highline/import'
|
9
|
+
require 'highline/compatibility'
|
10
|
+
require 'octokit'
|
11
|
+
require 'teachers_pet/actions/base'
|
12
|
+
|
13
|
+
module TeachersPet
|
14
|
+
module Actions
|
15
|
+
class CreateRepos < Base
|
16
|
+
def read_info
|
17
|
+
@repository = ask('What repository name should be created for each student?') { |q| q.validate = /\w+/ }
|
18
|
+
@organization = ask("What is the organization name?") { |q| q.default = TeachersPet::Configuration.organization }
|
19
|
+
@student_file = self.get_students_file_path
|
20
|
+
@instructor_file = self.get_instructors_file_path
|
21
|
+
@add_init_files = confirm('Add .gitignore and README.md files? (skip this if you are pushing starter files.)', false)
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_files
|
25
|
+
@students = read_file(@student_file, 'Students')
|
26
|
+
@instructors = read_file(@instructor_file, 'Instructors')
|
27
|
+
end
|
28
|
+
|
29
|
+
def create
|
30
|
+
confirm("Create #{@students.keys.size} repositories for students and give access to instructors?")
|
31
|
+
|
32
|
+
# create a repo for each student
|
33
|
+
self.init_client
|
34
|
+
|
35
|
+
org_hash = read_organization(@organization)
|
36
|
+
abort('Organization could not be found') if org_hash.nil?
|
37
|
+
puts "Found organization at: #{org_hash[:login]}"
|
38
|
+
|
39
|
+
# Load the teams - there should be one team per student.
|
40
|
+
# Repositories are given permissions by teams
|
41
|
+
org_teams = get_teams_by_name(@organization)
|
42
|
+
# For each student - create a repository, and give permissions to their "team"
|
43
|
+
# The repository name is teamName-repository
|
44
|
+
puts "\nCreating assignment repositories for students..."
|
45
|
+
@students.keys.sort.each do |student|
|
46
|
+
unless org_teams.key?(student)
|
47
|
+
puts(" ** ERROR ** - no team for #{student}")
|
48
|
+
next
|
49
|
+
end
|
50
|
+
repo_name = "#{student}-#{@repository}"
|
51
|
+
|
52
|
+
if repository?(@organization, repo_name)
|
53
|
+
puts " --> Already exists, skipping '#{repo_name}'"
|
54
|
+
next
|
55
|
+
end
|
56
|
+
|
57
|
+
git_ignore_template = "C++" ## This is specific to my current class, you'll want to change
|
58
|
+
git_ignore_template = '' unless @add_init_files
|
59
|
+
puts " --> Creating '#{repo_name}'"
|
60
|
+
@client.create_repository(repo_name,
|
61
|
+
{
|
62
|
+
:description => "#{@repository} created for #{student}",
|
63
|
+
:private => !TeachersPet::Configuration.reposPublic, ## Current default, repositories are private to the student & instructors
|
64
|
+
:has_issues => true, # seems like a resonable default
|
65
|
+
:has_wiki => false,
|
66
|
+
:has_downloads => false,
|
67
|
+
:organization => @organization,
|
68
|
+
:team_id => org_teams[student][:id],
|
69
|
+
:auto_init => @add_init_files,
|
70
|
+
:gitignore_template => git_ignore_template
|
71
|
+
})
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def run
|
76
|
+
self.read_info
|
77
|
+
self.load_files
|
78
|
+
self.create
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# Author: Mike Helmick
|
2
|
+
# Creates "teams" for an origanization. In this scenario - each team consists of
|
3
|
+
# one student, and any instructors for the course.
|
4
|
+
|
5
|
+
# The students and instructors files contain 1 userid per line.
|
6
|
+
# - For teams, the students file should be "teamName studentName studentName"
|
7
|
+
# We recommend that instructors also be created as students for ease of testing.
|
8
|
+
|
9
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..')
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'highline/question'
|
13
|
+
require 'highline/import'
|
14
|
+
require 'highline/compatibility'
|
15
|
+
require 'octokit'
|
16
|
+
|
17
|
+
require 'teachers_pet/actions/base'
|
18
|
+
|
19
|
+
module TeachersPet
|
20
|
+
module Actions
|
21
|
+
class CreateTeams < Base
|
22
|
+
def read_info
|
23
|
+
@organization = ask("What is the organization name?") { |q| q.default = TeachersPet::Configuration.organization }
|
24
|
+
@student_file = self.get_students_file_path
|
25
|
+
@instructor_file = self.get_instructors_file_path
|
26
|
+
end
|
27
|
+
|
28
|
+
def load_files
|
29
|
+
@students = read_file(@student_file, 'Students')
|
30
|
+
@instructors = read_file(@instructor_file, 'Instructors')
|
31
|
+
end
|
32
|
+
|
33
|
+
def create
|
34
|
+
self.init_client
|
35
|
+
confirm("Create teams for #{@students.size} students/teams?")
|
36
|
+
|
37
|
+
existing = Hash.new
|
38
|
+
teams = @client.organization_teams(@organization)
|
39
|
+
teams.each { |team| existing[team[:name]] = team }
|
40
|
+
|
41
|
+
puts "\nDetermining which students need teams created..."
|
42
|
+
todo = Hash.new
|
43
|
+
@students.keys.each do |team|
|
44
|
+
if existing[team].nil?
|
45
|
+
puts " -> #{team}"
|
46
|
+
todo[team] = true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
if todo.empty?
|
51
|
+
puts "\nAll teams exist"
|
52
|
+
else
|
53
|
+
puts "\nCreating team..."
|
54
|
+
todo.keys.each do |team|
|
55
|
+
puts " -> '#{team}' ..."
|
56
|
+
@client.create_team(@organization,
|
57
|
+
{
|
58
|
+
:name => team,
|
59
|
+
:permission => 'push'
|
60
|
+
})
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
puts "\nAdjusting team memberships"
|
65
|
+
teams = @client.organization_teams(@organization)
|
66
|
+
teams.each do |team|
|
67
|
+
team_members = get_team_member_logins(team[:id])
|
68
|
+
if team[:name].eql?('Owners')
|
69
|
+
puts "*** OWNERS *** - Ensuring instructors are owners"
|
70
|
+
@instructors.keys.each do |instructor|
|
71
|
+
unless team_members.include?(instructor)
|
72
|
+
@client.add_team_member(team[:id], instructor)
|
73
|
+
puts " -> '#{instructor}' has been made an owner for this course"
|
74
|
+
else
|
75
|
+
puts " -> '#{instructor}' is already an owner"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
elsif @students.key?(team[:name])
|
79
|
+
puts "Validating membership for team '#{team[:name]}'"
|
80
|
+
# If there isn't a team member that is the same name as the team, and we already know
|
81
|
+
# there is a student with the same name, add that student to the team.
|
82
|
+
#unless team_members.include?(team[:name])
|
83
|
+
@students[team[:name]].each do |student|
|
84
|
+
puts " -> Adding '#{team[:name]}' to the team"
|
85
|
+
@client.add_team_member(team[:id], student)
|
86
|
+
end
|
87
|
+
# Originally, instructors were added to the student's team, but that isn't needed
|
88
|
+
# since instructors are addded to the Owners team that can see all repositories.
|
89
|
+
else
|
90
|
+
puts "*** Team name '#{team[:name]}' does not match any students, ignoring. ***"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def run
|
96
|
+
self.read_info
|
97
|
+
self.load_files
|
98
|
+
self.create
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..')
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'highline/question'
|
5
|
+
require 'highline/import'
|
6
|
+
require 'highline/compatibility'
|
7
|
+
require 'octokit'
|
8
|
+
require 'teachers_pet/actions/base'
|
9
|
+
|
10
|
+
module TeachersPet
|
11
|
+
module Actions
|
12
|
+
class ForkCollab < Base
|
13
|
+
def read_info
|
14
|
+
@repository = ask("Which repository? (name/owner)")
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_forks
|
18
|
+
@client.forks(@repository)
|
19
|
+
end
|
20
|
+
|
21
|
+
def promote
|
22
|
+
self.init_client
|
23
|
+
|
24
|
+
forks = self.get_forks
|
25
|
+
|
26
|
+
confirm("Are you sure you want to add #{forks.count} users as collaborators on '#{@repository}'?")
|
27
|
+
|
28
|
+
forks.each do |fork|
|
29
|
+
login = fork.owner.login
|
30
|
+
if fork.owner.type == "User"
|
31
|
+
result = @client.add_collab(@repository, login)
|
32
|
+
puts "#{login} - #{result}"
|
33
|
+
else
|
34
|
+
puts "#{login} - false (Organization)"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def run
|
40
|
+
self.read_info
|
41
|
+
self.promote
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..')
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'highline/question'
|
5
|
+
require 'highline/import'
|
6
|
+
require 'highline/compatibility'
|
7
|
+
require 'octokit'
|
8
|
+
require 'teachers_pet/actions/base'
|
9
|
+
|
10
|
+
# This script should be run within a working directory that is a git repository.
|
11
|
+
# It will add a remote that is the name of each student team to your repository
|
12
|
+
|
13
|
+
module TeachersPet
|
14
|
+
module Actions
|
15
|
+
class PushFiles < Base
|
16
|
+
def read_info
|
17
|
+
@repository = ask('What repository name should pushed to for each student?') { |q| q.validate = /\w+/ }
|
18
|
+
|
19
|
+
@organization = ask("What is the organization name?") { |q| q.default = TeachersPet::Configuration.organization }
|
20
|
+
@student_file = self.get_students_file_path
|
21
|
+
@sshEndpoint = ask('What is the ssh endpoint?') { |q| q.default = TeachersPet::Configuration.sshEndpoint }
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_files
|
25
|
+
@students = read_file(@student_file, 'Students')
|
26
|
+
end
|
27
|
+
|
28
|
+
def push
|
29
|
+
confirm("Push files to student repositories?")
|
30
|
+
self.init_client
|
31
|
+
|
32
|
+
org_hash = read_organization(@organization)
|
33
|
+
abort('Organization could not be found') if org_hash.nil?
|
34
|
+
puts "Found organization at: #{org_hash[:url]}"
|
35
|
+
|
36
|
+
# Load the teams - there should be one team per student.
|
37
|
+
# Repositories are given permissions by teams
|
38
|
+
org_teams = get_teams_by_name(@organization)
|
39
|
+
|
40
|
+
# For each student - if an appropraite repository exists,
|
41
|
+
# add it to the list.
|
42
|
+
remotes_to_add = Hash.new
|
43
|
+
@students.keys.sort.each do |student|
|
44
|
+
unless org_teams.key?(student)
|
45
|
+
puts(" ** ERROR ** - no team for #{student}")
|
46
|
+
next
|
47
|
+
end
|
48
|
+
repo_name = "#{student}-#{@repository}"
|
49
|
+
|
50
|
+
unless repository?(@organization, repo_name)
|
51
|
+
puts(" ** ERROR ** - no repository called #{repo_name}")
|
52
|
+
end
|
53
|
+
if TeachersPet::Configuration.remoteSsh
|
54
|
+
remotes_to_add[student] = "git@#{@sshEndpoint}:#{@organization}/#{repo_name}.git"
|
55
|
+
else
|
56
|
+
remotes_to_add[student] = "#{@web_endpoint}#{@organization}/#{repo_name}.git"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
puts "Adding remotes and pushing files to student repositories."
|
61
|
+
remotes_to_add.keys.each do |remote|
|
62
|
+
puts "#{remote} --> #{remotes_to_add[remote]}"
|
63
|
+
`git remote add #{remote} #{remotes_to_add[remote]}`
|
64
|
+
`git push #{remote} master`
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def run
|
69
|
+
self.read_info
|
70
|
+
self.load_files
|
71
|
+
self.push
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Configuration for the edugit scripts. Modify this as desired to change
|
2
|
+
# your defaults in the prompts
|
3
|
+
module TeachersPet
|
4
|
+
class Configuration
|
5
|
+
|
6
|
+
# For github.com: 'https://api.github.com/'
|
7
|
+
# For GitHub Enterprise: 'https://yourServer.com/api/v3'
|
8
|
+
@@API_ENDPOINT = 'https://api.github.com/'
|
9
|
+
|
10
|
+
def self.apiEndpoint
|
11
|
+
@@API_ENDPOINT
|
12
|
+
end
|
13
|
+
|
14
|
+
# For github.com: https://www.github.com/'
|
15
|
+
# For GitHub Enterprise: ''https://yourServer.com/'
|
16
|
+
@@WEB_ENDPOINT = 'https://www.github.com/'
|
17
|
+
|
18
|
+
def self.webEndpoint
|
19
|
+
@@WEB_ENDPOINT
|
20
|
+
end
|
21
|
+
|
22
|
+
# The name of your organization
|
23
|
+
@@ORGANIZATION = 'edugit-test'
|
24
|
+
|
25
|
+
def self.organization
|
26
|
+
@@ORGANIZATION
|
27
|
+
end
|
28
|
+
|
29
|
+
# The name fo the file that contains the team definitions / students
|
30
|
+
@@STUDENTS_FILE = './students'
|
31
|
+
|
32
|
+
def self.studentsFile
|
33
|
+
@@STUDENTS_FILE
|
34
|
+
end
|
35
|
+
|
36
|
+
# The name of the file that contains the GitHub usernames of the instructors
|
37
|
+
@@INSTRUCTORS_FILE = './instructors'
|
38
|
+
|
39
|
+
def self.instructorsFile
|
40
|
+
@@INSTRUCTORS_FILE
|
41
|
+
end
|
42
|
+
|
43
|
+
# The default is to create the repositories as public.
|
44
|
+
# If you are using GitHub enterprise or a paid account, it make sense to make this private
|
45
|
+
@@REPOS_PUBLIC = true
|
46
|
+
|
47
|
+
def self.reposPublic
|
48
|
+
@@REPOS_PUBLIC
|
49
|
+
end
|
50
|
+
|
51
|
+
# github.com - set to 'github.com'
|
52
|
+
# GitHub Enterprise - 'yourserver.com'
|
53
|
+
@@SSH_ENDPOINT = 'github.com'
|
54
|
+
|
55
|
+
def self.sshEndpoint
|
56
|
+
@@SSH_ENDPOINT
|
57
|
+
end
|
58
|
+
|
59
|
+
## It is best to push remotes vis SSH, you can swith this to false to push as HTTPS
|
60
|
+
@@REMOTE_SSH = true
|
61
|
+
|
62
|
+
def self.remoteSsh
|
63
|
+
@@REMOTE_SSH
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
data/lib/teachers_pet/version.rb
CHANGED
data/lib/teachers_pet.rb
CHANGED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TeachersPet::Actions::CloneRepos do
|
4
|
+
let(:action) { TeachersPet::Actions::CloneRepos.new }
|
5
|
+
|
6
|
+
def respond(question, response)
|
7
|
+
action.stub(:ask).with(question).and_return(response)
|
8
|
+
end
|
9
|
+
|
10
|
+
before do
|
11
|
+
# fallback
|
12
|
+
action.stub(:ask){|question| raise("can't ask \"#{question}\"") }
|
13
|
+
action.stub(:choose){ raise("can't choose()") }
|
14
|
+
|
15
|
+
action.stub(:confirm)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "runs" do
|
19
|
+
respond("What repository name should be cloned for each student?", 'testrepo')
|
20
|
+
respond("What is the organization name?", 'testorg')
|
21
|
+
respond("What is the filename of the list of students?", students_list_fixture_path)
|
22
|
+
action.stub(get_clone_method: 'https')
|
23
|
+
respond("What is the organization name?", "testorg")
|
24
|
+
stub_github_config
|
25
|
+
|
26
|
+
request_stubs = []
|
27
|
+
|
28
|
+
request_stubs << stub_get_json('https://testteacher:abc123@api.github.com/orgs/testorg',
|
29
|
+
login: 'testorg',
|
30
|
+
url: 'https://api.github.com/orgs/testorg'
|
31
|
+
)
|
32
|
+
request_stubs << stub_get_json('https://testteacher:abc123@api.github.com/orgs/testorg/teams?per_page=100', student_teams)
|
33
|
+
student_usernames.each do |username|
|
34
|
+
request_stubs << stub_get_json("https://testteacher:abc123@api.github.com/repos/testorg/#{username}-testrepo", {})
|
35
|
+
action.should_receive(:execute).with("git clone https://www.github.com/testorg/#{username}-testrepo.git").once
|
36
|
+
end
|
37
|
+
|
38
|
+
action.run
|
39
|
+
|
40
|
+
request_stubs.each do |request_stub|
|
41
|
+
expect(request_stub).to have_been_requested.once
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TeachersPet::Actions::CreateTeams do
|
4
|
+
let(:action) { TeachersPet::Actions::CreateTeams.new }
|
5
|
+
|
6
|
+
before do
|
7
|
+
# fallback
|
8
|
+
action.stub(:ask){|question| raise("can't ask \"#{question}\"") }
|
9
|
+
action.stub(:choose){ raise("can't choose()") }
|
10
|
+
|
11
|
+
action.stub(:confirm)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "creates teams if none exist" do
|
15
|
+
respond("What is the organization name?", 'testorg')
|
16
|
+
respond("What is the filename of the list of students?", students_list_fixture_path)
|
17
|
+
respond("What is the filename of the list of instructors?", instructors_list_fixture_path)
|
18
|
+
stub_github_config
|
19
|
+
|
20
|
+
teams_stub = stub_get_json('https://testteacher:abc123@api.github.com/orgs/testorg/teams?per_page=100', [])
|
21
|
+
|
22
|
+
request_stubs = []
|
23
|
+
student_usernames.each do |student|
|
24
|
+
request_stubs << stub_request(:post, 'https://testteacher:abc123@api.github.com/orgs/testorg/teams').
|
25
|
+
with(body: {
|
26
|
+
name: student,
|
27
|
+
permission: 'push'
|
28
|
+
}.to_json)
|
29
|
+
end
|
30
|
+
|
31
|
+
action.run
|
32
|
+
|
33
|
+
expect(teams_stub).to have_been_requested.twice
|
34
|
+
request_stubs.each do |request_stub|
|
35
|
+
expect(request_stub).to have_been_requested.once
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "handles existing teams"
|
40
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'json'
|
3
|
+
require 'webmock/rspec'
|
4
|
+
|
5
|
+
require_relative File.join('..', 'lib', 'teachers_pet')
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
config.order = 'random'
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def stub_get_json(url, response)
|
16
|
+
stub_request(:get, url).to_return(
|
17
|
+
headers: {
|
18
|
+
'Content-Type' => 'application/json'
|
19
|
+
},
|
20
|
+
body: response.to_json
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def students_list_fixture_path
|
25
|
+
File.join(File.dirname(__FILE__), 'fixtures', 'students')
|
26
|
+
end
|
27
|
+
|
28
|
+
def instructors_list_fixture_path
|
29
|
+
File.join(File.dirname(__FILE__), 'fixtures', 'instructors')
|
30
|
+
end
|
31
|
+
|
32
|
+
def student_usernames
|
33
|
+
CSV.read(students_list_fixture_path).flatten
|
34
|
+
end
|
35
|
+
|
36
|
+
def student_teams
|
37
|
+
student_usernames.each_with_index.map do |username, i|
|
38
|
+
{
|
39
|
+
url: "https://api.github.com/teams/#{i}",
|
40
|
+
name: username,
|
41
|
+
id: i
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def respond(question, response)
|
47
|
+
action.stub(:ask).with(question).and_return(response)
|
48
|
+
end
|
49
|
+
|
50
|
+
def stub_github_config
|
51
|
+
respond("What is the API endpoint?", TeachersPet::Configuration.apiEndpoint)
|
52
|
+
respond("What is the Web endpoint?", TeachersPet::Configuration.webEndpoint)
|
53
|
+
respond("What is your username? (You must be an owner for the organization)?", 'testteacher')
|
54
|
+
action.stub(get_auth_method: 'password')
|
55
|
+
respond("What is your password?", 'abc123')
|
56
|
+
end
|
data/teachers_pet.gemspec
CHANGED
@@ -1,20 +1,29 @@
|
|
1
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'teachers_pet/version'
|
2
5
|
|
3
6
|
Gem::Specification.new do |s|
|
4
7
|
s.name = 'teachers_pet'
|
5
8
|
s.version = TeachersPet::VERSION
|
6
|
-
s.summary = "Command line tools
|
7
|
-
s.description = "A simple hello world gem"
|
9
|
+
s.summary = "Command line tools to help teachers use GitHub in their classrooms"
|
8
10
|
s.authors = ["John Britton"]
|
9
11
|
s.email = 'public@johndbritton.com'
|
10
|
-
s.homepage = 'http://github.com/
|
12
|
+
s.homepage = 'http://github.com/education/teachers_pet'
|
11
13
|
s.license = 'MIT'
|
12
14
|
|
13
|
-
s.
|
15
|
+
s.bindir = 'bin'
|
16
|
+
s.files = `git ls-files -z`.split("\x0")
|
17
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
14
19
|
s.require_paths = ['lib']
|
15
20
|
|
16
|
-
s.
|
17
|
-
s.
|
21
|
+
s.add_dependency 'highline', '~> 1.6.21'
|
22
|
+
s.add_dependency 'octokit', '~> 3.1.0'
|
18
23
|
|
19
|
-
s.
|
24
|
+
s.add_development_dependency 'bundler', '~> 1.5'
|
25
|
+
s.add_development_dependency 'guard-rspec', '~> 4.2'
|
26
|
+
s.add_development_dependency 'rake'
|
27
|
+
s.add_development_dependency 'rspec', '~> 2.14'
|
28
|
+
s.add_development_dependency 'webmock', '~> 1.17'
|
20
29
|
end
|
metadata
CHANGED
@@ -1,42 +1,154 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: teachers_pet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Britton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: highline
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.6.21
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.6.21
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: octokit
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
|
-
- -
|
31
|
+
- - "~>"
|
18
32
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
33
|
+
version: 3.1.0
|
20
34
|
type: :runtime
|
21
35
|
prerelease: false
|
22
36
|
version_requirements: !ruby/object:Gem::Requirement
|
23
37
|
requirements:
|
24
|
-
- -
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.1.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard-rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.2'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
25
74
|
- !ruby/object:Gem::Version
|
26
75
|
version: '0'
|
27
|
-
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.14'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.14'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: webmock
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.17'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.17'
|
111
|
+
description:
|
28
112
|
email: public@johndbritton.com
|
29
113
|
executables:
|
30
|
-
-
|
114
|
+
- clone_repos
|
115
|
+
- create_repos
|
116
|
+
- create_teams
|
117
|
+
- fork_collab
|
118
|
+
- push_files
|
31
119
|
extensions: []
|
32
120
|
extra_rdoc_files: []
|
33
121
|
files:
|
34
|
-
- .gitignore
|
35
|
-
-
|
122
|
+
- ".gitignore"
|
123
|
+
- ".rspec"
|
124
|
+
- ".travis.yml"
|
125
|
+
- CONTRIBUTING.md
|
126
|
+
- Gemfile
|
127
|
+
- Guardfile
|
128
|
+
- LICENSE
|
129
|
+
- README.md
|
130
|
+
- Rakefile
|
131
|
+
- bin/clone_repos
|
132
|
+
- bin/create_repos
|
133
|
+
- bin/create_teams
|
134
|
+
- bin/fork_collab
|
135
|
+
- bin/push_files
|
36
136
|
- lib/teachers_pet.rb
|
137
|
+
- lib/teachers_pet/actions/base.rb
|
138
|
+
- lib/teachers_pet/actions/clone_repos.rb
|
139
|
+
- lib/teachers_pet/actions/create_repos.rb
|
140
|
+
- lib/teachers_pet/actions/create_teams.rb
|
141
|
+
- lib/teachers_pet/actions/fork_collab.rb
|
142
|
+
- lib/teachers_pet/actions/push_files.rb
|
143
|
+
- lib/teachers_pet/configuration.rb
|
37
144
|
- lib/teachers_pet/version.rb
|
145
|
+
- spec/actions/clone_repos_spec.rb
|
146
|
+
- spec/actions/create_teams_spec.rb
|
147
|
+
- spec/fixtures/instructors
|
148
|
+
- spec/fixtures/students
|
149
|
+
- spec/spec_helper.rb
|
38
150
|
- teachers_pet.gemspec
|
39
|
-
homepage: http://github.com/
|
151
|
+
homepage: http://github.com/education/teachers_pet
|
40
152
|
licenses:
|
41
153
|
- MIT
|
42
154
|
metadata: {}
|
@@ -46,18 +158,24 @@ require_paths:
|
|
46
158
|
- lib
|
47
159
|
required_ruby_version: !ruby/object:Gem::Requirement
|
48
160
|
requirements:
|
49
|
-
- -
|
161
|
+
- - ">="
|
50
162
|
- !ruby/object:Gem::Version
|
51
163
|
version: '0'
|
52
164
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
165
|
requirements:
|
54
|
-
- -
|
166
|
+
- - ">="
|
55
167
|
- !ruby/object:Gem::Version
|
56
168
|
version: '0'
|
57
169
|
requirements: []
|
58
170
|
rubyforge_project:
|
59
|
-
rubygems_version: 2.
|
171
|
+
rubygems_version: 2.2.2
|
60
172
|
signing_key:
|
61
173
|
specification_version: 4
|
62
|
-
summary: Command line tools
|
63
|
-
test_files:
|
174
|
+
summary: Command line tools to help teachers use GitHub in their classrooms
|
175
|
+
test_files:
|
176
|
+
- spec/actions/clone_repos_spec.rb
|
177
|
+
- spec/actions/create_teams_spec.rb
|
178
|
+
- spec/fixtures/instructors
|
179
|
+
- spec/fixtures/students
|
180
|
+
- spec/spec_helper.rb
|
181
|
+
has_rdoc:
|
data/bin/forkcollab
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'io/console'
|
4
|
-
require 'octokit'
|
5
|
-
|
6
|
-
puts "Create a personal access token: https://github.com/settings/applications"
|
7
|
-
print "Token: "
|
8
|
-
token = STDIN.noecho(&:gets).chomp
|
9
|
-
puts
|
10
|
-
|
11
|
-
print "Repository (name/owner): "
|
12
|
-
nwo = gets.chomp
|
13
|
-
|
14
|
-
client = Octokit::Client.new :access_token => token
|
15
|
-
|
16
|
-
forks = client.forks(nwo)
|
17
|
-
|
18
|
-
puts "Are you sure you want to add #{forks.count} users as collaborators on '#{nwo}'?"
|
19
|
-
print "Please type the name of the repository to confirm: "
|
20
|
-
confirm = gets.chomp
|
21
|
-
|
22
|
-
if confirm == nwo
|
23
|
-
forks.each do |fork|
|
24
|
-
if fork.owner.type == "User"
|
25
|
-
result = client.add_collab(nwo, fork.owner)
|
26
|
-
puts "#{fork.owner.login} - #{result}"
|
27
|
-
else
|
28
|
-
puts "#{fork.owner.login} - false (Organization)"
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|