ruql 0.0.2 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/ruql +11 -7
- data/lib/ruql.rb +1 -0
- data/lib/ruql/renderers/html5_renderer.rb +6 -2
- data/lib/ruql/renderers/html_form_renderer.rb +198 -0
- data/templates/html5.html.erb +2 -4
- data/templates/htmlform.html.erb +44 -0
- metadata +4 -2
data/bin/ruql
CHANGED
@@ -4,17 +4,18 @@ require 'ruql'
|
|
4
4
|
require 'getopt/long'
|
5
5
|
|
6
6
|
def usage
|
7
|
-
name = $0
|
7
|
+
name = File.basename $0
|
8
8
|
STDERR.puts <<eos
|
9
9
|
Usage: #{name} filename.rb renderer [options]
|
10
10
|
filename.rb contains questions expressed in RuQL
|
11
11
|
|
12
12
|
renderer choices are:
|
13
|
-
Html5
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
Html5 - HTML 5 suitable for Web display/printing
|
14
|
+
HtmlForm - HTML forms
|
15
|
+
EdXml - XML for OpenEdX platform in-course questions
|
16
|
+
AutoQCM - LaTeX for use with AutoQCM (http://home.gna.org/auto-qcm)
|
17
|
+
Coursera - [obsolete] Coursera HTML/XML format for online auto-grading
|
18
|
+
JSON - [partially implemented] JSON format
|
18
19
|
|
19
20
|
Global options:
|
20
21
|
-l <loglevel>, --log=<loglevel>
|
@@ -35,9 +36,11 @@ The EdXML renderer supports these options:
|
|
35
36
|
NOTE: The 'points' and 'randomize' attributes of questions are not honored by
|
36
37
|
some systems.
|
37
38
|
|
38
|
-
The HTML5
|
39
|
+
The HTML5 and HTML Forms renderers supports these options:
|
39
40
|
-c <href>, --css=<href>
|
40
41
|
embed <href> for stylesheet into generated HTML5
|
42
|
+
-j <src>, --js=<src>
|
43
|
+
embed <src> for JavaScrips
|
41
44
|
-t <file.html.erb>, --template=<file.html.erb>
|
42
45
|
Use file.html.erb as HTML template rather than generating our own file.
|
43
46
|
file.html.erb should have <%= yield %> where questions should go.
|
@@ -72,6 +75,7 @@ def main
|
|
72
75
|
raise "Unknown renderer '#{renderer}'" unless Quiz.get_renderer(renderer)
|
73
76
|
opts = Getopt::Long.getopts(
|
74
77
|
['-c', '--css', Getopt::REQUIRED],
|
78
|
+
['-j', '--js', Getopt::REQUIRED],
|
75
79
|
['-t', '--template', Getopt::REQUIRED],
|
76
80
|
['-s', '--solutions', Getopt::BOOLEAN],
|
77
81
|
['-n', '--name', Getopt::REQUIRED],
|
data/lib/ruql.rb
CHANGED
@@ -7,6 +7,7 @@ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__)))
|
|
7
7
|
# renderers
|
8
8
|
require 'ruql/renderers/xml_renderer'
|
9
9
|
require 'ruql/renderers/html5_renderer'
|
10
|
+
require 'ruql/renderers/html_form_renderer'
|
10
11
|
require 'ruql/renderers/edxml_renderer'
|
11
12
|
require 'ruql/renderers/auto_qcm_renderer'
|
12
13
|
require 'ruql/renderers/json_renderer'
|
@@ -68,7 +68,7 @@ class Html5Renderer
|
|
68
68
|
@h.ol :class => 'answers' do
|
69
69
|
answers.each do |answer|
|
70
70
|
if @show_solutions
|
71
|
-
render_answer_for_solutions(answer, q.raw
|
71
|
+
render_answer_for_solutions(answer, q.raw?, q.class == TrueFalse)
|
72
72
|
else
|
73
73
|
if q.raw? then @h.li { |l| l << answer.answer_text } else @h.li answer.answer_text end
|
74
74
|
end
|
@@ -104,8 +104,12 @@ class Html5Renderer
|
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
-
def render_answer_for_solutions(answer,raw)
|
107
|
+
def render_answer_for_solutions(answer,raw,is_true_false = nil)
|
108
108
|
args = {:class => (answer.correct? ? 'correct' : 'incorrect')}
|
109
|
+
if is_true_false
|
110
|
+
answer.answer_text.prepend(
|
111
|
+
answer.correct? ? "CORRECT: " : "INCORRECT: ")
|
112
|
+
end
|
109
113
|
@h.li(args) do
|
110
114
|
if raw then @h.p { |p| p << answer.answer_text } else @h.p answer.answer_text end
|
111
115
|
if answer.has_explanation?
|
@@ -0,0 +1,198 @@
|
|
1
|
+
class HtmlFormRenderer
|
2
|
+
require 'builder'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
attr_reader :output
|
6
|
+
|
7
|
+
def initialize(quiz,options={})
|
8
|
+
@css = options.delete('c') || options.delete('css')
|
9
|
+
@js = options.delete('j') || options.delete('js')
|
10
|
+
@show_solutions = options.delete('s') || options.delete('solutions')
|
11
|
+
@template = options.delete('t') ||
|
12
|
+
options.delete('template') #||
|
13
|
+
#File.join(Gem.loaded_specs['ruql'].full_gem_path, 'templates/htmlform.html.erb')
|
14
|
+
@output = ''
|
15
|
+
@quiz = quiz
|
16
|
+
@h = Builder::XmlMarkup.new(:target => @output, :indent => 2)
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_quiz
|
20
|
+
if @template
|
21
|
+
render_with_template do
|
22
|
+
render_questions
|
23
|
+
@output
|
24
|
+
end
|
25
|
+
else
|
26
|
+
@h.html do
|
27
|
+
@h.head do
|
28
|
+
@h.title @quiz.title
|
29
|
+
@h.link(:rel => 'stylesheet', :type =>'text/css', :href =>@css) if @css
|
30
|
+
@h.script(:type => 'text/javascript', :src => @js) do
|
31
|
+
end if @js
|
32
|
+
end
|
33
|
+
@h.body do
|
34
|
+
render_questions
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def render_with_template
|
42
|
+
# local variables that should be in scope in the template
|
43
|
+
quiz = @quiz
|
44
|
+
title = "Quiz" unless @title
|
45
|
+
# the ERB template includes 'yield' where questions should go:
|
46
|
+
output = ERB.new(IO.read(File.expand_path @template)).result(binding)
|
47
|
+
@output = output
|
48
|
+
end
|
49
|
+
|
50
|
+
def render_questions
|
51
|
+
render_random_seed
|
52
|
+
@h.form do
|
53
|
+
@h.ol :class => 'questions' do
|
54
|
+
@quiz.questions.each_with_index do |q,i|
|
55
|
+
case q
|
56
|
+
when SelectMultiple then render_select_multiple(q,i)
|
57
|
+
when MultipleChoice, TrueFalse then render_multiple_choice(q,i)
|
58
|
+
when FillIn then render_fill_in(q, i)
|
59
|
+
else
|
60
|
+
raise "Unknown question type: #{q}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
@h.input(:type => 'submit', :value => 'Enviar')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def render_multiple_choice(q,index)
|
70
|
+
render_question_text(q, index) do
|
71
|
+
answers =
|
72
|
+
if q.class == TrueFalse then q.answers.sort.reverse # True always first
|
73
|
+
elsif q.randomize then q.answers.sort_by { rand }
|
74
|
+
else q.answers
|
75
|
+
end
|
76
|
+
@h.ol :class => 'answers' do
|
77
|
+
answers.each do |answer|
|
78
|
+
if @show_solutions
|
79
|
+
render_answer_for_solutions(answer, q.raw?)
|
80
|
+
else
|
81
|
+
#if q.raw? then @h.li { |l| l << answer.answer_text } else @h.li answer.answer_text end
|
82
|
+
@h.input(:type => 'radio', :name => 'a', :class => 'select') { |p|
|
83
|
+
p << answer.answer_text
|
84
|
+
p << '</br>'
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
def render_select_multiple(q,index)
|
94
|
+
render_question_text(q, index) do
|
95
|
+
answers =
|
96
|
+
if q.randomize then q.answers.sort_by { rand }
|
97
|
+
else q.answers
|
98
|
+
end
|
99
|
+
@h.ol :class => 'answers' do
|
100
|
+
answers.each do |answer|
|
101
|
+
if @show_solutions
|
102
|
+
render_answer_for_solutions(answer, q.raw?)
|
103
|
+
else
|
104
|
+
#if q.raw? then @h.li { |l| l << answer.answer_text } else @h.li answer.answer_text end
|
105
|
+
@h.input(:type => 'checkbox', :name => 'b', :class => 'check') { |p|
|
106
|
+
p << answer.answer_text
|
107
|
+
p << '</br>'
|
108
|
+
}
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
def render_fill_in(q, idx)
|
117
|
+
render_question_text(q, idx) do
|
118
|
+
if @show_solutions
|
119
|
+
answer = q.answers[0]
|
120
|
+
if answer.has_explanation?
|
121
|
+
if q.raw? then @h.p(:class => 'explanation') { |p| p << answer.explanation }
|
122
|
+
else @h.p(answer.explanation, :class => 'explanation') end
|
123
|
+
end
|
124
|
+
answers = (answer.answer_text.kind_of?(Array) ? answer.answer_text : [answer.answer_text])
|
125
|
+
@h.ol :class => 'answers' do
|
126
|
+
answers.each do |answer|
|
127
|
+
if answer.kind_of?(Regexp)
|
128
|
+
answer = answer.inspect
|
129
|
+
if !q.case_sensitive
|
130
|
+
answer += 'i'
|
131
|
+
end
|
132
|
+
end
|
133
|
+
@h.li do
|
134
|
+
if q.raw? then @h.p { |p| p << answer } else @h.p answer end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def render_answer_for_solutions(answer,raw)
|
143
|
+
args = {:class => (answer.correct? ? 'correct' : 'incorrect')}
|
144
|
+
@h.li(args) do
|
145
|
+
if raw then @h.p { |p| p << answer.answer_text } else @h.p answer.answer_text end
|
146
|
+
if answer.has_explanation?
|
147
|
+
if raw then @h.p(:class => 'explanation') { |p| p << answer.explanation }
|
148
|
+
else @h.p(answer.explanation, :class => 'explanation') end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def render_question_text(question,index)
|
154
|
+
html_args = {
|
155
|
+
:id => "question-#{index}",
|
156
|
+
:class => ['question', question.class.to_s.downcase, (question.multiple ? 'multiple' : '')]
|
157
|
+
.join(' ')
|
158
|
+
}
|
159
|
+
@h.li html_args do
|
160
|
+
@h.div :class => 'text' do
|
161
|
+
qtext = "[#{question.points} point#{'s' if question.points>1}] " <<
|
162
|
+
('Select ALL that apply: ' if question.multiple).to_s <<
|
163
|
+
if question.class == FillIn then question.question_text.gsub(/\-+/, '')
|
164
|
+
else question.question_text
|
165
|
+
end
|
166
|
+
#if question.raw?
|
167
|
+
# @h.p { |p| p << qtext }
|
168
|
+
#else
|
169
|
+
qtext.each_line do |p|
|
170
|
+
@h.p do |par|
|
171
|
+
par << p # preserves HTML markup
|
172
|
+
@h.input(:type => 'text', :class => 'fillin') if (question.class == FillIn)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
#end
|
176
|
+
end
|
177
|
+
yield # render answers
|
178
|
+
end
|
179
|
+
self
|
180
|
+
end
|
181
|
+
|
182
|
+
def quiz_header
|
183
|
+
@h.div(:id => 'student-name') do
|
184
|
+
@h.p 'Name:'
|
185
|
+
@h.p 'Student ID:'
|
186
|
+
end
|
187
|
+
if @quiz.options[:instructions]
|
188
|
+
@h.div :id => 'instructions' do
|
189
|
+
@quiz.options[:instructions].each_line { |p| @h.p p }
|
190
|
+
end
|
191
|
+
end
|
192
|
+
self
|
193
|
+
end
|
194
|
+
|
195
|
+
def render_random_seed
|
196
|
+
@h.comment! "Seed: #{@quiz.seed}"
|
197
|
+
end
|
198
|
+
end
|
data/templates/html5.html.erb
CHANGED
@@ -6,13 +6,11 @@
|
|
6
6
|
.header { text-align: right; font-weight: bold; padding-right: 30%; line-height: 300%; }
|
7
7
|
h1 { text-align: center; }
|
8
8
|
ol.questions { list-style-type: number; }
|
9
|
-
li.question { page-break-inside: avoid; border-bottom: 1px solid grey;
|
9
|
+
li.question { page-break-inside: avoid; border-bottom: 1px solid grey; }
|
10
10
|
li.multiplechoice ol.answers { list-style-type: lower-alpha; }
|
11
|
-
li.multiplechoice ol.answers li { padding-bottom: 0.5ex; }
|
12
11
|
li.selectmultiple ol.answers { vertical-align: center; list-style-type: none; list-style-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUAQMAAAC3R49OAAAABlBMVEUAAAD///+l2Z/dAAAAEklEQVQImWNgAIL6/w+ogoEAAKI4Kp2NVIeDAAAAAElFTkSuQmCC'); }
|
13
12
|
li.truefalse ol.answers { list-style-type: none; }
|
14
|
-
|
15
|
-
.correct { color: green }
|
13
|
+
.correct { color: green; font-weight: bold; border: 1px solid black; padding: 0.2ex; }
|
16
14
|
.incorrect { color: red }
|
17
15
|
.explanation { font-style: italic }
|
18
16
|
.instructions { clear: both; border: 1px solid black; align: center; padding: 2ex; margin: 2ex; }
|
@@ -0,0 +1,44 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title><%= quiz.title %></title>
|
4
|
+
<style type="text/css" media="all">
|
5
|
+
body { font-family: Times, serif; }
|
6
|
+
.header { text-align: right; font-weight: bold; padding-right: 30%; line-height: 300%; }
|
7
|
+
h1 { text-align: center; }
|
8
|
+
ol.questions { list-style-type: number; }
|
9
|
+
li.question { page-break-inside: avoid; border-bottom: 1px solid grey; padding-bottom: 2ex; }
|
10
|
+
li.multiplechoice ol.answers { list-style-type: lower-alpha; }
|
11
|
+
li.multiplechoice ol.answers li { padding-bottom: 0.5ex; }
|
12
|
+
li.selectmultiple ol.answers { vertical-align: center; list-style-type: none; list-style-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUAQMAAAC3R49OAAAABlBMVEUAAAD///+l2Z/dAAAAEklEQVQImWNgAIL6/w+ogoEAAKI4Kp2NVIeDAAAAAElFTkSuQmCC'); }
|
13
|
+
li.truefalse ol.answers { list-style-type: none; }
|
14
|
+
li.truefalse ol.answers li { width: 15%; display: inline-block; }
|
15
|
+
.correct { color: green }
|
16
|
+
.incorrect { color: red }
|
17
|
+
.explanation { font-style: italic }
|
18
|
+
.instructions { clear: both; border: 1px solid black; align: center; padding: 2ex; margin: 2ex; }
|
19
|
+
</style>
|
20
|
+
</head>
|
21
|
+
<body>
|
22
|
+
<div class="header">
|
23
|
+
<div id="name">Name:</div>
|
24
|
+
<div id="sid">SID:</div>
|
25
|
+
</div>
|
26
|
+
<h1><%= quiz.title %></h1>
|
27
|
+
<div class="instructions">
|
28
|
+
<ul>
|
29
|
+
<li>No books, notes, or electronic devices allowed. </li>
|
30
|
+
<li>Time limit is 30 minutes.</li>
|
31
|
+
<li><%= quiz.num_questions %> multiple-choice questions, points indicated per question,
|
32
|
+
<%= quiz.points %> points total. Points per question are intended
|
33
|
+
to reflect approximate times they should take, at about 1 point per minute.</li>
|
34
|
+
<li>For 'select all that apply' questions worth N points,
|
35
|
+
you get 1/N of the points for each RIGHT answer that you check, plus
|
36
|
+
1/N of the points for each WRONG answer that you correctly
|
37
|
+
leave unchecked. That is, equal weight is given to deciding
|
38
|
+
whether each choice is part of the right answer or not.</li>
|
39
|
+
</ul>
|
40
|
+
<b>Good skill!</b>
|
41
|
+
</div>
|
42
|
+
<%= yield %>
|
43
|
+
</body>
|
44
|
+
</html>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2014-05-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: builder
|
@@ -63,10 +63,12 @@ files:
|
|
63
63
|
- lib/ruql/renderers/auto_qcm_renderer.rb
|
64
64
|
- lib/ruql/renderers/edxml_renderer.rb
|
65
65
|
- lib/ruql/renderers/html5_renderer.rb
|
66
|
+
- lib/ruql/renderers/html_form_renderer.rb
|
66
67
|
- lib/ruql/renderers/json_renderer.rb
|
67
68
|
- lib/ruql/renderers/xml_renderer.rb
|
68
69
|
- templates/autoqcm.tex.erb
|
69
70
|
- templates/html5.html.erb
|
71
|
+
- templates/htmlform.html.erb
|
70
72
|
- bin/ruql
|
71
73
|
homepage: http://github.com/saasbook/ruql
|
72
74
|
licenses:
|