| @@ -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 | ||||