Browse Source

Quick hack to implement quiz HTML files

master
Peter J. Jones 7 years ago
parent
commit
9d05f728da

+ 1
- 1
commands/processor.rb View File

@@ -10,6 +10,6 @@ class Processor < Edify::Command
end

raise("please give the file to process") unless @argv.size == 1
Edify::Processor.new(@argv.first, $stdout)
Edify::Processor.new(@argv.first, $stdout).process
end
end

+ 83
- 0
commands/quiz.rb View File

@@ -0,0 +1,83 @@
class Quiz < Edify::Command

##############################################################################
description("process quiz files for HTML or devalot")

##############################################################################
def run
@config = Edify::Quiz::Config.new
@out_html_file = 'quiz.html'
@out_css_file = 'quiz.css'
@out_js_file = 'quiz.js'
@out_key_file = 'quiz.key'

parse_command_line do |p|
p.banner = 'Usage: edify quiz [options]'

p.on('-m', '--markdown=DIR', 'Set the markdown directory as DIR') do |m|
@config.question_markdown_dir = File.expand_path(m)
end

p.on('-y', '--yaml=DIR', 'Set the question YAML directory as DIR') do |y|
@config.question_meta_dir = File.expand_path(y)
end

p.on('-I', '--add-path=PATH', 'Add a path to the load path') do |p|
@config.prepend_load_path(p)
end
end

[@out_html_file, @out_css_file, @out_js_file, @out_key_file].each do |file|
raise("file already exists: #{file}") if File.exist?(file)
end

load_questions
generate_html
end

##############################################################################
private

##############################################################################
def load_questions
random = Random.new

Dir.foreach(@config.question_meta_dir) do |file|
next if file.match(/^\./) or !file.match(/\.yml$/)
verbose("loading question #{file}")
question = Edify::Quiz::Question.new(@config, file)

if !question.valid?
full_path = File.join(@config.question_meta_dir, file)
errors = question.errors.full_messages
raise("invalid question: #{full_path}: #{errors}")
end

question.answers.shuffle!(random: random)
@config.questions << question
end

@config.questions.shuffle!(random: random)
end

##############################################################################
def generate_html
template = File.expand_path('quiz/quiz.html.erb', Edify::TEMPLATES)
result = ERB.new(File.read(template)).result(@config.instance_eval {binding})
File.open(@out_html_file, 'w') {|f| f.write(result)}

css_file = File.expand_path('quiz/quiz.css', Edify::TEMPLATES)
FileUtils.cp(css_file, @out_css_file)

js_file = File.expand_path('quiz/quiz.js', Edify::TEMPLATES)
FileUtils.cp(js_file, @out_js_file)

File.open(@out_key_file, 'w') do |file|
key = (?a .. ?z).to_a
@config.questions.each_with_index do |q, i|
correct = q.answers.index {|a| a.correct}
file.puts("#{i+1}: #{key[correct]}")
end
end
end
end

+ 2
- 1
edify.gemspec View File

@@ -15,9 +15,10 @@ Gem::Specification.new do |gem|
gem.files = Dir.glob('**/*').select {|f| !File.directory?(f)}
gem.executables = ['edify']
gem.require_path = 'lib'
gem.add_dependency('sinatra', '~> 1.3.1')
gem.add_dependency('json', '~> 1.6.1')
gem.add_dependency('rest-client', '~> 1.6.7')
gem.add_dependency('rdiscount', '~> 1.6.8')
gem.add_dependency('activemodel', '~> 3.2.6')
end

+ 10
- 1
lib/edify.rb View File

@@ -4,15 +4,24 @@ require('optparse')
require('erb')
require('yaml')
require('fileutils')
require('open3')
require('active_model')
require('restclient')
require('json')

################################################################################
module Edify
class Error < Exception; end

##############################################################################
TEMPLATES = File.expand_path('../templates', File.dirname(__FILE__))

##############################################################################
class Error < RuntimeError; end
require('edify/version')

##############################################################################
autoload('Command', 'edify/command')
autoload('Marker', 'edify/marker')
autoload('Processor', 'edify/processor')
autoload('Quiz', 'edify/quiz')
end

+ 19
- 12
lib/edify/command.rb View File

@@ -1,24 +1,25 @@
class Edify::Command
##############################################################################
GLOBAL_OPTIONS = {
:host => 'www.devalot.com',
:force => false,
:host => 'www.devalot.com',
:force => false,
:verbose => false,
}
##############################################################################
attr_reader(:argv, :options)
##############################################################################
def self.inherited (klass)
commands << klass
end
##############################################################################
def self.commands
class << self; self; end.instance_eval {@commands ||= []}
end
##############################################################################
def self.description (string=nil)
class << self; self; end.instance_eval do
@@ -27,21 +28,21 @@ class Edify::Command
@description
end
end
##############################################################################
def self.name
to_s.downcase
end
##############################################################################
def initialize (argv)
@argv = argv
@options = GLOBAL_OPTIONS.dup
end
##############################################################################
private
##############################################################################
def parse_command_line (&block)
@options = OpenStruct.new(@options)
@@ -51,10 +52,11 @@ class Edify::Command
p.on('-h', '--help', 'This message') {$stdout.puts(p); exit}
p.on('--host=NAME', 'The name of the host running didactic') {|h| @options.host = h}
p.on('--force', 'Force an unsafe operation') {|f| @options.force = f}
p.on('--verbose', 'Enable verbose output') {|v| @options.verbose = v}
block.call(p) if block
end.parse!(@argv)
end
##############################################################################
def fetch_tags_from_host
response = RestClient.get("http://#{options.host}/tags.json")
@@ -63,4 +65,9 @@ class Edify::Command
raise("Whoa, I expected a tags array") unless tags.is_a?(Array)
tags
end

##############################################################################
def verbose (message)
$stdout.puts(message) if @options.verbose
end
end

+ 26
- 7
lib/edify/processor.rb View File

@@ -6,24 +6,43 @@ class Edify::Processor
'.rb' => 'ruby',
}

##############################################################################
attr_accessor(:load_path)

##############################################################################
def initialize (input_file, output_stream=$stdout)
@in_file = input_file
@output = output_stream
@source_base = File.dirname(@in_file)
@load_path = []
@load_path << File.dirname(@in_file)
end

##############################################################################
def process
content = ERB.new(File.read(@in_file), nil, '<>').result(binding)
@output.write(content)
end

##############################################################################
def source_base (base)
@source_base = File.expand_path(base)
def prepend_load_path (base)
@load_path.unshift(File.expand_path(base))
end

##############################################################################
def source_file (file, marker=nil)
path = File.expand_path(file, @source_base)
# A method for backwards compatibility.
alias_method(:source_base, :prepend_load_path)

##############################################################################
def source_file (file, *markers)
dir = @load_path.detect {|p| File.exist?(File.join(p, file))}

if dir.nil?
paths = @load_path.map {|p| "\t#{p}"}.join("\n")
error = "can't find source file (#{file}) in load_path:\n#{paths}"
raise(Edify::Error, error)
end

path = File.expand_path(file, dir)
type = EXT_MAP[File.extname(path)]

result = ''
@@ -31,9 +50,9 @@ class Edify::Processor
header += " {.#{type}}" if type
result << header << "\n"

if marker
if markers.size != 0
m = Edify::Marker.new(path)
result << m.text_inside_marker(marker)
markers.each {|name| result << m.text_inside_marker(name)}
else
result << File.read(path)
end

+ 11
- 0
lib/edify/quiz.rb View File

@@ -0,0 +1,11 @@
module Edify::Quiz

##############################################################################
SOURCE_DIR = File.expand_path('quiz', File.dirname(__FILE__))

##############################################################################
Dir.foreach(SOURCE_DIR) do |file|
next if file.match(/^\./)
require(File.join(SOURCE_DIR, file)) if file.match(/\.rb$/)
end
end

+ 6
- 0
lib/edify/quiz/answer.rb View File

@@ -0,0 +1,6 @@
class Edify::Quiz::Answer

##############################################################################
attr_accessor(:text, :correct)

end

+ 43
- 0
lib/edify/quiz/config.rb View File

@@ -0,0 +1,43 @@
class Edify::Quiz::Config

##############################################################################
include(ActiveModel::Validations)

##############################################################################
attr_accessor(:load_path, :question_meta_dir, :question_markdown_dir)
attr_accessor(:html_title, :questions)

##############################################################################
class FileValidator < ActiveModel::EachValidator
def validate_each (record, attribute, value)
if !File.exist?(value)
record.errors.add(attribute, "does not exist")
end
end
end

##############################################################################
validates(:question_markdown_dir, :presence => true, :file => true)
validates(:question_meta_dir, :presence => true, :file => true)

##############################################################################
def initialize
@load_path = %w(code)
@question_meta_dir = 'meta/questions'
@question_markdown_dir = 'questions'
@html_title = 'Assessment'
@questions = []
end

##############################################################################
def question_meta_file (name)
base = File.basename(name, File.extname(name))
File.join(question_meta_dir, base) + '.yml'
end

##############################################################################
def question_markdown_file (name)
base = File.basename(name, File.extname(name))
File.join(question_markdown_dir, base) + '.md'
end
end

+ 56
- 0
lib/edify/quiz/question.rb View File

@@ -0,0 +1,56 @@
class Edify::Quiz::Question

##############################################################################
include(ActiveModel::Validations)

##############################################################################
attr_accessor(:answers, :tags, :html)

##############################################################################
validates(:answers, :presence => true, :length => {:minimum => 1})
validates(:tags, :presence => true)

##############################################################################
def initialize (config, base)
@answers = []
@tags = ""
@html = ""
@config = config
@meta_file = @config.question_meta_file(base)
@markdown_file = @config.question_markdown_file(base)

load_meta_file
generate_html if valid?
end

##############################################################################
private

##############################################################################
def load_meta_file
data = YAML.load_file(@meta_file)
@tags = data['tags']

Array(data['answers']).each_with_index do |text, index|
answer = Edify::Quiz::Answer.new
answer.text = text
answer.correct = index.zero?
@answers << answer
end
end

##############################################################################
def generate_html
markdown = StringIO.new

processor = Edify::Processor.new(@markdown_file, markdown)
processor.load_path = @config.load_path
processor.process

Open3.popen2('pandoc -f markdown -t html -i') do |wr, rd|
wr.write(markdown.string)
wr.close
@html = rd.read
end
end
end

+ 30
- 0
templates/quiz/quiz.css View File

@@ -0,0 +1,30 @@
/* Questions */
pre.sourceCode {
margin: 20px;
padding: 10px;
border: 1px solid #888;
}

/* Answers */
.answers {
list-style-type: lower-alpha;
}

/* From pandoc */
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
margin: 0; padding: 0; vertical-align: baseline; border: none; }
table.sourceCode { width: 100%; }
td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
td.sourceCode { padding-left: 5px; }
code > span.kw { color: #007020; font-weight: bold; }
code > span.dt { color: #902000; }
code > span.dv { color: #40a070; }
code > span.bn { color: #40a070; }
code > span.fl { color: #40a070; }
code > span.ch { color: #4070a0; }
code > span.st { color: #4070a0; }
code > span.co { color: #60a0b0; font-style: italic; }
code > span.ot { color: #007020; }
code > span.al { color: #ff0000; font-weight: bold; }
code > span.fu { color: #06287e; }
code > span.er { color: #ff0000; font-weight: bold; }

+ 29
- 0
templates/quiz/quiz.html.erb View File

@@ -0,0 +1,29 @@
<html>
<head>
<title><%= html_title %></title>
<link rel="stylesheet" type="text/css" href="quiz.css"/>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script type="text/javascript" src="quiz.js"></script>
</head>

<body>
<% questions.each_with_index do |q, i| %>
<div class="item">
<h2>Question <%= i + 1 %> of <%= questions.size %></h2>

<div class="question">
<%= q.html %>
</div>

<ol class="answers">
<% q.answers.each do |a| %>
<li><%= a.text %></li>
<% end %>
</ol>
</div>
<% end %>

<input type="button" name="prev" value="Previous Question" style="display:none"/>
<input type="button" name="next" value="Next Question">
</body>
</html>

+ 45
- 0
templates/quiz/quiz.js View File

@@ -0,0 +1,45 @@
function EdifyQuiz () {
this.initialize();
}

EdifyQuiz.prototype = {
initialize: function () {
var self = this;

this.items = $('.item');
this.next = $('input[name="next"]');
this.prev = $('input[name="prev"]');
this.current = 0;

this.items.hide();
this.show(0);

this.next.click(function() {
self.show(self.current + 1);
});

this.prev.click(function() {
self.show(self.current - 1);
});
},
show: function (i) {
$(this.items[this.current]).hide();
$(this.items[i]).show();
this.current = i;

if (this.current == 0) {
this.prev.hide();
this.next.show();
} else if (this.current == (this.items.length - 1)) {
this.prev.show();
this.next.hide();
} else {
this.prev.show();
this.next.show();
}
}
};

$(document).ready(function () {
new EdifyQuiz();
});

Loading…
Cancel
Save