ruql_cqb 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,63 @@
1
+ class QualtricsRenderer
2
+
3
+ attr_reader :output
4
+
5
+ def initialize(quiz, options={})
6
+ @output = ''
7
+ @quiz = quiz
8
+ @template = options.delete('t') || options.delete('template')
9
+ end
10
+
11
+ def render_quiz
12
+ quiz = @quiz # make quiz object available in template's scope
13
+ with_erb_template(IO.read(File.expand_path @template)) do
14
+ output = ''
15
+ @quiz.questions.each_with_index do |q,i|
16
+ next_question = render_question q,i
17
+ output << next_question
18
+ end
19
+ output
20
+ end
21
+ end
22
+
23
+ def with_erb_template(template)
24
+ # template will 'yield' back to render_quiz to render the questions
25
+ @output = ERB.new(template).result(binding)
26
+ end
27
+
28
+ def render_question(q,index)
29
+ case q
30
+ when SelectMultiple,TrueFalse then render(q, index, 'Multiple') # These are subclasses of MultipleChoice, should go first
31
+ when MultipleChoice then render(q, index, 'Single')
32
+ else
33
+ @quiz.logger.error "Question #{index} (#{q.question_text[0,15]}...): Only written to handle multiple_choice, truefalse, or select_multiple questions at this time."
34
+ ''
35
+ end
36
+ end
37
+
38
+ def render(question, index, type='')
39
+ output = ''
40
+ output << "[[Question:MC:#{type}Answer]]\n"
41
+ output << "[[ID:#{index}]]\n"
42
+ if type == 'Multiple'
43
+ question.question_text = "Select ALL that apply. " + question.question_text
44
+ elsif type == 'Single'
45
+ question.question_text = "Choose ONE answer. " + question.question_text
46
+ end
47
+ output << question.question_text << "\n"
48
+
49
+ # answers - ignore randomization
50
+
51
+ output << "[[AdvancedChoices]]\n"
52
+ question.answers.each do |answer|
53
+ output << "[[Choice]]\n"
54
+ output << "#{answer.answer_text}\n"
55
+ end
56
+ if type == 'Multiple'
57
+ output << "[[Choice]]\n"
58
+ output << "<i>None of these answers are correct.</i>\n"
59
+ end
60
+ output
61
+ end
62
+
63
+ end
@@ -0,0 +1,148 @@
1
+ class XMLRenderer
2
+ require 'builder'
3
+
4
+ attr_reader :output
5
+ def initialize(quiz,options={})
6
+ @output = ''
7
+ @b = Builder::XmlMarkup.new(:target => @output, :indent => 2)
8
+ @quiz = quiz
9
+ end
10
+
11
+ def render(thing)
12
+ case thing
13
+ when MultipleChoice,SelectMultiple,TrueFalse then render_multiple_choice(thing)
14
+ when FillIn then render_fill_in(thing)
15
+ else
16
+ raise "Unknown question type: #{thing}"
17
+ end
18
+ end
19
+
20
+ def render_quiz
21
+ # entire quiz can be in one question group, as long as we specify
22
+ # that ALL question from the group must be used to make the quiz.
23
+ xml_quiz do
24
+ # after preamble...
25
+ @b.question_groups do
26
+ @b.question_group(:select => @quiz.questions.length) do
27
+ @quiz.questions.each do |question|
28
+ self.render(question)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ @output
34
+ end
35
+
36
+ def render_fill_in(question)
37
+ @b.question :type => 'GS_Short_Answer_Question_Simple', :id => question.object_id.to_s(16) do
38
+ @b.metadata {
39
+ @b.parameters {
40
+ @b.rescale_score question.points
41
+ @b.type 'regexp'
42
+ }
43
+ }
44
+ # since we want all the options to appear, we create N option
45
+ # groups each containig 1 option, and specify that option to
46
+ # always be selected for inclusion in the quiz. If the original
47
+ # question specified 'random', use the 'randomize' attribute on
48
+ # option_groups to scramble the order in which displayed;
49
+ # otherwise, display in same order as answers appear in source.
50
+ @b.data {
51
+ @b.text { @b.cdata!(question.question_text) }
52
+ @b.option_groups(:randomize => !!question.randomize) {
53
+ @b.option_group(:select => 'all') {
54
+ question.answers.each do |answer|
55
+ option_args = {}
56
+ option_args['selected_score'] = answer.correct? ? 1 : 0
57
+ option_args['unselected_score'] =
58
+ question.multiple ? 1 - option_args['selected_score'] : 0
59
+ option_args['id'] = answer.object_id.to_s(16)
60
+ @b.option(option_args) do
61
+ answer_text = answer.answer_text
62
+ if answer_text.kind_of?(Regexp)
63
+ answer_text = answer_text.inspect
64
+ if !question.case_sensitive
65
+ answer_text += 'i'
66
+ end
67
+ end
68
+ @b.text { @b.cdata!(answer_text) }
69
+ @b.explanation { @b.cdata!(answer.explanation) } if answer.has_explanation?
70
+ end
71
+ end
72
+ }
73
+ }
74
+ }
75
+ end
76
+ end
77
+
78
+ def render_multiple_choice(question)
79
+ @b.question :type => 'GS_Choice_Answer_Question', :id => question.object_id.to_s(16) do
80
+ @b.metadata {
81
+ @b.parameters {
82
+ @b.rescale_score question.points
83
+ @b.choice_type (question.multiple ? 'checkbox' : 'radio')
84
+ }
85
+ }
86
+ # since we want all the options to appear, we create N option
87
+ # groups each containig 1 option, and specify that option to
88
+ # always be selected for inclusion in the quiz. If the original
89
+ # question specified 'random', use the 'randomize' attribute on
90
+ # option_groups to scramble the order in which displayed;
91
+ # otherwise, display in same order as answers appear in source.
92
+ @b.data {
93
+ @b.text { @b.cdata!(question.question_text) }
94
+ @b.option_groups(:randomize => !!question.randomize) {
95
+ question.answers.each do |a|
96
+ @b.option_group(:select => 'all') {
97
+ self.render_multiple_choice_answer a, question.multiple
98
+ }
99
+ end
100
+ }
101
+ }
102
+ end
103
+ end
104
+ alias :render_true_false :render_multiple_choice
105
+
106
+ def render_multiple_choice_answer(answer, multiple_allowed)
107
+ option_args = {}
108
+ option_args['selected_score'] = answer.correct? ? 1 : 0
109
+ option_args['unselected_score'] =
110
+ multiple_allowed ? 1 - option_args['selected_score'] : 0
111
+ option_args['id'] = answer.object_id.to_s(16)
112
+ @b.option(option_args) do
113
+ @b.text { @b.cdata!(answer.answer_text) }
114
+ @b.explanation { @b.cdata!(answer.explanation) } if answer.has_explanation?
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def options_to_xml(h)
121
+ h.each_pair do |k,v|
122
+ if v.is_a?(Hash)
123
+ @b.__send__(k.to_sym) do
124
+ options_to_xml v
125
+ end
126
+ else
127
+ @b.__send__(k.to_sym, v)
128
+ end
129
+ end
130
+ end
131
+
132
+
133
+ def xml_quiz
134
+ @b.quiz do
135
+ @b.metadata do
136
+ @b.type 'quiz'
137
+ @b.title @quiz.title
138
+ options_to_xml @quiz.options
139
+ end
140
+ @b.preamble
141
+ @b.data do
142
+ yield
143
+ end
144
+ end
145
+ end
146
+
147
+ end
148
+
@@ -0,0 +1,7 @@
1
+ class SelectMultiple < MultipleChoice
2
+ def initialize(text='', opts={})
3
+ super
4
+ self.multiple = true
5
+ end
6
+
7
+ end
@@ -0,0 +1,27 @@
1
+ module TexOutput
2
+
3
+ def add_newlines(str)
4
+ str.gsub(Regexp.new('<pre>(.*?)</pre>', Regexp::MULTILINE | Regexp::IGNORECASE) ) do |code_section|
5
+ code_section.gsub!("\n"){"\\\\"}
6
+ code_section.gsub!(" "){"\\hspace*{2em}"}
7
+ end
8
+ end
9
+
10
+ @@tex_replace = {
11
+ /_/ => '\textunderscore{}',
12
+ Regexp.new('<tt>([^<]+)</tt>', Regexp::IGNORECASE) => "\\texttt{\\1}",
13
+ Regexp.new('<pre>(.*?)</pre>', Regexp::MULTILINE | Regexp::IGNORECASE) => "\\texttt{\\1}",
14
+ }
15
+
16
+ @@tex_escape = Regexp.new '\$|&|%|#|\{|\}'
17
+
18
+ def to_tex(str)
19
+ str = str.gsub(@@tex_escape) { |match| "\\#{match}" }
20
+ str = add_newlines(str)
21
+
22
+ @@tex_replace.each_pair do |match, replace|
23
+ str = str.gsub(match, replace)
24
+ end
25
+ str
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ class TrueFalse < Question
2
+
3
+ def initialize(text, correct_answer, opts=nil)
4
+ super
5
+ opts ||= {}
6
+ opts[:explanation] ||= ''
7
+ correct_answer = !!correct_answer # ensure 'true' or 'false' only
8
+ self.question_text = "True or False: #{text}"
9
+ self.answer correct_answer.to_s.capitalize
10
+ self.distractor (!correct_answer).to_s.capitalize, :explanation => opts[:explanation]
11
+ end
12
+
13
+ def multiple ; false ; end
14
+ def incorrect_answer ; self.answers.reject(&:correct).first ; end
15
+ def explanation ; incorrect_answer.explanation ; end
16
+
17
+ end
@@ -0,0 +1 @@
1
+ <%= yield %>
@@ -0,0 +1,42 @@
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; }
10
+ li.multiplechoice ol.answers { list-style-type: lower-alpha; }
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'); }
12
+ li.truefalse ol.answers { list-style-type: none; }
13
+ .correct { color: green; font-weight: bold; border: 1px solid black; padding: 0.2ex; }
14
+ .incorrect { color: red }
15
+ .explanation { font-style: italic }
16
+ .instructions { clear: both; border: 1px solid black; align: center; padding: 2ex; margin: 2ex; }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <div class="header">
21
+ <div id="name">Name:</div>
22
+ <div id="sid">SID:</div>
23
+ </div>
24
+ <h1><%= quiz.title %></h1>
25
+ <div class="instructions">
26
+ <ul>
27
+ <li>No books, notes, or electronic devices allowed. </li>
28
+ <li>Time limit is 30 minutes.</li>
29
+ <li><%= quiz.num_questions %> multiple-choice questions, points indicated per question,
30
+ <%= quiz.points %> points total. Points per question are intended
31
+ to reflect approximate times they should take, at about 1 point per minute.</li>
32
+ <li>For 'select all that apply' questions worth N points,
33
+ you get 1/N of the points for each RIGHT answer that you check, plus
34
+ 1/N of the points for each WRONG answer that you correctly
35
+ leave unchecked. That is, equal weight is given to deciding
36
+ whether each choice is part of the right answer or not.</li>
37
+ </ul>
38
+ <b>Good skill!</b>
39
+ </div>
40
+ <%= yield %>
41
+ </body>
42
+ </html>
@@ -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 ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruql_cqb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Armando Fox
8
+ - Aaron Zhang
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-04-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: builder
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: getopt
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ description: Ruby-embedded DSL for creating short-answer quiz questions
43
+ email: aaron.zhang@berkeley.edu
44
+ executables:
45
+ - ruql
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - bin/ruql
50
+ - lib/ruql.rb
51
+ - lib/ruql/answer.rb
52
+ - lib/ruql/fill_in.rb
53
+ - lib/ruql/multiple_choice.rb
54
+ - lib/ruql/question.rb
55
+ - lib/ruql/quiz.rb
56
+ - lib/ruql/renderer.rb
57
+ - lib/ruql/renderers/auto_qcm_renderer.rb
58
+ - lib/ruql/renderers/edxml_renderer.rb
59
+ - lib/ruql/renderers/html5_renderer.rb
60
+ - lib/ruql/renderers/html_form_renderer.rb
61
+ - lib/ruql/renderers/json_renderer.rb
62
+ - lib/ruql/renderers/qualtrics_renderer.rb
63
+ - lib/ruql/renderers/xml_renderer.rb
64
+ - lib/ruql/select_multiple.rb
65
+ - lib/ruql/tex_output.rb
66
+ - lib/ruql/true_false.rb
67
+ - templates/autoqcm.tex.erb
68
+ - templates/html5.html.erb
69
+ - templates/htmlform.html.erb
70
+ homepage: http://github.com/saasbook/ruql
71
+ licenses:
72
+ - CC By-SA
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.4.5
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Ruby question language
94
+ test_files: []