aladdin 0.0.8 → 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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