@@ -31,7 +31,8 @@ To show all known classes, semesters, and assignments: | |||||
rake list | rake list | ||||
To load a roster for a specific semester: | |||||
To load a roster (a newline-delimited list of student identifiers) for a | |||||
specific semester: | |||||
rake roster cs123 myroster.csv semester=sp16 | rake roster cs123 myroster.csv semester=sp16 | ||||
@@ -26,19 +26,19 @@ module KGrader | |||||
end | end | ||||
def roster(course, semester, rosterfile) | def roster(course, semester, rosterfile) | ||||
course = fetch_course course | |||||
course = Course.new(@fs, course) | |||||
semester ||= course.current_semester | semester ||= course.current_semester | ||||
course.roster(semester).load rosterfile | course.roster(semester).load rosterfile | ||||
end | end | ||||
def grade(course, semester, assignment, options = {}) | def grade(course, semester, assignment, options = {}) | ||||
course = fetch_course course | |||||
course = Course.new(@fs, course) | |||||
semester ||= course.current_semester | semester ||= course.current_semester | ||||
course.task(semester, assignment).grade options | course.task(semester, assignment).grade options | ||||
end | end | ||||
def commit(course, semester, assignment, options = {}) | def commit(course, semester, assignment, options = {}) | ||||
course = fetch_course course | |||||
course = Course.new(@fs, course) | |||||
semester ||= course.current_semester | semester ||= course.current_semester | ||||
course.task(semester, assignment).commit options | course.task(semester, assignment).commit options | ||||
end | end | ||||
@@ -58,12 +58,6 @@ module KGrader | |||||
end | end | ||||
private | private | ||||
def fetch_course(course) | |||||
Course.new(@fs, course) | |||||
rescue FilesystemError | |||||
KGrader::die "unknown course" | |||||
end | |||||
def reset_jail | def reset_jail | ||||
FileUtils.rm_rf @fs.jail | FileUtils.rm_rf @fs.jail | ||||
FileUtils.mkdir @fs.jail | FileUtils.mkdir @fs.jail | ||||
@@ -11,6 +11,8 @@ module KGrader | |||||
@config = @fs.load @fs.course_config(@name) | @config = @fs.load @fs.course_config(@name) | ||||
@rosters = {} | @rosters = {} | ||||
rescue FilesystemError | |||||
raise CourseError, "unknown or invalid course: #{name}" | |||||
end | end | ||||
def roster(semester) | def roster(semester) | ||||
@@ -30,12 +32,7 @@ module KGrader | |||||
end | end | ||||
def current_semester | def current_semester | ||||
case @config['semesters'] | |||||
when 'faspYY' | |||||
KGrader::season + DateTime.now.strftime('%y') | |||||
when 'faspYYYY' | |||||
KGrader::season + DateTime.now.strftime('%Y') | |||||
end | |||||
KGrader::current_semester @config['semesters'] | |||||
end | end | ||||
end | end | ||||
end | end |
@@ -2,6 +2,18 @@ module KGrader | |||||
class KGraderError < StandardError | class KGraderError < StandardError | ||||
end | end | ||||
class ArgumentError < KGraderError | |||||
end | |||||
class FilesystemError < KGraderError | class FilesystemError < KGraderError | ||||
end | end | ||||
class ConfigError < KGraderError | |||||
end | |||||
class CourseError < KGraderError | |||||
end | |||||
class RosterError < KGraderError | |||||
end | |||||
end | end |
@@ -52,10 +52,10 @@ module KGrader | |||||
when '.csv' | when '.csv' | ||||
File.read(path).split("\n").map! { |line| line.split "," } | File.read(path).split("\n").map! { |line| line.split "," } | ||||
else | else | ||||
raise FilesystemError, "unknown file type" | |||||
raise FilesystemError, "unknown file type: #{path}" | |||||
end | end | ||||
rescue SystemCallError # Errno::ENOENT, etc. | rescue SystemCallError # Errno::ENOENT, etc. | ||||
raise FilesystemError, "can't read file" | |||||
raise FilesystemError, "can't read file: #{path}" | |||||
end | end | ||||
end | end | ||||
end | end |
@@ -13,10 +13,14 @@ module KGrader | |||||
@students = @fs.load(filename).map! { |item| item.first } | @students = @fs.load(filename).map! { |item| item.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 | |||||
raise RosterError, err | |||||
end | end | ||||
def students | def students | ||||
@students ||= @fs.load(rosterfile).map! { |item| item.first } | @students ||= @fs.load(rosterfile).map! { |item| item.first } | ||||
rescue FilesystemError | |||||
raise RosterError, "unknown semester: #{semester}" | |||||
end | end | ||||
private | private | ||||
@@ -6,23 +6,32 @@ module KGrader | |||||
@course = course | @course = course | ||||
@semester = semester | @semester = semester | ||||
@assignment = assignment | @assignment = assignment | ||||
@roster = @course.roster @semester | |||||
@students = @course.roster(@semester).students | |||||
end | end | ||||
def grade(options = {}) | def grade(options = {}) | ||||
students = @roster.students | |||||
students = @students | |||||
students &= options[:students] unless options[:students].nil? | students &= options[:students] unless options[:students].nil? | ||||
due = options.fetch(:due, Time.now) | |||||
fetch = options.fetch(:fetch, true) | |||||
regrade = options.fetch(:regrade, false) | |||||
# TODO | # TODO | ||||
puts "Grading #{@course.name}:#{@semester} assignment #{@assignment}..." | puts "Grading #{@course.name}:#{@semester} assignment #{@assignment}..." | ||||
puts "- options: #{options}" | |||||
puts "- students: #{students.inspect}" | puts "- students: #{students.inspect}" | ||||
puts "- due: #{due}" | |||||
puts "- fetch: #{fetch}" | |||||
puts "- regrade: #{regrade}" | |||||
end | end | ||||
def commit(options = {}) | def commit(options = {}) | ||||
students = @students | |||||
students &= options[:students] unless options[:students].nil? | |||||
# TODO | # TODO | ||||
puts "Committing #{@course.name}:#{@semester} assignment #{@assignment}..." | puts "Committing #{@course.name}:#{@semester} assignment #{@assignment}..." | ||||
puts "- options: #{options}" | |||||
puts "- students: #{students.inspect}" | |||||
end | end | ||||
end | end | ||||
end | end |
@@ -1,10 +1,6 @@ | |||||
require 'date' | |||||
require 'time' | |||||
module KGrader | module KGrader | ||||
def self.die(error) | |||||
Kernel::abort "fatal: #{error}" | |||||
end | |||||
def self.parse_args(raw, num, keywords) | def self.parse_args(raw, num, keywords) | ||||
args = [] | args = [] | ||||
options = {} | options = {} | ||||
@@ -13,7 +9,9 @@ module KGrader | |||||
if arg.include? '=' | if arg.include? '=' | ||||
key, val = arg.split('=', 2) | key, val = arg.split('=', 2) | ||||
key = key.to_sym | key = key.to_sym | ||||
die "unknown keyword #{key}" unless keywords.include? key | |||||
unless keywords.include? key | |||||
raise ArgumentError, "unknown keyword: #{key}" | |||||
end | |||||
options[key] = case keywords[key] | options[key] = case keywords[key] | ||||
when :string | when :string | ||||
val | val | ||||
@@ -21,20 +19,28 @@ module KGrader | |||||
%w(true yes 1 t y).include? val.downcase | %w(true yes 1 t y).include? val.downcase | ||||
when :array | when :array | ||||
val.split(",").map! { |x| x.strip.downcase } | val.split(",").map! { |x| x.strip.downcase } | ||||
when :datetime | |||||
DateTime.parse(val) | |||||
when :time | |||||
Time.parse(val) | |||||
end | end | ||||
else | else | ||||
args << arg | args << arg | ||||
end | end | ||||
end | end | ||||
die "too few arguments" if args.size < num | |||||
die "too many arguments" if args.size > num | |||||
raise ArgumentError, "too few arguments" if args.size < num | |||||
raise ArgumentError, "too many arguments" if args.size > num | |||||
return args, options | return args, options | ||||
end | end | ||||
def self.season | |||||
DateTime.now.strftime('%m').to_i <= 6 ? 'sp' : 'fa' | |||||
def self.current_semester(format) | |||||
season = Time.now.strftime('%m').to_i <= 6 ? 'sp' : 'fa' | |||||
case format | |||||
when 'faspYY' | |||||
season + Time.now.strftime('%y') | |||||
when 'faspYYYY' | |||||
season + Time.now.strftime('%Y') | |||||
else | |||||
raise ConfigError, "unknown semester format: #{format}" | |||||
end | |||||
end | end | ||||
end | end |
@@ -1,13 +1,21 @@ | |||||
require_relative 'lib/kgrader' | require_relative 'lib/kgrader' | ||||
def cli | |||||
KGrader::CLI.new Rake.application.original_dir | |||||
def die(error) | |||||
abort "fatal: [#{error.class}] #{error}" | |||||
end | |||||
def run | |||||
yield KGrader::CLI.new Rake.application.original_dir | |||||
rescue KGrader::KGraderError => err | |||||
die err | |||||
end | end | ||||
def parse_args(num, keywords = {}) | def parse_args(num, keywords = {}) | ||||
args, options = KGrader::parse_args ARGV.drop(1), num, keywords | args, options = KGrader::parse_args ARGV.drop(1), num, keywords | ||||
args.each { |arg| task arg.to_sym {} } | args.each { |arg| task arg.to_sym {} } | ||||
args + [options] | args + [options] | ||||
rescue KGrader::KGraderError => err | |||||
die err | |||||
end | end | ||||
task :default => :help do ; end | task :default => :help do ; end | ||||
@@ -24,31 +32,31 @@ task :help do | |||||
end | end | ||||
task :list do | task :list do | ||||
cli.list | |||||
run { |cli| cli.list } | |||||
end | end | ||||
task :roster do | task :roster do | ||||
course, rosterfile, options = parse_args 2, { :semester => :string } | course, rosterfile, options = parse_args 2, { :semester => :string } | ||||
cli.roster course, options[:semester], rosterfile | |||||
run { |cli| cli.roster course, options[:semester], rosterfile } | |||||
end | end | ||||
task :grade do | task :grade do | ||||
course, assignment, options = parse_args 2, | course, assignment, options = parse_args 2, | ||||
{ :semester => :string, :students => :array, :due => :datetime, | |||||
{ :semester => :string, :students => :array, :due => :time, | |||||
:fetch => :bool, :regrade => :bool } | :fetch => :bool, :regrade => :bool } | ||||
cli.grade course, options[:semester], assignment, options | |||||
run { |cli| cli.grade course, options[:semester], assignment, options } | |||||
end | end | ||||
task :commit do | task :commit do | ||||
course, assignment, options = parse_args 2, | course, assignment, options = parse_args 2, | ||||
{ :semester => :string, :students => :array } | { :semester => :string, :students => :array } | ||||
cli.commit course, options[:semester], assignment, options | |||||
run { |cli| cli.commit course, options[:semester], assignment, options } | |||||
end | end | ||||
task :clean do | task :clean do | ||||
cli.clean | |||||
run { |cli| cli.clean } | |||||
end | end | ||||
task :clobber do | task :clobber do | ||||
cli.clobber | |||||
run { |cli| cli.clobber } | |||||
end | end |