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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f36424722d7858fc0043388e0bcef810ddf656e4
4
- data.tar.gz: 64f08b1237efe768726c9074273fda972c751680
3
+ metadata.gz: 8d62671e4c95a71dec14536a930d4ac5c10797e3
4
+ data.tar.gz: 96e9ba4259c1ba1a4b07dea794d2d2839e3d68ae
5
5
  SHA512:
6
- metadata.gz: 7350ceb335a2e36be33902c8a0dde0dc6f423f7eb85f0e7ad5b3291cc77c8e054b21b4162f6364256caa55336f8df78f5cc4a5065564f9dec13c56e470036b8c
7
- data.tar.gz: 3a26fba34d62deadce6085e68f3d64fdb38c4bb28dcd4c92d350f1f9d554be36e94f08c0c9a23fafbc8cb02c4c9c9f8656a8f9cd078e1975e0c9016aec1cd1de
6
+ metadata.gz: e7dc36f5eeb2e7f9789484e5168accf43054459e7d970915ce7b771cef8fbe2335353408a3126b46a767a47a55ec3e663236b9f694be3e9279ac9fe6b2b286b2
7
+ data.tar.gz: 02781fd91885ce245291bb5c4cce1191546f211a67ecfedad60f999da65d3491efbee186e05b298c5319460e9fca2b5094f24ad56bf258c0e6342338682c7d84
@@ -1 +1,6 @@
1
1
  # Use the default set of cops as a starting point
2
+
3
+ # The use of double quotes fits with https://houndci.com (Thoughtbot's code
4
+ # checker), and with Mike Stok's personal preferences.
5
+ Style/StringLiterals:
6
+ EnforcedStyle: double_quotes
@@ -6,6 +6,13 @@
6
6
 
7
7
  ### Bugs fixed
8
8
 
9
+ ## 0.0.3 (2014-08-08)
10
+
11
+ ### New features
12
+
13
+ # [#11](https://github.com/mikestok/quiz_master/issues/11): Make a basic program
14
+ work
15
+
9
16
  ## 0.0.2 (2014-07-24)
10
17
 
11
18
  ### New Features
data/README.md CHANGED
@@ -26,33 +26,65 @@ Or install it yourself as:
26
26
 
27
27
  ## Usage
28
28
 
29
- At the moment the only working part is a **crude** text formatter:
29
+ As of release 0.0.3 it can generate reordered quizzes like this:
30
30
 
31
31
  ```ruby
32
- require 'quiz_master'
32
+ #!/usr/bin/env ruby
33
33
 
34
+ require "rubygems"
35
+ require "quiz_master"
34
36
  include QuizMaster
35
37
 
36
- q = Question.new(
37
- prompt: 'This is the prompt. It poses the question.',
38
- answers: [
39
- Answer.new('This is the first answer', '5'),
40
- Answer.new('This is the second answer'),
41
- Answer.new('This is the third answer', '3'),
42
- Answer.new('{{3}} and {{5}}')
43
- ]
44
- )
45
-
46
- TextQuestionFormatter.new.format(q, 1)
47
- # =>
48
- # 1) This is the prompt. It poses the question.
49
- #
50
- # a) This is the first answer
51
- # b) This is the second answer
52
- # c) This is the third answer
53
- # d) (a) and (c)
38
+ quiz_source = Quiz.new(
39
+ [
40
+ {
41
+ prompt: "Who were orignal members of Genesis?",
42
+ answers: [
43
+ Answer.new("Phil Collins", false, tag: "phil"),
44
+ Answer.new("Tony Banks", true),
45
+ Answer.new("Steve Hackett", false, tag: "steve"),
46
+ Answer.new("{{phil}} and {{steve}}", false)
47
+ ]
48
+ },
49
+ {
50
+ prompt: "Which numbers are prime?",
51
+ answers: [
52
+ Answer.new("1", false),
53
+ Answer.new("2", false, tag: "2"),
54
+ Answer.new("3", false),
55
+ Answer.new("All of the above except {{2}}", true, anchored: true)
56
+ ]
57
+ },
58
+ {
59
+ prompt: "What is the colour of a Banana?",
60
+ answers: [
61
+ Answer.new("Green", false),
62
+ Answer.new("Red", false, tag: "red"),
63
+ Answer.new("Yellow", false),
64
+ Answer.new("Black", false),
65
+ Answer.new("Brown", false),
66
+ Answer.new("Blue", false, tag: "blue"),
67
+ Answer.new("All of the colours above", false, anchored: true),
68
+ Answer.new("All of the colours above except {{blue}} and {{red}}", true,
69
+ anchored: true)
70
+ ]
71
+ }
72
+ ].map { |q| Question.new(q) })
73
+
74
+ 3.times do
75
+ answer_reorderings = quiz_source.answer_reordering_vectors
76
+ question_reordering = quiz_source.question_reordering_vector
77
+
78
+ quiz = QuizVariant.new(quiz_source, answer_reorderings, question_reordering)
79
+
80
+ puts TextQuestionSheetFormatter.new.format(quiz)
81
+ puts TextAnswerSheetFormatter.new.format(quiz)
82
+ end
54
83
  ```
55
84
 
85
+ Generating three (probably) different versions of the same quiz and an answer
86
+ sheet for each.
87
+
56
88
  ## Contributing
57
89
 
58
90
  1. Fork it ( https://github.com/mikestok/quiz_master/fork )
data/Rakefile CHANGED
@@ -1,26 +1,26 @@
1
- require 'bundler/gem_tasks'
2
- require 'rubocop/rake_task'
3
- require 'rake/testtask'
4
- require 'rdoc/task'
1
+ require "bundler/gem_tasks"
2
+ require "rubocop/rake_task"
3
+ require "rake/testtask"
4
+ require "rdoc/task"
5
5
 
6
- task default: [:test]
6
+ task default: %i(rubocop test)
7
7
 
8
- desc 'Run RuboCop on the lib directory'
8
+ desc "Run RuboCop on the lib directory"
9
9
  RuboCop::RakeTask.new(:rubocop) do |task|
10
- task.patterns = ['Rakefile', 'lib/**/*.rb']
10
+ task.patterns = %w(Rakefile test/**/*.rb lib/**/*.rb)
11
11
  # only show the files with failures
12
- task.formatters = ['files']
12
+ task.formatters = ["clang"]
13
13
  # don't abort rake on failure
14
- task.fail_on_error = false
14
+ task.fail_on_error = true
15
15
  end
16
16
 
17
17
  Rake::TestTask.new do |t|
18
- t.libs << 'test'
19
- t.test_files = FileList['test/**/test_*.rb']
18
+ t.libs << "test"
19
+ t.test_files = FileList["test/**/*_spec.rb"]
20
20
  end
21
21
 
22
22
  RDoc::Task.new do |rdoc|
23
- rdoc.main = 'README.md'
24
- rdoc.rdoc_files.include('README.md', 'CHANGELOG.md', 'lib/**/*.rb')
25
- rdoc.options << '--markup=tomdoc'
23
+ rdoc.main = "README.md"
24
+ rdoc.rdoc_files.include("README.md", "CHANGELOG.md", "lib/**/*.rb")
25
+ rdoc.options << "--markup=tomdoc"
26
26
  end
@@ -1,17 +1,44 @@
1
- require 'quiz_master/version'
2
- require 'quiz_master/text_question_formatter'
3
- require 'quiz_master/text_answer_sheet_formatter'
4
- require 'quiz_master/text_question_sheet_formatter'
5
- require 'quiz_master/question'
6
- require 'quiz_master/rendered_question'
7
- require 'quiz_master/answer'
8
- require 'quiz_master/quiz'
9
- require 'quiz_master/answers_renderer'
10
- require 'quiz_master/text_assembler'
1
+ require "quiz_master/version"
2
+ require "quiz_master/text_question_formatter"
3
+ require "quiz_master/text_answer_sheet_formatter"
4
+ require "quiz_master/text_question_sheet_formatter"
5
+ require "quiz_master/question"
6
+ require "quiz_master/rendered_question"
7
+ require "quiz_master/answer"
8
+ require "quiz_master/quiz"
9
+ require "quiz_master/quiz_variant"
10
+ require "quiz_master/answers_renderer"
11
+ require "quiz_master/text_assembler"
12
+ require "quiz_master/reorderer"
13
+ require "quiz_master/reordering_vector"
11
14
 
12
15
  # This provides a namespace for all of the components.
13
16
  module QuizMaster
14
17
  # This error is raised when something discovers that there are the wrong
15
18
  # number of correct answers in a set of answers.
16
19
  NumberOfCorrectAnswersError = Class.new(StandardError)
20
+ # This error is raised when we try and move an anchored answer in a question.
21
+ MovingAnchoredAnswerError = Class.new(StandardError)
22
+ # This error is raised when we create a ReorderingVector and it doesn't
23
+ # contain the indices for an array of the right size.
24
+ BadReorderingVectorIndicesError = Class.new(StandardError)
25
+
26
+ # rubocop:disable Style/MethodName
27
+
28
+ # Public: This is a convenience method to let us construct a ReorderingVector
29
+ # from an Array of indices.
30
+ #
31
+ # array - An Array of values to put in the ReorderingVector
32
+ #
33
+ # Returns a ReorderingVector whose indices are copied from the Array.
34
+ # Raises BadReorderingVectorIndicesError if the supplied Array isn't a set
35
+ # of indices.
36
+ def ReorderingVector(array)
37
+ array.sort == (0 ... array.size).to_a ||
38
+ fail(BadReorderingVectorIndicesError,
39
+ "#{array.inspect} must contain (0 ... #{array.size})")
40
+
41
+ ReorderingVector.new([false] * array.size) { array }
42
+ end
43
+ # rubocop:enable Style/MethodName
17
44
  end
@@ -15,18 +15,21 @@ module QuizMaster
15
15
  #
16
16
  # text - a String of text for the answer.
17
17
  # correct - whether this answer is true or false.
18
- # tag - an optional String which can be used to reference this Answer
19
- # from other Answers.
18
+ # tag: - A String which can be used to reference this Answer
19
+ # from other Answers. Optional.
20
+ # anchored: - A value to be used in boolean context to determine if this
21
+ # Answer is anchored. Optional.
20
22
  #
21
23
  # Examples
22
24
  #
23
25
  # a1 = Answer.new('Correct!', true)
24
26
  # a1.text # => 'Correct!'
25
27
  # a1.correct? # => true
26
- def initialize(text, correct, tag = nil)
28
+ def initialize(text, correct, tag: nil, anchored: false)
27
29
  @text = text.to_s
28
30
  @correct = correct
29
31
  @tag = tag
32
+ @anchored = anchored
30
33
  end
31
34
 
32
35
  # Public: Tell whether this Answer is correct
@@ -35,5 +38,12 @@ module QuizMaster
35
38
  def correct?
36
39
  @correct ? true : false
37
40
  end
41
+
42
+ # Public: Tell whether this Answer is anchored
43
+ #
44
+ # Returns +true+ or +false+
45
+ def anchored?
46
+ @anchored ? true : false
47
+ end
38
48
  end
39
49
  end
@@ -1,13 +1,13 @@
1
1
  module QuizMaster
2
- # Public: This renders a list of answers into a new list of answers updating
3
- # tag references enclosed in {{...}} so that the new answers have tags
2
+ # This renders a list of answers into a new list of Answers updating
3
+ # tag references enclosed in {{...}} so that the new Answers have tags
4
4
  # starting at +first_tag+
5
5
  class AnswersRenderer
6
6
  include Enumerable
7
7
 
8
8
  # Public: Initialize an AnswersRenderer object.
9
9
  #
10
- # answers - An Array of Answer objects.
10
+ # answers - An Array of Answer objects.
11
11
  # first_tag - Something that responds to #succ which is used as the initial
12
12
  # tag in the stream of yielded Answers.
13
13
  def initialize(answers:, first_tag:)
@@ -21,13 +21,16 @@ module QuizMaster
21
21
  # Yields an Answer object.
22
22
  #
23
23
  # Returns nothing.
24
- def each
25
- @rendered_answers.each do |a|
26
- yield a
27
- end
24
+ def each(&block)
25
+ @rendered_answers.each(&block)
28
26
  end
29
27
 
30
- private
28
+ # Public: Gets the Array of all the rendered Answers.
29
+ #
30
+ # Returns an Array of rendered Answers.
31
+ def all_answers
32
+ @rendered_answers.dup
33
+ end
31
34
 
32
35
  # Private: Create a mapping from the tags used in the original questions
33
36
  # passed to our initialize to the tags we'll use in the rendered answer.
@@ -46,7 +49,7 @@ module QuizMaster
46
49
  #
47
50
  # Returns a Hash mapping the original_answers tags to rendered answers
48
51
  # tags.
49
- def make_tag_map(original_answers)
52
+ private def make_tag_map(original_answers)
50
53
  tagged_list(original_answers).each_with_object({}) do |(tag, ans), map|
51
54
  map[ans.tag] = tag if ans.tag
52
55
  end
@@ -59,9 +62,10 @@ module QuizMaster
59
62
  # original_answers - and Array of Answer objects
60
63
  #
61
64
  # Returns an Array of Answer objects.
62
- def render_answers(original_answers)
65
+ private def render_answers(original_answers)
63
66
  tagged_list(original_answers).map do |tag, answer|
64
- Answer.new(replace_tags(answer.text), answer.correct?, tag)
67
+ Answer.new(replace_tags(answer.text), answer.correct?,
68
+ tag: tag, anchored: true)
65
69
  end
66
70
  end
67
71
 
@@ -72,7 +76,7 @@ module QuizMaster
72
76
  #
73
77
  # Returns a String with the tag place holders replaced with the correct
74
78
  # tags.
75
- def replace_tags(string)
79
+ private def replace_tags(string)
76
80
  string
77
81
  .split(/({{[^}]+}})/)
78
82
  .each_with_object(TextAssembler.new) do |fragment, assembler|
@@ -94,7 +98,7 @@ module QuizMaster
94
98
  # # => [['a', 'foo'], ['b', 'bar'], ['c', 'baz']
95
99
  #
96
100
  # Returns an Array whose elements are Arrays containing [tag, list_element]
97
- def tagged_list(list)
101
+ private def tagged_list(list)
98
102
  list.zip(tag_sequence).map do |element, tag|
99
103
  [tag, element]
100
104
  end
@@ -104,7 +108,7 @@ module QuizMaster
104
108
  # starting with @first_tag.
105
109
  #
106
110
  # Returns an Enumerator which generates tags.
107
- def tag_sequence
111
+ private def tag_sequence
108
112
  Enumerator.new do |yielder|
109
113
  tag = @first_tag
110
114
  loop do
@@ -1,3 +1,5 @@
1
+ require_relative "answers_renderer"
2
+
1
3
  module QuizMaster
2
4
  # A simple class to link a prompt to a list of Answers.
3
5
  class Question
@@ -27,5 +29,72 @@ module QuizMaster
27
29
  def correct_answer
28
30
  answers.find(&:correct?).clone
29
31
  end
32
+
33
+ # Public: Return a new Question with the Answers reordered by the
34
+ # given specification.
35
+ #
36
+ # spec - A Reorderer, initialized to specify the reordering to perform.
37
+ #
38
+ # Returns a new Question with the Answers reordered.
39
+ # Raises MovingAnchoredAnswerError if we attempt to move an anchored answer.
40
+ def reorder_answers(spec)
41
+ fail(MovingAnchoredAnswerError) if moving_anchored_answer?(spec)
42
+ self.class.new(prompt: prompt, answers: spec.reorder(answers))
43
+ end
44
+
45
+ # Public: Return a reordering vector for this question, with un-anchored
46
+ # answers shuffled according to the shuffle_block.
47
+ #
48
+ # klass - The class for the returned reordering vector. Optional.
49
+ # shuffle_block - A block which specifies how the un-anchored answer indices
50
+ # should be shuffled. It is passed an array of indices and
51
+ # is expected to return an array containing the same indices
52
+ # possibly in a different order. Optional.
53
+ #
54
+ # Examples
55
+ #
56
+ # # If this question has 4 answers with the last one anchored
57
+ # q.reordering_vector
58
+ # # => #<QuizMaster::ReorderingVector:0x???? @indices=[1, 2, 0, 3]>
59
+ # #
60
+ # # Where the first three indices can be shuffled in to any order.
61
+ #
62
+ # Returns an object of class klass.
63
+ def reordering_vector(klass = ReorderingVector, &shuffle_block)
64
+ shuffle_block ||= ->(a) { a.shuffle }
65
+ klass.new(answers.map(&:anchored?), &shuffle_block)
66
+ end
67
+
68
+ # Public: This generates a new object whose answers have been rendered.
69
+ #
70
+ # first_tag - The tag to be used for the first question, it must
71
+ # respond to succ. (Optional).
72
+ # result_class - The class of the object to return. I assume it is some
73
+ # sort of class which is compatible with Question.
74
+ # (Optional).
75
+ # renderer_class - The class to use for rendering the list of answers.
76
+ # (Optional).
77
+ #
78
+ # Returns result_class object which is the Question with the answers
79
+ # rendered.
80
+ def render_answers(first_tag: "a",
81
+ result_class: self.class,
82
+ renderer_class: AnswersRenderer)
83
+ result_class.new(prompt: prompt,
84
+ answers: renderer_class.new(answers: answers,
85
+ first_tag: first_tag
86
+ ).all_answers
87
+ )
88
+ end
89
+
90
+ # Private: A predicate to tell us if the given spec will move any Answer
91
+ # which is anchored.
92
+ #
93
+ # spec - A Reorderer
94
+ #
95
+ # Returns true or false.
96
+ private def moving_anchored_answer?(spec)
97
+ answers.values_at(*spec.moving_indices).any? { |a| a.anchored? }
98
+ end
30
99
  end
31
100
  end
@@ -1,3 +1,6 @@
1
+ require_relative "reordering_vector"
2
+ require_relative "reorderer"
3
+
1
4
  module QuizMaster
2
5
  # A quiz is a set of questions and their answers.
3
6
  #
@@ -17,10 +20,86 @@ module QuizMaster
17
20
  # questions - An Array of Questions.
18
21
  # header - A String containg header text (optional).
19
22
  # footer - A String containg footer text (optional).
20
- def initialize(questions, header: '', footer: '')
23
+ def initialize(questions, header: "", footer: "")
21
24
  @questions = questions
22
25
  @header = header
23
26
  @footer = footer
24
27
  end
28
+
29
+ # Public: Return a ReorderingVector for the Answers for each of the
30
+ # Questions in the Quiz.
31
+ #
32
+ # The arguments are used for testing only and should not be supplied in
33
+ # "normal" code.
34
+ #
35
+ # klass - The class of the Array elements to return. (optional).
36
+ # shuffle_block - A block to shuffle un-anchored Answers in a question.
37
+ # (optional).
38
+ #
39
+ # Returns an Array of ReorderingVectors.
40
+ def answer_reordering_vectors(klass = ReorderingVector, &shuffle_block)
41
+ questions.map do |q|
42
+ q.reordering_vector(klass, &shuffle_block)
43
+ end
44
+ end
45
+
46
+ # Public: Return a ReorderingVector for the Questions in the Quiz.
47
+ #
48
+ # The arguments are used for testing only and should not be supplied in
49
+ # "normal" code.
50
+ #
51
+ # klass - The class of the object to return. (optional).
52
+ # shuffle_block - A block to shuffle Questions in the Quiz. (optional).
53
+ #
54
+ # Returns a ReorderingVector.
55
+ def question_reordering_vector(klass = ReorderingVector, &shuffle_block)
56
+ shuffle_block ||= ->(a) { a.shuffle }
57
+ # We assume that none of the Questions in a Quiz is anchored, hence the
58
+ # [false] * questions.count.
59
+ klass.new([false] * questions.count, &shuffle_block)
60
+ end
61
+
62
+ # Public: This generates a new object with the same Questions as this
63
+ # Quiz but in a different order.
64
+ #
65
+ # vector - A vector specifying how the Questions are to be
66
+ # reordered.
67
+ # reorderer_class - The class of the Reorderer to use. (Optional).
68
+ #
69
+ # Returns a new Quiz with the questions reordered.
70
+ def reorder_questions(vector, reorderer_class = Reorderer)
71
+ reorderer = reorderer_class.new(*vector)
72
+ self.class.new(reorderer.reorder(questions),
73
+ header: header,
74
+ footer: footer)
75
+ end
76
+
77
+ # Public: This generates a new object where the Answers to each question
78
+ # have been reordered.
79
+ #
80
+ # array_of_vectors - An array of vectors specifiying how the Questions'
81
+ # Answers are to be re-ordered.
82
+ #
83
+ # Returns a new Quiz with the Questions' Answers reordered.
84
+ def reorder_answers(array_of_vectors)
85
+ new_questions = questions.zip(array_of_vectors).map do |q, vector|
86
+ question_with_reordered_answers(q, vector)
87
+ end
88
+
89
+ self.class.new(new_questions, header: header, footer: footer)
90
+ end
91
+
92
+ # Private: Create a new version of a question with its answers re-ordered.
93
+ #
94
+ # q - The "source" question.
95
+ # vector - The ReorderingVector which specifies how the answers are to be
96
+ # reordered.
97
+ #
98
+ # Returns a new Question which contains the answers from the source question
99
+ # reordered.
100
+ private def question_with_reordered_answers(q, vector)
101
+ r = Reorderer.new(*vector)
102
+ Question.new(prompt: q.prompt, answers: r.reorder(q.answers))
103
+ end
25
104
  end
26
105
  end