ruql 0.1.5 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1a384710404460b95c945d6a1fb850c88531db8a
4
- data.tar.gz: 5c6af8b878133a20c3f7b0e4e38a184b4b0b7400
2
+ SHA256:
3
+ metadata.gz: 044765c22d47d756e025febb9a46d69b6adcf470ec6928fc9b6bdfd8a41e4545
4
+ data.tar.gz: e21c05f59ac94b7b36e57e4b1e9abfefe1daa262ea1ab0f9f02d47d3a3f9dd7c
5
5
  SHA512:
6
- metadata.gz: feb04ca43e5e83c6e6a97f042ab03b3692663a23f835783121edd68bc3ebba1b3ffe728ab682edf053055ef64222625cf8f056a93112ebc287314f475edb0f4a
7
- data.tar.gz: 0414a7abbd51a50703b290c2a912575b6fbaead7330cfbcede247950dd67e0e4d84a6f6e6d35120d26e8fdbcb37a6b103c75d512211e16b9db8eb2fcdc2b5736
6
+ metadata.gz: 5bb2f04a48da6b593d1698e809ceb5c7ec05aadcf6120b8c4f28a596265cff345fc89238cd5626b699ac49105a1ad7d6f90e9fad3c6497ca4eef2493eea52b5b
7
+ data.tar.gz: 350b0853fb6acac7c2818539afc93ba18b3405380f9a113921f1dc36e87736148e0819edbf91db63b7e2d9ab6d001a033617fc87fc6e06c8420e89ab645267d4
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ .byebug_history
13
+ Gemfile.lock
14
+ TAGS
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.3
7
+ before_install: gem install bundler -v 1.17.3
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in ruql.gemspec
6
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,6 @@
1
+ Copyright (c) 2012 Strawberry Canyon LLC.
2
+
3
+ Contents of this repository are governed by the Creative Commons
4
+ Attribution - Noncommercial - ShareAlike 3.0 Unported license:
5
+
6
+ http://creativecommons.org/licenses/by-nc-sa/3.0/
@@ -0,0 +1,308 @@
1
+ [![CodeClimate](https://codeclimate.com/github/saasbook/ruql/badges/gpa.svg)](https://codeclimate.com/github/saasbook/ruql)
2
+ [![Coverage](https://codeclimate.com/github/saasbook/ruql/badges/coverage.svg)](https://codeclimate.com/github/saasbook/ruql/coverage)
3
+ [![Gem Version](https://badge.fury.io/rb/ruql.svg)](https://badge.fury.io/rb/ruql)
4
+
5
+ Ruby-based DSL for Authoring Quiz Questions
6
+ ===========================================
7
+
8
+ This is a simple gem that takes a set of questions (a "quiz") written in
9
+ RuQL ("Ruby quiz language" or "Ruby question language" - a DSL embedded
10
+ in Ruby), and produces one of several possible output formats.
11
+
12
+ Some types of questions or question elements that can be expressed in
13
+ RuQL cannot be expressed by some LMSs, and some LMS-specific question
14
+ features cannot be expressed in RuQL.
15
+
16
+
17
+ Installation
18
+ ============
19
+
20
+ `gem install ruql` to install this from RubyGems. It works with Ruby
21
+ >=1.9.2
22
+
23
+ You'll also need to install one or more formatters to produce quiz output.
24
+
25
+ Installation: Current Formatters
26
+ ================================
27
+
28
+ * [ruql-html](https://github.com/saasbook/ruql-html): produces HTML 5 output using a default or user-supplied HTML
29
+ template
30
+ * [ruql-canvas](https://github.com/saasbook/ruql-canvas): creates a quiz in Canvas LMS using its REST API
31
+
32
+
33
+ Running RuQL
34
+ ============
35
+
36
+ Create your `quiz.rb` file as described below, then run:
37
+
38
+ `ruql formatter-name quiz.rb`
39
+
40
+ Where `formatter-name` is from the list above, eg `html` for the
41
+ `ruql-html` formatter.
42
+
43
+ `ruql formatter-name -h` lists the options supported by that
44
+ formatter, and `ruql -h` shows RuQL's global options.
45
+
46
+
47
+ Creating Quiz Questions in RuQL
48
+ ===============================
49
+
50
+ RuQL supports a few different types of short-answer questions and can
51
+ output them in a variety of formats compatible with different Learning
52
+ Management Systems or in printable form.
53
+
54
+ RuQL is a DSL embedded in Ruby, so you can include expressions in
55
+ questions, for example, to generate simple variants of a question along
56
+ with its solution.
57
+
58
+ In addition to the basic parts of a question (prompt text, answers,
59
+ etc.), each question can also have an optional list of one or more
60
+ _tags_, and an optional _comment_. These attributes
61
+ are available to formatters, but most formatters ignore them.
62
+ They can be used to provide information about the
63
+ question's topic(s) or other info useful to a question-management app
64
+ such as [Course Question Bank](https://github.com/saasbook/coursequestionbank).
65
+
66
+ A question can also optionally have a _group_. Questions with the
67
+ same group name (within the scope of a single quiz) are grouped in
68
+ a "pool" from which a single question is selected at quiz generation time.
69
+ For "static" formatters such as HTML, RuQL will pick a random
70
+ question. For some LMSs, like Canvas, the questions are put into a
71
+ "quiz group" and any given student gets a randomly chosen one at runtime.
72
+
73
+ Preparing a quiz
74
+ ----------------
75
+
76
+ A quiz is an `.rb` file with a collection of questions:
77
+
78
+ quiz 'Example quiz name here' do
79
+ # (questions here)
80
+ end
81
+
82
+ You create a quiz by putting the quiz in its own file and
83
+ copying-and-pasting the questions you want into it. (Yes, that's ugly.
84
+ Soon, questions will have unique IDs and you'll be able to create a quiz
85
+ by reference.)
86
+
87
+ Multiple-choice questions with a single correct answer
88
+ ------------------------------------------------------
89
+
90
+ You can provide a generic `explanation` clause, and/or override it with
91
+ specific explanations to accompany right or wrong answers.
92
+ Choices are rendered in the order in which
93
+ they appear in the RuQL markup, but capable LMSs or formatters can be
94
+ told to randomize them. This example also shows the use of tags to
95
+ include metadata about a question's topic, and the use of groups to
96
+ place two questions into a group from which one will be chosen at
97
+ random for the quiz.
98
+
99
+ ```ruby
100
+ choice_answer do
101
+ tags 'US states', 'geography'
102
+ group 'states-1'
103
+ text "What is the largest US state?"
104
+ explanation "Not big enough." # for distractors without their own explanation
105
+ answer 'Alaska'
106
+ distractor 'Hawaii'
107
+ distractor 'Texas', :explanation => "That's pretty big, but think colder."
108
+ end
109
+ choice_answer do
110
+ tags 'US cities', 'geography'
111
+ group 'states-1'
112
+ text "What is the largest US city?"
113
+ answer 'New York'
114
+ distractor 'Los Angeles'
115
+ distractor 'Chicago'
116
+ end
117
+ ```
118
+
119
+ Specifying `:raw => true` allows HTML markup in the question to be
120
+ passed through unescaped, such as for `<pre>` or `<code>` blocks.
121
+
122
+
123
+ ```ruby
124
+ choice_answer :raw => true do
125
+ text %Q{What does the following code do:
126
+ <pre>
127
+ puts "Hello world!"
128
+ </pre>
129
+ }
130
+ distractor 'Throws an exception', :explanation => "Don't be an idiot."
131
+ answer 'Prints a friendly message'
132
+ end
133
+ ```
134
+
135
+ Multiple-choice "select all that apply" questions
136
+ -------------------------------------------------
137
+
138
+ These use the same syntax as single-choice questions, but multiple
139
+ `answer` clauses are allowed:
140
+
141
+ ```ruby
142
+ select_multiple do
143
+ text "Which are American political parties?"
144
+ answer "Democrats"
145
+ answer "Republicans"
146
+ answer "Greens", :explanation => "Yes, they're a party!"
147
+ distractor "Tories", :explanation => "They're British"
148
+ distractor "Social Democrats"
149
+ end
150
+ ```
151
+
152
+ True or false questions
153
+ -----------------------
154
+
155
+ Internally, true/false questions are treated as a special case of
156
+ multiple-choice questions with a single correct answer, but there's a
157
+ shortcut syntax for them.
158
+
159
+ ```ruby
160
+ truefalse 'The week has 7 days.', true
161
+ truefalse 'The earth is flat.', false, :explanation => 'No, just looks that way'
162
+ ```
163
+
164
+ Short-answer fill-in-the-blanks questions
165
+ -----------------------------------------
166
+
167
+ Put three or more hyphens in a row where you want the "blanks" to be,
168
+ and provide a string or regexp to check the answer; all regexps are
169
+ case-INSENSITIVE unless :case_sensitive => true is passed.
170
+
171
+ ```ruby
172
+ fill_in :points => 2 do
173
+ text 'The capital of California is ---.'
174
+ answer 'sacramento'
175
+ end
176
+ ```
177
+
178
+ Optional distractors can capture common incorrect answers. As with all
179
+ question types, an optional `:explanation` can accompany a correct
180
+ answer or a distractor; its usage varies with the LMS, but a typical use
181
+ is to display a hint if the wrong answer is given, or to display
182
+ explanatory text accompanying the correct answer.
183
+
184
+ ```ruby
185
+ fill_in do
186
+ text 'The visionary founder of Apple is ---'
187
+ answer /^ste(ve|phen)\s+jobs$/
188
+ distractor /^steve\s+wozniak/, :explanation => 'Almost, but not quite.'
189
+ end
190
+ ```
191
+
192
+ You can have multiple blanks per question and pass an array of regexps
193
+ or strings to check them. Passing `:order => false` means that the
194
+ order in which blanks are filled doesn't matter. The number of elements
195
+ in the array must exactly match the number of blanks.
196
+
197
+ ```ruby
198
+ fill_in do
199
+ text 'The --- brown fox jumped over the lazy ---'
200
+ answer [/fox/, /dog/], :explanation => 'This sentence contains all of the letters of the English Alphabet'
201
+ end
202
+
203
+ fill_in do
204
+ text 'The three stooges are ---, ---, and ---.'
205
+ answer %w(larry moe curly), :order => false
206
+ end
207
+ ```
208
+
209
+
210
+
211
+ Questions with one or more dropdown-menu choices
212
+ ------------------------------------------------
213
+
214
+ A question can consist of one or more dropdown menus and interstitial text (which is rendered verbatim without newlines,
215
+ so use `:raw => true` to embed `<br>` tags if you want breaks). A choice selector needs
216
+ two arguments: the 0-based index of the correct choice, and an array of strings of all the choices.
217
+ The `label` method provides interstitial text that separates the dropdowns.
218
+
219
+ ```ruby
220
+ dropdown do
221
+ text "Arguably the world's first theme park was"
222
+ choice 0, ['Disneyland', 'Mickey World', 'Teenage Mutant Ninja Turtles Park']
223
+ label "located in"
224
+ choice 2, ['Beijing, China', 'Paris, France', 'California, USA']
225
+ end
226
+ ```
227
+
228
+
229
+ Additional arguments and options
230
+ --------------------------------
231
+
232
+ The following arguments and options have different behavior (or no
233
+ effect on behavior) depending on what format questions are emitted in:
234
+
235
+ 1. All question types accept a `:name => 'something'` argument, which some
236
+ output generators use to create a displayable name for the question or
237
+ to identify it within a group of questions.
238
+
239
+ 2. The optional `tag` clause is followed by a string or array of strings,
240
+ and associates the given tag(s) with the question, in anticipation of
241
+ future tools that can use this information.
242
+
243
+ 3. The optional `comment` clause is followed by a string and allows a
244
+ free-text comment to be added to a question.
245
+
246
+
247
+ Adding your own renderer
248
+ ========================
249
+
250
+ **This documentation is incomplete**
251
+
252
+ If you're creating the `foobar` formatter, the gem should
253
+ be named `ruql-foobar` and have the following directory
254
+ structure (heavily recommend using Bundler):
255
+
256
+ ```
257
+ ruql-foobar:
258
+ .
259
+ ├── CODE_OF_CONDUCT.md
260
+ ├── Gemfile
261
+ ├── README.md
262
+ ├── Rakefile
263
+ ├── bin
264
+ │   ├── console
265
+ │   └── setup
266
+ ├── lib
267
+ │   └── ruql
268
+ │   ├── foobar
269
+ │   │   ├── foobar.rb
270
+ │   │   └── version.rb
271
+ │   └── foobar.rb
272
+ ├── result.json
273
+ ├── ruql-foobar.gemspec
274
+ ├── spec
275
+ │   ├── ruql
276
+ │   │   └── foobar_spec.rb
277
+ │   └── spec_helper.rb
278
+ └── templates
279
+ └── quiz.json
280
+
281
+
282
+ ```
283
+
284
+ The main entry point should be in
285
+ `ruql-foobar/lib/ruql/foobar/foobar.rb` and include the following at a
286
+ minimum:
287
+
288
+ ```ruby
289
+ module Ruql
290
+ module Foobar
291
+ def initialize(quiz, options={})
292
+ # initialize yourself, given a Quiz object and command-line
293
+ # options in Getoptlong format
294
+ end
295
+ def self.allowed_options
296
+ opts = [['--foobar-option1', Getoptlong::REQUIRED_ARGUMENT],
297
+ ['--foobar-option2', Getoptlong::BOOLEAN_ARGUMENT]]
298
+ help_text = "Some text describing what the options do"
299
+ return [help_text, opts]
300
+ end
301
+ def render_quiz
302
+ # render your quiz however you like. See the descriptions of
303
+ # the Quiz class and various subclasses of Question and Answer.
304
+ end
305
+ end
306
+ end
307
+ ```
308
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ruql"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/ruql CHANGED
@@ -1,121 +1,94 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # require 'ruql'
3
+ require 'rubygems'
4
4
  require_relative '../lib/ruql'
5
- require 'getopt/long'
6
-
7
- def usage
8
- name = File.basename $0
9
- STDERR.puts <<eos
10
- Usage: #{name} filename.rb renderer [options]
11
- filename.rb contains questions expressed in RuQL
12
-
13
- renderer choices are:
14
- Html5 - HTML 5 suitable for Web display/printing
15
- HtmlForm - HTML forms
16
- EdXml - XML for OpenEdX platform in-course questions
17
- AutoQCM - LaTeX for use with AutoQCM (http://home.gna.org/auto-qcm)
18
- JSON - [partially implemented] JSON format
19
- Qualtrics -[partially implemented] Qualtrics survey (txt) format
5
+ require 'getoptlong'
20
6
 
7
+ module Ruql
8
+ class Runner
9
+
10
+ def initialize
11
+ @renderer = nil
12
+ @options = {}
13
+ @filename = nil
14
+ @template = nil
15
+ @solutions = nil
16
+ @help = ''
17
+ @additional_options = [[]]
18
+ end
19
+
20
+ def version
21
+ puts Ruql::VERSION
22
+ exit true
23
+ end
24
+
25
+ def load_renderer
26
+ renderer = ARGV.shift
27
+ if renderer == 'stats'
28
+ @renderer = Ruql::Stats
29
+ @help = ''
30
+ @additional_options = []
31
+ else
32
+ begin
33
+ require "ruql/#{renderer}"
34
+ @renderer = Object.const_get("Ruql::" + renderer.gsub(/(?:_|^)(\w)/){$1.upcase})
35
+ @help,@additional_options = @renderer.allowed_options
36
+ rescue LoadError => e
37
+ raise Ruql::OptionsError.new("Formatter '#{renderer}' requires the gem ruql-#{renderer} to be installed")
38
+ end
39
+ end
40
+ end
41
+
42
+ def run
43
+ usage if ARGV.length < 1 || ARGV[0] =~ /^--?[Hh]/
44
+ version if ARGV[0] =~ /^-v/
45
+ load_renderer
46
+
47
+ options = [
48
+ ['-S', '--solutions', GetoptLong::NO_ARGUMENT],
49
+ ['-V', '--verbose', GetoptLong::NO_ARGUMENT]
50
+ ]
51
+ opts = GetoptLong.new(*(options + @additional_options))
52
+
53
+ opts.each do |opt,arg|
54
+ usage(help) if (opt == '--help' || opt == '-H')
55
+ # anything else gets passed to renderer
56
+ @options[opt] = arg
57
+ end
58
+
59
+ @filename = ARGV.pop || usage
60
+ raise "Cannot read #{@filename}" unless File.readable? @filename
61
+
62
+ ## Quiz.set_yaml_file opts.delete("y") || opts.delete("yaml")
63
+ Quiz.set_options(@options)
64
+ Quiz.instance_eval "#{IO.read(@filename)}"
65
+ Quiz.quizzes.each { |quiz| puts quiz.render_with(@renderer, @options) }
66
+ end
67
+
68
+ def usage(help='')
69
+ name = File.basename $0
70
+ STDERR.puts <<eos
71
+ Usage: #{name} <formatter> [options] filename.rb
72
+
73
+ filename.rb contains questions expressed in RuQL. Formatters are packaged as separate gems
74
+ named ruql-*, for example, formatter 'html' is provided by the gem ruql-html, which must
75
+ be installed. See #{Gem.loaded_specs['ruql'].homepage} for available formatters or to add your own.
76
+
77
+ The special renderer 'stats' will just show stats for the input file without generating output.
21
78
  Global options:
22
- -r <file>, --report=<file>
23
- Write to <file> a 4-line report of #points, #questions, and first and last
24
- question#. Especially useful in conjunction with the -a/--start option.
25
- -a <first>, --start=<first>
26
- Sets the starting ordinal value (1=first) for question numbering. Default 1.
27
- If a filename is given, looks for a line of the form "last NN" and
28
- starts at the number following NN.
29
- -l <loglevel>, --log=<loglevel>
30
- In increasing verbosity, they are 'error' (nonfatal), 'warn', 'info',
31
- 'debug'; default is 'warn'
32
- -p <points>, --points-threshold <points>
33
- If a question's point value is more than <points> (default: 0), include
34
- the string "[N points]" at the beginning of the question text. Make this a
35
- large number to avoid any points info being printed.
36
- -P <points-string>, --points-string <points-string>
37
- String for formatting the points description prepended to the question text,
38
- with %d as a placeholder for the point value and %s as a placeholder for
39
- pluralizing the word 'points' or whatever. Default is "[%d point%s]".
40
- -R, --no-randomize
41
- Present the answer choices in the order they appear in the RuQL file(s),
42
- even for questions that have :randomize => true.
43
-
44
- The EdXML renderer supports these options:
45
- -n <name>, --name=<name>
46
- Only render the question(s) that have :name => 'name'.
47
- NOTE: Some markup that is legal in RuQL questions will break the EdX parser.
48
- Manually check your questions as you enter them into EdX. Code seems to
49
- be particularly troublesome.
50
- NOTE: The 'points' and 'randomize' attributes of questions are not honored by
51
- some systems.
52
-
53
- -y <file.yml>, --yaml=<file.yml>
54
- Render open-assessment questions using info in given Yaml file.
55
-
56
- The HTML5 and HTML Forms renderers supports these options:
57
- -o <type>, --list-type=<type>
58
- Sets the type of HTML list to use for answer choices: 'u' for unordered
59
- (<ul>) or 'o' for ordered (<ol>). Default is 'o'.
60
- -j <src>, --js=<src>
61
- embed <src> for JavaScript
62
- -t <file.html.erb>, --template=<file.html.erb>
63
- Use file.html.erb as HTML template rather than generating our own file.
64
- file.html.erb should have <%= yield %> where questions should go.
65
- The default template in the templates/ directory of RuQL will be used
66
- if this option is not given.
67
- The following local variables will be replaced with their values in
68
- the template:
69
- <%= quiz.title %> - the quiz title
70
- <%= quiz.num_questions %> - total number of questions
71
- <%= quiz.points %> - total number of points for whole quiz
72
- -s, --solutions
73
- generate solutions (showing correct answers and explanations)
74
- NOTE: If there is more than one quiz (collection of questions) in the file,
75
- a complete <html>...</html> block is produced in the output for EACH quiz.
76
-
77
- The AutoQCM renderer supports these options:
78
- -t <file.tex.erb>, --template=<file.tex.erb>
79
- MANDATORY: Use file.tex.erb as LaTeX/AutoQCM template.
80
- The file should have <%= yield %> where questions should go.
81
- See the description of template under HTML5 renderer for variable
82
- substitutions that can occur in the quiz body.
83
-
84
- The JSON renderer currently supports no options
85
-
86
- The Qualtrics renderer supports these options:
87
- -t <file.txt.erb>, --template=<file.txt.erb>
88
- The file should have <%= yield %> where questions should go. Since this just creates survey questions, grading information is ignored. Currently supports the same type of questions as the AutoQCM renderer.
79
+ -S, --solutions
80
+ Generate a version of the output with solutions included (not supported by all formatters)
81
+ -V, --verbose
82
+ Show verbose output for debugging
83
+
84
+ #{help}
89
85
 
90
86
  eos
91
- exit
92
- end
93
-
94
- def main
95
- filename = ARGV.shift
96
- raise "Cannot read #{filename}" unless File.readable? filename
97
- renderer = ARGV.shift
98
- raise "Unknown renderer '#{renderer}'" unless Quiz.get_renderer(renderer)
99
-
100
- opts = Getopt::Long.getopts(
101
- ['-v', '--version', Getopt::BOOLEAN],
102
- ['-o', '--list-type', Getopt::REQUIRED],
103
- ['-r', '--report', Getopt::REQUIRED],
104
- ['-a', '--start', Getopt::REQUIRED],
105
- ['-c', '--css', Getopt::REQUIRED],
106
- ['-j', '--js', Getopt::REQUIRED],
107
- ['-t', '--template', Getopt::REQUIRED],
108
- ['-s', '--solutions', Getopt::BOOLEAN],
109
- ['-n', '--name', Getopt::REQUIRED],
110
- ['-l', '--log', Getopt::REQUIRED],
111
- ['-P', '--points-string', Getopt::REQUIRED],
112
- ['-p', '--points-threshold', Getopt::REQUIRED],
113
- ['-R', '--no-randomize', Getopt::BOOLEAN],
114
- ['-y', '--yaml', Getopt::REQUIRED]
115
- )
116
- Quiz.instance_eval "#{IO.read(filename)}"
117
- Quiz.quizzes.each { |quiz| puts quiz.render_with(renderer, opts) }
118
- end
119
-
120
- usage if ARGV.length < 2
121
- main
87
+ exit false
88
+ end
89
+ end
90
+ end
91
+
92
+ Ruql::Runner.new.run
93
+
94
+