senkyoshi 1.0.2 → 1.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 +4 -4
- data/README.md +11 -12
- data/lib/senkyoshi/canvas_course.rb +62 -19
- data/lib/senkyoshi/configuration.rb +29 -0
- data/lib/senkyoshi/models/announcement.rb +7 -7
- data/lib/senkyoshi/models/assignment.rb +3 -1
- data/lib/senkyoshi/models/assignment_group.rb +24 -5
- data/lib/senkyoshi/models/blog.rb +4 -3
- data/lib/senkyoshi/models/content.rb +9 -7
- data/lib/senkyoshi/models/content_file.rb +8 -3
- data/lib/senkyoshi/models/course.rb +5 -6
- data/lib/senkyoshi/models/file.rb +0 -4
- data/lib/senkyoshi/models/file_resource.rb +23 -0
- data/lib/senkyoshi/models/forum.rb +5 -5
- data/lib/senkyoshi/models/gradebook.rb +44 -3
- data/lib/senkyoshi/models/group.rb +4 -3
- data/lib/senkyoshi/models/outcome_definition.rb +52 -0
- data/lib/senkyoshi/models/qti.rb +11 -22
- data/lib/senkyoshi/models/question_bank.rb +13 -6
- data/lib/senkyoshi/models/questions/matching.rb +25 -2
- data/lib/senkyoshi/models/resource.rb +41 -5
- data/lib/senkyoshi/models/staff_info.rb +5 -8
- data/lib/senkyoshi/models/wikipage.rb +3 -3
- data/lib/senkyoshi/tasks.rb +11 -0
- data/lib/senkyoshi/version.rb +1 -1
- data/lib/senkyoshi/xml_parser.rb +15 -17
- data/lib/senkyoshi.rb +24 -2
- metadata +5 -3
- data/lib/senkyoshi/config.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7187188584379d9e573651fe9147e7bb50497ca
|
4
|
+
data.tar.gz: e595d29609ad508dfc443cf1543b0eecdd382ea5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c16a2de94714d3b58e2acaa22e17eb58fae79c1e17a13d8c987660ae60ffc3bf8e17d733b7ae1bc116e9d8e672a37d910c0efcce2952caad8d00f9032f94766f
|
7
|
+
data.tar.gz: 4d333e8b3a2e0b6000a41d3b49d28a2a2fe17f3add5a1b0248ecda417fa333df4a0aaae9e0c4d980d9bbf599c971ac449dd41adcb840b4726cdbbcc97c715343
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Senkyoshi Converter [](https://travis-ci.org/atomicjolt/senkyoshi)
|
2
2
|
|
3
|
-
|
3
|
+
Senkyoshi converts exported Blackboard packages into Canvas .imscc packages. It also allows you to upload those converted packages to a Canvas instance.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -21,13 +21,13 @@ Or install it yourself as:
|
|
21
21
|
$ gem install senkyoshi
|
22
22
|
```
|
23
23
|
|
24
|
-
Create a `Rakefile` and add
|
24
|
+
Create a `Rakefile` and add:
|
25
25
|
```ruby
|
26
26
|
require "senkyoshi/tasks"
|
27
27
|
Senkyoshi::Tasks.install_tasks
|
28
28
|
```
|
29
29
|
|
30
|
-
Create a `senkyoshi.yml` and add credentials
|
30
|
+
Create a `senkyoshi.yml` and add credentials:
|
31
31
|
```yaml
|
32
32
|
# Generally looks like https://< mycanvas_instance >/api
|
33
33
|
:canvas_url: <canvas instance api url>
|
@@ -55,23 +55,23 @@ Create a `senkyoshi.yml` and add credentials
|
|
55
55
|
|
56
56
|
## Usage
|
57
57
|
|
58
|
-
Run the rake task to convert from .zip to .imscc
|
58
|
+
Run the rake task to convert from .zip to .imscc:
|
59
59
|
```sh
|
60
60
|
rake senkyoshi:imscc
|
61
61
|
```
|
62
|
-
This will take all your files in your source folder and convert them to your outputs folder
|
62
|
+
This will take all your files in your source folder and convert them to your outputs folder.
|
63
63
|
|
64
|
-
Run converting files in parallel
|
64
|
+
Run converting files in parallel:
|
65
65
|
```sh
|
66
66
|
time rake senkyoshi:imscc -m
|
67
67
|
```
|
68
68
|
|
69
|
-
Delete entire outputs folder
|
69
|
+
Delete entire outputs folder:
|
70
70
|
```sh
|
71
71
|
rake clean
|
72
72
|
```
|
73
73
|
|
74
|
-
Upload to
|
74
|
+
Upload to Canvas to process:
|
75
75
|
```sh
|
76
76
|
rake senkyoshi:upload
|
77
77
|
```
|
@@ -84,7 +84,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
84
84
|
|
85
85
|
## Contributing
|
86
86
|
|
87
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
87
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/atomicjolt/senkyoshi. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
88
88
|
|
89
89
|
|
90
90
|
## License
|
@@ -92,6 +92,5 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERN
|
|
92
92
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
93
93
|
|
94
94
|
|
95
|
-
### Things
|
96
|
-
People
|
97
|
-
Blogs
|
95
|
+
### Things Not Quite Implemented
|
96
|
+
People, Groups, Sets, Blogs
|
@@ -1,11 +1,7 @@
|
|
1
1
|
require "pandarus"
|
2
|
-
require "senkyoshi/config"
|
3
2
|
require "senkyoshi/models/scorm_package"
|
4
3
|
require "rest-client"
|
5
4
|
|
6
|
-
require "openssl"
|
7
|
-
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
|
8
|
-
|
9
5
|
module Senkyoshi
|
10
6
|
##
|
11
7
|
# This class represents a canvas course for which we are uploading data to
|
@@ -43,8 +39,8 @@ module Senkyoshi
|
|
43
39
|
##
|
44
40
|
def self.client
|
45
41
|
@client ||= Pandarus::Client.new(
|
46
|
-
prefix: Senkyoshi.canvas_url,
|
47
|
-
token: Senkyoshi.canvas_token,
|
42
|
+
prefix: Senkyoshi.configuration.canvas_url,
|
43
|
+
token: Senkyoshi.configuration.canvas_token,
|
48
44
|
)
|
49
45
|
end
|
50
46
|
|
@@ -54,7 +50,7 @@ module Senkyoshi
|
|
54
50
|
def self.from_metadata(metadata, blackboard_export = nil)
|
55
51
|
course_name = metadata[:name] || metadata[:title]
|
56
52
|
canvas_course = client.create_new_course(
|
57
|
-
Senkyoshi.account_id,
|
53
|
+
Senkyoshi.configuration.account_id,
|
58
54
|
course: {
|
59
55
|
name: course_name,
|
60
56
|
},
|
@@ -66,9 +62,49 @@ module Senkyoshi
|
|
66
62
|
# Creates a canvas assignment from a scorm package that has already been
|
67
63
|
# uploaded to a scorm manager
|
68
64
|
##
|
69
|
-
def create_scorm_assignment(scorm_package, course_id)
|
70
|
-
|
71
|
-
|
65
|
+
def create_scorm_assignment(scorm_package, course_id, local)
|
66
|
+
if local
|
67
|
+
_create_scorm_assignment_local(scorm_package)
|
68
|
+
else
|
69
|
+
_create_scorm_assignment_external(scorm_package, course_id)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Assembles the launch url with the course_id
|
75
|
+
##
|
76
|
+
def _scorm_launch_url(package_id)
|
77
|
+
"#{Senkyoshi.configuration.scorm_launch_url}?course_id=#{package_id}"
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Creates a scorm assignment from a Canvas course object
|
82
|
+
##
|
83
|
+
def _create_scorm_assignment_local(scorm_package)
|
84
|
+
url = _scorm_launch_url(scorm_package["package_id"])
|
85
|
+
|
86
|
+
payload = {
|
87
|
+
title: scorm_package["title"],
|
88
|
+
submission_types: "external_tool",
|
89
|
+
integration_id: scorm_package["package_id"],
|
90
|
+
integration_data: {
|
91
|
+
provider: "atomic-scorm",
|
92
|
+
},
|
93
|
+
external_tool_tag_attributes: {
|
94
|
+
url: url,
|
95
|
+
},
|
96
|
+
points_possible: scorm_package["points_possible"],
|
97
|
+
}
|
98
|
+
|
99
|
+
# @course_resource in this case is a Canvas course object
|
100
|
+
@course_resource.assignments.create(payload)
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Creates a scorm assignment using the Canvas api
|
105
|
+
##
|
106
|
+
def _create_scorm_assignment_external(scorm_package, course_id)
|
107
|
+
url = _scorm_launch_url(scorm_package["package_id"])
|
72
108
|
|
73
109
|
payload = {
|
74
110
|
assignment__submission_types__: ["external_tool"],
|
@@ -97,13 +133,13 @@ module Senkyoshi
|
|
97
133
|
zip = scorm_package.write_zip tmp_name
|
98
134
|
File.open(zip, "rb") do |file|
|
99
135
|
RestClient.post(
|
100
|
-
"#{Senkyoshi.scorm_url}/api/scorm_courses",
|
136
|
+
"#{Senkyoshi.configuration.scorm_url}/api/scorm_courses",
|
101
137
|
{
|
102
138
|
oauth_consumer_key: "scorm-player",
|
103
139
|
lms_course_id: course_id,
|
104
140
|
file: file,
|
105
141
|
},
|
106
|
-
SharedAuthorization: Senkyoshi.scorm_shared_auth,
|
142
|
+
SharedAuthorization: Senkyoshi.configuration.scorm_shared_auth,
|
107
143
|
) do |resp|
|
108
144
|
result = JSON.parse(resp.body)["response"]
|
109
145
|
result["points_possible"] = scorm_package.points_possible
|
@@ -115,8 +151,10 @@ module Senkyoshi
|
|
115
151
|
##
|
116
152
|
# Creates assignments from all previously uploaded scorm packages
|
117
153
|
##
|
118
|
-
def create_scorm_assignments(scorm_packages, course_id)
|
119
|
-
scorm_packages.each
|
154
|
+
def create_scorm_assignments(scorm_packages, course_id, local)
|
155
|
+
scorm_packages.each do |pack|
|
156
|
+
create_scorm_assignment(pack, course_id, local)
|
157
|
+
end
|
120
158
|
end
|
121
159
|
|
122
160
|
##
|
@@ -132,6 +170,14 @@ module Senkyoshi
|
|
132
170
|
end
|
133
171
|
end
|
134
172
|
|
173
|
+
def process_scorm(local: false)
|
174
|
+
create_scorm_assignments(
|
175
|
+
upload_scorm_packages(@scorm_packages),
|
176
|
+
@course_resource.id,
|
177
|
+
local,
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
135
181
|
##
|
136
182
|
# Create a migration for the course
|
137
183
|
# and upload the imscc file to be imported into the course
|
@@ -152,10 +198,7 @@ module Senkyoshi
|
|
152
198
|
puts "Done uploading: #{name}"
|
153
199
|
|
154
200
|
puts "Creating Scorm: #{name}"
|
155
|
-
|
156
|
-
upload_scorm_packages(@scorm_packages),
|
157
|
-
@course_resource.id,
|
158
|
-
)
|
201
|
+
process_scorm
|
159
202
|
puts "Done creating scorm: #{name}"
|
160
203
|
end
|
161
204
|
|
@@ -176,7 +219,7 @@ module Senkyoshi
|
|
176
219
|
RestClient.post(
|
177
220
|
response.headers[:location],
|
178
221
|
nil,
|
179
|
-
Authorization: "Bearer #{Senkyoshi.canvas_token}",
|
222
|
+
Authorization: "Bearer #{Senkyoshi.configuration.canvas_token}",
|
180
223
|
)
|
181
224
|
end
|
182
225
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module Senkyoshi
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :canvas_url
|
6
|
+
attr_accessor :canvas_token
|
7
|
+
attr_accessor :account_id
|
8
|
+
attr_accessor :scorm_url
|
9
|
+
attr_accessor :scorm_launch_url
|
10
|
+
attr_accessor :scorm_shared_auth
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@canvas_url = Configuration._config[:canvas_url]
|
14
|
+
@canvas_token = Configuration._config[:canvas_token]
|
15
|
+
@account_id = Configuration._config[:account_id] || :self
|
16
|
+
@scorm_url = Configuration._config[:scorm_url]
|
17
|
+
@scorm_launch_url = Configuration._config[:scorm_launch_url]
|
18
|
+
@scorm_shared_auth = Configuration._config[:scorm_shared_auth]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self._config
|
22
|
+
@config ||= if File.exists? "senkyoshi.yml"
|
23
|
+
YAML::load(File.read("senkyoshi.yml"))
|
24
|
+
else
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
|
-
require "senkyoshi/models/
|
1
|
+
require "senkyoshi/models/file_resource"
|
2
2
|
|
3
3
|
module Senkyoshi
|
4
|
-
class Announcement <
|
5
|
-
def initialize
|
4
|
+
class Announcement < FileResource
|
5
|
+
def initialize(resource_id)
|
6
|
+
super(resource_id)
|
6
7
|
@title = ""
|
7
8
|
@text = ""
|
8
9
|
@delayed_post = ""
|
9
10
|
@posted_at = ""
|
10
|
-
@identifier = Senkyoshi.create_random_hex
|
11
11
|
@dependency = Senkyoshi.create_random_hex
|
12
12
|
@type = "announcement"
|
13
13
|
end
|
@@ -21,13 +21,13 @@ module Senkyoshi
|
|
21
21
|
self
|
22
22
|
end
|
23
23
|
|
24
|
-
def canvas_conversion(course,
|
24
|
+
def canvas_conversion(course, resources)
|
25
25
|
announcement = CanvasCc::CanvasCC::Models::Announcement.new
|
26
26
|
announcement.title = @title
|
27
|
-
announcement.text = @text
|
27
|
+
announcement.text = fix_html(@text, resources)
|
28
28
|
announcement.delayed_post = @delayed_post
|
29
29
|
announcement.posted_at = @posted_at
|
30
|
-
announcement.identifier = @
|
30
|
+
announcement.identifier = @id
|
31
31
|
announcement.dependency = @dependency
|
32
32
|
course.announcements << announcement
|
33
33
|
course
|
@@ -16,7 +16,9 @@ module Senkyoshi
|
|
16
16
|
assignment.submission_types << "online_upload"
|
17
17
|
assignment.grading_type = "points"
|
18
18
|
|
19
|
-
@files.each
|
19
|
+
@files.each do |file|
|
20
|
+
assignment.body << file.canvas_conversion(resources)
|
21
|
+
end
|
20
22
|
course = create_module(course)
|
21
23
|
course.assignments << assignment
|
22
24
|
end
|
@@ -3,21 +3,40 @@ require "senkyoshi/models/resource"
|
|
3
3
|
module Senkyoshi
|
4
4
|
class AssignmentGroup < Resource
|
5
5
|
attr_reader :id
|
6
|
-
def initialize(name,
|
6
|
+
def initialize(name, id)
|
7
7
|
@title = name
|
8
8
|
@group_weight = ""
|
9
9
|
@rules = {}
|
10
|
-
@id =
|
10
|
+
@id = id
|
11
11
|
end
|
12
12
|
|
13
|
-
def canvas_conversion
|
13
|
+
def canvas_conversion
|
14
14
|
assignment_group = CanvasCc::CanvasCC::Models::AssignmentGroup.new
|
15
15
|
assignment_group.identifier = @id
|
16
16
|
assignment_group.title = @title
|
17
17
|
assignment_group.group_weight = @group_weight
|
18
18
|
assignment_group.rules = @rules
|
19
|
-
|
20
|
-
|
19
|
+
assignment_group
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.create_assignment_group(group_name)
|
23
|
+
id = Senkyoshi.create_random_hex
|
24
|
+
group = AssignmentGroup.new(group_name, id)
|
25
|
+
group.canvas_conversion
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.find_group(course, category)
|
29
|
+
course.assignment_groups.
|
30
|
+
detect { |a| a.title == category }
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.find_or_create(course, category)
|
34
|
+
assignment_group = find_group(course, category)
|
35
|
+
if !assignment_group
|
36
|
+
assignment_group = AssignmentGroup.create_assignment_group(category)
|
37
|
+
course.assignment_groups << assignment_group
|
38
|
+
end
|
39
|
+
assignment_group
|
21
40
|
end
|
22
41
|
end
|
23
42
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
-
require "senkyoshi/models/
|
1
|
+
require "senkyoshi/models/file_resource"
|
2
2
|
|
3
3
|
module Senkyoshi
|
4
|
-
class Blog <
|
5
|
-
def initialize
|
4
|
+
class Blog < FileResource
|
5
|
+
def initialize(resource_id)
|
6
|
+
super(resource_id)
|
6
7
|
@title = ""
|
7
8
|
@description = ""
|
8
9
|
@is_public = true
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require "senkyoshi/models/
|
1
|
+
require "senkyoshi/models/file_resource"
|
2
2
|
require "senkyoshi/models/content_file"
|
3
3
|
|
4
4
|
module Senkyoshi
|
5
|
-
class Content <
|
5
|
+
class Content < FileResource
|
6
6
|
CONTENT_TYPES = {
|
7
7
|
"x-bb-asmt-test-link" => "Quiz",
|
8
8
|
"x-bb-asmt-survey-link" => "Quiz",
|
@@ -30,12 +30,13 @@ module Senkyoshi
|
|
30
30
|
"Senkyoshi::Quiz" => "Quizzes::Quiz",
|
31
31
|
}.freeze
|
32
32
|
|
33
|
-
attr_accessor(:title, :body, :
|
33
|
+
attr_accessor(:title, :body, :files, :url)
|
34
34
|
attr_reader(:extendeddata)
|
35
35
|
|
36
36
|
def self.from(xml, pre_data, resource_xids)
|
37
37
|
type = xml.xpath("/CONTENT/CONTENTHANDLER/@value").first.text
|
38
38
|
type.slice! "resource/"
|
39
|
+
|
39
40
|
xml.xpath("//FILES/FILE").each do |file|
|
40
41
|
file_name = ContentFile.clean_xid file.at("NAME").text
|
41
42
|
is_attachment = CONTENT_TYPES[type] == "Attachment"
|
@@ -44,10 +45,10 @@ module Senkyoshi
|
|
44
45
|
break
|
45
46
|
end
|
46
47
|
end
|
48
|
+
|
47
49
|
if content_type = CONTENT_TYPES[type]
|
48
50
|
content_class = Senkyoshi.const_get content_type
|
49
|
-
|
50
|
-
content.iterate_xml(xml, pre_data)
|
51
|
+
content_class.new(pre_data[:file_name]).iterate_xml(xml, pre_data)
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
@@ -64,10 +65,11 @@ module Senkyoshi
|
|
64
65
|
@type = xml.xpath("/CONTENT/RENDERTYPE/@value").first.text
|
65
66
|
@parent_id = pre_data[:parent_id]
|
66
67
|
@module_type = MODULE_TYPES[self.class.name]
|
67
|
-
|
68
|
+
|
68
69
|
if pre_data[:assignment_id] && !pre_data[:assignment_id].empty?
|
69
70
|
@id = pre_data[:assignment_id]
|
70
71
|
end
|
72
|
+
|
71
73
|
@files = xml.xpath("//FILES/FILE").map do |file|
|
72
74
|
ContentFile.new(file)
|
73
75
|
end
|
@@ -80,7 +82,7 @@ module Senkyoshi
|
|
80
82
|
module_item.canvas_conversion
|
81
83
|
end
|
82
84
|
|
83
|
-
def get_pre_data(xml, file_name)
|
85
|
+
def self.get_pre_data(xml, file_name)
|
84
86
|
id = xml.xpath("/CONTENT/@id").first.text
|
85
87
|
parent_id = xml.xpath("/CONTENT/PARENTID/@value").first.text
|
86
88
|
{
|
@@ -26,10 +26,15 @@ module Senkyoshi
|
|
26
26
|
canvas_file.file_path.split("/").last
|
27
27
|
end
|
28
28
|
|
29
|
-
def canvas_conversion(canvas_file = nil)
|
30
|
-
path =
|
29
|
+
def canvas_conversion(resources, canvas_file = nil)
|
30
|
+
path = if canvas_file
|
31
|
+
canvas_file.file_path
|
32
|
+
else
|
33
|
+
resource = resources.detect_xid(@name)
|
34
|
+
resource.path if resource
|
35
|
+
end
|
31
36
|
query = "?canvas_download=1&canvas_qs_wrap=1"
|
32
|
-
href = "
|
37
|
+
href = "#{FILE_BASE}/#{path}#{query}"
|
33
38
|
%{
|
34
39
|
<a
|
35
40
|
class="instructure_scribd_file instructure_file_link"
|
@@ -1,14 +1,14 @@
|
|
1
|
-
require "senkyoshi/models/
|
1
|
+
require "senkyoshi/models/file_resource"
|
2
2
|
|
3
3
|
module Senkyoshi
|
4
|
-
class Course <
|
4
|
+
class Course < FileResource
|
5
5
|
##
|
6
6
|
# This class represents a reader for one zip file, and allows the usage of
|
7
7
|
# individual files within zip file
|
8
8
|
##
|
9
|
-
def initialize
|
9
|
+
def initialize(resource_id = nil)
|
10
|
+
super(resource_id)
|
10
11
|
@course_code = ""
|
11
|
-
@identifier = ""
|
12
12
|
@title = ""
|
13
13
|
@description = ""
|
14
14
|
@is_public = false
|
@@ -17,7 +17,6 @@ module Senkyoshi
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def iterate_xml(data, _)
|
20
|
-
@identifier = data["id"]
|
21
20
|
@title = Senkyoshi.get_attribute_value(data, "TITLE")
|
22
21
|
@description = Senkyoshi.get_description(data)
|
23
22
|
@is_public = Senkyoshi.get_attribute_value(data, "ISAVAILABLE")
|
@@ -27,7 +26,7 @@ module Senkyoshi
|
|
27
26
|
end
|
28
27
|
|
29
28
|
def canvas_conversion(course, _resources = nil)
|
30
|
-
course.identifier = @
|
29
|
+
course.identifier = @id
|
31
30
|
course.title = @title
|
32
31
|
course.description = @description
|
33
32
|
course.is_public = @is_public
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "senkyoshi/models/resource"
|
2
|
+
|
3
|
+
module Senkyoshi
|
4
|
+
##
|
5
|
+
# Class to represent a resource constructed from a single 'dat' file.
|
6
|
+
##
|
7
|
+
class FileResource < Resource
|
8
|
+
attr_reader(:id)
|
9
|
+
|
10
|
+
def initialize(id = nil)
|
11
|
+
@id = id
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.from(xml, pre_data, _resource_xids = nil)
|
15
|
+
resource = new(pre_data[:file_name])
|
16
|
+
resource.iterate_xml(xml, pre_data)
|
17
|
+
end
|
18
|
+
|
19
|
+
def iterate_xml(_xml, _pre_data)
|
20
|
+
self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
require "senkyoshi/models/
|
1
|
+
require "senkyoshi/models/file_resource"
|
2
2
|
|
3
3
|
module Senkyoshi
|
4
|
-
class Forum <
|
5
|
-
def initialize
|
4
|
+
class Forum < FileResource
|
5
|
+
def initialize(resource_id)
|
6
|
+
super(resource_id)
|
6
7
|
@title = ""
|
7
8
|
@text = ""
|
8
|
-
@identifier = Senkyoshi.create_random_hex
|
9
9
|
@discussion_type = "threaded"
|
10
10
|
end
|
11
11
|
|
@@ -19,7 +19,7 @@ module Senkyoshi
|
|
19
19
|
discussion = CanvasCc::CanvasCC::Models::Discussion.new
|
20
20
|
discussion.title = @title
|
21
21
|
discussion.text = @text
|
22
|
-
discussion.identifier = @
|
22
|
+
discussion.identifier = @id
|
23
23
|
discussion.discussion_type = @discussion_type
|
24
24
|
course.discussions << discussion
|
25
25
|
course
|
@@ -1,6 +1,23 @@
|
|
1
|
+
require "senkyoshi/models/outcome_definition"
|
2
|
+
require "senkyoshi/models/file_resource"
|
3
|
+
|
1
4
|
module Senkyoshi
|
2
|
-
class Gradebook
|
3
|
-
|
5
|
+
class Gradebook < FileResource
|
6
|
+
attr_reader(:outcome_definitions, :categories)
|
7
|
+
|
8
|
+
def initialize(resource_id = nil, categories = [], outcome_definitions = [])
|
9
|
+
super(resource_id)
|
10
|
+
@categories = categories
|
11
|
+
@outcome_definitions = outcome_definitions
|
12
|
+
end
|
13
|
+
|
14
|
+
def iterate_xml(xml_data, _)
|
15
|
+
@categories = Gradebook.get_categories(xml_data)
|
16
|
+
@outcome_definitions = get_outcome_definitions(xml_data).compact
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.get_pre_data(data, _)
|
4
21
|
categories = get_categories(data)
|
5
22
|
data.search("OUTCOMEDEFINITIONS").children.map do |outcome|
|
6
23
|
category_id = outcome.at("CATEGORYID").attributes["value"].value
|
@@ -14,7 +31,7 @@ module Senkyoshi
|
|
14
31
|
end
|
15
32
|
end
|
16
33
|
|
17
|
-
def get_categories(data)
|
34
|
+
def self.get_categories(data)
|
18
35
|
data.at("CATEGORIES").children.
|
19
36
|
each_with_object({}) do |category, categories|
|
20
37
|
id = category.attributes["id"].value
|
@@ -23,5 +40,29 @@ module Senkyoshi
|
|
23
40
|
categories[id] = title
|
24
41
|
end
|
25
42
|
end
|
43
|
+
|
44
|
+
def get_outcome_definitions(xml)
|
45
|
+
xml.xpath("//OUTCOMEDEFINITION").map do |outcome_def|
|
46
|
+
category_id = outcome_def.xpath("CATEGORYID/@value").first.value
|
47
|
+
OutcomeDefinition.from(outcome_def, @categories[category_id])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def convert_categories(course)
|
52
|
+
@categories.each do |category|
|
53
|
+
if AssignmentGroup.find_group(course, category.last).nil?
|
54
|
+
course.assignment_groups <<
|
55
|
+
AssignmentGroup.create_assignment_group(category.last)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def canvas_conversion(course, resources = nil)
|
61
|
+
convert_categories(course)
|
62
|
+
@outcome_definitions.
|
63
|
+
select { |outcome_def| OutcomeDefinition.orphan? outcome_def }.
|
64
|
+
each { |outcome_def| outcome_def.canvas_conversion course, resources }
|
65
|
+
course
|
66
|
+
end
|
26
67
|
end
|
27
68
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
-
require "senkyoshi/models/
|
1
|
+
require "senkyoshi/models/file_resource"
|
2
2
|
|
3
3
|
module Senkyoshi
|
4
|
-
class Group <
|
5
|
-
def initialize
|
4
|
+
class Group < FileResource
|
5
|
+
def initialize(resource_id)
|
6
|
+
super(resource_id)
|
6
7
|
@name = ""
|
7
8
|
@description = ""
|
8
9
|
@is_public = true
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "senkyoshi/models/resource"
|
2
|
+
require "senkyoshi/models/assignment_group"
|
3
|
+
|
4
|
+
module Senkyoshi
|
5
|
+
class OutcomeDefinition < Resource
|
6
|
+
include Senkyoshi
|
7
|
+
attr_reader :id, :content_id, :asidataid, :is_user_created
|
8
|
+
def self.from(xml, category)
|
9
|
+
outcome_definition = OutcomeDefinition.new(category)
|
10
|
+
outcome_definition.iterate_xml(xml)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(category)
|
14
|
+
@category = category
|
15
|
+
end
|
16
|
+
|
17
|
+
def iterate_xml(xml)
|
18
|
+
@content_id = xml.xpath("./CONTENTID/@value").text
|
19
|
+
@asidataid = xml.xpath("./ASIDATAID/@value").text
|
20
|
+
@id = xml.xpath("./@id").text
|
21
|
+
@title = xml.xpath("./TITLE/@value").text
|
22
|
+
@points_possible = xml.xpath("./POINTSPOSSIBLE/@value").text
|
23
|
+
@is_user_created = true? xml.xpath("./ISUSERCREATED/@value").text
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Determine if an outcome definition is user created and linked to any
|
29
|
+
# other 'CONTENT' or assignments
|
30
|
+
##
|
31
|
+
def self.orphan?(outcome_def)
|
32
|
+
outcome_def.content_id.empty? &&
|
33
|
+
outcome_def.asidataid.empty? &&
|
34
|
+
outcome_def.is_user_created
|
35
|
+
end
|
36
|
+
|
37
|
+
def canvas_conversion(course, _ = nil)
|
38
|
+
assignment_group = AssignmentGroup.find_or_create(course, @category)
|
39
|
+
assignment = CanvasCc::CanvasCC::Models::Assignment.new
|
40
|
+
assignment.identifier = Senkyoshi.create_random_hex
|
41
|
+
assignment.assignment_group_identifier_ref = assignment_group.identifier
|
42
|
+
assignment.title = @title
|
43
|
+
assignment.position = 1
|
44
|
+
assignment.points_possible = @points_possible
|
45
|
+
assignment.workflow_state = "published"
|
46
|
+
assignment.grading_type = "points"
|
47
|
+
|
48
|
+
course.assignments << assignment
|
49
|
+
course
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/senkyoshi/models/qti.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require "senkyoshi/models/assignment_group"
|
2
2
|
require "senkyoshi/models/assignment"
|
3
3
|
require "senkyoshi/models/question"
|
4
|
-
require "senkyoshi/models/
|
4
|
+
require "senkyoshi/models/file_resource"
|
5
5
|
|
6
6
|
QTI_TYPE = {
|
7
7
|
"Test" => "Assessment",
|
@@ -10,15 +10,16 @@ QTI_TYPE = {
|
|
10
10
|
}.freeze
|
11
11
|
|
12
12
|
module Senkyoshi
|
13
|
-
class QTI <
|
14
|
-
def self.from(data, pre_data)
|
13
|
+
class QTI < FileResource
|
14
|
+
def self.from(data, pre_data, _resource_xids = nil)
|
15
15
|
type = data.at("bbmd_assessmenttype").text
|
16
16
|
qti_class = Senkyoshi.const_get QTI_TYPE[type]
|
17
|
-
qti = qti_class.new
|
17
|
+
qti = qti_class.new(pre_data[:file_name])
|
18
18
|
qti.iterate_xml(data, pre_data)
|
19
19
|
end
|
20
20
|
|
21
|
-
def initialize
|
21
|
+
def initialize(resource_id = nil)
|
22
|
+
super(resource_id)
|
22
23
|
@title = ""
|
23
24
|
@description = ""
|
24
25
|
@quiz_type = ""
|
@@ -85,7 +86,7 @@ module Senkyoshi
|
|
85
86
|
end
|
86
87
|
end
|
87
88
|
|
88
|
-
def get_pre_data(xml, _)
|
89
|
+
def self.get_pre_data(xml, _)
|
89
90
|
{
|
90
91
|
original_file_name: xml.xpath("/COURSEASSESSMENT/
|
91
92
|
ASMTID/@value").first.text,
|
@@ -109,8 +110,8 @@ module Senkyoshi
|
|
109
110
|
def canvas_conversion(course, resources)
|
110
111
|
assessment = CanvasCc::CanvasCC::Models::Assessment.new
|
111
112
|
assessment.identifier = @id
|
112
|
-
|
113
|
-
assignment = create_assignment
|
113
|
+
assignment_group = AssignmentGroup.find_or_create(course, @group_name)
|
114
|
+
assignment = create_assignment(assignment_group.identifier)
|
114
115
|
assignment.quiz_identifier_ref = assessment.identifier
|
115
116
|
course.assignments << assignment
|
116
117
|
assessment = setup_assessment(assessment, assignment, resources)
|
@@ -187,22 +188,10 @@ module Senkyoshi
|
|
187
188
|
question_group
|
188
189
|
end
|
189
190
|
|
190
|
-
def
|
191
|
-
group = course.assignment_groups.detect { |a| a.title == @group_name }
|
192
|
-
if group
|
193
|
-
@group_id = group.identifier
|
194
|
-
else
|
195
|
-
@group_id = Senkyoshi.create_random_hex
|
196
|
-
assignment_group = AssignmentGroup.new(@group_name, @group_id)
|
197
|
-
course = assignment_group.canvas_conversion(course, resources)
|
198
|
-
end
|
199
|
-
course
|
200
|
-
end
|
201
|
-
|
202
|
-
def create_assignment
|
191
|
+
def create_assignment(group_id)
|
203
192
|
assignment = CanvasCc::CanvasCC::Models::Assignment.new
|
204
193
|
assignment.identifier = Senkyoshi.create_random_hex
|
205
|
-
assignment.assignment_group_identifier_ref =
|
194
|
+
assignment.assignment_group_identifier_ref = group_id
|
206
195
|
assignment.title = @title
|
207
196
|
assignment.position = 1
|
208
197
|
assignment.submission_types << "online_quiz"
|
@@ -2,6 +2,12 @@ require "senkyoshi/models/qti"
|
|
2
2
|
|
3
3
|
module Senkyoshi
|
4
4
|
class QuestionBank < QTI
|
5
|
+
TAGS = {
|
6
|
+
"<p><span size=\"2\" style=\"font-size: small;\">.</span></p>" => "",
|
7
|
+
"<p>.</p>" => "",
|
8
|
+
}.freeze
|
9
|
+
TAGS_RE = Regexp.union(TAGS.keys)
|
10
|
+
|
5
11
|
def canvas_conversion(course, resources)
|
6
12
|
question_bank = CanvasCc::CanvasCC::Models::QuestionBank.new
|
7
13
|
question_bank.identifier = @id
|
@@ -28,7 +34,7 @@ module Senkyoshi
|
|
28
34
|
question_bank.questions = []
|
29
35
|
questions.each do |item|
|
30
36
|
question = item.canvas_conversion(question_bank, resources)
|
31
|
-
question.material = clean_up_material(question)
|
37
|
+
question.material = clean_up_material(question.material)
|
32
38
|
question_bank.questions << question
|
33
39
|
end
|
34
40
|
question_bank
|
@@ -36,11 +42,12 @@ module Senkyoshi
|
|
36
42
|
|
37
43
|
# This is to remove the random extra <p>.</p> included in the
|
38
44
|
# description that is just randomly there
|
39
|
-
def clean_up_material(
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
45
|
+
def clean_up_material(material)
|
46
|
+
if material
|
47
|
+
material = material.gsub(TAGS_RE, TAGS)
|
48
|
+
material = material.strip
|
49
|
+
end
|
50
|
+
material
|
44
51
|
end
|
45
52
|
end
|
46
53
|
end
|
@@ -2,6 +2,17 @@ require "senkyoshi/models/question"
|
|
2
2
|
|
3
3
|
module Senkyoshi
|
4
4
|
class Matching < Question
|
5
|
+
TAGS_PATTERN = Regexp.union(
|
6
|
+
/<\/?p[^>]*>/i, # <p> tags
|
7
|
+
/<\/?b[^>]*>/i, # <b> tags
|
8
|
+
/<\/?strong[^>]*>/i, # <strong> tags
|
9
|
+
/<\/?em[^>]*>/i, # <em> tags
|
10
|
+
/<\/?span[^>]*>/i, # <span> tags
|
11
|
+
/<\/?font[^>]*>/i, # <font> tags
|
12
|
+
/<\/?i(?!mg)[^>]*>/i, # <i> tags, no <img>
|
13
|
+
/ style=("|')[^"']*("|')/i, # inline styles
|
14
|
+
)
|
15
|
+
|
5
16
|
def initialize
|
6
17
|
super
|
7
18
|
@matches = []
|
@@ -9,6 +20,10 @@ module Senkyoshi
|
|
9
20
|
@distractors = []
|
10
21
|
end
|
11
22
|
|
23
|
+
def strip_select_html(text)
|
24
|
+
text.gsub(TAGS_PATTERN, "")
|
25
|
+
end
|
26
|
+
|
12
27
|
def iterate_xml(data)
|
13
28
|
super
|
14
29
|
resprocessing = data.at("resprocessing")
|
@@ -18,20 +33,23 @@ module Senkyoshi
|
|
18
33
|
if match_block = data.at("flow[@class=RIGHT_MATCH_BLOCK]")
|
19
34
|
matches_array = match_block.
|
20
35
|
search("flow[@class=FORMATTED_TEXT_BLOCK]").
|
21
|
-
map(
|
36
|
+
map { |match| strip_select_html(match.text) }
|
22
37
|
end
|
38
|
+
|
23
39
|
if response_block = data.at("flow[@class=RESPONSE_BLOCK]")
|
24
40
|
response_block.children.each do |response|
|
25
41
|
id = response.at("response_lid").attributes["ident"].value
|
26
42
|
question = response.at("mat_formattedtext").text
|
27
43
|
answer_id = @matching_answers[id]
|
28
44
|
answer = ""
|
45
|
+
|
29
46
|
flow_label = response.at("flow_label")
|
30
47
|
flow_label.children.each_with_index do |label, index|
|
31
48
|
if label.attributes["ident"].value == answer_id
|
32
49
|
answer = matches_array[index]
|
33
50
|
end
|
34
51
|
end
|
52
|
+
|
35
53
|
answers << answer
|
36
54
|
@matches << { id: id, question_text: question, answer_text: answer }
|
37
55
|
end
|
@@ -40,7 +58,12 @@ module Senkyoshi
|
|
40
58
|
self
|
41
59
|
end
|
42
60
|
|
43
|
-
def canvas_conversion(assessment,
|
61
|
+
def canvas_conversion(assessment, resources)
|
62
|
+
@matches.each do |match|
|
63
|
+
match[:question_text] = fix_html(match[:question_text], resources)
|
64
|
+
match[:answer_text] = fix_html(match[:answer_text], resources)
|
65
|
+
end
|
66
|
+
|
44
67
|
@question.matches = @matches
|
45
68
|
@question.distractors = @distractors
|
46
69
|
super
|
@@ -19,17 +19,53 @@ module Senkyoshi
|
|
19
19
|
false
|
20
20
|
end
|
21
21
|
|
22
|
+
def strip_xid(name)
|
23
|
+
name.gsub(/__xid-[0-9]+_[0-9]+/, "")
|
24
|
+
end
|
25
|
+
|
26
|
+
def _matches_directory_xid?(xid, directory)
|
27
|
+
dir_xid = directory[/xid-[0-9]+_[0-9]+\z/]
|
28
|
+
xid == dir_xid
|
29
|
+
end
|
30
|
+
|
31
|
+
def _find_directories(resources)
|
32
|
+
resources.resources.map do |resource|
|
33
|
+
if resource.respond_to?(:location)
|
34
|
+
File.dirname(resource.location)[/csfiles.*/]
|
35
|
+
end
|
36
|
+
end.uniq.compact
|
37
|
+
end
|
38
|
+
|
39
|
+
def _fix_path(original_src, resources, dir_names)
|
40
|
+
xid = original_src.split("/").last
|
41
|
+
file_resource = resources.detect_xid(xid)
|
42
|
+
|
43
|
+
if file_resource
|
44
|
+
"#{FILE_BASE}/#{file_resource.path}"
|
45
|
+
else
|
46
|
+
matching_dir = dir_names.detect do |dir|
|
47
|
+
_matches_directory_xid?(xid, dir)
|
48
|
+
end
|
49
|
+
|
50
|
+
if matching_dir
|
51
|
+
"#{DIR_BASE}/#{strip_xid(matching_dir)}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
22
56
|
def _search_and_replace(resources, node_html, tag, attr)
|
57
|
+
dir_names = _find_directories(resources)
|
58
|
+
|
23
59
|
node_html.search(tag).each do |element|
|
24
60
|
original_src = element[attr]
|
61
|
+
|
25
62
|
if original_src
|
26
|
-
|
27
|
-
|
28
|
-
if file_resource
|
29
|
-
element[attr] = "#{BASE}/#{file_resource.path}"
|
30
|
-
end
|
63
|
+
path = _fix_path(original_src, resources, dir_names)
|
64
|
+
element[attr] = path if path
|
31
65
|
end
|
32
66
|
end
|
33
67
|
end
|
68
|
+
|
69
|
+
def self.get_pre_data(_xml, _file_name); end
|
34
70
|
end
|
35
71
|
end
|
@@ -1,15 +1,12 @@
|
|
1
|
-
require "senkyoshi/models/
|
1
|
+
require "senkyoshi/models/file_resource"
|
2
2
|
require "active_support/core_ext/string"
|
3
3
|
|
4
4
|
module Senkyoshi
|
5
|
-
class StaffInfo <
|
6
|
-
attr_reader(
|
7
|
-
:id,
|
8
|
-
:title,
|
9
|
-
:entries,
|
10
|
-
)
|
5
|
+
class StaffInfo < FileResource
|
6
|
+
attr_reader(:title, :entries)
|
11
7
|
|
12
|
-
def initialize
|
8
|
+
def initialize(resource_id = nil)
|
9
|
+
super(resource_id)
|
13
10
|
@entries = []
|
14
11
|
end
|
15
12
|
|
@@ -19,10 +19,10 @@ module Senkyoshi
|
|
19
19
|
# Add page links to page body
|
20
20
|
@files.each do |file|
|
21
21
|
if canvas_file = course.files.detect { |f| f.identifier == file.name }
|
22
|
-
page.body << file.canvas_conversion(canvas_file)
|
22
|
+
page.body << file.canvas_conversion(resources, canvas_file)
|
23
23
|
else
|
24
|
-
page.body <<
|
25
|
-
" -- doesn't exist in blackboard</p>"
|
24
|
+
page.body <<
|
25
|
+
"<p>File: #{file.linkname} -- doesn't exist in blackboard</p>"
|
26
26
|
end
|
27
27
|
end
|
28
28
|
course.pages << page
|
data/lib/senkyoshi/tasks.rb
CHANGED
@@ -54,6 +54,17 @@ module Senkyoshi
|
|
54
54
|
##
|
55
55
|
def self.install_tasks
|
56
56
|
namespace :senkyoshi do
|
57
|
+
desc "Convert a single given blackboard cartridge to a canvas cartridge"
|
58
|
+
task :imscc_single, [:file_path] do |_t, args|
|
59
|
+
file_path = args.file_path
|
60
|
+
if file_path
|
61
|
+
imscc_path = file_path.clone.ext(".imscc")
|
62
|
+
Senkyoshi.parse_and_process_single(file_path, imscc_path)
|
63
|
+
else
|
64
|
+
puts "No file given"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
57
68
|
desc "Convert blackboard cartridges to canvas cartridges"
|
58
69
|
task imscc: SOURCE_FILES.pathmap(
|
59
70
|
"%{^#{SOURCE_NAME}/,#{OUTPUT_DIR}/}X.imscc",
|
data/lib/senkyoshi/version.rb
CHANGED
data/lib/senkyoshi/xml_parser.rb
CHANGED
@@ -56,6 +56,7 @@ module Senkyoshi
|
|
56
56
|
questestinterop: "QTI",
|
57
57
|
content: "Content",
|
58
58
|
staffinfo: "StaffInfo",
|
59
|
+
gradebook: "Gradebook",
|
59
60
|
}.freeze
|
60
61
|
|
61
62
|
PRE_RESOURCE_TYPE = {
|
@@ -80,16 +81,10 @@ module Senkyoshi
|
|
80
81
|
single_pre_data = get_single_pre_data(pre_data, file)
|
81
82
|
res_class = Senkyoshi.const_get RESOURCE_TYPE[type.to_sym]
|
82
83
|
case type
|
83
|
-
when "content"
|
84
|
-
Content.from(xml_data, single_pre_data, resource_xids)
|
85
|
-
when "questestinterop"
|
86
|
-
single_pre_data ||= { file_name: file }
|
87
|
-
QTI.from(xml_data, single_pre_data)
|
88
84
|
when "staffinfo"
|
89
85
|
staff_info.iterate_xml(xml_data, single_pre_data)
|
90
86
|
else
|
91
|
-
|
92
|
-
resource.iterate_xml(xml_data, single_pre_data)
|
87
|
+
res_class.from(xml_data, single_pre_data, resource_xids)
|
93
88
|
end
|
94
89
|
end
|
95
90
|
end
|
@@ -98,7 +93,7 @@ module Senkyoshi
|
|
98
93
|
def self.get_single_pre_data(pre_data, file)
|
99
94
|
pre_data.detect do |d|
|
100
95
|
d[:file_name] == file || d[:assignment_id] == file
|
101
|
-
end
|
96
|
+
end || { file_name: file }
|
102
97
|
end
|
103
98
|
|
104
99
|
def self.iterator_master(resources, zip_file)
|
@@ -119,9 +114,9 @@ module Senkyoshi
|
|
119
114
|
iterator_master(resources, zip_file) do |xml_data, type, file|
|
120
115
|
if PRE_RESOURCE_TYPE[type.to_sym]
|
121
116
|
res_class = Senkyoshi.const_get PRE_RESOURCE_TYPE[type.to_sym]
|
122
|
-
resource_class = res_class.new
|
123
117
|
pre_data[type] ||= []
|
124
|
-
|
118
|
+
data = res_class.get_pre_data(xml_data, file)
|
119
|
+
pre_data[type].push(data) if data
|
125
120
|
end
|
126
121
|
end
|
127
122
|
pre_data = connect_content(pre_data)
|
@@ -130,13 +125,16 @@ module Senkyoshi
|
|
130
125
|
|
131
126
|
def self.connect_content(pre_data)
|
132
127
|
pre_data["content"].each do |content|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
128
|
+
if pre_data["gradebook"]
|
129
|
+
gradebook = pre_data["gradebook"].first.
|
130
|
+
detect { |g| g[:content_id] == content[:file_name] }
|
131
|
+
content.merge!(gradebook) if gradebook
|
132
|
+
end
|
133
|
+
if pre_data["courseassessment"]
|
134
|
+
course_assessment = pre_data["courseassessment"].
|
135
|
+
detect { |ca| ca[:original_file_name] == content[:assignment_id] }
|
136
|
+
content.merge!(course_assessment) if course_assessment
|
137
|
+
end
|
140
138
|
end
|
141
139
|
pre_data["content"]
|
142
140
|
end
|
data/lib/senkyoshi.rb
CHANGED
@@ -2,6 +2,7 @@ require "senkyoshi/version"
|
|
2
2
|
require "senkyoshi/xml_parser"
|
3
3
|
require "senkyoshi/canvas_course"
|
4
4
|
require "senkyoshi/collection"
|
5
|
+
require "senkyoshi/configuration"
|
5
6
|
|
6
7
|
require "canvas_cc"
|
7
8
|
require "optparse"
|
@@ -12,7 +13,24 @@ require "zip"
|
|
12
13
|
require "senkyoshi/exceptions"
|
13
14
|
|
14
15
|
module Senkyoshi
|
15
|
-
|
16
|
+
FILE_BASE = "$IMS-CC-FILEBASE$".freeze
|
17
|
+
DIR_BASE = "$CANVAS_COURSE_REFERENCE$/files/folder".freeze
|
18
|
+
|
19
|
+
class << self
|
20
|
+
attr_writer :configuration
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.configuration
|
24
|
+
@configuration ||= Configuration.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.reset
|
28
|
+
@configuration = Configuration.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.configure
|
32
|
+
yield configuration
|
33
|
+
end
|
16
34
|
|
17
35
|
def self.parse(zip_path, imscc_path)
|
18
36
|
Zip::File.open(zip_path) do |file|
|
@@ -30,6 +48,10 @@ module Senkyoshi
|
|
30
48
|
end
|
31
49
|
end
|
32
50
|
|
51
|
+
def self.parse_and_process_single(zip_path, imscc_path)
|
52
|
+
Senkyoshi.parse(zip_path, imscc_path)
|
53
|
+
end
|
54
|
+
|
33
55
|
def self.read_file(zip_file, file_name)
|
34
56
|
zip_file.find_entry(file_name).get_input_stream.read
|
35
57
|
rescue NoMethodError
|
@@ -37,7 +59,7 @@ module Senkyoshi
|
|
37
59
|
end
|
38
60
|
|
39
61
|
def self.build_file(course, imscc_path, resources)
|
40
|
-
folder =
|
62
|
+
folder = File.dirname(imscc_path)
|
41
63
|
file = CanvasCc::CanvasCC::CartridgeCreator.new(course).create(folder)
|
42
64
|
File.rename(file, imscc_path)
|
43
65
|
cleanup resources
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: senkyoshi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Atomic Jolt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-01-
|
11
|
+
date: 2017-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry-byebug
|
@@ -166,7 +166,7 @@ files:
|
|
166
166
|
- lib/senkyoshi.rb
|
167
167
|
- lib/senkyoshi/canvas_course.rb
|
168
168
|
- lib/senkyoshi/collection.rb
|
169
|
-
- lib/senkyoshi/
|
169
|
+
- lib/senkyoshi/configuration.rb
|
170
170
|
- lib/senkyoshi/exceptions.rb
|
171
171
|
- lib/senkyoshi/models/announcement.rb
|
172
172
|
- lib/senkyoshi/models/answer.rb
|
@@ -180,11 +180,13 @@ files:
|
|
180
180
|
- lib/senkyoshi/models/course.rb
|
181
181
|
- lib/senkyoshi/models/external_url.rb
|
182
182
|
- lib/senkyoshi/models/file.rb
|
183
|
+
- lib/senkyoshi/models/file_resource.rb
|
183
184
|
- lib/senkyoshi/models/forum.rb
|
184
185
|
- lib/senkyoshi/models/gradebook.rb
|
185
186
|
- lib/senkyoshi/models/group.rb
|
186
187
|
- lib/senkyoshi/models/module.rb
|
187
188
|
- lib/senkyoshi/models/module_item.rb
|
189
|
+
- lib/senkyoshi/models/outcome_definition.rb
|
188
190
|
- lib/senkyoshi/models/qti.rb
|
189
191
|
- lib/senkyoshi/models/question.rb
|
190
192
|
- lib/senkyoshi/models/question_bank.rb
|
data/lib/senkyoshi/config.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
require "yaml"
|
2
|
-
|
3
|
-
module Senkyoshi
|
4
|
-
def self.canvas_url
|
5
|
-
Senkyoshi._config[:canvas_url]
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.canvas_token
|
9
|
-
Senkyoshi._config[:canvas_token]
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.scorm_launch_url
|
13
|
-
Senkyoshi._config[:scorm_launch_url]
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.scorm_url
|
17
|
-
Senkyoshi._config[:scorm_url]
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.scorm_shared_auth
|
21
|
-
Senkyoshi._config[:scorm_shared_auth]
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.account_id
|
25
|
-
Senkyoshi._config[:account_id] || :self
|
26
|
-
end
|
27
|
-
|
28
|
-
def self._config
|
29
|
-
@config ||= if File.exists? "senkyoshi.yml"
|
30
|
-
YAML::load(File.read("senkyoshi.yml"))
|
31
|
-
else
|
32
|
-
{}
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|