teachers_pet 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +7 -0
- data/Guardfile +3 -1
- data/README.md +53 -28
- data/Rakefile +1 -1
- data/bin/teachers_pet +4 -0
- data/lib/teachers_pet.rb +4 -1
- data/lib/teachers_pet/actions/add_to_team.rb +31 -0
- data/lib/teachers_pet/actions/base.rb +36 -111
- data/lib/teachers_pet/actions/clone_repos.rb +9 -36
- data/lib/teachers_pet/actions/create_repos.rb +16 -39
- data/lib/teachers_pet/actions/create_student_teams.rb +35 -0
- data/lib/teachers_pet/actions/fork_collab.rb +4 -19
- data/lib/teachers_pet/actions/open_issue.rb +17 -32
- data/lib/teachers_pet/actions/push_files.rb +8 -22
- data/lib/teachers_pet/cli.rb +22 -0
- data/lib/teachers_pet/client_decorator.rb +59 -0
- data/lib/teachers_pet/commands/add_to_team.rb +12 -0
- data/lib/teachers_pet/commands/clone_repos.rb +15 -0
- data/lib/teachers_pet/commands/create_repos.rb +15 -0
- data/lib/teachers_pet/commands/create_student_teams.rb +13 -0
- data/lib/teachers_pet/commands/fork_collab.rb +12 -0
- data/lib/teachers_pet/commands/open_issue.rb +18 -0
- data/lib/teachers_pet/commands/push_files.rb +15 -0
- data/lib/teachers_pet/configuration.rb +0 -13
- data/lib/teachers_pet/version.rb +1 -1
- data/spec/actions/base_spec.rb +13 -0
- data/spec/cli_spec.rb +17 -0
- data/spec/commands/add_to_team_spec.rb +43 -0
- data/spec/commands/clone_repos_spec.rb +33 -0
- data/spec/commands/create_repos_spec.rb +55 -0
- data/spec/commands/create_student_teams_spec.rb +90 -0
- data/spec/commands/fork_collab_spec.rb +95 -0
- data/spec/commands/open_issue_spec.rb +63 -0
- data/spec/commands/push_files_spec.rb +33 -0
- data/spec/fixtures/empty +0 -0
- data/spec/fixtures/teams +1 -0
- data/spec/spec_helper.rb +13 -53
- data/spec/support/command_helpers.rb +11 -0
- data/spec/support/common_helpers.rb +52 -0
- data/teachers_pet.gemspec +6 -2
- metadata +89 -29
- data/bin/clone_repos +0 -6
- data/bin/create_repos +0 -6
- data/bin/create_teams +0 -6
- data/bin/fork_collab +0 -6
- data/bin/open_issue +0 -6
- data/bin/push_files +0 -6
- data/lib/teachers_pet/actions/create_teams.rb +0 -101
- data/spec/actions/clone_repos_spec.rb +0 -44
- data/spec/actions/create_repos_spec.rb +0 -65
- data/spec/actions/create_teams_spec.rb +0 -40
- data/spec/actions/open_issue_spec.rb +0 -71
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 93ce5e5b5d6591f2c8d571ca24a6fca2c8b15366
|
4
|
+
data.tar.gz: f32ef808412ae9c2367ba836a3830fbd5ae96d05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e06710f83c1fe8cb527dfe292b163b148b5ed163211624567a0a35b4238bda1f28eda81cdfacac8a2572a8241faf03f14a0226f79416e4122dff5e63b4d6a6fa
|
7
|
+
data.tar.gz: d58fc7219bcd4fa26d25c8428b074da3a93ea6a1a6c9ced5102e7428a4950848df90f6387917a0d51a1acfd16634c7a5401298eea9bc0355b55febb8903ba809
|
data/CONTRIBUTING.md
CHANGED
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# teachers_pet [![Build Status](https://travis-ci.org/education/teachers_pet.svg?branch=master)](https://travis-ci.org/education/teachers_pet)
|
2
2
|
|
3
|
-
|
3
|
+
**WARNING: This documentation may contain unreleased changes. See [rubydoc.info/gems/teachers_pet](http://rubydoc.info/gems/teachers_pet) for the version of this README corresponding to the latest release.**
|
4
|
+
|
5
|
+
Command line tool to help teachers use GitHub in their classrooms.
|
4
6
|
|
5
7
|
## Philosophy
|
6
8
|
|
@@ -27,64 +29,87 @@ gem update teachers_pet
|
|
27
29
|
|
28
30
|
To use the latest-and-greatest code from this repository, see the instructions in [CONTRIBUTING.md](CONTRIBUTING.md).
|
29
31
|
|
30
|
-
##
|
32
|
+
## Typical workflow
|
33
|
+
|
34
|
+
...when using the [sandboxing](https://education.github.com/guide/sandboxing) method with [private repositories](https://education.github.com/guide/private_repos):
|
35
|
+
|
36
|
+
### Basic setup
|
31
37
|
|
32
|
-
|
33
|
-
|
38
|
+
1. Create an organization (you will be an owner by default). The organization should reflect the name of your course. See [the classroom guide](https://education.github.com/guide#2-create-an-organization-for-your-class) for more info.
|
39
|
+
1. Have each student/instructor create GitHub accounts.
|
40
|
+
1. Create a `students` file (you can use an alternate filename and specify with the `--students` option if you like)
|
34
41
|
* Individual assignments: one username per line
|
35
42
|
* Group assignments: one team per line in the format `teamName username username username`
|
36
|
-
|
37
|
-
|
43
|
+
1. Add the GitHub username of all instructors to an `Owners.csv` file (one per line)
|
44
|
+
1. Run the following:
|
38
45
|
|
39
|
-
|
46
|
+
```bash
|
47
|
+
teachers_pet create_student_teams ...
|
48
|
+
teachers_pet add_to_team --members Owners.csv ...
|
49
|
+
```
|
40
50
|
|
41
|
-
|
51
|
+
### Assignments
|
52
|
+
|
53
|
+
```bash
|
54
|
+
teachers_pet create_repos ...
|
55
|
+
teachers_pet push_files ...
|
56
|
+
# Multiple times:
|
57
|
+
teachers_pet open_issue ...
|
58
|
+
|
59
|
+
# Then, after the assignment is due,
|
60
|
+
teachers_pet clone_repos ...
|
61
|
+
```
|
62
|
+
|
63
|
+
## Authentication
|
64
|
+
|
65
|
+
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) (2FA) 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):
|
42
66
|
|
43
67
|
https://github.com/settings/tokens/new?description=teachers_pet&scopes=repo%2Cpublic_repo%2Cwrite%3Aorg%2Crepo%3Astatus%2Cread%3Aorg%2Cuser%2Cadmin%3Aorg
|
44
68
|
|
45
|
-
|
69
|
+
Once created, specify the token using the `--token` option, or if you add the `TEACHERS_PET_GITHUB_TOKEN` environment variable to your `.bash_profile` (or equivalent – example below), it will be picked up by `teachers_pet`.
|
46
70
|
|
47
71
|
```bash
|
48
|
-
|
72
|
+
# replace YOUR_TOKEN_HERE below
|
73
|
+
echo "\n\nexport TEACHERS_PET_GITHUB_TOKEN=YOUR_TOKEN_HERE" >> ~/.bash_profile
|
74
|
+
source ~/.bash_profile
|
49
75
|
```
|
50
76
|
|
51
77
|
## Actions
|
52
78
|
|
53
|
-
|
54
|
-
|
55
|
-
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.
|
56
|
-
|
57
|
-
### Collaborator access
|
58
|
-
|
59
|
-
Give collaborator access to everyone who has forked your repository.
|
79
|
+
**To learn the options for each action, run**
|
60
80
|
|
61
81
|
```bash
|
62
|
-
|
82
|
+
teachers_pet help
|
83
|
+
# or
|
84
|
+
teachers_pet help COMMAND
|
63
85
|
```
|
64
86
|
|
65
|
-
###
|
87
|
+
### Giving others access
|
66
88
|
|
67
|
-
|
89
|
+
You may need to give other people access to various repositories using teams – the `add_to_team` command can help do this in bulk.
|
68
90
|
|
69
|
-
|
91
|
+
### Creating assignments
|
70
92
|
|
71
|
-
|
93
|
+
When using the [sandboxing](https://education.github.com/guide/sandboxing) setup, you will need to create the repositories for the students. For each assignment, use the `create_repos` action to create a repository for each student. The repositories are technically created per team, but if you use `create_student_teams` first, then there will be one team per student.
|
72
94
|
|
73
|
-
###
|
95
|
+
### Collaborator access
|
96
|
+
|
97
|
+
Give [collaborator access](https://help.github.com/articles/what-are-the-different-access-permissions#collaborator) to everyone who has forked your repository using `fork_collab`. Mostly useful for GitHub demonstrations, where the students can quickly be added to a repository without having to worry about collecting usernames.
|
74
98
|
|
75
|
-
|
99
|
+
### Pushing starter files
|
76
100
|
|
77
|
-
|
101
|
+
When creating repositories for students, you will often want to include boilerplate files. After running `create_repos`, create a canonical copy of the starter files (e.g. [`.gitignore`](https://github.com/github/gitignore#readme), `Makefile`s, etc.) in a repository. From the local clone of the repository, use the `push_files` action to place that code in the repositories for each student. This works by creating a Git remote for each student repository, and doing a `git push` to each one.
|
102
|
+
|
103
|
+
### Opening issues
|
78
104
|
|
79
|
-
|
105
|
+
After running `create_repos`, instructors can open issues in student repos as a way to list requirements of the assignment, goals, or instructions for patching, using the `open_issue` command.
|
80
106
|
|
81
107
|
### Pulling repositories for grading
|
82
108
|
|
83
|
-
When grading, use the `clone_repos`
|
109
|
+
When grading, use the `clone_repos` command to clone all the repositories in the organization that match the username-repository naming scheme that is generated when `create_repos` is run.
|
84
110
|
|
85
111
|
## Related projects
|
86
112
|
|
87
113
|
* https://education.github.com/guide
|
88
114
|
* https://github.com/hogbait/6170_repo_management
|
89
|
-
* https://github.com/mikehelmick/edugit-scripts
|
90
115
|
* https://github.com/UCSB-CS-Using-GitHub-In-Courses/github-acad-scripts
|
data/Rakefile
CHANGED
data/bin/teachers_pet
ADDED
data/lib/teachers_pet.rb
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
module TeachersPet
|
2
|
+
module Actions
|
3
|
+
class AddToTeam < Base
|
4
|
+
def read_members_file
|
5
|
+
file = self.members
|
6
|
+
puts "Loading members to add:"
|
7
|
+
read_file(file).keys
|
8
|
+
end
|
9
|
+
|
10
|
+
def team_name
|
11
|
+
file = self.members
|
12
|
+
File.basename(file, File.extname(file))
|
13
|
+
end
|
14
|
+
|
15
|
+
def team
|
16
|
+
teams_by_name = self.client.existing_teams_by_name(self.organization)
|
17
|
+
teams_by_name[self.team_name] || self.client.create_team(self.organization, team_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_members_to_owners
|
21
|
+
member_list = self.read_members_file
|
22
|
+
self.client.add_users_to_team(organization, self.team, member_list)
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
self.init_client
|
27
|
+
self.add_members_to_owners
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,120 +1,53 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'configuration'
|
1
|
+
require 'active_support/core_ext/hash/keys'
|
4
2
|
require 'octokit'
|
3
|
+
require_relative File.join('..', 'configuration')
|
5
4
|
|
6
|
-
## Common code for the edugit scripts.
|
7
5
|
module TeachersPet
|
8
6
|
module Actions
|
9
7
|
class Base
|
10
|
-
|
11
|
-
auth_method = nil
|
12
|
-
|
13
|
-
choose do |menu|
|
14
|
-
menu.prompt = "Login via oAuth or Password? "
|
15
|
-
menu.choice :oAuth do
|
16
|
-
auth_method = 'oauth'
|
17
|
-
end
|
18
|
-
menu.choice :Password do
|
19
|
-
auth_method = 'password'
|
20
|
-
end
|
21
|
-
end
|
8
|
+
attr_reader :client, :options
|
22
9
|
|
23
|
-
|
10
|
+
def initialize(opts={})
|
11
|
+
@options = opts.symbolize_keys
|
24
12
|
end
|
25
13
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@authmethod = self.get_auth_method
|
34
|
-
|
35
|
-
if @authmethod == 'oauth'
|
36
|
-
@oauthtoken = ask('What is your oAuth token?') { |q| q.default = ENV['ghe_oauth'] }
|
37
|
-
end
|
38
|
-
if @authmethod == 'password'
|
39
|
-
@password = ask('What is your password?') { |q| q.echo = false }
|
14
|
+
def method_missing(meth, *args, &block)
|
15
|
+
# Support boolean options ending, by calling them with '?' at the end
|
16
|
+
key = meth.to_s.sub(/\?\z/, '').to_sym
|
17
|
+
if self.options.has_key?(key)
|
18
|
+
self.options[key]
|
19
|
+
else
|
20
|
+
super
|
40
21
|
end
|
41
22
|
end
|
42
23
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
c.api_endpoint = @api_endpoint
|
49
|
-
c.web_endpoint = @web_endpoint
|
24
|
+
def octokit_config
|
25
|
+
opts = {
|
26
|
+
api_endpoint: self.api,
|
27
|
+
web_endpoint: self.web,
|
28
|
+
login: self.username,
|
50
29
|
# Organizations can get big, pull in all pages
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
if @authmethod == 'password'
|
55
|
-
@client = Octokit::Client.new(:login => @username, :password => @password)
|
56
|
-
end
|
57
|
-
if @authmethod == 'oauth'
|
58
|
-
@client = Octokit::Client.new(:login => @username, :access_token => @oauthtoken)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def read_organization(organization)
|
63
|
-
abort("GitHub client not initialized") if @client.nil?
|
64
|
-
@client.organization(organization)
|
65
|
-
end
|
30
|
+
auto_paginate: true
|
31
|
+
}
|
66
32
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
protected
|
72
|
-
def repository?(organization, repo_name)
|
73
|
-
begin
|
74
|
-
@client.repository("#{organization}/#{repo_name}")
|
75
|
-
rescue
|
76
|
-
return false
|
33
|
+
if self.options[:token]
|
34
|
+
opts[:access_token] = self.token
|
35
|
+
else
|
36
|
+
opts[:password] = self.password
|
77
37
|
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def get_students_file_path
|
81
|
-
ask("What is the filename of the list of students?") { |q| q.default = TeachersPet::Configuration.studentsFile }
|
82
|
-
end
|
83
38
|
|
84
|
-
|
85
|
-
ask("What is the filename of the list of instructors?") { |q| q.default = TeachersPet::Configuration.instructorsFile }
|
39
|
+
opts
|
86
40
|
end
|
87
41
|
|
88
|
-
def
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
repos[repo[:name]] = repo
|
94
|
-
print " '#{repo[:name]}'"
|
95
|
-
end
|
96
|
-
print "\n";
|
97
|
-
return repos
|
98
|
-
end
|
99
|
-
|
100
|
-
def get_teams_by_name(organization)
|
101
|
-
org_teams = @client.organization_teams(organization)
|
102
|
-
teams = Hash.new
|
103
|
-
org_teams.each do |team|
|
104
|
-
teams[team[:name]] = team
|
105
|
-
end
|
106
|
-
return teams
|
107
|
-
end
|
108
|
-
|
109
|
-
def get_team_member_logins(team_id)
|
110
|
-
@client.team_members(team_id).map do |member|
|
111
|
-
member[:login]
|
112
|
-
end
|
42
|
+
def init_client
|
43
|
+
puts "=" * 50
|
44
|
+
puts "Authenticating to GitHub..."
|
45
|
+
octokit = Octokit::Client.new(self.octokit_config)
|
46
|
+
@client = TeachersPet::ClientDecorator.new(octokit)
|
113
47
|
end
|
114
48
|
|
115
|
-
def read_file(filename
|
49
|
+
def read_file(filename)
|
116
50
|
map = Hash.new
|
117
|
-
puts "Loading #{type}:"
|
118
51
|
File.open(filename).each_line do |team|
|
119
52
|
# Team can be a single user, or a team name and multiple users
|
120
53
|
# Trim whitespace, otherwise issues occur
|
@@ -139,22 +72,14 @@ module TeachersPet
|
|
139
72
|
end
|
140
73
|
end
|
141
74
|
end
|
142
|
-
return map
|
143
|
-
end
|
144
75
|
|
145
|
-
|
146
|
-
|
147
|
-
confirmed = false
|
148
|
-
choose do |menu|
|
149
|
-
menu.prompt = message
|
76
|
+
map
|
77
|
+
end
|
150
78
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
abort("Creation canceled by user")
|
156
|
-
end
|
157
|
-
return confirmed
|
79
|
+
def read_students_file
|
80
|
+
student_file = self.students
|
81
|
+
puts "Loading students:"
|
82
|
+
read_file(student_file)
|
158
83
|
end
|
159
84
|
end
|
160
85
|
end
|
@@ -1,58 +1,31 @@
|
|
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 'teachers_pet/actions/base'
|
13
|
-
|
14
1
|
module TeachersPet
|
15
2
|
module Actions
|
16
3
|
class CloneRepos < Base
|
17
4
|
def read_info
|
18
|
-
@repository =
|
19
|
-
@organization =
|
20
|
-
@student_file = self.get_students_file_path
|
5
|
+
@repository = self.repository
|
6
|
+
@organization = self.organization
|
21
7
|
end
|
22
8
|
|
23
9
|
def load_files
|
24
|
-
@students =
|
10
|
+
@students = self.read_students_file
|
25
11
|
end
|
26
12
|
|
27
13
|
def get_clone_method
|
28
|
-
|
29
|
-
choose do |menu|
|
30
|
-
menu.prompt = "Clone via? "
|
31
|
-
menu.choice :ssh do
|
32
|
-
cloneMethod = 'ssh'
|
33
|
-
end
|
34
|
-
menu.choice :https do
|
35
|
-
cloneMethod = 'https'
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
cloneMethod
|
14
|
+
self.clone_method
|
40
15
|
end
|
41
16
|
|
42
17
|
def create
|
43
18
|
cloneMethod = self.get_clone_method
|
44
19
|
|
45
|
-
confirm("Clone all repositories?")
|
46
|
-
|
47
20
|
# create a repo for each student
|
48
21
|
self.init_client
|
49
22
|
|
50
|
-
org_hash =
|
23
|
+
org_hash = self.client.organization(@organization)
|
51
24
|
abort('Organization could not be found') if org_hash.nil?
|
52
25
|
puts "Found organization at: #{org_hash[:url]}"
|
53
26
|
|
54
27
|
# Load the teams - there should be one team per student.
|
55
|
-
org_teams = get_teams_by_name(@organization)
|
28
|
+
org_teams = self.client.get_teams_by_name(@organization)
|
56
29
|
# For each student - pull the repository if it exists
|
57
30
|
puts "\nCloning assignment repositories for students..."
|
58
31
|
@students.keys.each do |student|
|
@@ -62,16 +35,16 @@ module TeachersPet
|
|
62
35
|
end
|
63
36
|
repo_name = "#{student}-#{@repository}"
|
64
37
|
|
65
|
-
unless repository?(@organization, repo_name)
|
38
|
+
unless self.client.repository?(@organization, repo_name)
|
66
39
|
puts " ** ERROR ** - Can't find expected repository '#{repo_name}'"
|
67
40
|
next
|
68
41
|
end
|
69
42
|
|
70
43
|
|
71
|
-
sshEndpoint =
|
44
|
+
sshEndpoint = self.web.gsub("https://","git@").gsub("/",":")
|
72
45
|
command = "git clone #{sshEndpoint}#{@organization}/#{repo_name}.git"
|
73
46
|
if cloneMethod.eql?('https')
|
74
|
-
command = "git clone #{
|
47
|
+
command = "git clone #{self.web}#{@organization}/#{repo_name}.git"
|
75
48
|
end
|
76
49
|
puts " --> Cloning: '#{command}'"
|
77
50
|
self.execute(command)
|