brown_noser 0.1.2 → 0.2.0
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.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +4 -2
- data/README.md +27 -10
- data/lib/brown_noser/version.rb +1 -1
- data/lib/brown_noser.rb +9 -2
- data/lib/cheating_detection.rb +105 -0
- data/lib/project_repo_sync.rb +5 -9
- data/lib/pull_branch_lister.rb +16 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5d664dcc46feadfc8739def0c8d3920c4c4e093
|
4
|
+
data.tar.gz: 026a938363429d9accce0a82651d4c757668b32f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98e396aeca9adee12359253d4619ad56e0e9787199ed0b7524031a9df3ae4f5691e4debd9c83cf9dcbf8e66569f9bc7a10bfe9f830835a00af6ebb918b12613f
|
7
|
+
data.tar.gz: d3ed1ef2733619002ac66d96e375a85817461913b3c46891ece862e71d153b3166b07bf03973b96b9bb65217a2d491b9dfe82de68b45fddaac3a15ac4d3dc966
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
|
4
|
+
brown_noser (0.1.2)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -14,6 +14,7 @@ GEM
|
|
14
14
|
foreman (0.78.0)
|
15
15
|
thor (~> 0.19.1)
|
16
16
|
method_source (0.8.2)
|
17
|
+
moss_ruby (1.1.2)
|
17
18
|
multipart-post (2.0.0)
|
18
19
|
octokit (4.2.0)
|
19
20
|
sawyer (~> 0.6.0, >= 0.5.3)
|
@@ -33,12 +34,13 @@ PLATFORMS
|
|
33
34
|
|
34
35
|
DEPENDENCIES
|
35
36
|
awesome_print
|
37
|
+
brown_noser!
|
36
38
|
bundler (~> 1.10)
|
37
39
|
foreman
|
40
|
+
moss_ruby
|
38
41
|
octokit (~> 4.0)
|
39
42
|
pry
|
40
43
|
rake
|
41
|
-
teachers_pet!
|
42
44
|
|
43
45
|
BUNDLED WITH
|
44
46
|
1.10.6
|
data/README.md
CHANGED
@@ -1,15 +1,21 @@
|
|
1
|
-
#
|
1
|
+
# BrownNoser
|
2
2
|
|
3
|
-
|
3
|
+
This tool is to help manage and inspect Git Repos used for our the Coursework at WCCCEDU.
|
4
4
|
|
5
|
-
|
5
|
+
## Major Milestones
|
6
|
+
- Sync pull request branches locally so they can be reviewed and graded locally
|
7
|
+
- Search all submissions to validate academnic honesty
|
8
|
+
- Step through each submission branch and build/compile/execute the code
|
9
|
+
- Run automated tests and fuzzy acceptance on output on each submission based on a project described _Rubric_ file
|
10
|
+
- Provide DSL to work with compiled and web based execution
|
11
|
+
- Integrate with Linters to verify student style to save time on pedantic comments by automating comments on student repos for teachers review.
|
6
12
|
|
7
13
|
## Installation
|
8
14
|
|
9
15
|
Add this line to your application's Gemfile:
|
10
16
|
|
11
17
|
```ruby
|
12
|
-
gem '
|
18
|
+
gem 'brown_noser', '~> 0.1.2'
|
13
19
|
```
|
14
20
|
|
15
21
|
And then execute:
|
@@ -18,21 +24,32 @@ And then execute:
|
|
18
24
|
|
19
25
|
Or install it yourself as:
|
20
26
|
|
21
|
-
$ gem install
|
27
|
+
$ gem install brown_noser
|
22
28
|
|
23
29
|
## Usage
|
24
30
|
|
25
|
-
|
31
|
+
This gem exposes the `pet` command
|
26
32
|
|
27
|
-
|
33
|
+
### Sync PR Branches
|
34
|
+
Bring unmerged branches to local repo for easy viewing and searching without merging.
|
35
|
+
```
|
36
|
+
pet <user> <repo> -u <username> -p <password> -s
|
37
|
+
pet <user> <repo> --username <username> --pasword <password> --sync
|
38
|
+
```
|
28
39
|
|
29
|
-
|
40
|
+
### Search for patterns in local repo (Help find cheating)
|
41
|
+
```
|
42
|
+
pet -f 'Your query or Regex'
|
43
|
+
pet --find 'Your query or Regex'
|
44
|
+
```
|
45
|
+
|
46
|
+
## Development
|
30
47
|
|
31
|
-
|
48
|
+
Clone and run `bundle install` to receive deps.
|
32
49
|
|
33
50
|
## Contributing
|
34
51
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
52
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/WCCCEDU/brown_noser.
|
36
53
|
|
37
54
|
|
38
55
|
## License
|
data/lib/brown_noser/version.rb
CHANGED
data/lib/brown_noser.rb
CHANGED
@@ -2,6 +2,8 @@ require 'optparse'
|
|
2
2
|
autoload :ClientResolver, File.expand_path(File.dirname(__FILE__)) + '/client_resolver.rb'
|
3
3
|
autoload :ProjectRepoSync, File.expand_path(File.dirname(__FILE__)) + '/project_repo_sync.rb'
|
4
4
|
autoload :ProjectRepoSearcher, File.expand_path(File.dirname(__FILE__)) + '/project_repo_searcher.rb'
|
5
|
+
autoload :CheatingDetection, File.expand_path(File.dirname(__FILE__)) + '/cheating_detection.rb'
|
6
|
+
autoload :PullBranchLister, File.expand_path(File.dirname(__FILE__)) + '/pull_branch_lister.rb'
|
5
7
|
|
6
8
|
class BrownNoser
|
7
9
|
attr_reader :options
|
@@ -13,8 +15,9 @@ class BrownNoser
|
|
13
15
|
|
14
16
|
opts.on('-s', '--sync', 'Sync') { |v| @options[:sync_flag] = true }
|
15
17
|
opts.on('-f', '--find QUERY', 'Find') { |v| @options[:query] = v }
|
16
|
-
opts.on('-u', '--username USER', '
|
17
|
-
opts.on('-p', '--password PASS', '
|
18
|
+
opts.on('-u', '--username USER', 'Github User') { |v| @options[:username] = v }
|
19
|
+
opts.on('-p', '--password PASS', 'Github Pass') { |v| @options[:password] = v }
|
20
|
+
opts.on('-c', '--cheat MOSSID', 'Moss Userid') { |v| @options[:moss_id] = v }
|
18
21
|
|
19
22
|
end.parse!
|
20
23
|
end
|
@@ -30,12 +33,16 @@ class BrownNoser
|
|
30
33
|
def run
|
31
34
|
sync = @options[:sync_flag]
|
32
35
|
find = @options[:query]
|
36
|
+
cheat = @options[:moss_id]
|
33
37
|
resolve_client
|
34
38
|
if sync
|
35
39
|
repo_syncer = ProjectRepoSync.new ARGV[0], ARGV[1]
|
36
40
|
repo_syncer.sync_assignment_branches
|
37
41
|
elsif find
|
38
42
|
searcher = ProjectRepoSearcher.new.search find
|
43
|
+
elsif cheat
|
44
|
+
puts "CHEAT"
|
45
|
+
cheat_detection = CheatingDetection.new(ARGV[0], ARGV[1], cheat).detect
|
39
46
|
end
|
40
47
|
end
|
41
48
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'moss_ruby'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class CheatingDetection
|
5
|
+
|
6
|
+
TEMP_DIR = 'brown_noser_cheat_detection'
|
7
|
+
|
8
|
+
def initialize(user, repo, moss_id = 000000000)
|
9
|
+
@user = user
|
10
|
+
@repo = repo
|
11
|
+
@moss_id = moss_id
|
12
|
+
end
|
13
|
+
|
14
|
+
def detect
|
15
|
+
pull_details = PullBranchLister.new(@user, @repo).list
|
16
|
+
|
17
|
+
recreate_tmp_dir
|
18
|
+
|
19
|
+
commands = pull_details.map &command_orgnaizer()
|
20
|
+
|
21
|
+
commands.flatten.each do |command|
|
22
|
+
prepared_command = command.call
|
23
|
+
puts prepared_command unless prepared_command.empty?
|
24
|
+
`#{prepared_command}` unless prepared_command.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create the MossRuby object
|
28
|
+
@moss ||= MossRuby.new(@moss_id) #replace 000000000 with your user id
|
29
|
+
|
30
|
+
# Set options -- the options will already have these default values
|
31
|
+
@moss.options[:max_matches] = 10
|
32
|
+
@moss.options[:directory_submission] = false
|
33
|
+
@moss.options[:show_num_matches] = 250
|
34
|
+
@moss.options[:experimental_server] = false
|
35
|
+
@moss.options[:comment] = ""
|
36
|
+
@moss.options[:language] = "cc"
|
37
|
+
|
38
|
+
# Create a file hash, with the files to be processed
|
39
|
+
to_check = MossRuby.empty_file_hash
|
40
|
+
#MossRuby.add_file(to_check, "#{TEMP_DIR}/**/*.h")
|
41
|
+
MossRuby.add_file(to_check, "#{TEMP_DIR}/**/*.cpp")
|
42
|
+
|
43
|
+
# Get server to process files
|
44
|
+
url = @moss.check to_check
|
45
|
+
|
46
|
+
IO.write "brown_noser.html", "<div><h3>Cheat Detection Results</h3><br/><a href='#{url}'>#{url}</a></div>"
|
47
|
+
FileUtils.rm_rf TEMP_DIR
|
48
|
+
|
49
|
+
# Get results
|
50
|
+
results = @moss.extract_results url
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
def make_folder_for_branch(user, branch)
|
55
|
+
->(){ FileUtils.mkdir_p("#{TEMP_DIR}/#{user}_#{branch}") }
|
56
|
+
end
|
57
|
+
|
58
|
+
def copy_file(source, dest)
|
59
|
+
->(){ "cp \"#{source}\" \"#{dest}\"" }
|
60
|
+
end
|
61
|
+
|
62
|
+
def extract_source_files(user, branch, dest_folder)
|
63
|
+
->(){
|
64
|
+
files = `git ls-tree --full-name --name-only -r #{user}/#{branch} | grep '.h$\\|.cpp$'`.split("\n")
|
65
|
+
copy_files = files.map do |file|
|
66
|
+
copy_file(file, "#{dest_folder}/#{file}").call
|
67
|
+
end
|
68
|
+
copy_files.join(" && ")
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def recreate_tmp_dir
|
73
|
+
FileUtils.rm_rf TEMP_DIR
|
74
|
+
FileUtils.mkdir TEMP_DIR
|
75
|
+
end
|
76
|
+
|
77
|
+
def command_orgnaizer
|
78
|
+
->(pull_context){
|
79
|
+
[
|
80
|
+
make_folder_for_branch(pull_context[0], pull_context[1]),
|
81
|
+
ProjectRepoSync::git_checkout("#{pull_context[0]}/#{pull_context[1]}"),
|
82
|
+
extract_source_files(pull_context[0], pull_context[1], "#{TEMP_DIR}/#{pull_context[0]}_#{pull_context[1]}")
|
83
|
+
]
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def extract_results
|
88
|
+
results.each_with_index { |match, i|
|
89
|
+
match.each { |file|
|
90
|
+
report_match = <<-HTML
|
91
|
+
<div class="match">
|
92
|
+
<h3>#{file[:filename]}</h3>
|
93
|
+
<h4>#{file[:pct]}</h4>
|
94
|
+
<h4>#{file[:url]}</h4>
|
95
|
+
<h4>#{file[:part_url]}</h4>
|
96
|
+
<div class="code">#{file[:html]}</div>
|
97
|
+
</div>
|
98
|
+
HTML
|
99
|
+
result_html += report_match
|
100
|
+
}
|
101
|
+
result_html += "<hr/>"
|
102
|
+
}
|
103
|
+
IO.write "cheat_report.html", result_html
|
104
|
+
end
|
105
|
+
end
|
data/lib/project_repo_sync.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
class ProjectRepoSync
|
2
2
|
attr_reader :user, :repo
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
def self.git_checkout(branch)
|
5
|
+
->(){ "git checkout #{branch}" }
|
6
|
+
end
|
6
7
|
|
7
8
|
def initialize(user, repo)
|
8
9
|
@client = ClientResolver.client
|
@@ -11,8 +12,7 @@ class ProjectRepoSync
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def sync_assignment_branches
|
14
|
-
|
15
|
-
pull_details = pulls.map(&PULL_EXTRACTOR).map(&GIT_COMMAND_EXTRACTOR)
|
15
|
+
pull_details = PullBranchLister.new(@user, @repo).list
|
16
16
|
|
17
17
|
%x(git checkout -f master | git branch | grep -v "^..master$" | sed 's/^[ *]*//' | sed 's/^/git branch -D /' | bash)
|
18
18
|
|
@@ -34,10 +34,6 @@ private
|
|
34
34
|
->(){ "git checkout -B #{user}/#{branch} master" }
|
35
35
|
end
|
36
36
|
|
37
|
-
def git_checkout(branch)
|
38
|
-
->(){ "git checkout #{branch}" }
|
39
|
-
end
|
40
|
-
|
41
37
|
def git_pull(repo_url, branch)
|
42
38
|
->(){ "git pull #{repo_url} #{branch}" }
|
43
39
|
end
|
@@ -47,7 +43,7 @@ private
|
|
47
43
|
[
|
48
44
|
git_checkout_new( pull_context[0], pull_context[1] ),
|
49
45
|
git_pull( prep_repo(pull_context[0], @repo).call, pull_context[1] ),
|
50
|
-
git_checkout( 'master' )
|
46
|
+
ProjectRepoSync::git_checkout( 'master' )
|
51
47
|
]
|
52
48
|
}
|
53
49
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class PullBranchLister
|
2
|
+
|
3
|
+
PULL_EXTRACTOR = ->(pull){ pull.head.label }
|
4
|
+
GIT_COMMAND_EXTRACTOR = ->(pull_info){ pull_info.split(':') }
|
5
|
+
|
6
|
+
def initialize(user, repo)
|
7
|
+
@client = ClientResolver.client
|
8
|
+
@user = user
|
9
|
+
@repo = repo
|
10
|
+
end
|
11
|
+
|
12
|
+
def list
|
13
|
+
pulls = @client.pulls "#{@user}/#{@repo}"
|
14
|
+
pulls.map(&PULL_EXTRACTOR).map(&GIT_COMMAND_EXTRACTOR)
|
15
|
+
end
|
16
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brown_noser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Scarrone
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -84,9 +84,11 @@ files:
|
|
84
84
|
- brown_noser.gemspec
|
85
85
|
- lib/brown_noser.rb
|
86
86
|
- lib/brown_noser/version.rb
|
87
|
+
- lib/cheating_detection.rb
|
87
88
|
- lib/client_resolver.rb
|
88
89
|
- lib/project_repo_searcher.rb
|
89
90
|
- lib/project_repo_sync.rb
|
91
|
+
- lib/pull_branch_lister.rb
|
90
92
|
homepage: https://github.com/WCCCEDU/github_grading_tools_rb
|
91
93
|
licenses:
|
92
94
|
- MIT
|