| @@ -5,5 +5,6 @@ require_relative 'kgrader/course' | |||
| require_relative 'kgrader/errors' | |||
| require_relative 'kgrader/filesystem' | |||
| require_relative 'kgrader/roster' | |||
| require_relative 'kgrader/submission' | |||
| require_relative 'kgrader/task' | |||
| require_relative 'kgrader/util' | |||
| @@ -1,17 +1,39 @@ | |||
| require 'open3' | |||
| module KGrader::Backend | |||
| class SVN | |||
| def initialize(filesystem, config) | |||
| def initialize(filesystem, course, config) | |||
| @fs = filesystem | |||
| @course = course | |||
| @config = config | |||
| end | |||
| def fetch(semester, assignment, student) | |||
| url = @config['url'] % { | |||
| :semester => semester, :assignment => assignment, :student => student } | |||
| def revision(repo) | |||
| # TODO | |||
| puts "[fetching #{student}: #{url}]" | |||
| -1 | |||
| end | |||
| def clone(repo, semester, assignment, student) | |||
| url = get_url semester, assignment, student | |||
| run 'checkout', '--ignore-externals', url, repo | |||
| end | |||
| def update(repo) | |||
| run 'update', '--ignore-externals', '--accept', 'tf', repo | |||
| end | |||
| private | |||
| def run(*cmd) | |||
| Open3.capture2e('svn', *cmd) | |||
| end | |||
| def get_url(semester, assignment, student) | |||
| @config['url'] % { | |||
| :semester => semester, | |||
| :assignment => assignment, | |||
| :student => student | |||
| } | |||
| end | |||
| end | |||
| end | |||
| @@ -8,7 +8,7 @@ module KGrader | |||
| @config = @fs.load @fs.course_config(@name) | |||
| type = @config['backend'] | |||
| @backend = KGrader::backend(type).new self, @config[type] | |||
| @backend = KGrader::backend(type).new @fs, self, @config[type] | |||
| @rosters = {} | |||
| @assignments = {} | |||
| rescue FilesystemError | |||
| @@ -1,3 +1,4 @@ | |||
| require 'json' | |||
| require 'yaml' | |||
| module KGrader | |||
| @@ -37,6 +38,10 @@ module KGrader | |||
| File.join desk, courseid, semester, '_roster.csv' | |||
| end | |||
| def submission(courseid, semester, assignment, student) | |||
| File.join desk, courseid, semester, assignment, student | |||
| end | |||
| # ------------------------------------------------------------------------- | |||
| def courses | |||
| @@ -44,7 +49,7 @@ module KGrader | |||
| end | |||
| def assignments(courseid) | |||
| Dir[assignment courseid '*'].map! { |fn| File.basename File.dirname fn } | |||
| Dir[assignment courseid, '*'].map! { |fn| File.basename File.dirname fn } | |||
| end | |||
| def semesters(courseid) | |||
| @@ -55,6 +60,10 @@ module KGrader | |||
| def load(path) | |||
| case File.extname path | |||
| when '.txt' | |||
| File.read path | |||
| when '.json' | |||
| JSON.parse File.read(path) | |||
| when '.yml', '.yaml' | |||
| YAML.load File.read(path) | |||
| when '.csv' | |||
| @@ -0,0 +1,70 @@ | |||
| module KGrader | |||
| class Submission | |||
| attr_reader :course, :semester, :assignment, :student | |||
| def initialize(filesystem, course, semester, assignment, student) | |||
| @fs = filesystem | |||
| @course = course | |||
| @semester = semester | |||
| @assignment = assignment | |||
| @student = student | |||
| @root = @fs.submission @course.name, @semester, @assignment.name, student | |||
| @status = nil | |||
| end | |||
| def status | |||
| @status ||= @fs.load(statusfile).to_sym | |||
| end | |||
| def status=(new_status) | |||
| File.write statusfile, new_status | |||
| @status = new_status | |||
| end | |||
| def exists? | |||
| File.exists? statusfile | |||
| end | |||
| def create | |||
| FileUtils.mkdir_p @root | |||
| self.status = :init | |||
| end | |||
| def fetch(due) | |||
| if status == :init | |||
| @course.backend.clone repo, @semester, @assignment.id, @student | |||
| rewind due | |||
| self.status = :ungraded | |||
| else | |||
| oldrev = revision if status == :graded | |||
| self.status = :fetching | |||
| @course.backend.update repo | |||
| rewind due | |||
| self.status = revision == oldrev ? :graded : :ungraded | |||
| end | |||
| end | |||
| def grade | |||
| # TODO | |||
| # self.status = :graded | |||
| end | |||
| private | |||
| def statusfile | |||
| File.join @root, "status.txt" | |||
| end | |||
| def repo | |||
| File.join @root, "repo" | |||
| end | |||
| def revision | |||
| @course.backend.revision repo | |||
| end | |||
| def rewind(date) | |||
| # TODO | |||
| end | |||
| end | |||
| end | |||
| @@ -2,52 +2,62 @@ module KGrader | |||
| class Task | |||
| def initialize(filesystem, course, semester, assignment) | |||
| @fs = filesystem | |||
| @course = course | |||
| @semester = semester | |||
| @fs = filesystem | |||
| @course = course | |||
| @semester = semester | |||
| @assignment = @course.assignment assignment | |||
| @students = @course.roster(@semester).students | |||
| @students = @course.roster(@semester).students | |||
| end | |||
| def grade(options = {}) | |||
| students = @students | |||
| students &= options[:students] unless options[:students].nil? | |||
| submissions = get_submissions options[:students] | |||
| due = options.fetch(:due, Time.now) | |||
| fetch = options.fetch(:fetch, true) | |||
| regrade = options.fetch(:regrade, false) | |||
| # TODO | |||
| puts "[grading]" | |||
| puts "course => #{@course.name}" | |||
| puts "semester => #{@semester}" | |||
| puts "assignment => #{@assignment.name}" | |||
| puts "students => #{students.join ', '}" | |||
| puts "due => #{due}" | |||
| puts "fetch => #{fetch}" | |||
| puts "regrade => #{regrade}" | |||
| puts | |||
| count = submissions.count | |||
| puts "[grading #{count} student#{'s' if count != 1}]" | |||
| fetch_students students if fetch | |||
| submissions.each do |sub| | |||
| unless sub.exists? | |||
| puts "[init #{sub.student}]" | |||
| sub.create | |||
| end | |||
| end | |||
| if fetch | |||
| submissions.each do |sub| | |||
| puts "[fetch #{sub.student}]" | |||
| sub.fetch due | |||
| end | |||
| end | |||
| submissions.each do |sub| | |||
| sub.status = :ungraded if regrade | |||
| if sub.status == :ungraded | |||
| puts "[grade #{sub.student}]" | |||
| sub.grade | |||
| end | |||
| end | |||
| end | |||
| def commit(options = {}) | |||
| students = @students | |||
| students &= options[:students] unless options[:students].nil? | |||
| submissions = get_submissions options[:students] | |||
| # TODO | |||
| puts "[committing]" | |||
| puts "course => #{@course.name}" | |||
| puts "semester => #{@semester}" | |||
| puts "assignment => #{@assignment.name}" | |||
| puts "students => #{students.join ', '}" | |||
| puts "students => #{submissions.map { |sub| sub.student }.join ', '}" | |||
| end | |||
| private | |||
| def fetch_students(students) | |||
| students.each do |student| | |||
| @course.backend.fetch @semester, @assignment.id, student | |||
| def get_submissions(students) | |||
| students.nil? ? (students = @students) : (students &= @students) | |||
| students.map do |student| | |||
| Submission.new @fs, @course, @semester, @assignment, student | |||
| end | |||
| end | |||
| end | |||