@@ -10,6 +10,13 @@ It is written in Ruby. | |||||
Installation | Installation | ||||
------------ | ------------ | ||||
kgrader was developed using Ruby 2.2, though it should work with any recent | |||||
version of Ruby. | |||||
Install dependencies with gem: | |||||
gem install ruby-progressbar | |||||
Download kgrader over git: | Download kgrader over git: | ||||
git clone https://github.com/earwig/kgrader.git kgrader | git clone https://github.com/earwig/kgrader.git kgrader | ||||
@@ -24,21 +24,21 @@ module KGrader | |||||
course.roster(semester).load rosterfile | course.roster(semester).load rosterfile | ||||
end | end | ||||
def grade(course, semester, assignment, options = {}) | |||||
def grade(course, semester, assignment, students = nil, options = {}) | |||||
course = Course.new(@fs, course) | course = Course.new(@fs, course) | ||||
semester ||= course.current_semester | semester ||= course.current_semester | ||||
course.task(semester, assignment).grade options | |||||
course.task(semester, assignment, students).grade options | |||||
end | end | ||||
def commit(course, semester, assignment, options = {}) | |||||
def commit(course, semester, assignment, students = nil) | |||||
course = Course.new(@fs, course) | course = Course.new(@fs, course) | ||||
semester ||= course.current_semester | semester ||= course.current_semester | ||||
course.task(semester, assignment).commit options | |||||
course.task(semester, assignment, students).commit | |||||
end | end | ||||
def clean | def clean | ||||
# TODO: also purge uncommitted grades | |||||
clear_jail | clear_jail | ||||
# TODO: also purge uncommitted grades: set all graded to ungraded and delete all pending files | |||||
end | end | ||||
def clobber | def clobber | ||||
@@ -23,8 +23,8 @@ module KGrader | |||||
@assignments[name] ||= Assignment.new @fs, self, name | @assignments[name] ||= Assignment.new @fs, self, name | ||||
end | end | ||||
def task(semester, assignment) | |||||
Task.new @fs, self, semester, assignment | |||||
def task(semester, assignment, students = nil) | |||||
Task.new @fs, self, semester, assignment, students | |||||
end | end | ||||
def rosters | def rosters | ||||
@@ -10,7 +10,7 @@ module KGrader | |||||
end | end | ||||
def load(filename) | def load(filename) | ||||
@students = @fs.load(filename).map! { |item| item.first } | |||||
@students = @fs.load(filename).map! &:first | |||||
FileUtils.mkdir_p File.dirname(rosterfile) | FileUtils.mkdir_p File.dirname(rosterfile) | ||||
File.write rosterfile, @students.join("\n") | File.write rosterfile, @students.join("\n") | ||||
rescue FilesystemError => err | rescue FilesystemError => err | ||||
@@ -18,7 +18,7 @@ module KGrader | |||||
end | end | ||||
def students | def students | ||||
@students ||= @fs.load(rosterfile).map! { |item| item.first } | |||||
@students ||= @fs.load(rosterfile).map! &:first | |||||
rescue FilesystemError | rescue FilesystemError | ||||
raise RosterError, "unknown semester: #{semester}" | raise RosterError, "unknown semester: #{semester}" | ||||
end | end | ||||
@@ -29,6 +29,7 @@ module KGrader | |||||
def create | def create | ||||
FileUtils.mkdir_p @root | FileUtils.mkdir_p @root | ||||
self.status = :init | self.status = :init | ||||
nil | |||||
end | end | ||||
def fetch(due) | def fetch(due) | ||||
@@ -43,20 +44,48 @@ module KGrader | |||||
rewind due | rewind due | ||||
self.status = revision == oldrev ? :graded : :ungraded | self.status = revision == oldrev ? :graded : :ungraded | ||||
end | end | ||||
nil | |||||
end | end | ||||
def grade | def grade | ||||
# TODO | |||||
# TODO: | |||||
# self.status = :ungraded | |||||
# [grade the stuff] | |||||
# [save report to gradefile] | |||||
# FileUtils.touch pendingfile | |||||
# self.status = :graded | # self.status = :graded | ||||
# return grade summary string | |||||
sleep rand / 2 | |||||
'100%' | |||||
end | |||||
def commit | |||||
# TODO: | |||||
# if status == :graded && File.exists? pendingfile | |||||
# [copy gradefile to repo and commit] | |||||
# FileUtils.rm pendingfile | |||||
# end | |||||
sleep rand / 2 | |||||
nil | |||||
end | end | ||||
private | private | ||||
def repo | |||||
File.join @root, "repo" | |||||
end | |||||
def statusfile | def statusfile | ||||
File.join @root, "status.txt" | File.join @root, "status.txt" | ||||
end | end | ||||
def repo | |||||
File.join @root, "repo" | |||||
def gradefile | |||||
File.join @root, "grade.txt" | |||||
end | |||||
def pendingfile | |||||
File.join @root, "pending" | |||||
end | end | ||||
def revision | def revision | ||||
@@ -1,64 +1,71 @@ | |||||
require 'ruby-progressbar' | |||||
module KGrader | module KGrader | ||||
class Task | class Task | ||||
def initialize(filesystem, course, semester, assignment) | |||||
@fs = filesystem | |||||
@course = course | |||||
@semester = semester | |||||
@assignment = @course.assignment assignment | |||||
@students = @course.roster(@semester).students | |||||
def initialize(filesystem, course, semester, assignment, students = nil) | |||||
@fs = filesystem | |||||
@course = course | |||||
@semester = semester | |||||
@assignment = @course.assignment assignment | |||||
@submissions = get_submissions students | |||||
end | end | ||||
def grade(options = {}) | def grade(options = {}) | ||||
submissions = get_submissions options[:students] | |||||
due = options.fetch(:due, Time.now) | due = options.fetch(:due, Time.now) | ||||
fetch = options.fetch(:fetch, true) | fetch = options.fetch(:fetch, true) | ||||
regrade = options.fetch(:regrade, false) | regrade = options.fetch(:regrade, false) | ||||
count = submissions.count | |||||
puts "[grading #{count} student#{'s' if count != 1}]" | |||||
submissions.each do |sub| | |||||
unless sub.exists? | |||||
puts "[init #{sub.student}]" | |||||
sub.create | |||||
end | |||||
subtask 'setup' do |sub| | |||||
sub.create unless sub.exists? | |||||
end | end | ||||
if fetch | if fetch | ||||
submissions.each do |sub| | |||||
puts "[fetch #{sub.student}]" | |||||
subtask 'fetch' do |sub| | |||||
sub.fetch due | sub.fetch due | ||||
end | end | ||||
end | end | ||||
submissions.each do |sub| | |||||
sub.status = :ungraded if regrade | |||||
if sub.status == :ungraded | |||||
puts "[grade #{sub.student}]" | |||||
subtask 'grade' do |sub| | |||||
if sub.status == :init || sub.status == :fetching | |||||
next 'skip (need to fetch first)' | |||||
elsif sub.status == :graded && !regrade | |||||
next | |||||
else | |||||
sub.grade | sub.grade | ||||
end | end | ||||
end | end | ||||
end | end | ||||
def commit(options = {}) | |||||
submissions = get_submissions options[:students] | |||||
# TODO | |||||
puts "[committing]" | |||||
puts "course => #{@course.name}" | |||||
puts "semester => #{@semester}" | |||||
puts "assignment => #{@assignment.name}" | |||||
puts "students => #{submissions.map { |sub| sub.student }.join ', '}" | |||||
def commit | |||||
subtask 'commit', &:commit | |||||
end | end | ||||
private | private | ||||
def get_submissions(students) | def get_submissions(students) | ||||
students.nil? ? (students = @students) : (students &= @students) | |||||
roster = @course.roster(@semester).students | |||||
students.nil? ? (students = roster) : (students &= roster) | |||||
students.map do |student| | students.map do |student| | ||||
Submission.new @fs, @course, @semester, @assignment, student | Submission.new @fs, @course, @semester, @assignment, student | ||||
end | end | ||||
end | end | ||||
def student_len | |||||
@student_len ||= @submissions.map { |sub| sub.student.length }.max | |||||
end | |||||
def subtask(name) | |||||
progress = ProgressBar.create title: name, total: @submissions.size, | |||||
throttle_rate: 0, format: '%t [%b>%i] %j%% %e ' | |||||
@submissions.each.with_index do |sub, i| | |||||
job = "#{name} [#{sub.student.ljust student_len}]" | |||||
progress.title = "#{job}:" | |||||
result = yield sub | |||||
progress.title = name if i == @submissions.size - 1 | |||||
progress.log "#{job}#{': ' if result}#{result}" if result | |||||
progress.increment | |||||
end | |||||
end | |||||
end | end | ||||
end | end |
@@ -45,13 +45,15 @@ task :grade do | |||||
course, assignment, options = parse_args 2..2, | course, assignment, options = parse_args 2..2, | ||||
{ :semester => :string, :students => :array, :due => :time, | { :semester => :string, :students => :array, :due => :time, | ||||
:fetch => :bool, :regrade => :bool } | :fetch => :bool, :regrade => :bool } | ||||
run { |cli| cli.grade course, options[:semester], assignment, options } | |||||
semester, students = options[:semester], options[:students] | |||||
run { |cli| cli.grade course, semester, assignment, students, options } | |||||
end | end | ||||
task :commit do | task :commit do | ||||
course, assignment, options = parse_args 2..2, | course, assignment, options = parse_args 2..2, | ||||
{ :semester => :string, :students => :array } | { :semester => :string, :students => :array } | ||||
run { |cli| cli.commit course, options[:semester], assignment, options } | |||||
semester, students = options[:semester], options[:students] | |||||
run { |cli| cli.commit course, semester, assignment, students } | |||||
end | end | ||||
task :clean do | task :clean do | ||||