aladdin 0.0.8 → 0.1.0.pre
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.
- data/LICENSE +1 -1
- data/README.md +8 -31
- data/lib/aladdin.rb +22 -23
- data/lib/aladdin/app.rb +10 -21
- data/lib/aladdin/commands/new.rb +2 -2
- data/lib/aladdin/config.rb +18 -32
- data/lib/aladdin/constants.rb +0 -18
- data/lib/aladdin/submission.rb +5 -4
- data/lib/aladdin/support.rb +0 -1
- data/lib/aladdin/support/weak_comparator.rb +2 -0
- data/lib/aladdin/version.rb +1 -2
- data/skeleton/manifest.yml +9 -0
- metadata +15 -111
- data/lib/aladdin/render.rb +0 -9
- data/lib/aladdin/render/error.rb +0 -17
- data/lib/aladdin/render/html.rb +0 -136
- data/lib/aladdin/render/sanitize.rb +0 -90
- data/lib/aladdin/render/templates.rb +0 -2
- data/lib/aladdin/render/templates/header.rb +0 -52
- data/lib/aladdin/render/templates/image.rb +0 -54
- data/lib/aladdin/render/templates/multi.rb +0 -40
- data/lib/aladdin/render/templates/navigation.rb +0 -34
- data/lib/aladdin/render/templates/problem.rb +0 -114
- data/lib/aladdin/render/templates/short.rb +0 -30
- data/lib/aladdin/render/templates/table.rb +0 -109
- data/lib/aladdin/render/templates/template.rb +0 -34
- data/lib/aladdin/support/logger.rb +0 -29
- data/skeleton/manifest.json +0 -10
- data/views/haml/exe.haml +0 -5
- data/views/haml/header.haml +0 -5
- data/views/haml/img.haml +0 -6
- data/views/haml/multi.haml +0 -16
- data/views/haml/nav.haml +0 -5
- data/views/haml/short.haml +0 -12
- data/views/haml/table.haml +0 -28
- data/views/scss/_forms.scss +0 -9
- data/views/scss/_github.scss +0 -65
- data/views/scss/_mathjax.scss +0 -5
- data/views/scss/_pygment.scss +0 -15
- data/views/scss/_settings.scss +0 -271
- data/views/scss/app.scss +0 -75
- data/views/scss/general_foundicons.scss +0 -71
- data/views/scss/general_foundicons_ie7.scss +0 -56
data/lib/aladdin/render.rb
DELETED
data/lib/aladdin/render/error.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
# ~*~ encoding: utf-8 ~*~
|
2
|
-
module Aladdin
|
3
|
-
|
4
|
-
module Render
|
5
|
-
|
6
|
-
# The base exception for {Aladdin::Render} Errors
|
7
|
-
class Error < StandardError; end
|
8
|
-
|
9
|
-
# This exception is raised if a parse error occurs.
|
10
|
-
class ParseError < Error; end
|
11
|
-
|
12
|
-
# This exception is raised if a render error occurs.
|
13
|
-
class RenderError < Error; end
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
data/lib/aladdin/render/html.rb
DELETED
@@ -1,136 +0,0 @@
|
|
1
|
-
# ~*~ encoding: utf-8 ~*~
|
2
|
-
require 'aladdin/render/sanitize'
|
3
|
-
require 'aladdin/render/error'
|
4
|
-
require 'aladdin/render/templates'
|
5
|
-
|
6
|
-
module Aladdin
|
7
|
-
|
8
|
-
# aladdin-render module for all of Aladdin's rendering needs.
|
9
|
-
module Render
|
10
|
-
|
11
|
-
# HTML Renderer for Markdown.
|
12
|
-
#
|
13
|
-
# It creates pygmentized code blocks, supports hard-wraps, and only
|
14
|
-
# generates links for protocols which are considered safe. Adds support for
|
15
|
-
# embedded JSON, which are used to markup quizzes and tables. Refer to
|
16
|
-
# {CONFIGURATION} for more details.
|
17
|
-
#
|
18
|
-
# @see http://github.github.com/github-flavored-markdown/
|
19
|
-
class HTML < ::Redcarpet::Render::HTML
|
20
|
-
include Aladdin::Support::Logger
|
21
|
-
|
22
|
-
@sanitize = Sanitize.new
|
23
|
-
@entities = HTMLEntities.new
|
24
|
-
|
25
|
-
class << self; attr_reader :sanitize, :entities; end
|
26
|
-
|
27
|
-
# Paragraphs that start and end with braces are treated as JSON blocks
|
28
|
-
# and are parsed for questions/answers.
|
29
|
-
PROBLEM_REGEX = /\A\s*{.+\z/m
|
30
|
-
|
31
|
-
# Paragraphs that only contain images are rendered differently.
|
32
|
-
IMAGE_REGEX = /\A\s*<img[^<>]+>\s*\z/m
|
33
|
-
|
34
|
-
# Renderer configuration options.
|
35
|
-
CONFIGURATION = {
|
36
|
-
hard_wrap: true,
|
37
|
-
no_styles: true,
|
38
|
-
name: 'untitled'
|
39
|
-
}
|
40
|
-
|
41
|
-
# Creates a new HTML renderer.
|
42
|
-
# @param [Hash] options described in the RedCarpet documentation.
|
43
|
-
def initialize(options = {})
|
44
|
-
super options.merge(CONFIGURATION)
|
45
|
-
exe_template = File.join(Aladdin::VIEWS[:haml], 'exe.haml')
|
46
|
-
@name = options[:name]
|
47
|
-
@exe = Haml::Engine.new(File.read exe_template)
|
48
|
-
@nav, @headers = Navigation.new, Headers.new
|
49
|
-
@prob, @img = 0, 0 # indices for Problem #, Figure #
|
50
|
-
end
|
51
|
-
|
52
|
-
# Pygmentizes code blocks.
|
53
|
-
# @param [String] code code block contents
|
54
|
-
# @param [String] marker name of language, for syntax highlighting
|
55
|
-
# @return [String] highlighted code
|
56
|
-
def block_code(code, marker)
|
57
|
-
language, type, id = (marker || 'text').split ':'
|
58
|
-
highlighted = Albino.colorize code, language
|
59
|
-
case type
|
60
|
-
when 'demo', 'test'
|
61
|
-
executable id: id, raw: code, colored: highlighted
|
62
|
-
else highlighted end
|
63
|
-
end
|
64
|
-
|
65
|
-
# Detects problem blocks and image blocks.
|
66
|
-
# @param [String] text paragraph text
|
67
|
-
def paragraph(text)
|
68
|
-
case text
|
69
|
-
when PROBLEM_REGEX then problem(text)
|
70
|
-
when IMAGE_REGEX then block_image(text)
|
71
|
-
else p(text) end
|
72
|
-
rescue Error => e # fall back to paragraph
|
73
|
-
logger.warn e.message
|
74
|
-
p(text)
|
75
|
-
end
|
76
|
-
|
77
|
-
# Increases all header levels by one and keeps a navigation bar.
|
78
|
-
def header(text, level)
|
79
|
-
html, name = h(text, level += 1)
|
80
|
-
@nav.append(text, name) if level == 2
|
81
|
-
html
|
82
|
-
end
|
83
|
-
|
84
|
-
# Sanitizes the final document.
|
85
|
-
# @param [String] document html document
|
86
|
-
# @return [String] sanitized document
|
87
|
-
def postprocess(document)
|
88
|
-
HTML.sanitize.clean(@nav.render + document.force_encoding('utf-8'))
|
89
|
-
end
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
# Prepares an executable code block.
|
94
|
-
# @option opts [String] id author-supplied ID
|
95
|
-
# @option opts [String] raw code to execute
|
96
|
-
# @option opts [String] colored syntax highlighted code
|
97
|
-
# @return [String]
|
98
|
-
def executable(opts)
|
99
|
-
opts[:colored] + @exe.render(Object.new, id: opts[:id], raw: opts[:raw])
|
100
|
-
end
|
101
|
-
|
102
|
-
# Prepares a problem form. Raises {RenderError} or {ParseError} if the
|
103
|
-
# given text does not contain valid json markup for a problem.
|
104
|
-
# @param [String] json JSON markup
|
105
|
-
# @return [String] rendered HTML
|
106
|
-
def problem(json)
|
107
|
-
b = '\\' # unescape backslashes
|
108
|
-
problem = Problem.parse(HTML.entities.decode(json).gsub(b, b * 4))
|
109
|
-
problem.save! @name and problem.render(index: @prob += 1)
|
110
|
-
end
|
111
|
-
|
112
|
-
# Prepares a block image. Raises {RenderError} or {ParseError} if the
|
113
|
-
# given text does not contain a valid image block.
|
114
|
-
def block_image(text)
|
115
|
-
Image.parse(text).render(index: @img += 1)
|
116
|
-
end
|
117
|
-
|
118
|
-
# Wraps the given text with header tags.
|
119
|
-
# @return [String] rendered HTML
|
120
|
-
# @return [String] anchor name
|
121
|
-
def h(text, level)
|
122
|
-
header = @headers.add(text, level)
|
123
|
-
return header.render, header.name
|
124
|
-
end
|
125
|
-
|
126
|
-
# Wraps the given text with paragraph tags.
|
127
|
-
# @param [String] text paragraph text
|
128
|
-
# @return [String] wrapped text
|
129
|
-
def p(text)
|
130
|
-
'<p>' + text + '</p>'
|
131
|
-
end
|
132
|
-
|
133
|
-
end
|
134
|
-
|
135
|
-
end
|
136
|
-
end
|
@@ -1,90 +0,0 @@
|
|
1
|
-
# ~*~ encoding: utf-8 ~*~
|
2
|
-
|
3
|
-
module Aladdin
|
4
|
-
|
5
|
-
module Render
|
6
|
-
|
7
|
-
# Encapsulate sanitization options.
|
8
|
-
# Adapted from
|
9
|
-
# https://github.com/github/gollum/blob/master/lib/gollum/sanitization.rb
|
10
|
-
class Sanitize < ::Sanitize
|
11
|
-
|
12
|
-
# white-listed elements
|
13
|
-
ELEMENTS = [
|
14
|
-
'a', 'abbr', 'acronym', 'address', 'area', 'b', 'big',
|
15
|
-
'blockquote', 'br', 'button', 'caption', 'center', 'cite',
|
16
|
-
'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir',
|
17
|
-
'div', 'dl', 'dt', 'em', 'fieldset', 'font', 'form', 'h1',
|
18
|
-
'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input',
|
19
|
-
'ins', 'kbd', 'label', 'legend', 'li', 'map', 'menu',
|
20
|
-
'ol', 'optgroup', 'option', 'p', 'pre', 'q', 's', 'samp',
|
21
|
-
'select', 'small', 'span', 'strike', 'strong', 'sub',
|
22
|
-
'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th',
|
23
|
-
'thead', 'tr', 'tt', 'u', 'ul', 'var'
|
24
|
-
].freeze
|
25
|
-
|
26
|
-
# white-listed attributes
|
27
|
-
ATTRIBUTES = {
|
28
|
-
'a' => ['href', 'name', 'data-magellan-destination'],
|
29
|
-
'dd' => ['data-magellan-arrival'],
|
30
|
-
'dl' => ['data-magellan-expedition'],
|
31
|
-
'img' => ['src'],
|
32
|
-
:all => ['abbr', 'accept', 'accept-charset',
|
33
|
-
'accesskey', 'action', 'align', 'alt', 'axis',
|
34
|
-
'border', 'cellpadding', 'cellspacing', 'char',
|
35
|
-
'charoff', 'class', 'charset', 'checked', 'cite',
|
36
|
-
'clear', 'cols', 'colspan', 'color',
|
37
|
-
'compact', 'coords', 'datetime', 'dir',
|
38
|
-
'disabled', 'enctype', 'for', 'frame',
|
39
|
-
'headers', 'height', 'hreflang',
|
40
|
-
'hspace', 'id', 'ismap', 'label', 'lang',
|
41
|
-
'longdesc', 'maxlength', 'media', 'method',
|
42
|
-
'multiple', 'name', 'nohref', 'noshade',
|
43
|
-
'nowrap', 'prompt', 'readonly', 'rel', 'rev',
|
44
|
-
'rows', 'rowspan', 'rules', 'scope',
|
45
|
-
'selected', 'shape', 'size', 'span',
|
46
|
-
'start', 'summary', 'tabindex', 'target',
|
47
|
-
'title', 'type', 'usemap', 'valign', 'value',
|
48
|
-
'vspace', 'width']
|
49
|
-
}.freeze
|
50
|
-
|
51
|
-
# white-listed protocols
|
52
|
-
PROTOCOLS = {
|
53
|
-
'a' => {'href' => ['http', 'https', 'mailto', 'ftp', 'irc', 'apt', :relative]},
|
54
|
-
'img' => {'src' => ['http', 'https', :relative]}
|
55
|
-
}.freeze
|
56
|
-
|
57
|
-
# elements to remove (incl. contents)
|
58
|
-
REMOVE_CONTENTS = [
|
59
|
-
'script',
|
60
|
-
'style'
|
61
|
-
].freeze
|
62
|
-
|
63
|
-
# attributes to add to elements
|
64
|
-
ADD_ATTRIBUTES = {
|
65
|
-
'a' => {'rel' => 'nofollow'}
|
66
|
-
}
|
67
|
-
|
68
|
-
# Creates a new sanitizer with Aladdin's configuration.
|
69
|
-
def initialize
|
70
|
-
super config
|
71
|
-
end
|
72
|
-
|
73
|
-
private
|
74
|
-
|
75
|
-
# @return [Hash] configuration hash.
|
76
|
-
def config
|
77
|
-
{ elements: ELEMENTS.dup,
|
78
|
-
attributes: ATTRIBUTES.dup,
|
79
|
-
protocols: PROTOCOLS.dup,
|
80
|
-
add_attributes: ADD_ATTRIBUTES.dup,
|
81
|
-
remove_contents: REMOVE_CONTENTS.dup,
|
82
|
-
allow_comments: false
|
83
|
-
}
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
end
|
89
|
-
|
90
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
# ~*~ encoding: utf-8 ~*~
|
2
|
-
require 'active_support/core_ext/string/inflections'
|
3
|
-
|
4
|
-
module Aladdin
|
5
|
-
|
6
|
-
module Render
|
7
|
-
|
8
|
-
# Keeps track of headers within the same document. It's responsible for
|
9
|
-
# assigning unique names that can be used in the anchors.
|
10
|
-
class Headers
|
11
|
-
|
12
|
-
def initialize
|
13
|
-
@headers = {}
|
14
|
-
end
|
15
|
-
|
16
|
-
# Adds a new header to the set.
|
17
|
-
# @return [Header] header
|
18
|
-
def add(text, level=1)
|
19
|
-
name = text.parameterize
|
20
|
-
if @headers.include? name
|
21
|
-
name += '-%d' % (@headers[name] += 1)
|
22
|
-
else @headers[name] = 0 end
|
23
|
-
Header.new(text, level, name)
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
# Renders a header (e.g. +h1+, +h2+, ...) with anchors.
|
29
|
-
class Header < Template
|
30
|
-
|
31
|
-
# Name of template file for rendering headers.
|
32
|
-
TEMPLATE = 'header.haml'
|
33
|
-
|
34
|
-
attr_reader :name
|
35
|
-
|
36
|
-
# Creates a new header.
|
37
|
-
# @param [String] text header text
|
38
|
-
# @param [Fixnum] level 1 to 6
|
39
|
-
# @param [String] name anchor name
|
40
|
-
def initialize(text, level, name)
|
41
|
-
@text, @level, @name = text, level, name
|
42
|
-
end
|
43
|
-
|
44
|
-
def render(locals={})
|
45
|
-
super locals.merge(text: @text, level: @level, name: @name)
|
46
|
-
end
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|
@@ -1,54 +0,0 @@
|
|
1
|
-
# ~*~ encoding: utf-8 ~*~
|
2
|
-
require 'nokogiri'
|
3
|
-
|
4
|
-
module Aladdin
|
5
|
-
|
6
|
-
module Render
|
7
|
-
|
8
|
-
# Renders a block image with a figure number.
|
9
|
-
class Image < Template
|
10
|
-
|
11
|
-
# <img ...>
|
12
|
-
IMAGE_TAG = 'img'
|
13
|
-
|
14
|
-
# Name of template file for rendering block images
|
15
|
-
TEMPLATE = 'img.haml'
|
16
|
-
|
17
|
-
class << self
|
18
|
-
|
19
|
-
# Parses the given text for a block image.
|
20
|
-
def parse(text)
|
21
|
-
Image.new text
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
# Creates a new image.
|
27
|
-
def initialize(html)
|
28
|
-
@html = html
|
29
|
-
parse_or_raise
|
30
|
-
end
|
31
|
-
|
32
|
-
def render(locals={})
|
33
|
-
super locals.merge(img: @html, caption: @node['alt'])
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
# Parses the given HTML, or raise {ParseError} if it is invalid.
|
39
|
-
def parse_or_raise
|
40
|
-
frag = Nokogiri::HTML::DocumentFragment.parse(@html)
|
41
|
-
if 1 == frag.children.count and
|
42
|
-
node = frag.children.first and
|
43
|
-
node.is_a? Nokogiri::XML::Element and
|
44
|
-
node.name == IMAGE_TAG
|
45
|
-
@node = node
|
46
|
-
else raise ParseError.new 'Not really a block image.'
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
# ~*~ encoding: utf-8 ~*~
|
2
|
-
module Aladdin
|
3
|
-
|
4
|
-
module Render
|
5
|
-
|
6
|
-
# Renders multiple choice questions marked up in JSON as HTML.
|
7
|
-
# @example
|
8
|
-
#
|
9
|
-
# {
|
10
|
-
# "format": "multi",
|
11
|
-
# "question": "How tall is Mount Everest?",
|
12
|
-
# "answer": "A",
|
13
|
-
# "options": {
|
14
|
-
# "A": "452 inches",
|
15
|
-
# "B": "8.85 kilometers"
|
16
|
-
# }
|
17
|
-
# }
|
18
|
-
class Multi < Problem
|
19
|
-
|
20
|
-
# Required key in JSON markup. Associated value should be a dictionary of
|
21
|
-
# label -> choices.
|
22
|
-
OPTIONS = 'options'
|
23
|
-
|
24
|
-
# Name of template file for rendering multiple choice questions.
|
25
|
-
TEMPLATE = 'multi.haml'
|
26
|
-
|
27
|
-
# Checks if the given json contains a valid MCQ.
|
28
|
-
# @return [Boolean] true iff the json contains a valid MCQ.
|
29
|
-
def valid?
|
30
|
-
super and
|
31
|
-
@json[ANSWER].is_a? String and
|
32
|
-
@json.has_key?(OPTIONS) and
|
33
|
-
@json[OPTIONS].is_a? Hash
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
# ~*~ encoding: utf-8 ~*~
|
2
|
-
module Aladdin
|
3
|
-
|
4
|
-
module Render
|
5
|
-
|
6
|
-
# Keeps track of document sections and renders a navigation bar.
|
7
|
-
class Navigation < Template
|
8
|
-
|
9
|
-
# HAML template for navigation bar
|
10
|
-
TEMPLATE = 'nav.haml'
|
11
|
-
|
12
|
-
# Creates a new navigation bar.
|
13
|
-
def initialize
|
14
|
-
@sections = {}
|
15
|
-
end
|
16
|
-
|
17
|
-
# Adds a new section.
|
18
|
-
# @param [String] heading section heading
|
19
|
-
# @param [String] name anchor name
|
20
|
-
# @return [void]
|
21
|
-
def append(heading, name)
|
22
|
-
@sections[name] = heading
|
23
|
-
end
|
24
|
-
|
25
|
-
# Renders the navigation bar in HTML.
|
26
|
-
def render(locals={})
|
27
|
-
super locals.merge(sections: @sections)
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
@@ -1,114 +0,0 @@
|
|
1
|
-
# ~*~ encoding: utf-8 ~*~
|
2
|
-
module Aladdin
|
3
|
-
|
4
|
-
module Render
|
5
|
-
|
6
|
-
# Renders a single problem. This class doesn't do anything useful; use the
|
7
|
-
# child classes (e.g. {Aladdin::Render::Multi}) instead. Child classes should
|
8
|
-
# override {#valid?}.
|
9
|
-
class Problem < Template
|
10
|
-
|
11
|
-
# Required key in JSON markup. Value indicates type of problem.
|
12
|
-
FORMAT = 'format'
|
13
|
-
|
14
|
-
# Required key in JSON markup. Value contains question body.
|
15
|
-
QUESTION = 'question'
|
16
|
-
|
17
|
-
# Required key in JSON markup. Value contains answers.
|
18
|
-
ANSWER = 'answer'
|
19
|
-
|
20
|
-
# Optional key in JSON markup. Value contains problem ID.
|
21
|
-
ID = 'id'
|
22
|
-
|
23
|
-
# Required keys.
|
24
|
-
KEYS = [FORMAT, QUESTION, ANSWER]
|
25
|
-
|
26
|
-
class << self
|
27
|
-
|
28
|
-
# Parses the given text for questions and answers. If the given text
|
29
|
-
# does not contain valid JSON or does not contain the format key, raises
|
30
|
-
# an {Aladdin::Render::ParseError}.
|
31
|
-
# @param [String] text markdown text
|
32
|
-
def parse(text)
|
33
|
-
json = JSON.parse text
|
34
|
-
if json.is_a?(Hash) and json.has_key?(FORMAT)
|
35
|
-
get_instance(json)
|
36
|
-
else raise ParseError.new("Expected a JSON object containing the #{FORMAT} key.")
|
37
|
-
end
|
38
|
-
rescue JSON::JSONError => e
|
39
|
-
raise ParseError.new(e.message)
|
40
|
-
end
|
41
|
-
|
42
|
-
# Dynamically creates accessor methods for JSON values.
|
43
|
-
# @example
|
44
|
-
# accessor :id
|
45
|
-
def accessor(*args)
|
46
|
-
args.each { |arg| define_method(arg) { @json[arg] } }
|
47
|
-
end
|
48
|
-
|
49
|
-
# @return [Problem] problem
|
50
|
-
def get_instance(json)
|
51
|
-
klass = Aladdin::Render.const_get(json[FORMAT].capitalize)
|
52
|
-
raise NameError.new unless klass < Problem
|
53
|
-
klass.new(json)
|
54
|
-
rescue NameError
|
55
|
-
raise ParseError.new('Unrecognized format: %p' % json[FORMAT])
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
accessor ID, *KEYS
|
61
|
-
|
62
|
-
# Creates a new problem from the given JSON.
|
63
|
-
# @param [Hash] json parsed JSON object
|
64
|
-
def initialize(json)
|
65
|
-
@json = json
|
66
|
-
@json[ID] ||= SecureRandom.uuid
|
67
|
-
end
|
68
|
-
|
69
|
-
# @todo TODO should probably show some error message in the preview,
|
70
|
-
# so that the author doesn't have to read the logs.
|
71
|
-
def render(locals={})
|
72
|
-
raise RenderError.new('Invalid problem.') unless valid?
|
73
|
-
super @json.merge(locals)
|
74
|
-
end
|
75
|
-
|
76
|
-
# Saves the answer to a file on disk.
|
77
|
-
# @todo TODO should probably show some error message in the preview,
|
78
|
-
# so that the author doesn't have to read the logs.
|
79
|
-
def save!(name)
|
80
|
-
raise RenderError.new('Invalid problem.') unless valid?
|
81
|
-
solution = File.join(Aladdin::DATA_DIR, id + Aladdin::SOLUTION_EXT)
|
82
|
-
File.open(solution, 'wb+') { |file| Marshal.dump answer, file }
|
83
|
-
end
|
84
|
-
|
85
|
-
# Retrieves the answer from the given JSON object in a serializable form.
|
86
|
-
# @see #serializable
|
87
|
-
# @return [String, Numeric, TrueClass, FalseClass] answer
|
88
|
-
def answer
|
89
|
-
serialize @json[ANSWER]
|
90
|
-
end
|
91
|
-
|
92
|
-
private
|
93
|
-
|
94
|
-
# If +obj+ is one of String, Numeric, +true+, or +false+, the it's
|
95
|
-
# returned. Otherwise, +to_s+ is invoked on the object and returned.
|
96
|
-
# @return [String, Numeric, TrueClass, FalseClass] answer
|
97
|
-
def serialize(obj)
|
98
|
-
case obj
|
99
|
-
when String, Numeric, TrueClass, FalseClass then obj
|
100
|
-
else obj.to_s end
|
101
|
-
end
|
102
|
-
|
103
|
-
# Checks that all required {KEYS} exist in the JSON, and that the
|
104
|
-
# question is given as a string.
|
105
|
-
# @return [Boolean] true iff the parsed json contains a valid problem.
|
106
|
-
def valid?
|
107
|
-
KEYS.all? { |key| @json.has_key? key } and question.is_a? String
|
108
|
-
end
|
109
|
-
|
110
|
-
end
|
111
|
-
|
112
|
-
end
|
113
|
-
|
114
|
-
end
|