ruql 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|