senkyoshi 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +92 -0
  4. data/bin/console +14 -0
  5. data/bin/import_blackboard +6 -0
  6. data/lib/senkyoshi/canvas_course.rb +179 -0
  7. data/lib/senkyoshi/collection.rb +26 -0
  8. data/lib/senkyoshi/config.rb +35 -0
  9. data/lib/senkyoshi/exceptions.rb +13 -0
  10. data/lib/senkyoshi/models/announcement.rb +36 -0
  11. data/lib/senkyoshi/models/answer.rb +32 -0
  12. data/lib/senkyoshi/models/assessment.rb +95 -0
  13. data/lib/senkyoshi/models/assignment.rb +26 -0
  14. data/lib/senkyoshi/models/assignment_group.rb +23 -0
  15. data/lib/senkyoshi/models/blog.rb +22 -0
  16. data/lib/senkyoshi/models/content.rb +92 -0
  17. data/lib/senkyoshi/models/content_file.rb +26 -0
  18. data/lib/senkyoshi/models/course.rb +39 -0
  19. data/lib/senkyoshi/models/file.rb +99 -0
  20. data/lib/senkyoshi/models/forum.rb +28 -0
  21. data/lib/senkyoshi/models/gradebook.rb +30 -0
  22. data/lib/senkyoshi/models/group.rb +22 -0
  23. data/lib/senkyoshi/models/module.rb +22 -0
  24. data/lib/senkyoshi/models/module_item.rb +23 -0
  25. data/lib/senkyoshi/models/question.rb +188 -0
  26. data/lib/senkyoshi/models/questions/calculated.rb +72 -0
  27. data/lib/senkyoshi/models/questions/either_or.rb +31 -0
  28. data/lib/senkyoshi/models/questions/essay.rb +11 -0
  29. data/lib/senkyoshi/models/questions/file_upload.rb +4 -0
  30. data/lib/senkyoshi/models/questions/fill_in_blank.rb +15 -0
  31. data/lib/senkyoshi/models/questions/fill_in_blank_plus.rb +20 -0
  32. data/lib/senkyoshi/models/questions/hot_spot.rb +11 -0
  33. data/lib/senkyoshi/models/questions/jumbled_sentence.rb +37 -0
  34. data/lib/senkyoshi/models/questions/matching.rb +57 -0
  35. data/lib/senkyoshi/models/questions/multiple_answer.rb +56 -0
  36. data/lib/senkyoshi/models/questions/multiple_choice.rb +18 -0
  37. data/lib/senkyoshi/models/questions/numerical.rb +31 -0
  38. data/lib/senkyoshi/models/questions/opinion_scale.rb +18 -0
  39. data/lib/senkyoshi/models/questions/ordering.rb +40 -0
  40. data/lib/senkyoshi/models/questions/quiz_bowl.rb +11 -0
  41. data/lib/senkyoshi/models/questions/short_response.rb +4 -0
  42. data/lib/senkyoshi/models/questions/true_false.rb +15 -0
  43. data/lib/senkyoshi/models/quiz.rb +7 -0
  44. data/lib/senkyoshi/models/resource.rb +33 -0
  45. data/lib/senkyoshi/models/scorm_package.rb +109 -0
  46. data/lib/senkyoshi/models/staff_info.rb +72 -0
  47. data/lib/senkyoshi/models/wikipage.rb +34 -0
  48. data/lib/senkyoshi/tasks.rb +90 -0
  49. data/lib/senkyoshi/version.rb +3 -0
  50. data/lib/senkyoshi/xml_parser.rb +154 -0
  51. data/lib/senkyoshi.rb +69 -0
  52. metadata +250 -0
@@ -0,0 +1,90 @@
1
+ require "rake"
2
+ require "rake/clean"
3
+ require "senkyoshi"
4
+ require "byebug"
5
+
6
+ ## CHANGED THESE TO CHANGE THE FOLDER LOCATIONS
7
+ SOURCE_DIR = "sources".freeze
8
+ OUTPUT_DIR = "canvas".freeze
9
+ UPLOAD_DIR = "uploaded".freeze
10
+
11
+ ## Don't change these, these are just getting the last
12
+ ## of the folder name for the script below to use
13
+ SOURCE_NAME = SOURCE_DIR.split("/").last
14
+ OUTPUT_NAME = OUTPUT_DIR.split("/").last
15
+ UPLOAD_NAME = UPLOAD_DIR.split("/").last
16
+ SOURCE_FILES = Rake::FileList.new("#{SOURCE_DIR}/*.zip")
17
+ CONVERTED_FILES = Rake::FileList.new("#{OUTPUT_DIR}/*.imscc")
18
+
19
+ def source_for_imscc(imscc_file)
20
+ SOURCE_FILES.detect do |f|
21
+ path = imscc_file.pathmap("%{^#{OUTPUT_DIR}/,#{SOURCE_DIR}/}X")
22
+ f.ext("") == path
23
+ end
24
+ end
25
+
26
+ def source_for_upload_log(upload_log)
27
+ CONVERTED_FILES.detect do |f|
28
+ path = upload_log.pathmap("%{^#{UPLOAD_DIR}/,#{OUTPUT_DIR}/}X")
29
+ f.ext("") == path
30
+ end
31
+ end
32
+
33
+ def make_directories(name, upload_dir)
34
+ mkdir_p name.pathmap("%d")
35
+ mkdir_p upload_dir
36
+ end
37
+
38
+ def log_file(name)
39
+ sh "touch #{name}"
40
+ sh "date >> #{name}"
41
+ end
42
+
43
+ module Senkyoshi
44
+ class Tasks
45
+ extend Rake::DSL if defined? Rake::DSL
46
+
47
+ ##
48
+ # Creates rake tasks that can be ran from the gem.
49
+ #
50
+ # Add this to your Rakefile
51
+ #
52
+ # require "senkyoshi/tasks"
53
+ # Senkyoshi::Tasks.install_tasks
54
+ #
55
+ ##
56
+ def self.install_tasks
57
+ namespace :senkyoshi do
58
+ desc "Convert blackboard cartridges to canvas cartridges"
59
+ task imscc: SOURCE_FILES.pathmap(
60
+ "%{^#{SOURCE_NAME}/,#{OUTPUT_DIR}/}X.imscc",
61
+ )
62
+
63
+ directory OUTPUT_NAME
64
+
65
+ rule ".imscc" => [->(f) { source_for_imscc(f) }, OUTPUT_NAME] do |t|
66
+ make_directories(t.name, OUTPUT_DIR)
67
+ Senkyoshi.parse(t.source, t.name)
68
+ end
69
+
70
+ desc "Upload converted files to canvas"
71
+ task upload: CONVERTED_FILES.pathmap(
72
+ "%{^#{OUTPUT_NAME}/,#{UPLOAD_DIR}/}X.txt",
73
+ )
74
+
75
+ directory UPLOAD_NAME
76
+
77
+ rule ".txt" => [->(f) { source_for_upload_log(f) }, UPLOAD_NAME] do |t|
78
+ make_directories(t.name, UPLOAD_DIR)
79
+ Senkyoshi.initialize_course(t.source, source_for_imscc(t.source))
80
+ log_file(t.name)
81
+ end
82
+
83
+ desc "Completely delete all converted files"
84
+ task :clean do
85
+ rm_rf OUTPUT_DIR
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,3 @@
1
+ module Senkyoshi
2
+ VERSION = "1.0.0".freeze
3
+ end
@@ -0,0 +1,154 @@
1
+ require "require_all"
2
+ require_all "lib/senkyoshi/models/**/*.rb"
3
+ require_relative "exceptions"
4
+
5
+ module Senkyoshi
6
+ RESOURCE_TYPE = {
7
+ groups: "Group",
8
+ blog: "Blog",
9
+ announcement: "Announcement",
10
+ forum: "Forum",
11
+ course: "Course",
12
+ questestinterop: "Assessment",
13
+ content: "Content",
14
+ staffinfo: "StaffInfo",
15
+ }.freeze
16
+
17
+ PRE_RESOURCE_TYPE = {
18
+ content: "Content",
19
+ gradebook: "Gradebook",
20
+ }.freeze
21
+
22
+ def self.parse_manifest(zip_file, manifest)
23
+ doc = Nokogiri::XML.parse(manifest)
24
+ resources = doc.at("resources")
25
+ iterate_xml(resources, zip_file).flatten - ["", nil]
26
+ end
27
+
28
+ def self.iterate_xml(resources, zip_file)
29
+ pre_data = pre_iterator(resources, zip_file)
30
+ iterator_master(resources, zip_file) do |xml_data, type, file|
31
+ if RESOURCE_TYPE[type.to_sym]
32
+ single_pre_data = get_single_pre_data(pre_data, file)
33
+ res_class = Senkyoshi.const_get RESOURCE_TYPE[type.to_sym]
34
+ if type == "content"
35
+ Content.from(xml_data, single_pre_data)
36
+ else
37
+ resource = res_class.new
38
+ resource.iterate_xml(xml_data, single_pre_data)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def self.get_single_pre_data(pre_data, file)
45
+ pre_data.detect do |d|
46
+ d[:file_name] == file || d[:assignment_id] == file
47
+ end
48
+ end
49
+
50
+ def self.iterator_master(resources, zip_file)
51
+ resources.children.map do |resource|
52
+ file_name = resource.attributes["file"].value
53
+ file = File.basename(file_name, ".dat")
54
+ if zip_file.find_entry(file_name)
55
+ data_file = Senkyoshi.read_file(zip_file, file_name)
56
+ xml_data = Nokogiri::XML.parse(data_file).children.first
57
+ type = xml_data.name.downcase
58
+ yield xml_data, type, file
59
+ end
60
+ end
61
+ end
62
+
63
+ def self.pre_iterator(resources, zip_file)
64
+ pre_data = {}
65
+ iterator_master(resources, zip_file) do |xml_data, type, file|
66
+ if PRE_RESOURCE_TYPE[type.to_sym]
67
+ res_class = Senkyoshi.const_get PRE_RESOURCE_TYPE[type.to_sym]
68
+ resource_class = res_class.new
69
+ pre_data[type] ||= []
70
+ pre_data[type].push(resource_class.get_pre_data(xml_data, file))
71
+ end
72
+ end
73
+ pre_data = connect_content(pre_data)
74
+ build_heirarchy(pre_data)
75
+ end
76
+
77
+ def self.connect_content(pre_data)
78
+ pre_data["content"].each do |content|
79
+ gradebook = pre_data["gradebook"].first.
80
+ detect { |g| g[:content_id] == content[:file_name] }
81
+ if gradebook
82
+ content[:points] = gradebook[:points] || ""
83
+ content[:assignment_id] = gradebook[:assignment_id] || ""
84
+ end
85
+ end
86
+ pre_data["content"]
87
+ end
88
+
89
+ def self.build_heirarchy(pre_data)
90
+ parents_ids = pre_data.
91
+ select { |p| p[:parent_id] == "{unset id}" }.
92
+ map { |u| u[:id] }
93
+ pre_data.each do |content|
94
+ next if parents_ids.include?(content[:id])
95
+ next if parents_ids.include?(content[:parent_id])
96
+ parent_id = get_master_parent(pre_data, parents_ids,
97
+ content[:parent_id])
98
+ content[:parent_id] = parent_id
99
+ end
100
+ end
101
+
102
+ def self.get_master_parent(pre_data, parents_ids, parent_id)
103
+ parent = pre_data.detect { |p| p[:id] == parent_id }
104
+ if parents_ids.include? parent[:id]
105
+ parent[:id]
106
+ else
107
+ get_master_parent(pre_data, parents_ids, parent[:parent_id])
108
+ end
109
+ end
110
+
111
+ ##
112
+ # Iterate through course files and create new SenkyoshiFile for each
113
+ # non-metadata file.
114
+ ##
115
+ def self.iterate_files(zipfile)
116
+ files = zipfile.entries.select(&:file?)
117
+
118
+ file_names = files.map(&:name)
119
+ scorm_paths = ScormPackage.find_scorm_paths(zipfile)
120
+
121
+ files.select do |file|
122
+ SenkyoshiFile.valid_file?(file_names, scorm_paths, file)
123
+ end.
124
+ map { |file| SenkyoshiFile.new(file) }
125
+ end
126
+
127
+ def self.create_random_hex
128
+ SecureRandom.hex
129
+ end
130
+
131
+ def self.get_attribute_value(xml_data, type)
132
+ value = ""
133
+ if xml_data.children.at(type).attributes["value"]
134
+ value = xml_data.children.at(type).attributes["value"].value
135
+ end
136
+ value
137
+ end
138
+
139
+ def self.get_text(xml_data, type)
140
+ value = ""
141
+ if xml_data.children.at(type)
142
+ value = xml_data.children.at(type).text
143
+ end
144
+ value
145
+ end
146
+
147
+ def self.get_description(xml_data)
148
+ value = ""
149
+ if xml_data.children.at("DESCRIPTION")
150
+ value = xml_data.children.at("DESCRIPTION").text
151
+ end
152
+ value
153
+ end
154
+ end
data/lib/senkyoshi.rb ADDED
@@ -0,0 +1,69 @@
1
+ require "senkyoshi/version"
2
+ require "senkyoshi/xml_parser"
3
+ require "senkyoshi/canvas_course"
4
+ require "senkyoshi/collection"
5
+
6
+ require "canvas_cc"
7
+ require "optparse"
8
+ require "ostruct"
9
+ require "nokogiri"
10
+ require "zip"
11
+
12
+ require_relative "./senkyoshi/exceptions"
13
+
14
+ module Senkyoshi
15
+ IMPORTED_FILES_DIRNAME = "Imported".freeze
16
+ BASE = "$IMS-CC-FILEBASE$".freeze
17
+
18
+ def self.parse(zip_path, imscc_path)
19
+ Zip::File.open(zip_path) do |file|
20
+ manifest = read_file(file, "imsmanifest.xml")
21
+
22
+ resources = Senkyoshi::Collection.new
23
+ resources.add(Senkyoshi.parse_manifest(file, manifest))
24
+ resources.add(Senkyoshi.iterate_files(file))
25
+
26
+ course = create_canvas_course(resources, zip_path)
27
+ build_file(course, imscc_path)
28
+ end
29
+ end
30
+
31
+ def self.read_file(zip_file, file_name)
32
+ zip_file.find_entry(file_name).get_input_stream.read
33
+ rescue NoMethodError
34
+ raise Exceptions::MissingFileError
35
+ end
36
+
37
+ def self.build_file(course, imscc_path)
38
+ folder = imscc_path.split("/").first
39
+ file = CanvasCc::CanvasCC::CartridgeCreator.new(course).create(folder)
40
+ File.rename(file, imscc_path)
41
+ cleanup
42
+ puts "Created a file #{imscc_path}"
43
+ end
44
+
45
+ ##
46
+ # Perform any necessary cleanup from creating canvas cartridge
47
+ ##
48
+ def self.cleanup
49
+ SenkyoshiFile.cleanup
50
+ ScormPackage.cleanup
51
+ end
52
+
53
+ def self.create_canvas_course(resources, zip_name)
54
+ course = CanvasCc::CanvasCC::Models::Course.new
55
+ course.course_code = zip_name
56
+ resources.each do |resource|
57
+ course = resource.canvas_conversion(course, resources)
58
+ end
59
+ course
60
+ end
61
+
62
+ def self.initialize_course(canvas_file_path, blackboard_file_path)
63
+ metadata = Senkyoshi::CanvasCourse.metadata_from_file(canvas_file_path)
64
+ Zip::File.open(blackboard_file_path, "rb") do |bb_zip|
65
+ course = Senkyoshi::CanvasCourse.from_metadata(metadata, bb_zip)
66
+ course.upload_content(canvas_file_path)
67
+ end
68
+ end
69
+ end
metadata ADDED
@@ -0,0 +1,250 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: senkyoshi
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Atomic Jolt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-12-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pry-byebug
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.4'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: webmock
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '11.3'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '11.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubyzip
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.1'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: nokogiri
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.6.6
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.6.6
97
+ - !ruby/object:Gem::Dependency
98
+ name: fileutils
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.7'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.7'
111
+ - !ruby/object:Gem::Dependency
112
+ name: require_all
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 1.3.3
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 1.3.3
125
+ - !ruby/object:Gem::Dependency
126
+ name: pandarus
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.6'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.6'
139
+ - !ruby/object:Gem::Dependency
140
+ name: activesupport
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '4.2'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '4.2'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rest-client
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '2.0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '2.0'
167
+ description: Commandline tool for converting blackboard to canvas
168
+ email:
169
+ executables:
170
+ - console
171
+ - import_blackboard
172
+ extensions: []
173
+ extra_rdoc_files:
174
+ - README.md
175
+ files:
176
+ - LICENSE.txt
177
+ - README.md
178
+ - bin/console
179
+ - bin/import_blackboard
180
+ - lib/senkyoshi.rb
181
+ - lib/senkyoshi/canvas_course.rb
182
+ - lib/senkyoshi/collection.rb
183
+ - lib/senkyoshi/config.rb
184
+ - lib/senkyoshi/exceptions.rb
185
+ - lib/senkyoshi/models/announcement.rb
186
+ - lib/senkyoshi/models/answer.rb
187
+ - lib/senkyoshi/models/assessment.rb
188
+ - lib/senkyoshi/models/assignment.rb
189
+ - lib/senkyoshi/models/assignment_group.rb
190
+ - lib/senkyoshi/models/blog.rb
191
+ - lib/senkyoshi/models/content.rb
192
+ - lib/senkyoshi/models/content_file.rb
193
+ - lib/senkyoshi/models/course.rb
194
+ - lib/senkyoshi/models/file.rb
195
+ - lib/senkyoshi/models/forum.rb
196
+ - lib/senkyoshi/models/gradebook.rb
197
+ - lib/senkyoshi/models/group.rb
198
+ - lib/senkyoshi/models/module.rb
199
+ - lib/senkyoshi/models/module_item.rb
200
+ - lib/senkyoshi/models/question.rb
201
+ - lib/senkyoshi/models/questions/calculated.rb
202
+ - lib/senkyoshi/models/questions/either_or.rb
203
+ - lib/senkyoshi/models/questions/essay.rb
204
+ - lib/senkyoshi/models/questions/file_upload.rb
205
+ - lib/senkyoshi/models/questions/fill_in_blank.rb
206
+ - lib/senkyoshi/models/questions/fill_in_blank_plus.rb
207
+ - lib/senkyoshi/models/questions/hot_spot.rb
208
+ - lib/senkyoshi/models/questions/jumbled_sentence.rb
209
+ - lib/senkyoshi/models/questions/matching.rb
210
+ - lib/senkyoshi/models/questions/multiple_answer.rb
211
+ - lib/senkyoshi/models/questions/multiple_choice.rb
212
+ - lib/senkyoshi/models/questions/numerical.rb
213
+ - lib/senkyoshi/models/questions/opinion_scale.rb
214
+ - lib/senkyoshi/models/questions/ordering.rb
215
+ - lib/senkyoshi/models/questions/quiz_bowl.rb
216
+ - lib/senkyoshi/models/questions/short_response.rb
217
+ - lib/senkyoshi/models/questions/true_false.rb
218
+ - lib/senkyoshi/models/quiz.rb
219
+ - lib/senkyoshi/models/resource.rb
220
+ - lib/senkyoshi/models/scorm_package.rb
221
+ - lib/senkyoshi/models/staff_info.rb
222
+ - lib/senkyoshi/models/wikipage.rb
223
+ - lib/senkyoshi/tasks.rb
224
+ - lib/senkyoshi/version.rb
225
+ - lib/senkyoshi/xml_parser.rb
226
+ homepage: https://github.com/atomicjolt/senkyoshi
227
+ licenses:
228
+ - MIT
229
+ metadata: {}
230
+ post_install_message:
231
+ rdoc_options: []
232
+ require_paths:
233
+ - lib
234
+ required_ruby_version: !ruby/object:Gem::Requirement
235
+ requirements:
236
+ - - ">="
237
+ - !ruby/object:Gem::Version
238
+ version: '2.0'
239
+ required_rubygems_version: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ requirements: []
245
+ rubyforge_project:
246
+ rubygems_version: 2.5.1
247
+ signing_key:
248
+ specification_version: 4
249
+ summary: Converts Blackboard zip file to Canvas Common Cartridge
250
+ test_files: []