atomic_assessments_import 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4da5264007a4f90b2beb7c2237855d31229aac3e7266d8c2c6a47a30d364177b
4
+ data.tar.gz: 2d592d63a61a4888c5df9e4c374d68083b91c8db0fbef079330af3417a440912
5
+ SHA512:
6
+ metadata.gz: f7ee30d1484fbf993757311a0b76f2ddcfa2aa3dffc64b2bfb1ff42e5a02693cf27dad1c42f684bd8659955b8a5306024440ce462a99d37f8dceeb139dcb5d28
7
+ data.tar.gz: 514f3ac7e9d57e41cbcac462c3f8d55593e282584ddd827c1851d74008cc4549ef2271e83afa83c76e7a9babc6f867f2815d634bb76ca6f0db5350b4451d4741
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,56 @@
1
+ require:
2
+ - rubocop-rspec
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 3.3
6
+
7
+ Style/TrailingCommaInArguments:
8
+ Description: 'Checks for trailing comma in argument lists.'
9
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
10
+ EnforcedStyleForMultiline: comma
11
+ Enabled: false
12
+
13
+ Style/TrailingCommaInArrayLiteral:
14
+ Description: 'Checks for trailing comma in array literals.'
15
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
16
+ EnforcedStyleForMultiline: comma
17
+ Enabled: true
18
+
19
+ Style/TrailingCommaInHashLiteral:
20
+ Description: 'Checks for trailing comma in hash literals.'
21
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
22
+ EnforcedStyleForMultiline: comma
23
+ Enabled: false
24
+
25
+ Metrics/BlockLength:
26
+ Enabled: false
27
+
28
+ RSpec/ExampleLength:
29
+ Enabled: false
30
+
31
+ Style/StringLiterals:
32
+ Description: 'Checks if uses of quotes match the configured preference.'
33
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals'
34
+ EnforcedStyle: double_quotes
35
+ Enabled: true
36
+
37
+ Metrics/AbcSize:
38
+ Max: 50
39
+
40
+ Metrics/CyclomaticComplexity:
41
+ Max: 20
42
+
43
+ Metrics/MethodLength:
44
+ Max: 50
45
+
46
+ Metrics/ClassLength:
47
+ Max: 200
48
+
49
+ RSpec/MultipleExpectations:
50
+ Max: 16
51
+
52
+ Style/Documentation:
53
+ Enabled: false
54
+
55
+ Style/NegatedIf:
56
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-09-27
4
+
5
+ - Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Atomic Jolt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # AtomicAssessmentsImports
2
+
3
+ Import converters for atomic assessments
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add atomic_assessments_imports
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install atomic_assessments_imports
14
+
15
+ ## Conversion scripts
16
+
17
+ Convert a CSV to a learnosity archive:
18
+
19
+ $ bin/convert input.csv output.zip
20
+
21
+ Convert a CSV to json:
22
+
23
+ $ bin/convert_to_json input.csv
24
+
25
+ ## CSV input format
26
+
27
+ CSV Columns:
28
+
29
+ ### Question ID
30
+ External question id. Importing the same question ID twice will overwrite previous imports
31
+ ### Title
32
+ Item title
33
+ ### Tag: tag_type
34
+ Entries in this column represent tag names in type "tag_type". This column can be repeated any number of times with multiple tag types.
35
+ ### Question Text
36
+ Question stem
37
+ ### Question Type
38
+ Question type. One of:
39
+ - Multiple choice (default)
40
+ ### Template
41
+ Question type template. One of:
42
+ - Standard (default)
43
+ - Block layout
44
+ - Multiple response
45
+ - Block layout multiple response
46
+ - Choice matrix
47
+ - Choice matrix inline
48
+ - Choice matrix labels
49
+ "Standard" and "Block layout" are single response question types. The other templates are multiple response.
50
+ ### Correct Answer
51
+ Correct response option, e.g. "A"
52
+ For multiple response questions, use a semicolon separator. e.g. "A;C;D"
53
+ ### Option A
54
+ Text for option A
55
+ ### Option B
56
+ Text for option B
57
+ ### Option C through Option O
58
+ Text for subsequent options
59
+ ### Option A Feedback
60
+ Feedback for option A
61
+ ### Option B Feedback
62
+ Feedback for option B
63
+ ### Option C Feedback through Option O Feedback
64
+ Feedback for subsequent options
65
+ ### Scoring Type
66
+ Learnosity scoring type. One of:
67
+ - Partial Match Per Response (default)
68
+ - Partial Match
69
+ - Exact Match
70
+ ### Shuffle options
71
+ Whether to shuffle answers. One of:
72
+ - Yes
73
+ - No (default)
74
+ ### Points
75
+ Question points (default 1)
76
+ ### General Feedback
77
+ General feedback
78
+ ### Correct Feedback
79
+ Correct feedback
80
+ ### Partially Correct Feedback
81
+ Partially correct feedback
82
+ ### Incorrect Feedback
83
+ Incorrect feedback
84
+ ### Distractor Rationale
85
+ Distractor rationale feedback
86
+ ### Stimulus review
87
+ Stimulus (review only)
88
+ ### Acknowledgements
89
+ Acknowledgements
90
+ ### Instructor stimulus
91
+ Instructor stimulus
92
+ ### Sample Answer
93
+ Sample answer
94
+ ### Description
95
+ Item description
96
+ ### Alignment URL
97
+ URL used to generate standard alignment tags. This column can be repeated.
98
+
99
+
100
+
101
+
102
+ ## Development
103
+
104
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
105
+
106
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
107
+
108
+ ## Contributing
109
+
110
+ Bug reports and pull requests are welcome on GitHub at https://github.com/atomicjolt/atomic_assessments_imports.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "byebug"
4
+ require "csv"
5
+ require "active_support/core_ext/digest/uuid"
6
+
7
+ require_relative "questions/question"
8
+ require_relative "questions/multiple_choice"
9
+ require_relative "utils"
10
+
11
+ module AtomicAssessmentsImport
12
+ module CSV
13
+ class Converter
14
+ HEADERS = [
15
+ "question id",
16
+ "title",
17
+ /tag: *[a-zA-Z0-9_].*/,
18
+ "question text",
19
+ "question type",
20
+ "template",
21
+ "correct answer",
22
+ /option [a-o]/,
23
+ /option [a-o] feedback/,
24
+ "scoring type",
25
+ "shuffle options",
26
+ "points",
27
+ "general feedback",
28
+ "correct feedback",
29
+ "partially correct feedback",
30
+ "incorrect feedback",
31
+ "distractor rationale",
32
+ "stimulus review",
33
+ "acknowledgements",
34
+ "instructor stimulus",
35
+ "sample answer",
36
+ "description",
37
+ "alignment url",
38
+ ].freeze
39
+
40
+ def initialize(file)
41
+ @file = file
42
+ end
43
+
44
+ def convert
45
+ items = []
46
+ questions = []
47
+
48
+ ::CSV.foreach(
49
+ @file,
50
+ headers: true,
51
+ header_converters: lambda do |header|
52
+ normalized = header.strip
53
+ normalized =
54
+ if (m = normalized.match(/^tag:(.+)$/i))
55
+ "tag:#{m[1].strip}"
56
+ else
57
+ normalized.downcase
58
+ end
59
+ if !HEADERS.find { |h| h.is_a?(Regexp) ? h =~ normalized : h == normalized }
60
+ raise ArgumentError, "Unknown column: #{header}"
61
+ end
62
+
63
+ normalized
64
+ end,
65
+ converters: ->(data) { data&.strip }
66
+ ) do |row|
67
+ item, question_widgets = convert_row(row)
68
+
69
+ items << item
70
+ questions += question_widgets
71
+ rescue StandardError => e
72
+ raise e, "Error processing row: #{row.inspect} - #{e.message}"
73
+ end
74
+
75
+ {
76
+ activities: [],
77
+ items:,
78
+ questions:,
79
+ features: [],
80
+ errors: [],
81
+ }
82
+ end
83
+
84
+ private
85
+
86
+ def tags(row)
87
+ tags = {}
88
+ row.headers.each.with_index do |header, idx|
89
+ if header&.start_with?("tag:")
90
+ tag_name = header.gsub(/^tag:/, "").to_sym
91
+ tags[tag_name] ||= []
92
+ tags[tag_name] << row[idx] if row[idx].present?
93
+ end
94
+ end
95
+ if alignment_urls(row).present?
96
+ tags[:lrn_aligned] = alignment_urls(row).map do |url|
97
+ Digest::UUID.uuid_v5(Digest::UUID::URL_NAMESPACE, url)
98
+ end
99
+ end
100
+ tags
101
+ end
102
+
103
+ def alignment_urls(row)
104
+ if row.headers.include?("alignment url")
105
+ row.headers.filter_map.with_index do |header, idx|
106
+ row[idx] if header == "alignment url" && row[idx].present?
107
+ end
108
+ end.presence
109
+ end
110
+
111
+ def convert_row(row)
112
+ question = Questions::Question.load(row)
113
+ item = {
114
+ reference: SecureRandom.uuid,
115
+ title: row["title"] || "",
116
+ status: "published",
117
+ tags: tags(row),
118
+ metadata: {
119
+ import_date: Time.now.iso8601,
120
+ **{
121
+ question_id: row["question id"],
122
+ alignment: alignment_urls(row),
123
+ }.compact,
124
+ },
125
+ description: row["description"] || "",
126
+ questions: [
127
+ {
128
+ reference: question.reference,
129
+ type: question.question_type,
130
+ },
131
+ ],
132
+ features: [],
133
+ definition: {
134
+ widgets: [
135
+ {
136
+ reference: question.reference,
137
+ widget_type: "response",
138
+ },
139
+ ]
140
+ },
141
+ }
142
+ [item, [question.to_learnosity]]
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "question"
4
+
5
+ module AtomicAssessmentsImport
6
+ module CSV
7
+ module Questions
8
+ class MultipleChoice < Question
9
+ QUESTION_INDEXES = ("a".."o").to_a.freeze
10
+
11
+ def question_type
12
+ "mcq"
13
+ end
14
+
15
+ def question_data
16
+ raise ArgumentError, "Missing correct answer" if correct_responses.empty?
17
+ raise ArgumentError, "Missing options" if options.empty?
18
+
19
+ super.deep_merge(
20
+ {
21
+ multiple_responses: multiple_responses,
22
+ options: options,
23
+ validation: {
24
+ scoring_type: scoring_type,
25
+ valid_response: {
26
+ score: points,
27
+ value: correct_responses,
28
+ },
29
+ rounding: "none",
30
+ penalty: 1,
31
+ },
32
+ shuffle_options: Utils.parse_boolean(@row["shuffle options"], default: false),
33
+ ui_style: ui_style,
34
+ }
35
+ )
36
+ end
37
+
38
+ def metadata
39
+ super.merge(
40
+ {
41
+ distractor_rationale_response_level: distractor_rationale_response_level,
42
+ }
43
+ )
44
+ end
45
+
46
+ def options
47
+ QUESTION_INDEXES.filter_map.with_index do |value, cnt|
48
+ key = "option #{value}"
49
+ if @row[key].present?
50
+ {
51
+ label: @row[key],
52
+ value: cnt.to_s,
53
+ }
54
+ end
55
+ end
56
+ end
57
+
58
+ def correct_responses
59
+ correct = @row["correct answer"]&.split(";")&.map(&:strip)&.map(&:downcase) || []
60
+
61
+ correct.filter_map do |value|
62
+ QUESTION_INDEXES.find_index(value).to_s
63
+ end
64
+ end
65
+
66
+ def distractor_rationale_response_level
67
+ QUESTION_INDEXES.map do |value|
68
+ key = "option #{value} feedback"
69
+ @row[key].presence || ""
70
+ end.reverse.drop_while(&:blank?).reverse
71
+ end
72
+
73
+ def multiple_responses
74
+ case @row["template"]&.downcase
75
+ when "multiple response", "block layout multiple response", "choice matrix",
76
+ "choice matrix inline", "choice matrix labels"
77
+ true
78
+ else
79
+ false
80
+ end
81
+ end
82
+
83
+ def ui_style
84
+ case @row["template"]&.downcase
85
+ when "multiple response"
86
+ { type: "horizontal" }
87
+ when "block layout", "block layout multiple response"
88
+ { choice_label: "upper-alpha", type: "block" }
89
+ when "choice matrix"
90
+ { horizontal_lines: false, type: "table" }
91
+ when "choice matrix inline"
92
+ { horizontal_lines: false, type: "inline" }
93
+ when "choice matrix labels"
94
+ { stem_numeration: "upper-alpha", horizontal_lines: false, type: "table" }
95
+ when nil, "", "multiple choice"
96
+ { type: "horizontal" }
97
+ else
98
+ raise NotImplementedError, "Unknown template"
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtomicAssessmentsImport
4
+ module CSV
5
+ module Questions
6
+ class Question
7
+ def initialize(row)
8
+ @row = row
9
+ # @question_reference = Digest::UUID.uuid_v5(Digest::UUID::URL_NAMESPACE, "#{@item_url}/question")
10
+ @reference = SecureRandom.uuid
11
+ end
12
+
13
+ def self.load(row)
14
+ case row["question type"]
15
+ when nil, "", /multiple choice/i, /mcq/i
16
+ MultipleChoice.new(row)
17
+ else
18
+ raise NotImplementedError, "Unknown question type"
19
+ end
20
+ end
21
+
22
+ attr_reader :reference
23
+
24
+ def question_type
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def question_data
29
+ {
30
+ stimulus: @row["question text"],
31
+ type: question_type,
32
+ metadata: metadata,
33
+ **{
34
+ stimulus_review: @row["stimulus review"],
35
+ instructor_stimulus: @row["instructor stimulus"],
36
+ }.compact,
37
+ }
38
+ end
39
+
40
+ def metadata
41
+ {
42
+ distractor_rationale: @row["distractor rationale"],
43
+ sample_answer: @row["sample answer"],
44
+ acknowledgements: @row["acknowledgements"],
45
+ general_feedback: @row["general feedback"],
46
+ correct_feedback: @row["correct feedback"],
47
+ partially_correct_feedback: @row["partially correct feedback"],
48
+ incorrect_feedback: @row["incorrect feedback"],
49
+ }.compact
50
+ end
51
+
52
+ def scoring_type
53
+ case @row["scoring type"]
54
+ when nil, "", /Partial Match Per Response/i
55
+ "partialMatchV2"
56
+ when /Partial Match/i
57
+ "partialMatch"
58
+ when /Exact Match/i
59
+ "exactMatch"
60
+ else
61
+ raise NotImplementedError, "Unknown scoring type"
62
+ end
63
+ end
64
+
65
+ def points
66
+ if @row["points"].blank?
67
+ 1
68
+ else
69
+ Float(@row["points"])
70
+ end
71
+ rescue ArgumentError
72
+ 1
73
+ end
74
+
75
+ def to_learnosity
76
+ {
77
+ type: question_type,
78
+ widget_type: "response",
79
+ reference: @reference,
80
+ data: question_data,
81
+ }
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "byebug"
4
+ require "csv"
5
+ require "active_support/core_ext/digest/uuid"
6
+
7
+ require_relative "questions/question"
8
+ require_relative "questions/multiple_choice"
9
+
10
+ module AtomicAssessmentsImport
11
+ module CSV
12
+ module Utils
13
+ def self.parse_boolean(value, default:)
14
+ case value&.downcase
15
+ when "true", "yes", "y", "1"
16
+ true
17
+ when "false", "no", "n", "0"
18
+ false
19
+ else
20
+ default
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "csv/converter"
4
+
5
+ module AtomicAssessmentsImport
6
+ module CSV
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtomicAssessmentsImport
4
+ module Export
5
+ def self.create(path, data)
6
+ AtomicAssessmentsImport::Writer.new(path).open do |writer|
7
+ writer.write("export.json", { version: 2.0 }.to_json)
8
+
9
+ data[:activities].each do |activity|
10
+ writer.write("activities/#{activity[:reference]}.json", activity.to_json)
11
+ end
12
+
13
+ data[:questions].each do |question|
14
+ writer.write("questions/#{question[:reference]}.json", question.to_json)
15
+ end
16
+
17
+ data[:items].each do |item|
18
+ writer.write("items/#{item[:reference]}.json", item.to_json)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtomicAssessmentsImport
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zip"
4
+
5
+ module AtomicAssessmentsImport
6
+ class Writer
7
+ def initialize(path)
8
+ @path = path
9
+ end
10
+
11
+ def open
12
+ @zip = Zip::File.open(@path, Zip::File::CREATE)
13
+ yield self
14
+ ensure
15
+ @zip.close
16
+ end
17
+
18
+ def write(filename, content)
19
+ raise "Zip file is not open" unless @zip
20
+
21
+ @zip.get_output_stream(filename) { |file| file.write(content) }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/all"
4
+ require "mimemagic"
5
+ require_relative "atomic_assessments_import/version"
6
+ require_relative "atomic_assessments_import/csv"
7
+ require_relative "atomic_assessments_import/writer"
8
+ require_relative "atomic_assessments_import/export"
9
+
10
+ module AtomicAssessmentsImport
11
+ class Error < StandardError; end
12
+
13
+ def self.convert(path)
14
+ type = MimeMagic.by_path(path)&.type
15
+
16
+ converter =
17
+ case type
18
+ when "text/csv"
19
+ CSV::Converter.new(path)
20
+ else
21
+ raise ArgumentError, "Unsupported file type"
22
+ end
23
+
24
+ converter.convert
25
+ end
26
+
27
+ def self.convert_to_aa_format(input_path, output_path)
28
+ result = convert(input_path)
29
+ AtomicAssessmentsImport::Export.create(output_path, result)
30
+ {
31
+ errors: result[:errors],
32
+ }
33
+ end
34
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: atomic_assessments_import
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sean Collings
8
+ - Matt Petro
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2025-02-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: csv
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: mimemagic
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rubyzip
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ description: Importer to Convert different formats to AA's import format
71
+ email:
72
+ - support@atomicjolt.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".rspec"
78
+ - ".rubocop.yml"
79
+ - CHANGELOG.md
80
+ - LICENSE
81
+ - README.md
82
+ - Rakefile
83
+ - lib/atomic_assessments_import.rb
84
+ - lib/atomic_assessments_import/csv.rb
85
+ - lib/atomic_assessments_import/csv/converter.rb
86
+ - lib/atomic_assessments_import/csv/questions/multiple_choice.rb
87
+ - lib/atomic_assessments_import/csv/questions/question.rb
88
+ - lib/atomic_assessments_import/csv/utils.rb
89
+ - lib/atomic_assessments_import/export.rb
90
+ - lib/atomic_assessments_import/version.rb
91
+ - lib/atomic_assessments_import/writer.rb
92
+ homepage: https://github.com/atomicjolt/atomic_assessments_import
93
+ licenses: []
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 3.3.0
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubygems_version: 3.5.11
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: Importer to Convert different formats to AA's import format
114
+ test_files: []