quiz_master 0.0.2 → 0.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.
@@ -0,0 +1,36 @@
1
+ module QuizMaster
2
+ # This class allows us to build a QuizVariant which is a Quiz whose Questions
3
+ # and thier Answers have been re-ordered in the way specified by the orderings
4
+ # supplied to new.
5
+ #
6
+ # Once a QuizVariant has been made it is not intended to be mutated, it is an
7
+ # "end result" which can be used to generate output.
8
+ class QuizVariant
9
+ # Get the questions for this QuizVariant.
10
+ attr_reader :questions
11
+ # Get the header for this QuizVariant.
12
+ attr_reader :header
13
+ # Get the footer for this QuizVariant.
14
+ attr_reader :footer
15
+
16
+ # Public: Initialize this QuizVariant. The Answers are reordered before the
17
+ # Questions.
18
+ #
19
+ # source - A Quiz to be "shuffled" according to the reorderings.
20
+ # answer_orderings - An Array of ReorderingVectors which specify how each
21
+ # Question's Answers are to be reordered.
22
+ # question_ordering - A ReorderingVector which specifies how the Questions
23
+ # are to be reordered.
24
+ #
25
+ # Returns a QuizVariant.
26
+ def initialize(source, answer_orderings, question_ordering)
27
+ @header = source.header.dup
28
+ @footer = source.footer.dup
29
+ @questions = source
30
+ .reorder_answers(answer_orderings)
31
+ .reorder_questions(question_ordering)
32
+ .questions
33
+ .map(&:render_answers)
34
+ end
35
+ end
36
+ end
@@ -1,5 +1,5 @@
1
- require_relative 'question'
2
- require_relative 'answers_renderer'
1
+ require_relative "question"
2
+ require_relative "answers_renderer"
3
3
 
4
4
  module QuizMaster
5
5
  # This class is a question which has its answers rendered.
@@ -0,0 +1,68 @@
1
+ module QuizMaster
2
+ # This is responsible for reordering an Array. It is a place to put any
3
+ # logic and error checking.
4
+ #
5
+ # Examples
6
+ #
7
+ # r = Reorderer.new(0, 2, 1, 3)
8
+ # r.reorder(%w(a b c d)) # => %w(a c b d)
9
+ class Reorderer
10
+ # Public: Initialize a Reorderer.
11
+ #
12
+ # args - Specify an Array of indices which must not be empty and must
13
+ # contain the values 0 ... size of array.
14
+ #
15
+ # Examples
16
+ #
17
+ # r = Reorderer.new(2, 0, 1)
18
+ #
19
+ # Raises ArgumentError if the args can't be validated.
20
+ def initialize(*args)
21
+ @indices = args.to_a
22
+ indices_invalid? &&
23
+ fail(ArgumentError, "#{args.inspect} is a bad set of indices")
24
+ end
25
+
26
+ # Public: Reorder the passed array based on the indices we were initialized
27
+ # with.
28
+ #
29
+ # array - An Array containing elements you're interested in reordering.
30
+ #
31
+ # Examples
32
+ #
33
+ # r.reorder([1, 3, 5])
34
+ #
35
+ # Raises an ArgumentError if the size of the Array isn't the same as the
36
+ # size of the indices specified in new.
37
+ #
38
+ # Returns a new Array containing the reordered elements of array.
39
+ def reorder(array)
40
+ fail(ArgumentError, "array should have #{@indices.size} elements") if
41
+ array.size != @indices.size
42
+ array.values_at(*@indices)
43
+ end
44
+
45
+ # Determines which elements of a list will be moved when the list is
46
+ # reordered.
47
+ #
48
+ # Examples
49
+ #
50
+ # r = Reorderer.new(3,0,2,1)
51
+ # r.moving_indices # => [0, 1, 3]
52
+ # # ... so element 2 of an Array will be element 2 of the result
53
+ # # of the re-order, and elements 0, 1, and 3 will be moved in the
54
+ # # result:
55
+ # r.reorder(%w(zero one two three)) # => ["three", "zero", "two", "one"]
56
+ #
57
+ # Retruns an Array of source indices.
58
+ def moving_indices
59
+ @indices.each_with_index
60
+ .select { |destination, source| source != destination }
61
+ .map { |_, source| source }
62
+ end
63
+
64
+ private def indices_invalid?
65
+ @indices.empty? || @indices.sort != (0 ... @indices.size).to_a
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,95 @@
1
+ require "forwardable"
2
+
3
+ module QuizMaster
4
+ # A ReorderingVector is a way of specifying how an array should be reordered.
5
+ # It contains a mapping of source indices so you can say:
6
+ #
7
+ # reordered = original.values_at(*reordering_vector)
8
+ #
9
+ # The reordering is constructed "randomly" at initialization, and has the idea
10
+ # of "anchored" indices which are guaranteed to remain in their original
11
+ # place.
12
+ class ReorderingVector
13
+ extend Forwardable
14
+ include Comparable
15
+
16
+ # Public: Initialize a ReorderingVector.
17
+ #
18
+ # anchored - An Array of "truthy" values which indicate the size of the
19
+ # Array to reorder and which elements should be "anchored"
20
+ # at their current position in the reordering (indicated by
21
+ # a +true+ value).
22
+ # shuffle_block - A block used to shuffle the "un-anchored" indices. It is
23
+ # called with an Array as an argument and should return an
24
+ # Array containing the same elements. Optional
25
+ def initialize(anchored, &shuffle_block)
26
+ shuffle_block ||= ->(a) { a.shuffle }
27
+ @indices = create_indices(anchored, &shuffle_block)
28
+ end
29
+
30
+ ##
31
+ # :method: []
32
+ # :args: index
33
+ #
34
+ # Public: Get the element at the specified index.
35
+ #
36
+ # Returns the element at the specified index.
37
+ def_delegator :@indices, :[]
38
+
39
+ ##
40
+ # :method: size
41
+ #
42
+ # Public: Get the size of the ReorderingVector.
43
+ #
44
+ # Returns an Integer representing the size of the ReorderingVector.
45
+ def_delegator :@indices, :size
46
+
47
+ ##
48
+ # :method: each
49
+ #
50
+ # Public: Allows us to enumerate the elements of the ReorderingVector.
51
+ #
52
+ # Yields each element of the Reordering Vector.
53
+ #
54
+ # Returns an Enumerator if no block given.
55
+ def_delegator :@indices, :each
56
+
57
+ # Public: Convert a ReorderingVector to an Array
58
+ #
59
+ # Examples
60
+ #
61
+ # r = ReorderingVector.new([true, false, false, true])
62
+ # r.to_a # => [0, 2, 1, 3]
63
+ #
64
+ # Returns an Array representation of this object.
65
+ def to_a
66
+ @indices.dup
67
+ end
68
+
69
+ alias_method :to_ary, :to_a
70
+
71
+ # Public: Compare two ReorderingVectors. We include the Comparable module
72
+ # so this gives us all of the comparisons. We compare ReorderingVectors as
73
+ # Arrays.
74
+ #
75
+ # other - An other objecy which can be compared to an Array.
76
+ #
77
+ # Returns +1 / 0 / -1 for successful comparisons, and nil for incomparable
78
+ # values.
79
+ def <=>(other)
80
+ to_a <=> other.to_a
81
+ end
82
+
83
+ private def create_indices(anchored, &shuffle_block)
84
+ anchored_indices, floating_indices =
85
+ anchored.each_index.partition { |i| anchored[i] }
86
+
87
+ result = anchored_indices.each_with_object([]) { |i, a| a[i] = i }
88
+
89
+ shuffled = shuffle_block.call(floating_indices)
90
+ floating_indices.zip(shuffled).each_with_object(result) do |(to, from), a|
91
+ a[to] = from
92
+ end
93
+ end
94
+ end
95
+ end
@@ -13,7 +13,7 @@ module QuizMaster
13
13
  # This generates an infinite range which we can use for numbering the
14
14
  # questions.
15
15
  numbers = (1 .. Float::INFINITY)
16
- quiz.questions.zip(numbers).each_with_object('') do |(q, number), result|
16
+ quiz.questions.zip(numbers).each_with_object("") do |(q, number), result|
17
17
  result << "#{number}) #{q.correct_answer.tag}\n"
18
18
  end
19
19
  end
@@ -21,7 +21,7 @@ module QuizMaster
21
21
  # Public: Create a TextAssembler object
22
22
  def initialize
23
23
  @tags = []
24
- @format_string = ''
24
+ @format_string = ""
25
25
  end
26
26
 
27
27
  # Public: Add a tag reference to the text we are assembling.
@@ -35,7 +35,7 @@ module QuizMaster
35
35
  # Returns nothing.
36
36
  def add_tag_reference(tag)
37
37
  @tags << tag
38
- @format_string << '(%s)'
38
+ @format_string << "(%s)"
39
39
  end
40
40
 
41
41
  # Public: Add some literal text to the text we are assembling.
@@ -48,7 +48,7 @@ module QuizMaster
48
48
  #
49
49
  # Returns nothing.
50
50
  def add_text(text)
51
- @format_string << text.gsub('%', '%%')
51
+ @format_string << text.gsub("%", "%%")
52
52
  end
53
53
 
54
54
  # Public: Get the String representation of the TextAssembler.
@@ -36,7 +36,7 @@ module QuizMaster
36
36
  #
37
37
  # Returns a String containing the formatted Question.
38
38
  def format(question, number)
39
- answers = AnswersRenderer.new(answers: question.answers, first_tag: 'a')
39
+ answers = AnswersRenderer.new(answers: question.answers, first_tag: "a")
40
40
 
41
41
  answers.each_with_object("#{number}) #{question.prompt}\n\n") do |a, text|
42
42
  text << " #{a.tag}) #{a.text}\n"
@@ -1,8 +1,9 @@
1
- require_relative 'text_question_formatter'
1
+ require_relative "text_question_formatter"
2
2
 
3
3
  module QuizMaster
4
4
  # This is responsible for generating a text version of Quizes.
5
5
  class TextQuestionSheetFormatter
6
+ # Initialiize a TextQuestionSheetFormatter.
6
7
  def initialize
7
8
  @question_formatter = TextQuestionFormatter.new
8
9
  end
@@ -1,5 +1,5 @@
1
1
  #
2
2
  module QuizMaster
3
3
  # The Gem version. We will use semantic versioning: http://semver.org
4
- VERSION = '0.0.2'
4
+ VERSION = "0.0.3"
5
5
  end
@@ -1,17 +1,19 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
2
+ lib = File.expand_path("../lib", __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'quiz_master/version'
4
+ require "quiz_master/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = 'quiz_master'
7
+ spec.name = "quiz_master"
8
8
  spec.version = QuizMaster::VERSION
9
- spec.authors = ['Mike Stok']
10
- spec.email = ['mike@stok.ca']
11
- spec.summary = 'Simple manager for multiple choice quizzes'
9
+ spec.authors = ["Mike Stok"]
10
+ spec.email = ["mike@stok.ca"]
11
+ spec.summary = "Simple manager for multiple choice quizzes"
12
12
  # spec.description = %q{TODO: Write a longer description. Optional.}
13
- spec.homepage = 'https://github.com/mikestok/quiz_master'
14
- spec.license = 'MIT'
13
+ spec.homepage = "https://github.com/mikestok/quiz_master"
14
+ spec.license = "MIT"
15
+
16
+ spec.required_ruby_version = "~> 2.1"
15
17
 
16
18
  # Because there are likely to be / characters in the regular expressions
17
19
  # we relax the rubocop checks for a bit...
@@ -19,13 +21,13 @@ Gem::Specification.new do |spec|
19
21
  spec.files = `git ls-files -z`.split("\x0")
20
22
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
23
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
- spec.require_paths = ['lib']
24
+ spec.require_paths = ["lib"]
23
25
  # rubocop:enable Style/RegexpLiteral
24
26
 
25
- spec.add_development_dependency 'bundler', '~> 1.6'
26
- spec.add_development_dependency 'rake'
27
- spec.add_development_dependency 'rubocop'
28
- spec.add_development_dependency 'rdoc'
29
- spec.add_development_dependency 'inch'
30
- spec.add_development_dependency 'sparkr' # inch should have included this...
27
+ spec.add_development_dependency "bundler", "~> 1.6"
28
+ spec.add_development_dependency "rake"
29
+ spec.add_development_dependency "rubocop"
30
+ spec.add_development_dependency "rdoc"
31
+ spec.add_development_dependency "inch"
32
+ spec.add_development_dependency "sparkr" # inch should have included this...
31
33
  end
@@ -0,0 +1,55 @@
1
+ require "spec_helper"
2
+
3
+ describe Question do
4
+ describe "reorder_answers" do
5
+ it "can reorder its answers" do
6
+ q = example_questions.first
7
+ reordered = q.reorder_answers(Reorderer.new(2, 0, 1, 3))
8
+ reordered.answers.map(&:text).must_equal(
9
+ [
10
+ "This is the third answer",
11
+ "This is the first answer",
12
+ "This is the second answer",
13
+ "{{3}} and {{5}}"
14
+ ]
15
+ )
16
+ end
17
+
18
+ it 'raises an error if asked to move "anchored" answers' do
19
+ q = example_questions[2]
20
+ lambda do
21
+ q.reorder_answers(Reorderer.new(0, 7, 2, 3, 4, 5, 6, 1))
22
+ end.must_raise(MovingAnchoredAnswerError)
23
+ end
24
+ end
25
+
26
+ describe "reordering_vector" do
27
+ q = example_questions[2]
28
+ rv = q.reordering_vector { |a| a.reverse }
29
+
30
+ it "must be a ReorderingVector" do
31
+ rv.must_be_kind_of(ReorderingVector)
32
+ end
33
+
34
+ it "must have the expected order" do
35
+ rv.to_a.must_equal([5, 4, 3, 2, 1, 0, 6, 7])
36
+ end
37
+ end
38
+
39
+ describe "render_answers" do
40
+ first_tag = "a"
41
+ q = example_questions.first.render_answers(first_tag: first_tag)
42
+
43
+ it "must have the correct first answer and tag" do
44
+ af = q.answers.first
45
+ af.tag.must_equal first_tag
46
+ af.text.must_equal "This is the first answer"
47
+ end
48
+
49
+ it "must have the correct last answer and tag" do
50
+ al = q.answers.last
51
+ al.tag.must_equal "d"
52
+ al.text.must_equal "(a) and (c)"
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ require "spec_helper"
2
+
3
+ describe Quiz do
4
+ describe "answer_reordering_vectors" do
5
+ it "should generate expected answer reordering vectors" do
6
+ quiz_source = Quiz.new(example_questions)
7
+ vectors = quiz_source.answer_reordering_vectors { |a| a.reverse }
8
+
9
+ vectors.must_equal([
10
+ ReorderingVector([3, 2, 1, 0]),
11
+ ReorderingVector([3, 2, 1, 0]),
12
+ ReorderingVector([5, 4, 3, 2, 1, 0, 6, 7])
13
+ ])
14
+ end
15
+ end
16
+
17
+ describe "question_reordering_vector" do
18
+ it "should generate expected reordering vector" do
19
+ quiz_source = Quiz.new(example_questions)
20
+ v = quiz_source.question_reordering_vector { |a| a.reverse }
21
+
22
+ v.must_equal(ReorderingVector([2, 1, 0]))
23
+ end
24
+ end
25
+
26
+ describe "reorder_answers" do
27
+ quiz_source = Quiz.new(example_questions)
28
+ vectors = quiz_source.answer_reordering_vectors { |a| a.reverse }
29
+ quiz = quiz_source.reorder_answers(vectors)
30
+
31
+ it "should *not* have shuffled the questions" do
32
+ quiz.questions.first.prompt.must_equal(
33
+ EXAMPLE_QUESTION_PARAMS.first[:prompt]
34
+ )
35
+ end
36
+
37
+ it "should have shuffled the answers within the question" do
38
+ quiz.questions.first.answers.first.text.must_equal(
39
+ EXAMPLE_QUESTION_PARAMS.first[:answers].last.text
40
+ )
41
+ end
42
+ end
43
+
44
+ describe "reorder_questions" do
45
+ it "should return a quiz with questions re-ordered" do
46
+ quiz_source = Quiz.new(example_questions)
47
+ vector = quiz_source.question_reordering_vector { |a| a.reverse }
48
+ quiz = quiz_source.reorder_questions(vector)
49
+
50
+ quiz.questions.first.prompt.must_equal(
51
+ EXAMPLE_QUESTION_PARAMS.last[:prompt]
52
+ )
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+
3
+ describe QuizVariant do
4
+ describe "initialize" do
5
+ quiz_source = Quiz.new(example_questions)
6
+
7
+ answer_orderings = [
8
+ ReorderingVector([3, 1, 0, 2]),
9
+ ReorderingVector([1, 0, 2, 3]),
10
+ ReorderingVector([4, 0, 1, 3, 5, 2, 6, 7])
11
+ ]
12
+ question_ordering = ReorderingVector([0, 2, 1])
13
+
14
+ variant = QuizVariant.new(quiz_source, answer_orderings, question_ordering)
15
+
16
+ it "has questions in the right order" do
17
+ variant.questions[0].prompt.must_equal "Example 1 prompt"
18
+ variant.questions[1].prompt.must_equal "What is the colour of a Banana?"
19
+ variant.questions[2].prompt.must_equal "Example 2 prompt"
20
+ end
21
+
22
+ it "has answers in the right order" do
23
+ banana_question = variant.questions[1]
24
+ banana_question.answers[0].text.must_equal "Brown"
25
+ banana_question.answers[1].text.must_equal "Green"
26
+ banana_question.answers[6].text.must_equal "All of the colours above"
27
+ end
28
+
29
+ it "has the tags assigned" do
30
+ banana_question = variant.questions[1]
31
+ banana_question.answers[0].tag.must_equal "a"
32
+ end
33
+ end
34
+ end