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.
Files changed (43) hide show
  1. data/LICENSE +1 -1
  2. data/README.md +8 -31
  3. data/lib/aladdin.rb +22 -23
  4. data/lib/aladdin/app.rb +10 -21
  5. data/lib/aladdin/commands/new.rb +2 -2
  6. data/lib/aladdin/config.rb +18 -32
  7. data/lib/aladdin/constants.rb +0 -18
  8. data/lib/aladdin/submission.rb +5 -4
  9. data/lib/aladdin/support.rb +0 -1
  10. data/lib/aladdin/support/weak_comparator.rb +2 -0
  11. data/lib/aladdin/version.rb +1 -2
  12. data/skeleton/manifest.yml +9 -0
  13. metadata +15 -111
  14. data/lib/aladdin/render.rb +0 -9
  15. data/lib/aladdin/render/error.rb +0 -17
  16. data/lib/aladdin/render/html.rb +0 -136
  17. data/lib/aladdin/render/sanitize.rb +0 -90
  18. data/lib/aladdin/render/templates.rb +0 -2
  19. data/lib/aladdin/render/templates/header.rb +0 -52
  20. data/lib/aladdin/render/templates/image.rb +0 -54
  21. data/lib/aladdin/render/templates/multi.rb +0 -40
  22. data/lib/aladdin/render/templates/navigation.rb +0 -34
  23. data/lib/aladdin/render/templates/problem.rb +0 -114
  24. data/lib/aladdin/render/templates/short.rb +0 -30
  25. data/lib/aladdin/render/templates/table.rb +0 -109
  26. data/lib/aladdin/render/templates/template.rb +0 -34
  27. data/lib/aladdin/support/logger.rb +0 -29
  28. data/skeleton/manifest.json +0 -10
  29. data/views/haml/exe.haml +0 -5
  30. data/views/haml/header.haml +0 -5
  31. data/views/haml/img.haml +0 -6
  32. data/views/haml/multi.haml +0 -16
  33. data/views/haml/nav.haml +0 -5
  34. data/views/haml/short.haml +0 -12
  35. data/views/haml/table.haml +0 -28
  36. data/views/scss/_forms.scss +0 -9
  37. data/views/scss/_github.scss +0 -65
  38. data/views/scss/_mathjax.scss +0 -5
  39. data/views/scss/_pygment.scss +0 -15
  40. data/views/scss/_settings.scss +0 -271
  41. data/views/scss/app.scss +0 -75
  42. data/views/scss/general_foundicons.scss +0 -71
  43. data/views/scss/general_foundicons_ie7.scss +0 -56
@@ -1,9 +0,0 @@
1
- # ~*~ encoding: utf-8 ~*~
2
- require 'albino'
3
- require 'haml'
4
- require 'redcarpet'
5
- require 'htmlentities'
6
- require 'sanitize'
7
-
8
- require 'aladdin/support'
9
- require 'aladdin/render/html'
@@ -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
@@ -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,2 +0,0 @@
1
- base_path = 'aladdin/render/templates/'
2
- %w(template header image problem multi short table navigation).each { |f| require base_path + f }
@@ -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