require 'rubyXL' require_relative 'assignment'
# www.ablebits.com/office-addins-blog/2015/03/11/change-date-format-excel/
module KielcePlugins
module Schedule class Schedule #SCHEDULE_KEYS = [:week, :date, :topics, :notes, :reading, :milestones, :comments] SCHEDULE_KEYS = [:week, :date, :topics, :reading, :milestones, :comments] attr_accessor :assignments, :schedule_days def initialize(filename) workbook = RubyXL::Parser.parse(filename) @assignments = build_assignments(build_rows(workbook['Assignments'], Assignment::ASSIGNMENT_KEYS)) @schedule_days = build_schedule_days(build_rows(workbook['Schedule'], SCHEDULE_KEYS)) end def transform(value) if value.is_a? String # Replace link markup value.gsub!(/\[\[([^\s]+)(\s+(\S.*)|\s*)\]\]/) do text = $3.nil? ? "<code>#{$1}</code>" : $3 "<a href='#{$1}'>#{text}</a>" end value.gsub!(/<<(assign|due)\s+(\S.+)>>/) do #$stderr.puts "Found assignment ref #{$1} --- #{$2} -- #{@assignments[$2].inspect}" $stderr.puts "Assignment #{$2} not found" unless @assignments.has_key?($2) text = @assignments[$2].title(:full, true) if $1 == 'assign' "Assign #{text}" elsif $1 == 'due' "<b>Due</b> #{text}" else $stderr.puts "Unexpected match #{$1}" end end # end gsub! value.gsub!(/{{([^{}:]+):\s*([^{}]+)}}/) do text = $1 link_rule = $2 if (link_rule =~ /(.*)\!(.+)/) method = $1 param = $2 method = 'default' if method.empty? #$stderr.puts "Found link rule =>#{method}<= #{method.empty?} =>#{param}<=" link = $d.course.notesTemplates.method_missing(method, param) else link = link_rule end "(<a href='#{link}'>#{text}</a>)" end # end gsub end # end if value is string value end def build_rows(worksheet, keys) # Remove the first (header) row, and any empty rows. # Also, remove any rows after "END" first = true done = false rows = [] worksheet.each do |row| done = true if !row.nil? && !row[0].nil? && row[0].value == "END" unless first || row.nil? || done rows << row end first = false end # For each row, build a Hash describing the row. rows.map do |row| row_hash = { original: {} } keys.each_with_index do |item, index| row_hash[:original][item] = row[index].nil? ? nil : row[index].value row_hash[item] = row[index].nil? ? nil : transform(row[index].value) end row_hash end # end map end def build_assignments(rows) assignment_hash = {} rows.each { |row| assignment_hash[row[:id]] = Assignment.new(row) } assignment_hash end def build_schedule_days(schedule_rows) array_keys = SCHEDULE_KEYS.slice(2, SCHEDULE_KEYS.length - 2) current_week = nil schedule_days = [] schedule_day = nil schedule_rows.each do |row| # if there is a date, start a new day unless row[:date].nil? # push day in progress into array schedule_days << schedule_day unless schedule_day.nil? # create a new schedule_day Hash schedule_day = { begin_week: false } unless row[:week].nil? current_week = row[:week] schedule_day[:begin_week] = true end schedule_day[:week] = current_week schedule_day[:date] = row[:date] array_keys.each { |key| schedule_day[key] = [] } end # push non-nil values onto the corresponding array # array_keys.each { |key| schedule_day[key] << row[key] unless row[key].nil? } array_keys.each do |key| val = row[key] # skip any completely empty cells (they produce a value of nil) # Replace a single period with a whitespace. (Thus producing an empty cell in the table) # Similarly, treat cells beginnign with // as a comment and produce an empty cell in the table) unless row[key].nil? val = " " if val == '.' || val =~ /^\s*\/\// schedule_day[key] << val end end # Look for assignments / due dates and add information to @assignment objects original_milestones = row[:original][:milestones] if !original_milestones.nil? && original_milestones =~ /<<due\s+(.*)>>/ #$stderr.puts "Assignment #{$1} has due date of #{schedule_day[:date].strftime("%a. %-d %b.")}" @assignments[$1].due = schedule_day[:date] end if !original_milestones.nil? && original_milestones =~ /<<assign\s+(.*)>>/ #$stderr.puts "Assignment #{$1} has assignment date of #{schedule_day[:date].strftime("%a. %-d %b.")}" unless @assignments.has_key?($1) $stderr.puts "Key #{$1} not found in #{@assignments.keys.inspect}" end @assignments[$1].assigned = schedule_day[:date] end end # end each row schedule_days << schedule_day unless schedule_day.nil? schedule_days end def timeline_table table = [] table << <<TABLE <table class='kielceSchedule'> <tr> <th>Week</th> <th>Date</th> <th>Topics</th> <!-- <th>Notes</th> --> <th>Reading</th> <th>Milestones</th> </tr>
TABLE
first = true @schedule_days.each do |schedule_day| table << '<tr>' if schedule_day[:begin_week] unless first # Add a blank row of horizontal lines table << '<td></td><td></td><td></td><td></td><td></td><td></td></tr>' table << "<tr class='week_end'><td></td><td></td><td></td><td></td><td></td><td></td></tr>" table << '<tr>' end first = false week_value = schedule_day[:week] else week_value = '' end table << " <td class='week_column'>#{week_value}</td>" formatted_date = schedule_day[:date].strftime("%a. %-d %b.") table << " <td class='date_column'>#{formatted_date}</td>" table << " <td class='topics_column'>#{schedule_day[:topics].join("<br>")}</td>" # table << " <td class='topics_column'>#{schedule_day[:notes].join("<br>")}</td>" table << " <td class='reading_column'>#{schedule_day[:reading].join("<br>")}</td>" table << " <td class='milestones_column'>#{schedule_day[:milestones].join("<br>")}</td>" table << "</tr>" end table << "</table>" table.join("\n") end def timeline_style <<STYLE table.kielceSchedule { border-collapse: separate; border-spacing: 2px; } .kielceSchedule tr th { text-align: left; } .kielceSchedule tr td { vertical-align: top; padding-right: 10px; } .week_column, .date_column { white-space: nowrap; } .kielceSchedule tr th, .date_column, .topics_column, .notes_column, .reading_column, .milestones_column, .week_end td { border-bottom: 1px solid; }
STYLE
end def timeline_page <<PAGE <html> <head> <style> #{timeline_style} </style> </head> <body> #{timeline_table} </body> </html>
PAGE
end def assignment_style <<STYLE .kielceAssignmentTable { border-spacing: 35px 0; } .kielceAssignmentTable tr th { text-align: left; } .kielceAssignmentTable tr td { vertical-align: top; } .kielceAssignmentTable_due { white-space: nowrap; } .kielceAssignmentTable_title { white-space: nowrap; } .exam { background-color: lightgreen; }
STYLE
end def assignment_list list = <<TABLE <table class='kielceAssignmentTable'> <tr> <th>Due</th> <th>Name</th> <th>Details</th> </tr>
TABLE
by_date = @assignments.values.reject { |item| item.due.nil? || item.type == 'Lab' }.sort_by { |a| a.due } by_date.each do |assignment| list += ' <tr>' list += " <td class='kielceAssignmentTable_due'>#{assignment.due.strftime("%a. %-d %b.")}</td>\n" list += " <td class='kielceAssignmentTable_title'>#{assignment.title(:full, true)}</td>\n" list += " <td class='kielceAssignmentTable_details'>#{assignment.details}</td>\n" list += " </tr>\n\n" end list += "</table>\n" end def lab_list list = <<TABLE <table class='kielceAssignmentTable'> <tr> <th>Date</th> <th>Name</th> <th>Details</th> </tr>
TABLE
assigned_labs = @assignments.values.select { |item| item.type == 'Lab' && !item.assigned.nil? } by_date = assigned_labs.sort { |a, b| a.assigned <=> b.assigned } by_date.each do |assignment| list += ' <tr>' list += " <td class='kielceAssignmentTable_due'>#{assignment.assigned.strftime("%a. %-d %b.")}</td>\n" list += " <td class='kielceAssignmentTable_title'>#{assignment.title(:full, true)}</td>\n" list += " <td class='kielceAssignmentTable_details'>#{assignment.details}</td>\n" list += " </tr>\n\n" end list += "</table>\n" list end end # end Schedule
end # module end # end KielcePlugins
puts Schedule.new(ARGV).timeline_page