ruql 0.1.3 → 1.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 8a6637987324f5db32e7387b7183ea4591957817
4
- data.tar.gz: 05dc9bbffafb43de71688bbbd1a0b70302d51665
2
+ SHA256:
3
+ metadata.gz: 9fccf871f5ff726c5efff5cbc3871792bcdc6b9610296285a962018e906e9cc7
4
+ data.tar.gz: e8f08ac21c0e465767decc50022c69c7e4d770e631ba9a0df01b8fb058bfcb3e
5
5
  SHA512:
6
- metadata.gz: bd17352a49b61a9b0844cb0d043093c3ba8fe33ba302ac83aac60cb602a4f534074f7c02c4dd0f0772d301232dd360c57d997ddd6fd67ac171172983b0e8c93b
7
- data.tar.gz: 2f34ee1cd6b63f9f81ccd6bafecc7864143d4512d127d1a5740e035f73d9cf5c83abd68bd0eda6b232837bb1a68e60223977ca38920c9a8926b1d167779fd25d
6
+ metadata.gz: 14f33e1817d330f2fee438529a1a4ef86cf7f90a68156381c73f9a99db6b1b48da7706cad0847919102c648b1d1b4371ac6a88e869eaf8efacccc9c227ee54ec
7
+ data.tar.gz: 611b7d96d5597b9bd14f995c9aece5b8c46695b472470a9b7edf8a3322a8067e8bfa36ba470c032314c7c4cdb6c85f001e4ac16733fee73df85b79d37ab56106
@@ -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,105 +1,95 @@
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
+ ['-h', '--help', GetoptLong::NO_ARGUMENT],
49
+ ['-S', '--solutions', GetoptLong::NO_ARGUMENT],
50
+ ['-V', '--verbose', GetoptLong::NO_ARGUMENT]
51
+ ]
52
+ opts = GetoptLong.new(*(options + @additional_options))
53
+
54
+ opts.each do |opt,arg|
55
+ usage if (opt == '--help' || opt == '-H')
56
+ # anything else gets passed to renderer
57
+ @options[opt] = arg
58
+ end
59
+
60
+ @filename = ARGV.pop || usage
61
+ raise "Cannot read #{@filename}" unless File.readable? @filename
62
+
63
+ ## Quiz.set_yaml_file opts.delete("y") || opts.delete("yaml")
64
+ Quiz.set_options(@options)
65
+ Quiz.instance_eval "#{IO.read(@filename)}"
66
+ Quiz.quizzes.each { |quiz| puts quiz.render_with(@renderer, @options) }
67
+ end
68
+
69
+ def usage
70
+ name = File.basename $0
71
+ STDERR.puts <<eos
72
+ Usage: #{name} <formatter> [options] filename.rb
73
+
74
+ filename.rb contains questions expressed in RuQL. Formatters are packaged as separate gems
75
+ named ruql-*, for example, formatter 'html' is provided by the gem ruql-html, which must
76
+ be installed. See #{Gem.loaded_specs['ruql'].homepage} for available formatters or to add your own.
77
+
78
+ The special formatter 'stats' will just show stats for the input file without generating output.
21
79
  Global options:
22
- -l <loglevel>, --log=<loglevel>
23
- In increasing verbosity, they are 'error' (nonfatal), 'warn', 'info',
24
- 'debug'; default is 'warn'
25
- -p <penalty>, --penalty <penalty>
26
- The penalty for WRONG answers, as a fraction of the penalty for
27
- RIGHT answers. For a 2-point question, penalty=0.25 means 1/2 point
28
- (0.25 * 2) deducted for WRONG answer. Default is 0. Not all
29
- output formats are able to do something useful with this.
30
-
31
- The EdXML renderer supports these options:
32
- -n <name>, --name=<name>
33
- Only render the question(s) that have :name => 'name'.
34
- NOTE: Some markup that is legal in RuQL questions will break the EdX parser.
35
- Manually check your questions as you enter them into EdX. Code seems to
36
- be particularly troublesome.
37
- NOTE: The 'points' and 'randomize' attributes of questions are not honored by
38
- some systems.
39
-
40
- -y <file.yml>, --yaml=<file.yml>
41
- Render open-assessment questions using info in given Yaml file.
42
-
43
- The HTML5 and HTML Forms renderers supports these options:
44
- -o <type>, --list-type=<type>
45
- -a <first>, --list-start=<first>
46
- Sets the values of the 'start' and 'type' attributes for the HTML <ol>
47
- element. Type should be one of 1,a,A,i,I. Start should always be
48
- a number indicating the ordinal value (starting from 1) of first item.
49
- -j <src>, --js=<src>
50
- embed <src> for JavaScript
51
- -t <file.html.erb>, --template=<file.html.erb>
52
- Use file.html.erb as HTML template rather than generating our own file.
53
- file.html.erb should have <%= yield %> where questions should go.
54
- The default template in the templates/ directory of RuQL will be used
55
- if this option is not given.
56
- The following local variables will be replaced with their values in
57
- the template:
58
- <%= quiz.title %> - the quiz title
59
- <%= quiz.num_questions %> - total number of questions
60
- <%= quiz.points %> - total number of points for whole quiz
61
- -s, --solutions
62
- generate solutions (showing correct answers and explanations)
63
- NOTE: If there is more than one quiz (collection of questions) in the file,
64
- a complete <html>...</html> block is produced in the output for EACH quiz.
65
-
66
- The AutoQCM renderer supports these options:
67
- -t <file.tex.erb>, --template=<file.tex.erb>
68
- MANDATORY: Use file.tex.erb as LaTeX/AutoQCM template.
69
- The file should have <%= yield %> where questions should go.
70
- See the description of template under HTML5 renderer for variable
71
- substitutions that can occur in the quiz body.
72
-
73
- The JSON renderer currently supports no options
74
-
75
- The Qualtrics renderer supports these options:
76
- -t <file.txt.erb>, --template=<file.txt.erb>
77
- 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.
80
+ -S, --solutions
81
+ Generate a version of the output with solutions included (not supported by all formatters)
82
+ -V, --verbose
83
+ Show verbose output for debugging
84
+
85
+ #{@help}
78
86
 
79
87
  eos
80
- exit
81
- end
82
-
83
- def main
84
- filename = ARGV.shift
85
- raise "Cannot read #{filename}" unless File.readable? filename
86
- renderer = ARGV.shift
87
- raise "Unknown renderer '#{renderer}'" unless Quiz.get_renderer(renderer)
88
-
89
- opts = Getopt::Long.getopts(
90
- ['-o', '--list-type', Getopt::REQUIRED],
91
- ['-a', '--list-start', Getopt::REQUIRED],
92
- ['-c', '--css', Getopt::REQUIRED],
93
- ['-j', '--js', Getopt::REQUIRED],
94
- ['-t', '--template', Getopt::REQUIRED],
95
- ['-s', '--solutions', Getopt::BOOLEAN],
96
- ['-n', '--name', Getopt::REQUIRED],
97
- ['-l', '--log', Getopt::REQUIRED],
98
- ['-y', '--yaml', Getopt::REQUIRED]
99
- )
100
- Quiz.instance_eval "#{IO.read(filename)}"
101
- Quiz.quizzes.each { |quiz| puts quiz.render_with(renderer, opts) }
102
- end
103
-
104
- usage if ARGV.length < 2
105
- main
88
+ exit false
89
+ end
90
+ end
91
+ end
92
+
93
+ Ruql::Runner.new.run
94
+
95
+