ruql_cqb 0.0.0

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.
@@ -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: []