senkyoshi 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/atomicjolt/senkyoshi.svg?branch=master)](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
|