qti 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/null_logger.rb +2 -4
  3. data/lib/qti/models/base.rb +8 -10
  4. data/lib/qti/models/manifest.rb +4 -8
  5. data/lib/qti/v1/models/assessment.rb +4 -4
  6. data/lib/qti/v1/models/assessment_item.rb +2 -2
  7. data/lib/qti/v1/models/choices/fill_blank_choice.rb +1 -1
  8. data/lib/qti/v1/models/interactions/base_interaction.rb +1 -1
  9. data/lib/qti/v1/models/interactions/choice_interaction.rb +1 -1
  10. data/lib/qti/v1/models/interactions/fill_blank_interaction.rb +6 -7
  11. data/lib/qti/v1/models/interactions/ordering_interaction.rb +1 -1
  12. data/lib/qti/v1/models/scoring_data.rb +1 -1
  13. data/lib/qti/v2/models/assessment_item.rb +1 -1
  14. data/lib/qti/v2/models/assessment_test.rb +3 -3
  15. data/lib/qti/v2/models/base.rb +6 -6
  16. data/lib/qti/v2/models/choices/simple_choice.rb +1 -1
  17. data/lib/qti/v2/models/interactions/base_interaction.rb +1 -1
  18. data/lib/qti/v2/models/interactions/categorization_interaction.rb +1 -1
  19. data/lib/qti/v2/models/interactions/gap_match_interaction.rb +8 -12
  20. data/lib/qti/v2/models/interactions/match_interaction.rb +2 -2
  21. data/lib/qti/v2/models/non_assessment_test.rb +2 -2
  22. data/lib/qti/v2/models/scoring_data.rb +1 -1
  23. data/lib/qti/v2/models/stimulus_item.rb +0 -1
  24. data/lib/qti/version.rb +1 -1
  25. data/spec/gemfiles/nokogiri-1.6.gemfile.lock +126 -0
  26. data/spec/gemfiles/nokogiri-1.8.gemfile.lock +126 -0
  27. data/spec/gemfiles/rails-4.2.gemfile.lock +203 -0
  28. data/spec/gemfiles/rails-5.0.gemfile.lock +209 -0
  29. data/spec/gemfiles/rails-5.1.gemfile.lock +209 -0
  30. data/spec/gemfiles/sanitize-4.2.gemfile.lock +126 -0
  31. data/spec/gemfiles/sanitize-4.5.gemfile.lock +126 -0
  32. data/spec/lib/qti/models/base_spec.rb +10 -10
  33. data/spec/lib/qti/models/manifest_spec.rb +0 -1
  34. data/spec/lib/qti/v1/models/assessment_spec.rb +7 -8
  35. data/spec/lib/qti/v1/models/choices/fill_blank_choice_spec.rb +1 -1
  36. data/spec/lib/qti/v1/models/choices/logical_identifier_choice_spec.rb +1 -1
  37. data/spec/lib/qti/v1/models/interactions/fill_blank_interaction_spec.rb +15 -13
  38. data/spec/lib/qti/v1/models/interactions/ordering_interaction_spec.rb +1 -1
  39. data/spec/lib/qti/v2/models/assessment_item_spec.rb +3 -3
  40. data/spec/lib/qti/v2/models/assessment_test_spec.rb +6 -7
  41. data/spec/lib/qti/v2/models/choices/gap_match_choice_spec.rb +1 -1
  42. data/spec/lib/qti/v2/models/choices/simple_associable_choice_spec.rb +8 -8
  43. data/spec/lib/qti/v2/models/choices/simple_choice_spec.rb +1 -1
  44. data/spec/lib/qti/v2/models/interactions/categorization_interaction_spec.rb +3 -3
  45. data/spec/lib/qti/v2/models/interactions/gap_match_interaction_spec.rb +24 -23
  46. data/spec/lib/qti/v2/models/interactions/ordering_interaction_spec.rb +1 -1
  47. data/spec/lib/qti/v2/models/interactions/short_text_interaction_spec.rb +1 -1
  48. data/spec/lib/qti/v2/models/non_assessment_test_spec.rb +1 -1
  49. data/spec/lib/qti/v2/models/object_element_spec.rb +1 -1
  50. metadata +17 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 96229366751c574392ef6478af9c08bad96cec41
4
- data.tar.gz: ef9619aa3bdf5d8f83c46839f8d04953a79a717b
3
+ metadata.gz: 202d9ba3ef99c4e0df030cf13a8e25f4e19441c2
4
+ data.tar.gz: 077f7118e61d94d1d7796494e481355e0b399833
5
5
  SHA512:
6
- metadata.gz: e9b5a5f1d726675d1cf1b5e161120c73a23ac1c20e633cd738787c9d3f5edbfad5618ac52fcabcb779789c4a2d04b019b386ba99a1819d8316c0c6d5da6dae61
7
- data.tar.gz: 9fa1abca8886f8f5c86bc4e1bcd84a5890dc99ba2facc8e5aad2aa4642af7c95894f1970924b2018b7078400b210c7b8da5e8e43380125894416279f6fbe6074
6
+ metadata.gz: 6119e426a2f6c4b91cfb84ac7da7ca78317490439c0ace3ec067fc65ffa1b8b929979dfa76a4cb648b9d44781e5cb55d38490c89151ab1e66581c9315c8b88ff
7
+ data.tar.gz: 76f7caf87c7c9efd299b770d2adfcfa5737991dbaa3b2ee450616d0ae987bac40bd2a362932769f462006e80a11b40b4cb00c249cf48b1565c70fa50b71d559e
data/lib/null_logger.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  class NullLogger < Logger
2
- def initialize(*args)
3
- end
2
+ def initialize(*args); end
4
3
 
5
- def add(*args, &block)
6
- end
4
+ def add(*args, &block); end
7
5
  end
@@ -113,7 +113,7 @@ module Qti
113
113
  case node[:type]
114
114
  when /^image\//
115
115
  return replace_with_image(node, node[:data])
116
- when "text/html"
116
+ when 'text/html'
117
117
  return replace_with_html(node, path)
118
118
  end
119
119
  end
@@ -127,15 +127,13 @@ module Qti
127
127
  end
128
128
 
129
129
  def replace_with_html(node, path)
130
- begin
131
- content = File.read(path)
132
- html_content = Sanitize.fragment(content, sanitize_config(import_objects: false))
133
- node.name = 'div'
134
- node.add_child(Nokogiri::HTML.fragment(html_content))
135
- rescue StandardError => e
136
- warn "failed to import html object #{path}: #{e.message}"
137
- node.unlink
138
- end
130
+ content = File.read(path)
131
+ html_content = Sanitize.fragment(content, sanitize_config(import_objects: false))
132
+ node.name = 'div'
133
+ node.add_child(Nokogiri::HTML.fragment(html_content))
134
+ rescue StandardError => e
135
+ warn "failed to import html object #{path}: #{e.message}"
136
+ node.unlink
139
137
  end
140
138
  end
141
139
  end
@@ -3,29 +3,25 @@ require 'qti/v1/models/base'
3
3
  module Qti
4
4
  module Models
5
5
  class Manifest < Qti::Models::Base
6
-
7
6
  def assessment_test
8
7
  qti_2_x_href || qti_1_href || qti_2_non_assessment_href || unknown_type
9
8
  end
10
9
 
11
10
  def qti_1_href
12
11
  test_path = xpath_with_single_check("//xmlns:resources/xmlns:resource[@type='imsqti_xmlv1p2']/@href")&.content ||
13
- xpath_with_single_check("//xmlns:resources/xmlns:resource[@type='imsqti_xmlv1p2']/xmlns:file/@href")&.content
12
+ xpath_with_single_check("//xmlns:resources/xmlns:resource[@type='imsqti_xmlv1p2']/xmlns:file/@href")&.content
14
13
  Qti::V1::Models::Assessment.from_path!(remap_href_path(test_path), @package_root) if test_path
15
14
  end
16
15
 
17
16
  def qti_2_x_href
18
17
  test_path = xpath_with_single_check("//xmlns:resources/xmlns:resource[@type='imsqti_test_xmlv2p1']/@href")&.content ||
19
- xpath_with_single_check("//xmlns:resources/xmlns:resource[@type='imsqti_test_xmlv2p2']/@href")&.content
18
+ xpath_with_single_check("//xmlns:resources/xmlns:resource[@type='imsqti_test_xmlv2p2']/@href")&.content
20
19
  Qti::V2::Models::AssessmentTest.from_path!(remap_href_path(test_path), @package_root) if test_path
21
20
  end
22
21
 
23
22
  def qti_2_non_assessment_href
24
- qti2_ns = ["http://www.imsglobal.org/xsd/imsqti_metadata_v2p1", "http://www.imsglobal.org/xsd/imsqti_v2p1"]
25
- if qti2_ns.include?(xpath_with_single_check("//xmlns:manifest")&.namespaces["xmlns:imsqti"])
26
- # use imsmanifest.xml as the assessment XML if there is no assessment xml
27
- Qti::V2::Models::NonAssessmentTest.from_path!(@path, @package_root)
28
- end
23
+ item_path = @doc.at_xpath("//xmlns:resources/xmlns:resource[@type='imsqti_item_xmlv2p1']/@href")&.content
24
+ Qti::V2::Models::NonAssessmentTest.from_path!(@path, @package_root) if item_path
29
25
  end
30
26
 
31
27
  def unknown_type
@@ -16,12 +16,12 @@ module Qti
16
16
  Qti::V1::Models::AssessmentItem.new(assessment_item, @package_root)
17
17
  end
18
18
 
19
- def stimulus_ref(ref)
20
- return nil
19
+ def stimulus_ref(_ref)
20
+ nil
21
21
  end
22
22
 
23
- def create_stimulus(stimulus)
24
- raise "Stimulus type not supported for QTI version"
23
+ def create_stimulus(_stimulus)
24
+ raise 'Stimulus type not supported for QTI version'
25
25
  end
26
26
  end
27
27
  end
@@ -36,7 +36,7 @@ module Qti
36
36
 
37
37
  def has_points_possible_qti_metadata?
38
38
  if @doc.at_xpath('.//xmlns:qtimetadata').present?
39
- points_possible_label = qti_metadata_children.children.find {|node| node.text == "points_possible"}
39
+ points_possible_label = qti_metadata_children.children.find { |node| node.text == 'points_possible' }
40
40
  points_possible_label.present?
41
41
  else
42
42
  false
@@ -46,7 +46,7 @@ module Qti
46
46
  def points_possible
47
47
  @points_possible ||= begin
48
48
  if has_points_possible_qti_metadata?
49
- points_possible_label = qti_metadata_children.children.find {|node| node.text == "points_possible"}
49
+ points_possible_label = qti_metadata_children.children.find { |node| node.text == 'points_possible' }
50
50
  points_possible_node = points_possible_label.next.text
51
51
  else
52
52
  decvar_maxvalue
@@ -12,7 +12,7 @@ module Qti
12
12
 
13
13
  def identifier
14
14
  @identifier ||= @node.attributes['respident']&.value ||
15
- @node.attributes['ident']&.value
15
+ @node.attributes['ident']&.value
16
16
  end
17
17
 
18
18
  def item_body
@@ -5,7 +5,7 @@ module Qti
5
5
  class BaseInteraction < Qti::V1::Models::Base
6
6
  attr_reader :node
7
7
 
8
- def self.matches(node, parent)
8
+ def self.matches(_node, _parent)
9
9
  false
10
10
  end
11
11
 
@@ -6,7 +6,7 @@ module Qti
6
6
  # This will know if a class matches
7
7
  def self.matches(node, parent)
8
8
  matches = node.xpath('.//xmlns:response_lid')
9
- return false if matches.count > 1 || matches.empty?
9
+ return false if matches.count > 1 || matches.empty?
10
10
  rcardinality = matches.first.attributes['rcardinality']&.value || 'Single'
11
11
  return false if rcardinality == 'Ordered'
12
12
  new(node, parent)
@@ -45,22 +45,21 @@ module Qti
45
45
  end
46
46
 
47
47
  def scoring_data_structs
48
- answer_nodes.map { |value_node| ScoringData.new(
48
+ answer_nodes.map do |value_node|
49
+ ScoringData.new(
49
50
  value_node.content,
50
51
  rcardinality,
51
- {
52
- id: value_node.attributes['respident']&.value,
53
- case: value_node.attributes['case']&.value || 'no'
54
- }
52
+ id: value_node.attributes['respident']&.value,
53
+ case: value_node.attributes['case']&.value || 'no'
55
54
  )
56
- }
55
+ end
57
56
  end
58
57
 
59
58
  private
60
59
 
61
60
  def rcardinality
62
61
  @rcardinality ||= @node.at_xpath('.//xmlns:response_str/@rcardinality')&.value ||
63
- @node.at_xpath('.//xmlns:response_num/@rcardinality')&.value
62
+ @node.at_xpath('.//xmlns:response_num/@rcardinality')&.value
64
63
  end
65
64
 
66
65
  def answer_nodes
@@ -6,7 +6,7 @@ module Qti
6
6
  # This will know if a class matches
7
7
  def self.matches(node, parent)
8
8
  matches = node.xpath('.//xmlns:response_lid')
9
- return false if matches.count > 1 || matches.empty?
9
+ return false if matches.count > 1 || matches.empty?
10
10
  rcardinality = matches.first.attributes['rcardinality']&.value || 'Single'
11
11
  return false if rcardinality != 'Ordered'
12
12
  new(node, parent)
@@ -2,7 +2,7 @@ module Qti
2
2
  module V1
3
3
  module Models
4
4
  ScoringData = Struct.new(:values, :rcardinality, :id, :case) do
5
- def initialize(values, rcardinality, options={})
5
+ def initialize(values, rcardinality, options = {})
6
6
  super(values, rcardinality, options[:id], options[:case])
7
7
  end
8
8
  end
@@ -35,7 +35,7 @@ module Qti
35
35
  def points_possible
36
36
  @points_possible ||= begin
37
37
  xpath_with_single_check("//xmlns:outcomeDeclaration[@identifier='SCORE']/@normalMaximum")&.content ||
38
- xpath_with_single_check("//xmlns:outcomeDeclaration[@identifier='MAXSCORE']//xmlns:value")&.content
38
+ xpath_with_single_check("//xmlns:outcomeDeclaration[@identifier='MAXSCORE']//xmlns:value")&.content
39
39
  end
40
40
  end
41
41
 
@@ -5,7 +5,7 @@ module Qti
5
5
  module Models
6
6
  class AssessmentTest < Qti::V2::Models::Base
7
7
  def title
8
- @title ||= xpath_with_single_check('//xmlns:assessmentTest/@title')&.content || File.basename(@path, ".xml")
8
+ @title ||= xpath_with_single_check('//xmlns:assessmentTest/@title')&.content || File.basename(@path, '.xml')
9
9
  end
10
10
 
11
11
  def assessment_items
@@ -29,8 +29,8 @@ module Qti
29
29
  Qti::V2::Models::AssessmentItem.from_path!(assessment_item_ref, @package_root)
30
30
  end
31
31
 
32
- def stimulus_ref(ref)
33
- return nil
32
+ def stimulus_ref(_ref)
33
+ nil
34
34
  end
35
35
 
36
36
  def create_stimulus(stimulus_ref)
@@ -4,7 +4,7 @@ module Qti
4
4
  module V2
5
5
  module Models
6
6
  class Base < Qti::Models::Base
7
- BODY_ELEMENTS_CSS = %w(
7
+ BODY_ELEMENTS_CSS = %w[
8
8
  atomicBlock
9
9
  atomicInline
10
10
  caption
@@ -33,9 +33,9 @@ module Qti
33
33
  tr
34
34
  ul
35
35
  infoControl
36
- ).join(',').freeze
36
+ ].join(',').freeze
37
37
 
38
- INTERACTION_ELEMENTS_CSS = %w(
38
+ INTERACTION_ELEMENTS_CSS = %w[
39
39
  blockInteraction
40
40
  customInteraction
41
41
  inlineInteraction
@@ -55,11 +55,11 @@ module Qti
55
55
  orderInteraction
56
56
  sliderInteraction
57
57
  uploadInteraction
58
- ).join(',').freeze
58
+ ].join(',').freeze
59
59
 
60
- CHOICE_ELEMENTS_CSS = %w(
60
+ CHOICE_ELEMENTS_CSS = %w[
61
61
  simpleChoice
62
- ).join(',').freeze
62
+ ].join(',').freeze
63
63
 
64
64
  def qti_version
65
65
  2
@@ -5,7 +5,7 @@ module Qti
5
5
  module Models
6
6
  module Choices
7
7
  class SimpleChoice < Qti::V2::Models::Base
8
- PROHIBITED_NODE_NAMES = %w(feedbackInline).join(',').freeze
8
+ PROHIBITED_NODE_NAMES = %w[feedbackInline].join(',').freeze
9
9
 
10
10
  def initialize(node, parent)
11
11
  @node = node
@@ -5,7 +5,7 @@ module Qti
5
5
  class BaseInteraction < Qti::V2::Models::Base
6
6
  attr_reader :node
7
7
 
8
- def self.matches(node)
8
+ def self.matches(_node)
9
9
  false
10
10
  end
11
11
 
@@ -16,7 +16,7 @@ module Qti
16
16
  def self.use_match_interaction_implementation?(node)
17
17
  MatchItemTagProcessors::MatchInteractionTagProcessor.match_interaction_tag?(node) &&
18
18
  MatchItemTagProcessors::MatchInteractionTagProcessor.number_of_questions_per_answer(node)
19
- .any? { |n| n != 1 }
19
+ .any? { |n| n != 1 }
20
20
  end
21
21
 
22
22
  def scoring_data_structs
@@ -20,9 +20,7 @@ module Qti
20
20
  gap_node.children.filter('gapText').each(&:remove)
21
21
 
22
22
  gap_node.children.each do |child|
23
- if child.inner_text.strip.empty?
24
- child.remove
25
- end
23
+ child.remove if child.inner_text.strip.empty?
26
24
  end
27
25
 
28
26
  gap_node
@@ -68,7 +66,7 @@ module Qti
68
66
  else
69
67
  {
70
68
  type: 'text',
71
- value: stem_item.text.empty? ? " " : stem_item.text
69
+ value: stem_item.text.empty? ? ' ' : stem_item.text
72
70
  }
73
71
  end
74
72
  end
@@ -90,20 +88,18 @@ module Qti
90
88
  question_response_pairs = node.xpath('.//xmlns:correctResponse//xmlns:value').map do |value|
91
89
  value.content.split
92
90
  end
93
- question_response_pairs.map!{ |qrp| qrp.reverse }
91
+ question_response_pairs.map!(&:reverse)
94
92
  question_response_id_mapping = Hash[question_response_pairs]
95
- answer_nodes.map { |value_node|
93
+ answer_nodes.map do |value_node|
96
94
  node_id = value_node.attributes['identifier']&.value
97
- answer_choice = choices.find{ |choice| choice.attributes['identifier']&.value == question_response_id_mapping[node_id] }
95
+ answer_choice = choices.find { |choice| choice.attributes['identifier']&.value == question_response_id_mapping[node_id] }
98
96
  ScoringData.new(
99
97
  answer_choice.content,
100
98
  'directedPair',
101
- {
102
- id: node_id,
103
- case: false
104
- }
99
+ id: node_id,
100
+ case: false
105
101
  )
106
- }
102
+ end
107
103
  end
108
104
 
109
105
  def choices
@@ -36,13 +36,13 @@ module Qti
36
36
  def self.use_associate_interaction_implementation?(node)
37
37
  MatchItemTagProcessors::AssociateInteractionTagProcessor.associate_interaction_tag?(node) &&
38
38
  MatchItemTagProcessors::AssociateInteractionTagProcessor.number_of_questions_per_answer(node)
39
- .all? { |n| n == 1 }
39
+ .all? { |n| n == 1 }
40
40
  end
41
41
 
42
42
  def self.use_match_interaction_implementation?(node)
43
43
  MatchItemTagProcessors::MatchInteractionTagProcessor.match_interaction_tag?(node) &&
44
44
  MatchItemTagProcessors::MatchInteractionTagProcessor.number_of_questions_per_answer(node)
45
- .all? { |n| n == 1 }
45
+ .all? { |n| n == 1 }
46
46
  end
47
47
  end
48
48
  end
@@ -16,11 +16,11 @@ module Qti
16
16
  def stimulus_ref(assessment_item_ref)
17
17
  ref = assessment_item_ref.sub(@package_root, '')
18
18
  dependencies = @doc.xpath("//xmlns:resource[@href='#{ref}']/xmlns:dependency/@identifierref")
19
- return unless dependencies && dependencies.count == 1
19
+ return unless dependencies&.count == 1
20
20
  href = xpath_with_single_check("//xmlns:resource[@identifier='#{dependencies.first}']/@href")
21
21
  remap_href_path(href)
22
22
  end
23
23
  end
24
24
  end
25
25
  end
26
- end
26
+ end
@@ -2,7 +2,7 @@ module Qti
2
2
  module V2
3
3
  module Models
4
4
  ScoringData = Struct.new(:values, :type, :id, :case, :question_id, :questions_ids) do
5
- def initialize(values, type, options={})
5
+ def initialize(values, type, options = {})
6
6
  super(values, type, options[:id], options[:case], options[:question_id], options[:questions_ids])
7
7
  end
8
8
  end
@@ -2,7 +2,6 @@ module Qti
2
2
  module V2
3
3
  module Models
4
4
  class StimulusItem < Qti::V2::Models::Base
5
-
6
5
  def body
7
6
  @body ||= begin
8
7
  item_body_node = xpath_with_single_check('//html/body')
data/lib/qti/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Qti
2
- VERSION = '0.9.0'.freeze
2
+ VERSION = '0.9.1'.freeze
3
3
  end
@@ -0,0 +1,126 @@
1
+ PATH
2
+ remote: ../..
3
+ specs:
4
+ qti (0.9.1)
5
+ activesupport (>= 4.2.9, < 5.2)
6
+ dry-struct (~> 0.2.1)
7
+ nokogiri (>= 1.6.8, < 1.9)
8
+ rubyzip (~> 1.2)
9
+ sanitize (>= 4.2.0, < 5.0)
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ activesupport (5.1.4)
15
+ concurrent-ruby (~> 1.0, >= 1.0.2)
16
+ i18n (~> 0.7)
17
+ minitest (~> 5.1)
18
+ tzinfo (~> 1.1)
19
+ ast (2.3.0)
20
+ byebug (9.1.0)
21
+ coderay (1.1.2)
22
+ concurrent-ruby (1.0.5)
23
+ crass (1.0.2)
24
+ diff-lcs (1.3)
25
+ docile (1.1.5)
26
+ dry-configurable (0.7.0)
27
+ concurrent-ruby (~> 1.0)
28
+ dry-container (0.6.0)
29
+ concurrent-ruby (~> 1.0)
30
+ dry-configurable (~> 0.1, >= 0.1.3)
31
+ dry-core (0.3.3)
32
+ concurrent-ruby (~> 1.0)
33
+ dry-equalizer (0.2.0)
34
+ dry-logic (0.4.2)
35
+ dry-container (~> 0.2, >= 0.2.6)
36
+ dry-core (~> 0.2)
37
+ dry-equalizer (~> 0.2)
38
+ dry-struct (0.2.1)
39
+ dry-configurable (~> 0.1)
40
+ dry-equalizer (~> 0.2)
41
+ dry-types (~> 0.9, >= 0.9.0)
42
+ ice_nine (~> 0.11)
43
+ dry-types (0.12.0)
44
+ concurrent-ruby (~> 1.0)
45
+ dry-configurable (~> 0.1)
46
+ dry-container (~> 0.3)
47
+ dry-core (~> 0.2, >= 0.2.1)
48
+ dry-equalizer (~> 0.2)
49
+ dry-logic (~> 0.4, >= 0.4.2)
50
+ inflecto (~> 0.0.0, >= 0.0.2)
51
+ i18n (0.8.6)
52
+ ice_nine (0.11.2)
53
+ inflecto (0.0.2)
54
+ json (2.1.0)
55
+ method_source (0.9.0)
56
+ mini_portile2 (2.1.0)
57
+ minitest (5.10.3)
58
+ nokogiri (1.6.8.1)
59
+ mini_portile2 (~> 2.1.0)
60
+ nokogumbo (1.4.13)
61
+ nokogiri
62
+ parallel (1.12.0)
63
+ parser (2.4.0.0)
64
+ ast (~> 2.2)
65
+ powerpack (0.1.1)
66
+ pry (0.11.1)
67
+ coderay (~> 1.1.0)
68
+ method_source (~> 0.9.0)
69
+ rainbow (2.2.2)
70
+ rake
71
+ rake (0.9.6)
72
+ rspec (3.6.0)
73
+ rspec-core (~> 3.6.0)
74
+ rspec-expectations (~> 3.6.0)
75
+ rspec-mocks (~> 3.6.0)
76
+ rspec-core (3.6.0)
77
+ rspec-support (~> 3.6.0)
78
+ rspec-expectations (3.6.0)
79
+ diff-lcs (>= 1.2.0, < 2.0)
80
+ rspec-support (~> 3.6.0)
81
+ rspec-mocks (3.6.0)
82
+ diff-lcs (>= 1.2.0, < 2.0)
83
+ rspec-support (~> 3.6.0)
84
+ rspec-support (3.6.0)
85
+ rubocop (0.50.0)
86
+ parallel (~> 1.10)
87
+ parser (>= 2.3.3.1, < 3.0)
88
+ powerpack (~> 0.1)
89
+ rainbow (>= 2.2.2, < 3.0)
90
+ ruby-progressbar (~> 1.7)
91
+ unicode-display_width (~> 1.0, >= 1.0.1)
92
+ ruby-progressbar (1.8.3)
93
+ rubyzip (1.2.1)
94
+ sanitize (4.5.0)
95
+ crass (~> 1.0.2)
96
+ nokogiri (>= 1.4.4)
97
+ nokogumbo (~> 1.4.1)
98
+ simplecov (0.15.1)
99
+ docile (~> 1.1.0)
100
+ json (>= 1.8, < 3)
101
+ simplecov-html (~> 0.10.0)
102
+ simplecov-html (0.10.2)
103
+ thread_safe (0.3.6)
104
+ tzinfo (1.2.3)
105
+ thread_safe (~> 0.1)
106
+ unicode-display_width (1.3.0)
107
+ wwtd (1.3.0)
108
+
109
+ PLATFORMS
110
+ ruby
111
+
112
+ DEPENDENCIES
113
+ bundler (~> 1.15)
114
+ byebug (~> 9.0)
115
+ nokogiri (~> 1.6.8)
116
+ pry (~> 0)
117
+ qti!
118
+ rake (~> 0)
119
+ rspec (~> 3.6)
120
+ rspec-mocks (~> 3.6)
121
+ rubocop (~> 0.50.0)
122
+ simplecov (~> 0)
123
+ wwtd (~> 1.3)
124
+
125
+ BUNDLED WITH
126
+ 1.15.4