quiz_master 0.0.2 → 0.0.3

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
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