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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +7 -0
  3. data/Guardfile +3 -1
  4. data/README.md +53 -28
  5. data/Rakefile +1 -1
  6. data/bin/teachers_pet +4 -0
  7. data/lib/teachers_pet.rb +4 -1
  8. data/lib/teachers_pet/actions/add_to_team.rb +31 -0
  9. data/lib/teachers_pet/actions/base.rb +36 -111
  10. data/lib/teachers_pet/actions/clone_repos.rb +9 -36
  11. data/lib/teachers_pet/actions/create_repos.rb +16 -39
  12. data/lib/teachers_pet/actions/create_student_teams.rb +35 -0
  13. data/lib/teachers_pet/actions/fork_collab.rb +4 -19
  14. data/lib/teachers_pet/actions/open_issue.rb +17 -32
  15. data/lib/teachers_pet/actions/push_files.rb +8 -22
  16. data/lib/teachers_pet/cli.rb +22 -0
  17. data/lib/teachers_pet/client_decorator.rb +59 -0
  18. data/lib/teachers_pet/commands/add_to_team.rb +12 -0
  19. data/lib/teachers_pet/commands/clone_repos.rb +15 -0
  20. data/lib/teachers_pet/commands/create_repos.rb +15 -0
  21. data/lib/teachers_pet/commands/create_student_teams.rb +13 -0
  22. data/lib/teachers_pet/commands/fork_collab.rb +12 -0
  23. data/lib/teachers_pet/commands/open_issue.rb +18 -0
  24. data/lib/teachers_pet/commands/push_files.rb +15 -0
  25. data/lib/teachers_pet/configuration.rb +0 -13
  26. data/lib/teachers_pet/version.rb +1 -1
  27. data/spec/actions/base_spec.rb +13 -0
  28. data/spec/cli_spec.rb +17 -0
  29. data/spec/commands/add_to_team_spec.rb +43 -0
  30. data/spec/commands/clone_repos_spec.rb +33 -0
  31. data/spec/commands/create_repos_spec.rb +55 -0
  32. data/spec/commands/create_student_teams_spec.rb +90 -0
  33. data/spec/commands/fork_collab_spec.rb +95 -0
  34. data/spec/commands/open_issue_spec.rb +63 -0
  35. data/spec/commands/push_files_spec.rb +33 -0
  36. data/spec/fixtures/empty +0 -0
  37. data/spec/fixtures/teams +1 -0
  38. data/spec/spec_helper.rb +13 -53
  39. data/spec/support/command_helpers.rb +11 -0
  40. data/spec/support/common_helpers.rb +52 -0
  41. data/teachers_pet.gemspec +6 -2
  42. metadata +89 -29
  43. data/bin/clone_repos +0 -6
  44. data/bin/create_repos +0 -6
  45. data/bin/create_teams +0 -6
  46. data/bin/fork_collab +0 -6
  47. data/bin/open_issue +0 -6
  48. data/bin/push_files +0 -6
  49. data/lib/teachers_pet/actions/create_teams.rb +0 -101
  50. data/spec/actions/clone_repos_spec.rb +0 -44
  51. data/spec/actions/create_repos_spec.rb +0 -65
  52. data/spec/actions/create_teams_spec.rb +0 -40
  53. data/spec/actions/open_issue_spec.rb +0 -71
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 910209118ec442713281982a1e518e1444cc59a9
4
- data.tar.gz: 5db06d1c2a2df29f5fa6f9ceeec71d37c7b5c5a0
3
+ metadata.gz: 93ce5e5b5d6591f2c8d571ca24a6fca2c8b15366
4
+ data.tar.gz: f32ef808412ae9c2367ba836a3830fbd5ae96d05
5
5
  SHA512:
6
- metadata.gz: 7f158b180312b392af4c134264be6b7f250f950ac89012f067bf5605604eaf056702bf4d8474fe8f40efff5f1428b3937f74b947e8300af42b2e6abe71dcc8c7
7
- data.tar.gz: 27c399025cd1d07af44e005fb35117b632a92a04d17428fe5c9dbbf996ed8a05b53bb7976b4ea57ec95c5c5ea1aa50ae676d5562d7ad06405307ed6f1941e5d3
6
+ metadata.gz: e06710f83c1fe8cb527dfe292b163b148b5ed163211624567a0a35b4238bda1f28eda81cdfacac8a2572a8241faf03f14a0226f79416e4122dff5e63b4d6a6fa
7
+ data.tar.gz: d58fc7219bcd4fa26d25c8428b074da3a93ea6a1a6c9ced5102e7428a4950848df90f6387917a0d51a1acfd16634c7a5401298eea9bc0355b55febb8903ba809
data/CONTRIBUTING.md CHANGED
@@ -17,3 +17,10 @@ bundle exec rspec
17
17
  # or
18
18
  bundle exec guard
19
19
  ```
20
+
21
+ To see test coverage information:
22
+
23
+ ```bash
24
+ bundle exec rspec
25
+ open coverage/index.html
26
+ ```
data/Guardfile CHANGED
@@ -1,5 +1,7 @@
1
1
  # More info at https://github.com/guard/guard#readme
2
2
 
3
3
  guard :rspec, all_on_start: true, cmd: 'bundle exec rspec' do
4
- watch(%r{^(lib|spec)/.*})
4
+ watch(%r{^lib/.*}) { 'spec' }
5
+ watch(%r{^spec/.*})
6
+ watch('spec/spec_helper') { 'spec' }
5
7
  end
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
- Command line tools to help teachers use GitHub in their classrooms.
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
- ## Basic Setup
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
- * Create an organization (you will be an owner by default). The organization should reflect the name of your course.
33
- * Create a `students` file (you will be prompted for the path later)
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
- * Add the GitHub username of all instructors to an 'instructors' file (one per line)
37
- * Run `create_teams`
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
- ## Passwords / OAuth
46
+ ```bash
47
+ teachers_pet create_student_teams ...
48
+ teachers_pet add_to_team --members Owners.csv ...
49
+ ```
40
50
 
41
- 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):
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
- Then, add the following line to your `.bash_profile`:
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
- export ghe_oauth="YOUR_TOKEN_HERE"
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
- ### Creating assignments
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
- fork_collab
82
+ teachers_pet help
83
+ # or
84
+ teachers_pet help COMMAND
63
85
  ```
64
86
 
65
- ### Pushing starter files
87
+ ### Giving others access
66
88
 
67
- 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.
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
- While in the directory for the starter file repository, run the `push_repos` script.
91
+ ### Creating assignments
70
92
 
71
- This works by creating a git remote for each repository and thing doing a push to that repository.
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
- ### Opening issues
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
- After running `create_repos`, instructors can open issues in repos with `open_issue`. This action requires an `issue.md` file containing the body of the issue. The issue title and optional tags are added at runtime.
99
+ ### Pushing starter files
76
100
 
77
- One issue will be opened in every repo defined by the `students` file and repository name given by the user.
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
- Open issues in student repos as a way to list requirements of the assignment, goals, or instructions for patching.
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` script to clone all the repositories in the organization that match the username-repository naming scheme that is generated when `create_repos` is run.
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
@@ -3,4 +3,4 @@ require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
data/bin/teachers_pet ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative File.join('..', 'lib', 'teachers_pet')
4
+ TeachersPet::Cli.start(ARGV)
data/lib/teachers_pet.rb CHANGED
@@ -1,4 +1,7 @@
1
- Dir.glob(File.join(File.dirname(__FILE__), '**', '*.rb'), &method(:require))
1
+ require 'active_support/concern'
2
+
3
+ require 'require_all'
4
+ require_rel '.'
2
5
 
3
6
  module TeachersPet
4
7
  end
@@ -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
- $LOAD_PATH << File.join(File.dirname(__FILE__), '..')
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
- def get_auth_method
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
- auth_method
10
+ def initialize(opts={})
11
+ @options = opts.symbolize_keys
24
12
  end
25
13
 
26
- def config_github
27
- return unless @username.nil?
28
- @api_endpoint = ask('What is the API endpoint?') { |q| q.default = Configuration.apiEndpoint }
29
- @web_endpoint = ask("What is the Web endpoint?") { |q| q.default = Configuration.webEndpoint }
30
-
31
- @username = ask('What is your username? (You must be an owner for the organization)?') { |q| q.default = ENV['USER'] }
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 init_client
44
- self.config_github
45
- puts "=" * 50
46
- puts "Authenticating to GitHub..."
47
- Octokit.configure do |c|
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
- c.auto_paginate = true
52
- end
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
- def execute(command)
68
- `#{command}`
69
- end
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
- def get_instructors_file_path
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 get_existing_repos_by_names(organization)
89
- repos = Hash.new
90
- response = @client.organization_repositories(organization)
91
- print " Org repo names"
92
- response.each do |repo|
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, type)
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
- def confirm(message, abort_on_no = true)
146
- # confirm
147
- confirmed = false
148
- choose do |menu|
149
- menu.prompt = message
76
+ map
77
+ end
150
78
 
151
- menu.choice :yes do confirmed = true end
152
- menu.choice :no do confirmed = false end
153
- end
154
- if abort_on_no && !confirmed
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 = ask('What repository name should be cloned for each student?') { |q| q.validate = /\w+/ }
19
- @organization = ask("What is the organization name?") { |q| q.default = TeachersPet::Configuration.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 = read_file(@student_file, 'Students')
10
+ @students = self.read_students_file
25
11
  end
26
12
 
27
13
  def get_clone_method
28
- cloneMethod = 'https'
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 = read_organization(@organization)
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 = @web_endpoint.gsub("https://","git@").gsub("/",":")
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 #{@web_endpoint}#{@organization}/#{repo_name}.git"
47
+ command = "git clone #{self.web}#{@organization}/#{repo_name}.git"
75
48
  end
76
49
  puts " --> Cloning: '#{command}'"
77
50
  self.execute(command)