github-to-canvas-quiz 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +48 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/NOTES.md +53 -0
- data/README.md +89 -0
- data/Rakefile +8 -0
- data/bin/console +29 -0
- data/bin/github-to-canvas-quiz +15 -0
- data/bin/setup +8 -0
- data/github-to-canvas-quiz.gemspec +50 -0
- data/lib/github_to_canvas_quiz/builder/quiz.rb +110 -0
- data/lib/github_to_canvas_quiz/canvas_api/client.rb +80 -0
- data/lib/github_to_canvas_quiz/canvas_api/endpoints/quiz_questions.rb +29 -0
- data/lib/github_to_canvas_quiz/canvas_api/endpoints/quizzes.rb +25 -0
- data/lib/github_to_canvas_quiz/canvas_api/endpoints.rb +10 -0
- data/lib/github_to_canvas_quiz/cli.rb +31 -0
- data/lib/github_to_canvas_quiz/markdown_builder.rb +90 -0
- data/lib/github_to_canvas_quiz/markdown_converter.rb +44 -0
- data/lib/github_to_canvas_quiz/model/answer/fill_in_multiple_blanks.rb +34 -0
- data/lib/github_to_canvas_quiz/model/answer/matching.rb +35 -0
- data/lib/github_to_canvas_quiz/model/answer/multiple_answers.rb +33 -0
- data/lib/github_to_canvas_quiz/model/answer/multiple_choice.rb +33 -0
- data/lib/github_to_canvas_quiz/model/answer/multiple_dropdowns.rb +34 -0
- data/lib/github_to_canvas_quiz/model/answer/short_answer.rb +33 -0
- data/lib/github_to_canvas_quiz/model/answer/true_false.rb +33 -0
- data/lib/github_to_canvas_quiz/model/question.rb +61 -0
- data/lib/github_to_canvas_quiz/model/quiz.rb +62 -0
- data/lib/github_to_canvas_quiz/parser/canvas/answer/base.rb +19 -0
- data/lib/github_to_canvas_quiz/parser/canvas/answer/fill_in_multiple_blanks.rb +30 -0
- data/lib/github_to_canvas_quiz/parser/canvas/answer/matching.rb +31 -0
- data/lib/github_to_canvas_quiz/parser/canvas/answer/multiple_answers.rb +33 -0
- data/lib/github_to_canvas_quiz/parser/canvas/answer/multiple_choice.rb +33 -0
- data/lib/github_to_canvas_quiz/parser/canvas/answer/multiple_dropdowns.rb +30 -0
- data/lib/github_to_canvas_quiz/parser/canvas/answer/short_answer.rb +29 -0
- data/lib/github_to_canvas_quiz/parser/canvas/answer/true_false.rb +29 -0
- data/lib/github_to_canvas_quiz/parser/canvas/answer.rb +34 -0
- data/lib/github_to_canvas_quiz/parser/canvas/helpers.rb +25 -0
- data/lib/github_to_canvas_quiz/parser/canvas/question.rb +55 -0
- data/lib/github_to_canvas_quiz/parser/canvas/quiz.rb +44 -0
- data/lib/github_to_canvas_quiz/parser/markdown/answer/base.rb +22 -0
- data/lib/github_to_canvas_quiz/parser/markdown/answer/fill_in_multiple_blanks.rb +21 -0
- data/lib/github_to_canvas_quiz/parser/markdown/answer/matching.rb +23 -0
- data/lib/github_to_canvas_quiz/parser/markdown/answer/multiple_answers.rb +21 -0
- data/lib/github_to_canvas_quiz/parser/markdown/answer/multiple_choice.rb +19 -0
- data/lib/github_to_canvas_quiz/parser/markdown/answer/multiple_dropdowns.rb +21 -0
- data/lib/github_to_canvas_quiz/parser/markdown/answer/short_answer.rb +21 -0
- data/lib/github_to_canvas_quiz/parser/markdown/answer/true_false.rb +21 -0
- data/lib/github_to_canvas_quiz/parser/markdown/answer.rb +34 -0
- data/lib/github_to_canvas_quiz/parser/markdown/base.rb +22 -0
- data/lib/github_to_canvas_quiz/parser/markdown/helpers/node_parser.rb +21 -0
- data/lib/github_to_canvas_quiz/parser/markdown/helpers/node_scanner.rb +208 -0
- data/lib/github_to_canvas_quiz/parser/markdown/question.rb +76 -0
- data/lib/github_to_canvas_quiz/parser/markdown/quiz.rb +42 -0
- data/lib/github_to_canvas_quiz/repository_interface.rb +42 -0
- data/lib/github_to_canvas_quiz/reverse_markdown/converters/p.rb +20 -0
- data/lib/github_to_canvas_quiz/reverse_markdown/converters/pre.rb +51 -0
- data/lib/github_to_canvas_quiz/reverse_markdown/register.rb +9 -0
- data/lib/github_to_canvas_quiz/synchronizer/quiz.rb +140 -0
- data/lib/github_to_canvas_quiz/version.rb +5 -0
- data/lib/github_to_canvas_quiz.rb +62 -0
- metadata +348 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
class CLI < Thor
|
5
|
+
option :course, type: :numeric, required: true, desc: 'Canvas Course ID'
|
6
|
+
option :quiz, type: :numeric, required: true, desc: 'Canvas Quiz ID'
|
7
|
+
option :directory, default: '.',
|
8
|
+
desc: '(optional) Directory to output markdown files. Defaults to current directory'
|
9
|
+
desc 'backup', 'Backup a Canvas quiz and its questions as markdown'
|
10
|
+
def backup
|
11
|
+
puts '⬇️ Converting quiz...'
|
12
|
+
Builder::Quiz.new(client, options[:course], options[:quiz], options[:directory]).build
|
13
|
+
puts '✅ Done'
|
14
|
+
end
|
15
|
+
|
16
|
+
option :directory, default: '.',
|
17
|
+
desc: '(optional) Directory with markdown files to align. Defaults to current directory'
|
18
|
+
desc 'align', 'Updates a Canvas quiz from Markdown files.'
|
19
|
+
def align
|
20
|
+
puts '⬆️ Aligning quiz...'
|
21
|
+
Synchronizer::Quiz.new(client, options[:directory]).sync
|
22
|
+
puts '✅ Done'
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def client
|
28
|
+
@client ||= CanvasAPI::Client.new(api_key: ENV['CANVAS_API_KEY'], host: ENV['CANVAS_API_PATH'])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
# Custom DSL for building a Markdown string
|
5
|
+
class MarkdownBuilder
|
6
|
+
# Provides a custom DSL for building a Markdown string
|
7
|
+
# Usage:
|
8
|
+
#
|
9
|
+
# MarkdownBuilder.build do |md|
|
10
|
+
# md.h1('Hello')
|
11
|
+
# md.ul('item 1', 'item 2')
|
12
|
+
# md.blockquote('comment')
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# # => "# Hello\n\n- item 1\n- item 2\n\n> comment\n"
|
16
|
+
def self.build
|
17
|
+
raise ArgumentError, 'no block given' unless block_given?
|
18
|
+
|
19
|
+
builder = new
|
20
|
+
yield(builder)
|
21
|
+
builder.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_accessor :blocks
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@blocks = []
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [String] markdown produced by joining all blocks
|
31
|
+
def to_s
|
32
|
+
"#{blocks.join("\n\n")}\n"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Adds a frontmatter block
|
36
|
+
# @param hash [Hash] the data to covert to YAML for the frontmatter
|
37
|
+
def frontmatter(hash)
|
38
|
+
blocks << "#{hash.to_yaml.strip}\n---"
|
39
|
+
end
|
40
|
+
|
41
|
+
# Adds a H1 heading
|
42
|
+
# @param text [String] heading text
|
43
|
+
def h1(text)
|
44
|
+
blocks << "# #{text}"
|
45
|
+
end
|
46
|
+
|
47
|
+
# Adds a H2 heading
|
48
|
+
# @param text [String] heading text
|
49
|
+
def h2(text)
|
50
|
+
blocks << "## #{text}"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Adds a paragraph
|
54
|
+
# @param text [String] paragraph text
|
55
|
+
def p(text)
|
56
|
+
blocks << escape(text)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Adds a blockquote
|
60
|
+
# @param text [String] blockquote text
|
61
|
+
def blockquote(text)
|
62
|
+
text = text.gsub("\n", "\n> ")
|
63
|
+
blocks << "> #{text}"
|
64
|
+
end
|
65
|
+
|
66
|
+
# Adds multiple list items
|
67
|
+
# @param texts* [Strings] a list of text to convert to list items
|
68
|
+
def ul(*texts)
|
69
|
+
blocks << texts.map { |text| "- #{escape(text)}" }.join("\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
# Adds a block of arbitrary markdown
|
73
|
+
# @param markdown [String] markdown string
|
74
|
+
def md(markdown)
|
75
|
+
blocks << markdown.strip
|
76
|
+
end
|
77
|
+
|
78
|
+
# TODO: This doesn't belong here...
|
79
|
+
def html_to_markdown(html)
|
80
|
+
ReverseMarkdown.convert(html, github_flavored: true).strip
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# TODO: What needs escaping??
|
86
|
+
def escape(text)
|
87
|
+
text.gsub('_', '\_')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
# Custom HTML renderer for Redcarpet, using Rouge for syntax highlighting
|
5
|
+
class HTMLRenderer < Redcarpet::Render::HTML
|
6
|
+
include Rouge::Plugins::Redcarpet
|
7
|
+
end
|
8
|
+
|
9
|
+
# Convert a string of Markdown to HTML using Redcarpet and Rouge.
|
10
|
+
#
|
11
|
+
# Useage:
|
12
|
+
#
|
13
|
+
# MarkdownConverter.new("# Hello\n\nWorld\n").to_html
|
14
|
+
# # => "<h1>Hello</h1>\n\n<p>World</p>\n"
|
15
|
+
class MarkdownConverter
|
16
|
+
attr_reader :options, :markdown
|
17
|
+
|
18
|
+
OPTIONS = {
|
19
|
+
tables: true,
|
20
|
+
autolink: true,
|
21
|
+
fenced_code_blocks: true,
|
22
|
+
disable_indented_code_blocks: true,
|
23
|
+
no_intra_emphasis: true
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
# @param [String] markdown The markdown to be converted
|
27
|
+
# @param [Hash] options Overrides the defaults for the [Redcarpet](https://github.com/vmg/redcarpet) gem
|
28
|
+
def initialize(markdown, options = {})
|
29
|
+
@options = OPTIONS.merge(options)
|
30
|
+
@markdown = markdown
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [String] the markdown converted to HTML
|
34
|
+
def to_html
|
35
|
+
Redcarpet::Markdown.new(renderer, options).render(markdown)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def renderer
|
41
|
+
HTMLRenderer.new(escape_html: true)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Model
|
5
|
+
module Answer
|
6
|
+
class FillInMultipleBlanks
|
7
|
+
attr_accessor :title, :text, :comments, :blank_id
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
options.each do |key, value|
|
11
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_markdown
|
16
|
+
MarkdownBuilder.build do |md|
|
17
|
+
md.h2(title)
|
18
|
+
md.ul(text, blank_id)
|
19
|
+
md.blockquote(md.html_to_markdown(comments)) unless comments.empty?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
{
|
25
|
+
'answer_text' => text,
|
26
|
+
'answer_weight' => title == 'Correct' ? 100 : 0,
|
27
|
+
'answer_comment_html' => comments,
|
28
|
+
'blank_id' => blank_id
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Model
|
5
|
+
module Answer
|
6
|
+
class Matching
|
7
|
+
attr_accessor :title, :text, :comments, :left, :right
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
options.each do |key, value|
|
11
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_markdown
|
16
|
+
MarkdownBuilder.build do |md|
|
17
|
+
md.h2(title)
|
18
|
+
md.ul(left, right)
|
19
|
+
md.blockquote(md.html_to_markdown(comments)) unless comments.empty?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
{
|
25
|
+
'answer_text' => left,
|
26
|
+
'answer_weight' => 100,
|
27
|
+
'answer_comment_html' => comments,
|
28
|
+
'answer_match_left' => left,
|
29
|
+
'answer_match_right' => right
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Model
|
5
|
+
module Answer
|
6
|
+
class MultipleAnswers
|
7
|
+
attr_accessor :title, :text, :comments
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
options.each do |key, value|
|
11
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_markdown
|
16
|
+
MarkdownBuilder.build do |md|
|
17
|
+
md.h2(title)
|
18
|
+
md.md(md.html_to_markdown(text))
|
19
|
+
md.blockquote(md.html_to_markdown(comments)) unless comments.empty?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
{
|
25
|
+
'answer_html' => text,
|
26
|
+
'answer_weight' => title == 'Correct' ? 100 : 0,
|
27
|
+
'answer_comment_html' => comments
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Model
|
5
|
+
module Answer
|
6
|
+
class MultipleChoice
|
7
|
+
attr_accessor :title, :text, :comments
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
options.each do |key, value|
|
11
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_markdown
|
16
|
+
MarkdownBuilder.build do |md|
|
17
|
+
md.h2(title)
|
18
|
+
md.md(md.html_to_markdown(text))
|
19
|
+
md.blockquote(md.html_to_markdown(comments)) unless comments.empty?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
{
|
25
|
+
'answer_html' => text,
|
26
|
+
'answer_weight' => title == 'Correct' ? 100 : 0,
|
27
|
+
'answer_comment_html' => comments
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Model
|
5
|
+
module Answer
|
6
|
+
class MultipleDropdowns
|
7
|
+
attr_accessor :title, :text, :comments, :blank_id
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
options.each do |key, value|
|
11
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_markdown
|
16
|
+
MarkdownBuilder.build do |md|
|
17
|
+
md.h2(title)
|
18
|
+
md.ul(text, blank_id)
|
19
|
+
md.blockquote(md.html_to_markdown(comments)) unless comments.empty?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
{
|
25
|
+
'answer_text' => text,
|
26
|
+
'answer_weight' => title == 'Correct' ? 100 : 0,
|
27
|
+
'answer_comment_html' => comments,
|
28
|
+
'blank_id' => blank_id
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Model
|
5
|
+
module Answer
|
6
|
+
class ShortAnswer
|
7
|
+
attr_accessor :title, :text, :comments
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
options.each do |key, value|
|
11
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_markdown
|
16
|
+
MarkdownBuilder.build do |md|
|
17
|
+
md.h2(title)
|
18
|
+
md.p(text)
|
19
|
+
md.blockquote(md.html_to_markdown(comments)) unless comments.empty?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
{
|
25
|
+
'answer_text' => text,
|
26
|
+
'answer_weight' => title == 'Correct' ? 100 : 0,
|
27
|
+
'answer_comment_html' => comments
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Model
|
5
|
+
module Answer
|
6
|
+
class TrueFalse
|
7
|
+
attr_accessor :title, :text, :comments
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
options.each do |key, value|
|
11
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_markdown
|
16
|
+
MarkdownBuilder.build do |md|
|
17
|
+
md.h2(title)
|
18
|
+
md.p(text)
|
19
|
+
md.blockquote(md.html_to_markdown(comments)) unless comments.empty?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
{
|
25
|
+
'answer_text' => text,
|
26
|
+
'answer_weight' => title == 'Correct' ? 100 : 0,
|
27
|
+
'answer_comment_html' => comments
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Model
|
5
|
+
class Question
|
6
|
+
attr_accessor :course_id, :quiz_id, :id, :type, :sources, :name, :description, :answers, :distractors
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
options.each do |key, value|
|
10
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_markdown
|
15
|
+
MarkdownBuilder.build do |md|
|
16
|
+
md.frontmatter(frontmatter_hash)
|
17
|
+
md.h1(name)
|
18
|
+
md.md(md.html_to_markdown(description))
|
19
|
+
answers.each do |answer|
|
20
|
+
md.md(answer.to_markdown)
|
21
|
+
end
|
22
|
+
unless distractors.empty?
|
23
|
+
md.h2('Incorrect')
|
24
|
+
md.ul(*distractors)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_h
|
30
|
+
{
|
31
|
+
'question_name' => name,
|
32
|
+
'question_text' => description,
|
33
|
+
'question_type' => type,
|
34
|
+
'points_possible' => 1,
|
35
|
+
'neutral_comments_html' => sources.nil? || sources.empty? ? '' : sources_to_html,
|
36
|
+
'answers' => answers.map(&:to_h),
|
37
|
+
'matching_answer_incorrect_matches' => distractors.join("\n")
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def frontmatter_hash
|
42
|
+
{
|
43
|
+
'course_id' => course_id,
|
44
|
+
'quiz_id' => quiz_id,
|
45
|
+
'id' => id,
|
46
|
+
'type' => type,
|
47
|
+
'sources' => sources
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def sources_to_html
|
54
|
+
comments = sources.map do |source|
|
55
|
+
"<a href=\"#{source['url']}\">#{source['name']}</a>"
|
56
|
+
end.join
|
57
|
+
"<p><strong>Source/s:</strong> #{comments}</p>"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Model
|
5
|
+
class Quiz
|
6
|
+
attr_accessor :course_id, :id, :repo, :title, :description
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
options.each do |key, value|
|
10
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_markdown
|
15
|
+
MarkdownBuilder.build do |md|
|
16
|
+
md.frontmatter(frontmatter_hash)
|
17
|
+
md.h1(title)
|
18
|
+
md.md(md.html_to_markdown(description))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_h
|
23
|
+
{
|
24
|
+
'title' => title,
|
25
|
+
'description' => description_with_header,
|
26
|
+
'quiz_type' => 'assignment',
|
27
|
+
'shuffle_answers' => true,
|
28
|
+
'hide_results' => 'until_after_last_attempt',
|
29
|
+
'show_correct_answers_last_attempt' => true,
|
30
|
+
'allowed_attempts' => 3,
|
31
|
+
'scoring_policy' => 'keep_highest',
|
32
|
+
'one_question_at_a_time' => true
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def frontmatter_hash
|
37
|
+
{
|
38
|
+
'id' => id,
|
39
|
+
'course_id' => course_id,
|
40
|
+
'repo' => repo
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def description_with_header
|
47
|
+
[git_links_header, description].reject(&:nil?).join("\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
def git_links_header
|
51
|
+
return unless repo
|
52
|
+
|
53
|
+
<<~HTML
|
54
|
+
<div id='git-data-element' data-org='learn-co-curriculum' data-repo='#{repo}'></div>
|
55
|
+
<header class='fis-header'>
|
56
|
+
<a class='fis-git-link' href='https://github.com/learn-co-curriculum/#{repo}/issues/new' target='_blank' rel='noopener'><img id='issue-img' title='Create New Issue' alt='Create New Issue' /></a>
|
57
|
+
</header>
|
58
|
+
HTML
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Parser
|
5
|
+
module Canvas
|
6
|
+
module Answer
|
7
|
+
class Base
|
8
|
+
include Helpers
|
9
|
+
|
10
|
+
attr_reader :data
|
11
|
+
|
12
|
+
def initialize(data)
|
13
|
+
@data = data
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Parser
|
5
|
+
module Canvas
|
6
|
+
module Answer
|
7
|
+
class FillInMultipleBlanks < Base
|
8
|
+
def parse
|
9
|
+
Model::Answer::FillInMultipleBlanks.new(
|
10
|
+
title: title,
|
11
|
+
text: data['text'],
|
12
|
+
comments: comments,
|
13
|
+
blank_id: data['blank_id']
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def title
|
20
|
+
data.fetch('weight', 0).positive? ? 'Correct' : 'Incorrect'
|
21
|
+
end
|
22
|
+
|
23
|
+
def comments
|
24
|
+
choose_text(data.fetch('comments', ''), data.fetch('comments_html', ''))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Parser
|
5
|
+
module Canvas
|
6
|
+
module Answer
|
7
|
+
class Matching < Base
|
8
|
+
def parse
|
9
|
+
Model::Answer::Matching.new(
|
10
|
+
title: title,
|
11
|
+
text: data['text'],
|
12
|
+
comments: comments,
|
13
|
+
left: data['left'],
|
14
|
+
right: data['right']
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def title
|
21
|
+
data['left'].empty? ? 'Incorrect' : 'Correct'
|
22
|
+
end
|
23
|
+
|
24
|
+
def comments
|
25
|
+
choose_text(data.fetch('comments', ''), data.fetch('comments_html', ''))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Parser
|
5
|
+
module Canvas
|
6
|
+
module Answer
|
7
|
+
class MultipleAnswers < Base
|
8
|
+
def parse
|
9
|
+
Model::Answer::MultipleAnswers.new(
|
10
|
+
title: title,
|
11
|
+
text: text,
|
12
|
+
comments: comments
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def title
|
19
|
+
data.fetch('weight', 0).positive? ? 'Correct' : 'Incorrect'
|
20
|
+
end
|
21
|
+
|
22
|
+
def text
|
23
|
+
choose_text(data.fetch('text', ''), data.fetch('html', ''))
|
24
|
+
end
|
25
|
+
|
26
|
+
def comments
|
27
|
+
choose_text(data.fetch('comments', ''), data.fetch('comments_html', ''))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubToCanvasQuiz
|
4
|
+
module Parser
|
5
|
+
module Canvas
|
6
|
+
module Answer
|
7
|
+
class MultipleChoice < Base
|
8
|
+
def parse
|
9
|
+
Model::Answer::MultipleChoice.new(
|
10
|
+
title: title,
|
11
|
+
text: text,
|
12
|
+
comments: comments
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def title
|
19
|
+
data.fetch('weight', 0).positive? ? 'Correct' : 'Incorrect'
|
20
|
+
end
|
21
|
+
|
22
|
+
def text
|
23
|
+
choose_text(data.fetch('text', ''), data.fetch('html', ''))
|
24
|
+
end
|
25
|
+
|
26
|
+
def comments
|
27
|
+
choose_text(data.fetch('comments', ''), data.fetch('comments_html', ''))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|