| @@ -5,5 +5,6 @@ require_relative 'kgrader/course' | |||||
| require_relative 'kgrader/errors' | require_relative 'kgrader/errors' | ||||
| require_relative 'kgrader/filesystem' | require_relative 'kgrader/filesystem' | ||||
| require_relative 'kgrader/roster' | require_relative 'kgrader/roster' | ||||
| require_relative 'kgrader/submission' | |||||
| require_relative 'kgrader/task' | require_relative 'kgrader/task' | ||||
| require_relative 'kgrader/util' | require_relative 'kgrader/util' | ||||
| @@ -1,17 +1,39 @@ | |||||
| require 'open3' | |||||
| module KGrader::Backend | module KGrader::Backend | ||||
| class SVN | class SVN | ||||
| def initialize(filesystem, config) | |||||
| def initialize(filesystem, course, config) | |||||
| @fs = filesystem | @fs = filesystem | ||||
| @course = course | |||||
| @config = config | @config = config | ||||
| end | end | ||||
| def fetch(semester, assignment, student) | |||||
| url = @config['url'] % { | |||||
| :semester => semester, :assignment => assignment, :student => student } | |||||
| def revision(repo) | |||||
| # TODO | # 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 | end | ||||
| end | end | ||||
| @@ -8,7 +8,7 @@ module KGrader | |||||
| @config = @fs.load @fs.course_config(@name) | @config = @fs.load @fs.course_config(@name) | ||||
| type = @config['backend'] | type = @config['backend'] | ||||
| @backend = KGrader::backend(type).new self, @config[type] | |||||
| @backend = KGrader::backend(type).new @fs, self, @config[type] | |||||
| @rosters = {} | @rosters = {} | ||||
| @assignments = {} | @assignments = {} | ||||
| rescue FilesystemError | rescue FilesystemError | ||||
| @@ -1,3 +1,4 @@ | |||||
| require 'json' | |||||
| require 'yaml' | require 'yaml' | ||||
| module KGrader | module KGrader | ||||
| @@ -37,6 +38,10 @@ module KGrader | |||||
| File.join desk, courseid, semester, '_roster.csv' | File.join desk, courseid, semester, '_roster.csv' | ||||
| end | end | ||||
| def submission(courseid, semester, assignment, student) | |||||
| File.join desk, courseid, semester, assignment, student | |||||
| end | |||||
| # ------------------------------------------------------------------------- | # ------------------------------------------------------------------------- | ||||
| def courses | def courses | ||||
| @@ -44,7 +49,7 @@ module KGrader | |||||
| end | end | ||||
| def assignments(courseid) | def assignments(courseid) | ||||
| Dir[assignment courseid '*'].map! { |fn| File.basename File.dirname fn } | |||||
| Dir[assignment courseid, '*'].map! { |fn| File.basename File.dirname fn } | |||||
| end | end | ||||
| def semesters(courseid) | def semesters(courseid) | ||||
| @@ -55,6 +60,10 @@ module KGrader | |||||
| def load(path) | def load(path) | ||||
| case File.extname path | case File.extname path | ||||
| when '.txt' | |||||
| File.read path | |||||
| when '.json' | |||||
| JSON.parse File.read(path) | |||||
| when '.yml', '.yaml' | when '.yml', '.yaml' | ||||
| YAML.load File.read(path) | YAML.load File.read(path) | ||||
| when '.csv' | 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 | class Task | ||||
| def initialize(filesystem, course, semester, assignment) | def initialize(filesystem, course, semester, assignment) | ||||
| @fs = filesystem | |||||
| @course = course | |||||
| @semester = semester | |||||
| @fs = filesystem | |||||
| @course = course | |||||
| @semester = semester | |||||
| @assignment = @course.assignment assignment | @assignment = @course.assignment assignment | ||||
| @students = @course.roster(@semester).students | |||||
| @students = @course.roster(@semester).students | |||||
| end | end | ||||
| def grade(options = {}) | def grade(options = {}) | ||||
| students = @students | |||||
| students &= options[:students] unless options[:students].nil? | |||||
| 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) | ||||
| # 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 | end | ||||
| def commit(options = {}) | def commit(options = {}) | ||||
| students = @students | |||||
| students &= options[:students] unless options[:students].nil? | |||||
| submissions = get_submissions options[:students] | |||||
| # TODO | # TODO | ||||
| puts "[committing]" | puts "[committing]" | ||||
| puts "course => #{@course.name}" | puts "course => #{@course.name}" | ||||
| puts "semester => #{@semester}" | puts "semester => #{@semester}" | ||||
| puts "assignment => #{@assignment.name}" | puts "assignment => #{@assignment.name}" | ||||
| puts "students => #{students.join ', '}" | |||||
| puts "students => #{submissions.map { |sub| sub.student }.join ', '}" | |||||
| end | end | ||||
| private | 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 | end | ||||
| end | end | ||||