spirit 0.2 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/spirit.rb +8 -10
- data/lib/spirit/constants.rb +17 -7
- data/lib/spirit/document.rb +4 -9
- data/lib/spirit/errors.rb +0 -1
- data/lib/spirit/logger.rb +5 -6
- data/lib/spirit/manifest.rb +4 -5
- data/lib/spirit/render.rb +0 -2
- data/lib/spirit/render/errors.rb +0 -4
- data/lib/spirit/render/html.rb +17 -117
- data/lib/spirit/render/processable.rb +78 -0
- data/lib/spirit/render/processors.rb +15 -0
- data/lib/spirit/render/processors/base.rb +40 -0
- data/lib/spirit/render/processors/block_image_processor.rb +49 -0
- data/lib/spirit/render/processors/headers_processor.rb +41 -0
- data/lib/spirit/render/processors/layout_processor.rb +28 -0
- data/lib/spirit/render/processors/math_processor.rb +102 -0
- data/lib/spirit/render/processors/problems_processor.rb +76 -0
- data/lib/spirit/render/processors/pygments_processor.rb +22 -0
- data/lib/spirit/render/processors/sanitize_processor.rb +86 -0
- data/lib/spirit/render/templates.rb +1 -3
- data/lib/spirit/render/templates/header.rb +2 -3
- data/lib/spirit/render/templates/image.rb +6 -13
- data/lib/spirit/render/templates/multi.rb +9 -10
- data/lib/spirit/render/templates/navigation.rb +4 -5
- data/lib/spirit/render/templates/problem.rb +24 -28
- data/lib/spirit/render/templates/short.rb +2 -3
- data/lib/spirit/render/templates/table.rb +2 -2
- data/lib/spirit/render/templates/template.rb +12 -8
- data/lib/spirit/version.rb +1 -2
- data/views/header.haml +1 -1
- data/views/img.haml +2 -2
- data/views/layout.haml +27 -0
- data/views/multi.haml +10 -14
- data/views/nav.haml +2 -2
- data/views/short.haml +6 -11
- data/views/table.haml +20 -26
- metadata +36 -57
- data/lib/spirit/render/sanitize.rb +0 -90
- data/views/exe.haml +0 -5
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4844986a578645c04c236ef9ebe931cbfaf7aab9
|
4
|
+
data.tar.gz: 280b2cfbb472f5670eb364e9954c3e42bd87ca5a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 616c1c9e6fbf583efbded87e74baf193637cac7c582e80044165531d2a1beab46bf90530bbdf0e9013a1351a8f0c5e208dcc6c598208c82962a4b267804a57b7
|
7
|
+
data.tar.gz: 867670f2f3533580428c654c466b4d4d16d5e822710e13f98b253b63a865ae7f57c41fa24875af7f5ded5771bc856c16cf14454b76bcd7ae0d5f30398877bc4d
|
data/lib/spirit.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require 'spirit/version'
|
2
2
|
require 'spirit/logger'
|
3
3
|
require 'spirit/constants'
|
4
4
|
require 'spirit/errors'
|
@@ -6,17 +6,15 @@ require 'spirit/document'
|
|
6
6
|
require 'spirit/manifest'
|
7
7
|
|
8
8
|
module Spirit
|
9
|
-
extend self
|
10
9
|
|
11
|
-
|
10
|
+
mattr_accessor :logger
|
11
|
+
@@logger = Logger.new '/dev/null'
|
12
12
|
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
# Invoke with args for {Logger} to enable logging.
|
14
|
+
def self.reset_logger(io=STDOUT, *args)
|
15
|
+
self.logger = Logger.new(io, *args)
|
16
|
+
logger.formatter = Logger::Formatter.new
|
17
|
+
logger.info "Spirit v#{VERSION}"
|
18
18
|
end
|
19
19
|
|
20
|
-
initialize_logger
|
21
|
-
|
22
20
|
end
|
data/lib/spirit/constants.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
# ~*~ encoding: utf-8 ~*~
|
2
1
|
require 'tmpdir'
|
3
2
|
|
4
3
|
module Spirit
|
5
4
|
|
6
5
|
# Path to templates
|
7
|
-
VIEWS = File.join
|
6
|
+
VIEWS = File.join(File.dirname(__FILE__), *%w(.. .. views)).freeze
|
8
7
|
|
9
8
|
# Markdown extensions for Redcarpet
|
10
9
|
MARKDOWN_EXTENSIONS = {
|
@@ -13,15 +12,26 @@ module Spirit
|
|
13
12
|
fenced_code_blocks: true,
|
14
13
|
autolink: true,
|
15
14
|
strikethrough: true,
|
16
|
-
}
|
15
|
+
}.freeze
|
17
16
|
|
18
|
-
|
19
|
-
|
17
|
+
# Renderer configuration options
|
18
|
+
RENDERER_CONFIG = {
|
19
|
+
hard_wrap: true,
|
20
|
+
no_styles: true,
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
HAML_CONFIG = {
|
24
|
+
escape_html: true,
|
25
|
+
format: :html5,
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
SOLUTION_DIR = Dir.tmpdir.freeze
|
29
|
+
SOLUTION_EXT = '.sol'.freeze
|
20
30
|
|
21
31
|
# Name of index page.
|
22
|
-
INDEX = 'index.md'
|
32
|
+
INDEX = 'index.md'.freeze
|
23
33
|
|
24
34
|
# Name of manifest file.
|
25
|
-
MANIFEST = 'manifest.yml'
|
35
|
+
MANIFEST = 'manifest.yml'.freeze
|
26
36
|
|
27
37
|
end
|
data/lib/spirit/document.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
2
|
require 'spirit/constants'
|
3
3
|
require 'spirit/errors'
|
4
4
|
require 'spirit/render'
|
@@ -6,10 +6,10 @@ require 'spirit/render'
|
|
6
6
|
module Spirit
|
7
7
|
|
8
8
|
# Document written in Genie Markup Language.
|
9
|
-
# @todo TODO clean?
|
10
9
|
class Document
|
11
10
|
|
12
|
-
|
11
|
+
attr_reader :data, :engine, :rndr
|
12
|
+
delegate :solutions, to: :rndr
|
13
13
|
|
14
14
|
# Creates a new document from the given source. It should contain valid
|
15
15
|
# markdown + embedded YAML.
|
@@ -17,7 +17,7 @@ module Spirit
|
|
17
17
|
# @param [Hash] opts options for {::Redcarpet}
|
18
18
|
def initialize(source, opts={})
|
19
19
|
opts = MARKDOWN_EXTENSIONS.merge opts
|
20
|
-
rndr
|
20
|
+
@rndr = Render::HTML.new
|
21
21
|
@engine = ::Redcarpet::Markdown.new(rndr, opts)
|
22
22
|
@data = case
|
23
23
|
when source.respond_to?(:to_str) then source.to_str
|
@@ -25,11 +25,6 @@ module Spirit
|
|
25
25
|
else nil end
|
26
26
|
end
|
27
27
|
|
28
|
-
# @return [Boolean] true iff if was a clean parse with no errors.
|
29
|
-
def clean?
|
30
|
-
# TODO
|
31
|
-
end
|
32
|
-
|
33
28
|
# Rendered output is returned as a string if +anIO+ is not provided. The
|
34
29
|
# output is sanitized with {Spirit::Render::Sanitize}, and should be
|
35
30
|
# considered safe for embedding into a HTML page without further escaping or
|
data/lib/spirit/errors.rb
CHANGED
data/lib/spirit/logger.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# ~*~ encoding: utf-8 ~*~
|
2
1
|
require 'active_support/core_ext/logger'
|
3
2
|
|
4
3
|
module Spirit
|
@@ -6,13 +5,13 @@ module Spirit
|
|
6
5
|
# @see https://github.com/chriseppstein/compass/blob/stable/lib/compass/logger.rb
|
7
6
|
class Logger < ::Logger
|
8
7
|
|
9
|
-
COLORS = { clear: 0, red: 31, green: 32, blue: 35, yellow: 33, grey: 37 }
|
8
|
+
COLORS = { clear: 0, red: 31, green: 32, blue: 35, yellow: 33, grey: 37 }.freeze
|
10
9
|
|
11
10
|
ACTION_COLORS = {
|
12
|
-
|
13
|
-
:
|
14
|
-
:
|
15
|
-
}
|
11
|
+
error: :red,
|
12
|
+
warning: :yellow,
|
13
|
+
problem: :blue,
|
14
|
+
}.freeze
|
16
15
|
|
17
16
|
# Record that an action has occurred.
|
18
17
|
def record(action, *args)
|
data/lib/spirit/manifest.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# ~*~ encoding: utf-8 ~*~
|
2
1
|
require 'active_support/core_ext/hash'
|
3
2
|
require 'active_support/core_ext/string'
|
4
3
|
require 'yaml'
|
@@ -14,11 +13,11 @@ module Spirit
|
|
14
13
|
verify: { 'bin' => 'string', 'arg_prefix' => 'string' },
|
15
14
|
title: 'string',
|
16
15
|
description: 'string',
|
17
|
-
categories: %w
|
18
|
-
static_paths: %w
|
19
|
-
}
|
16
|
+
categories: %w[string array],
|
17
|
+
static_paths: %w[string array]
|
18
|
+
}.freeze
|
20
19
|
|
21
|
-
# Creates a new
|
20
|
+
# Creates a new manifest from the given source.
|
22
21
|
def initialize(hash)
|
23
22
|
super nil
|
24
23
|
hash ||= {}
|
data/lib/spirit/render.rb
CHANGED
data/lib/spirit/render/errors.rb
CHANGED
data/lib/spirit/render/html.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
|
2
3
|
require 'spirit/render/errors'
|
3
|
-
require 'spirit/render/sanitize'
|
4
4
|
require 'spirit/render/templates'
|
5
|
+
require 'spirit/render/processable'
|
6
|
+
require 'spirit/render/processors'
|
5
7
|
|
6
8
|
module Spirit
|
7
|
-
|
8
9
|
module Render
|
9
10
|
|
10
11
|
# HTML Renderer for Genie Markup Language, which is just GitHub Flavored
|
@@ -13,124 +14,23 @@ module Spirit
|
|
13
14
|
# @see Spirit::Tilt::Template
|
14
15
|
# @see http://github.github.com/github-flavored-markdown/
|
15
16
|
class HTML < ::Redcarpet::Render::HTML
|
17
|
+
include Processable
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# Paragraphs that start and end with '---' are treated as embedded YAML
|
21
|
-
# and are parsed for questions/answers.
|
22
|
-
PROBLEM_REGEX = /^"""$(.*?)^"""$/m
|
19
|
+
delegate :solutions, to: :problems
|
20
|
+
attr_accessor :navigation, :problems, :nesting
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
}
|
22
|
+
use Processors::SanitizeProcessor
|
23
|
+
use Processors::MathProcessor
|
24
|
+
use Processors::LayoutProcessor
|
25
|
+
use Processors::ProblemsProcessor
|
26
|
+
use Processors::PygmentsProcessor
|
27
|
+
use Processors::BlockImageProcessor
|
28
|
+
use Processors::HeadersProcessor
|
32
29
|
|
33
30
|
# Creates a new HTML renderer.
|
34
|
-
# @param
|
35
|
-
def initialize(
|
36
|
-
super
|
37
|
-
@nav, @headers = Navigation.new, Headers.new
|
38
|
-
@prob, @img = 0, 0 # indices for Problem #, Figure #
|
39
|
-
@name = options.delete(:name) || 'untitled'
|
40
|
-
end
|
41
|
-
|
42
|
-
# Pygmentizes code blocks.
|
43
|
-
# @param [String] code code block contents
|
44
|
-
# @param [String] marker name of language, for syntax highlighting
|
45
|
-
# @return [String] highlighted code
|
46
|
-
def block_code(code, marker)
|
47
|
-
#language, type, id = (marker || 'text').split ':'
|
48
|
-
#highlighted = Albino.colorize code, language
|
49
|
-
language, _, _ = (marker || 'text').split ':'
|
50
|
-
Albino.colorize code, language
|
51
|
-
# TODO
|
52
|
-
#case type
|
53
|
-
#when 'demo', 'test'
|
54
|
-
# executable id: id, raw: code, colored: highlighted
|
55
|
-
#else highlighted end
|
56
|
-
end
|
57
|
-
|
58
|
-
# Detects block images and renders them as such.
|
59
|
-
# @return [String] rendered html
|
60
|
-
def paragraph(text)
|
61
|
-
case text
|
62
|
-
when IMAGE_REGEX then block_image(text)
|
63
|
-
else p(text) end
|
64
|
-
rescue RenderError => e # fall back to paragraph
|
65
|
-
Spirit.logger.warn e.message
|
66
|
-
p(text)
|
67
|
-
end
|
68
|
-
|
69
|
-
# Increases all header levels by one and keeps a navigation bar.
|
70
|
-
# @return [String] rendered html
|
71
|
-
def header(text, level)
|
72
|
-
html, name = h(text, level += 1)
|
73
|
-
@nav.append(text, name) if level == 2
|
74
|
-
html
|
75
|
-
end
|
76
|
-
|
77
|
-
# Runs a first pass through the document to look for problem blocks.
|
78
|
-
# @param [String] document markdown document
|
79
|
-
def preprocess(document)
|
80
|
-
document.gsub(PROBLEM_REGEX) { |yaml| problem $1 }
|
81
|
-
end
|
82
|
-
|
83
|
-
# Sanitizes the final document.
|
84
|
-
# @param [String] document html document
|
85
|
-
# @return [String] sanitized document
|
86
|
-
def postprocess(document)
|
87
|
-
HTML.sanitize.clean(@nav.render + document.force_encoding('utf-8'))
|
88
|
-
end
|
89
|
-
|
90
|
-
private
|
91
|
-
|
92
|
-
# Prepares an executable code block.
|
93
|
-
# @option opts [String] id author-supplied ID
|
94
|
-
# @option opts [String] raw code to execute
|
95
|
-
# @option opts [String] colored syntax highlighted code
|
96
|
-
# @return [String]
|
97
|
-
#def executable(opts)
|
98
|
-
# opts[:colored] + @exe.render(Object.new, id: opts[:id], raw: opts[:raw])
|
99
|
-
#end
|
100
|
-
|
101
|
-
# Prepares a problem form. Returns +yaml+ if the given text does not
|
102
|
-
# contain valid yaml markup for a problem.
|
103
|
-
# @param [String] yaml YAML markup
|
104
|
-
# @return [String] rendered HTML
|
105
|
-
def problem(yaml)
|
106
|
-
problem = Problem.parse(yaml)
|
107
|
-
Spirit.logger.record :problem, "ID: #{problem.id}"
|
108
|
-
problem.save!(@name) and problem.render(index: @prob += 1)
|
109
|
-
rescue RenderError
|
110
|
-
yaml
|
111
|
-
end
|
112
|
-
|
113
|
-
# Prepares a block image. Raises {RenderError} if the given text does not
|
114
|
-
# contain a valid image block.
|
115
|
-
# @param [String] text markdown text
|
116
|
-
# @return [String] rendered HTML
|
117
|
-
def block_image(text)
|
118
|
-
Image.parse(text).render(index: @img += 1)
|
119
|
-
end
|
120
|
-
|
121
|
-
# Wraps the given text with header tags.
|
122
|
-
# @return [String] rendered HTML
|
123
|
-
# @return [String] anchor name
|
124
|
-
def h(text, level)
|
125
|
-
header = @headers.add(text, level)
|
126
|
-
return header.render, header.name
|
127
|
-
end
|
128
|
-
|
129
|
-
# Wraps the given text with paragraph tags.
|
130
|
-
# @param [String] text paragraph text
|
131
|
-
# @return [String] rendered html
|
132
|
-
def p(text)
|
133
|
-
'<p>' + text + '</p>'
|
31
|
+
# @param [Hash] opts described in the RedCarpet documentation
|
32
|
+
def initialize(opts={})
|
33
|
+
super RENDERER_CONFIG.merge opts
|
134
34
|
end
|
135
35
|
|
136
36
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
require 'active_support/concern'
|
3
|
+
|
4
|
+
module Spirit
|
5
|
+
module Render
|
6
|
+
|
7
|
+
# Provides methods to attach processors.
|
8
|
+
module Processable
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
class_attribute :processors,
|
13
|
+
instance_reader: false, instance_writer: false
|
14
|
+
self.processors = {} # array of processor classes
|
15
|
+
attr_accessor :processors # array of processor instances
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
|
20
|
+
# @example using a processor
|
21
|
+
# include Processable
|
22
|
+
# use Processor::MathProcessor
|
23
|
+
def use(processor)
|
24
|
+
processor.events.each do |event|
|
25
|
+
processors[event] ||= []
|
26
|
+
processors[event] << processor
|
27
|
+
define event unless method_defined? event
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def define(event)
|
32
|
+
define_method(event) { |*args| invoke_callbacks event, *args }
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def preprocess(document)
|
38
|
+
instantiate_processors document
|
39
|
+
invoke_callbacks :preprocess, document
|
40
|
+
end
|
41
|
+
|
42
|
+
def postprocess(document)
|
43
|
+
invoke_callbacks_in_reverse :postprocess, document
|
44
|
+
end
|
45
|
+
|
46
|
+
# Instantiate processors. {#processors} becomes a hash from classes to
|
47
|
+
# instances.
|
48
|
+
def instantiate_processors(document)
|
49
|
+
processors = self.class.processors.values.flatten.uniq
|
50
|
+
instances = processors.map { |c| [ c, c.new(self, document) ] }
|
51
|
+
self.processors = Hash[instances]
|
52
|
+
end
|
53
|
+
private :instantiate_processors
|
54
|
+
|
55
|
+
def invoke_callbacks(event, *args)
|
56
|
+
processors = processors_for event
|
57
|
+
processors.each { |p| args = p.invoke_callbacks_for event, *args }
|
58
|
+
args
|
59
|
+
end
|
60
|
+
private :invoke_callbacks
|
61
|
+
|
62
|
+
def invoke_callbacks_in_reverse(event, *args)
|
63
|
+
processors = processors_for(event).reverse
|
64
|
+
processors.each { |p| args = p.invoke_callbacks_for event, *args }
|
65
|
+
args
|
66
|
+
end
|
67
|
+
private :invoke_callbacks_in_reverse
|
68
|
+
|
69
|
+
def processors_for(event)
|
70
|
+
classes = self.class.processors[event] || []
|
71
|
+
classes.map { |c| processors[c] }.compact
|
72
|
+
end
|
73
|
+
private :processors_for
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spirit/render/processors/base'
|
2
|
+
require 'spirit/render/processors/math_processor'
|
3
|
+
require 'spirit/render/processors/sanitize_processor'
|
4
|
+
require 'spirit/render/processors/layout_processor'
|
5
|
+
require 'spirit/render/processors/problems_processor'
|
6
|
+
require 'spirit/render/processors/pygments_processor'
|
7
|
+
require 'spirit/render/processors/block_image_processor'
|
8
|
+
require 'spirit/render/processors/headers_processor'
|
9
|
+
|
10
|
+
module Spirit
|
11
|
+
module Render
|
12
|
+
module Processors
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
require 'spirit/constants'
|
4
|
+
|
5
|
+
module Spirit
|
6
|
+
module Render
|
7
|
+
module Processors
|
8
|
+
|
9
|
+
class Base
|
10
|
+
|
11
|
+
class_attribute :hooks
|
12
|
+
|
13
|
+
def self.inherited(subclass)
|
14
|
+
subclass.hooks = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.process(event, callback)
|
18
|
+
hooks[event] ||= []
|
19
|
+
hooks[event] << callback
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.events
|
23
|
+
hooks.keys
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(*args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def invoke_callbacks_for(event, *args)
|
30
|
+
hooks[event].each do |h|
|
31
|
+
args = public_send(h, *args)
|
32
|
+
end
|
33
|
+
args
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|