| @@ -10,6 +10,13 @@ It is written in Ruby. | |||
| 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: | |||
| git clone https://github.com/earwig/kgrader.git kgrader | |||
| @@ -24,21 +24,21 @@ module KGrader | |||
| course.roster(semester).load rosterfile | |||
| end | |||
| def grade(course, semester, assignment, options = {}) | |||
| def grade(course, semester, assignment, students = nil, options = {}) | |||
| course = Course.new(@fs, course) | |||
| semester ||= course.current_semester | |||
| course.task(semester, assignment).grade options | |||
| course.task(semester, assignment, students).grade options | |||
| end | |||
| def commit(course, semester, assignment, options = {}) | |||
| def commit(course, semester, assignment, students = nil) | |||
| course = Course.new(@fs, course) | |||
| semester ||= course.current_semester | |||
| course.task(semester, assignment).commit options | |||
| course.task(semester, assignment, students).commit | |||
| end | |||
| def clean | |||
| # TODO: also purge uncommitted grades | |||
| clear_jail | |||
| # TODO: also purge uncommitted grades: set all graded to ungraded and delete all pending files | |||
| end | |||
| def clobber | |||
| @@ -23,8 +23,8 @@ module KGrader | |||
| @assignments[name] ||= Assignment.new @fs, self, name | |||
| 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 | |||
| def rosters | |||
| @@ -10,7 +10,7 @@ module KGrader | |||
| end | |||
| def load(filename) | |||
| @students = @fs.load(filename).map! { |item| item.first } | |||
| @students = @fs.load(filename).map! &:first | |||
| FileUtils.mkdir_p File.dirname(rosterfile) | |||
| File.write rosterfile, @students.join("\n") | |||
| rescue FilesystemError => err | |||
| @@ -18,7 +18,7 @@ module KGrader | |||
| end | |||
| def students | |||
| @students ||= @fs.load(rosterfile).map! { |item| item.first } | |||
| @students ||= @fs.load(rosterfile).map! &:first | |||
| rescue FilesystemError | |||
| raise RosterError, "unknown semester: #{semester}" | |||
| end | |||
| @@ -29,6 +29,7 @@ module KGrader | |||
| def create | |||
| FileUtils.mkdir_p @root | |||
| self.status = :init | |||
| nil | |||
| end | |||
| def fetch(due) | |||
| @@ -43,20 +44,48 @@ module KGrader | |||
| rewind due | |||
| self.status = revision == oldrev ? :graded : :ungraded | |||
| end | |||
| nil | |||
| end | |||
| def grade | |||
| # TODO | |||
| # TODO: | |||
| # self.status = :ungraded | |||
| # [grade the stuff] | |||
| # [save report to gradefile] | |||
| # FileUtils.touch pendingfile | |||
| # 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 | |||
| private | |||
| def repo | |||
| File.join @root, "repo" | |||
| end | |||
| def statusfile | |||
| File.join @root, "status.txt" | |||
| end | |||
| def repo | |||
| File.join @root, "repo" | |||
| def gradefile | |||
| File.join @root, "grade.txt" | |||
| end | |||
| def pendingfile | |||
| File.join @root, "pending" | |||
| end | |||
| def revision | |||
| @@ -1,64 +1,71 @@ | |||
| require 'ruby-progressbar' | |||
| module KGrader | |||
| 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 | |||
| def grade(options = {}) | |||
| submissions = get_submissions options[:students] | |||
| due = options.fetch(:due, Time.now) | |||
| fetch = options.fetch(:fetch, true) | |||
| 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 | |||
| if fetch | |||
| submissions.each do |sub| | |||
| puts "[fetch #{sub.student}]" | |||
| subtask 'fetch' do |sub| | |||
| sub.fetch due | |||
| 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 | |||
| 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 | |||
| private | |||
| 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| | |||
| Submission.new @fs, @course, @semester, @assignment, student | |||
| 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 | |||
| @@ -45,13 +45,15 @@ task :grade do | |||
| course, assignment, options = parse_args 2..2, | |||
| { :semester => :string, :students => :array, :due => :time, | |||
| :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 | |||
| task :commit do | |||
| course, assignment, options = parse_args 2..2, | |||
| { :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 | |||
| task :clean do | |||