moft 1.0.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.
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/bin/moft +83 -0
- data/lib/moft.rb +89 -0
- data/lib/moft/command.rb +27 -0
- data/lib/moft/commands/build.rb +64 -0
- data/lib/moft/commands/new.rb +50 -0
- data/lib/moft/commands/serve.rb +33 -0
- data/lib/moft/configuration.rb +171 -0
- data/lib/moft/converter.rb +48 -0
- data/lib/moft/converters/identity.rb +21 -0
- data/lib/moft/converters/markdown.rb +43 -0
- data/lib/moft/converters/markdown/kramdown_parser.rb +44 -0
- data/lib/moft/converters/markdown/maruku_parser.rb +47 -0
- data/lib/moft/converters/markdown/rdiscount_parser.rb +26 -0
- data/lib/moft/converters/markdown/redcarpet_parser.rb +40 -0
- data/lib/moft/converters/textile.rb +50 -0
- data/lib/moft/convertible.rb +152 -0
- data/lib/moft/core_ext.rb +68 -0
- data/lib/moft/deprecator.rb +34 -0
- data/lib/moft/draft.rb +35 -0
- data/lib/moft/errors.rb +4 -0
- data/lib/moft/filters.rb +141 -0
- data/lib/moft/generator.rb +4 -0
- data/lib/moft/generators/pagination.rb +131 -0
- data/lib/moft/layout.rb +42 -0
- data/lib/moft/logger.rb +52 -0
- data/lib/moft/mime.types +85 -0
- data/lib/moft/page.rb +147 -0
- data/lib/moft/plugin.rb +75 -0
- data/lib/moft/post.rb +377 -0
- data/lib/moft/site.rb +422 -0
- data/lib/moft/static_file.rb +70 -0
- data/lib/moft/tags/gist.rb +30 -0
- data/lib/moft/tags/highlight.rb +83 -0
- data/lib/moft/tags/include.rb +37 -0
- data/lib/moft/tags/post_url.rb +46 -0
- data/lib/site_template/_config.yml +1 -0
- data/lib/site_template/_layouts/default.html +38 -0
- data/lib/site_template/_layouts/post.html +6 -0
- data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +24 -0
- data/lib/site_template/css/screen.css +189 -0
- data/lib/site_template/css/syntax.css +60 -0
- data/lib/site_template/images/.gitkeep +0 -0
- data/lib/site_template/images/rss.png +0 -0
- data/lib/site_template/index.html +13 -0
- data/moft.gemspec +100 -0
- metadata +412 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
module Moft
|
2
|
+
class Converter < Plugin
|
3
|
+
# Public: Get or set the pygments prefix. When an argument is specified,
|
4
|
+
# the prefix will be set. If no argument is specified, the current prefix
|
5
|
+
# will be returned.
|
6
|
+
#
|
7
|
+
# pygments_prefix - The String prefix (default: nil).
|
8
|
+
#
|
9
|
+
# Returns the String prefix.
|
10
|
+
def self.pygments_prefix(pygments_prefix = nil)
|
11
|
+
@pygments_prefix = pygments_prefix if pygments_prefix
|
12
|
+
@pygments_prefix
|
13
|
+
end
|
14
|
+
|
15
|
+
# Public: Get or set the pygments suffix. When an argument is specified,
|
16
|
+
# the suffix will be set. If no argument is specified, the current suffix
|
17
|
+
# will be returned.
|
18
|
+
#
|
19
|
+
# pygments_suffix - The String suffix (default: nil).
|
20
|
+
#
|
21
|
+
# Returns the String suffix.
|
22
|
+
def self.pygments_suffix(pygments_suffix = nil)
|
23
|
+
@pygments_suffix = pygments_suffix if pygments_suffix
|
24
|
+
@pygments_suffix
|
25
|
+
end
|
26
|
+
|
27
|
+
# Initialize the converter.
|
28
|
+
#
|
29
|
+
# Returns an initialized Converter.
|
30
|
+
def initialize(config = {})
|
31
|
+
@config = config
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get the pygments prefix.
|
35
|
+
#
|
36
|
+
# Returns the String prefix.
|
37
|
+
def pygments_prefix
|
38
|
+
self.class.pygments_prefix
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get the pygments suffix.
|
42
|
+
#
|
43
|
+
# Returns the String suffix.
|
44
|
+
def pygments_suffix
|
45
|
+
self.class.pygments_suffix
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Moft
|
2
|
+
module Converters
|
3
|
+
class Markdown < Converter
|
4
|
+
safe true
|
5
|
+
|
6
|
+
pygments_prefix "\n"
|
7
|
+
pygments_suffix "\n"
|
8
|
+
|
9
|
+
def setup
|
10
|
+
return if @setup
|
11
|
+
@parser = case @config['markdown']
|
12
|
+
when 'redcarpet'
|
13
|
+
RedcarpetParser.new @config
|
14
|
+
when 'kramdown'
|
15
|
+
KramdownParser.new @config
|
16
|
+
when 'rdiscount'
|
17
|
+
RDiscountParser.new @config
|
18
|
+
when 'maruku'
|
19
|
+
MarukuParser.new @config
|
20
|
+
else
|
21
|
+
STDERR.puts "Invalid Markdown processor: #{@config['markdown']}"
|
22
|
+
STDERR.puts " Valid options are [ maruku | rdiscount | kramdown | redcarpet ]"
|
23
|
+
raise FatalException.new("Invalid Markdown process: #{@config['markdown']}")
|
24
|
+
end
|
25
|
+
@setup = true
|
26
|
+
end
|
27
|
+
|
28
|
+
def matches(ext)
|
29
|
+
rgx = '(' + @config['markdown_ext'].gsub(',','|') +')'
|
30
|
+
ext =~ Regexp.new(rgx, Regexp::IGNORECASE)
|
31
|
+
end
|
32
|
+
|
33
|
+
def output_ext(ext)
|
34
|
+
".html"
|
35
|
+
end
|
36
|
+
|
37
|
+
def convert(content)
|
38
|
+
setup
|
39
|
+
@parser.convert(content)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Moft
|
2
|
+
module Converters
|
3
|
+
class Markdown
|
4
|
+
class KramdownParser
|
5
|
+
def initialize(config)
|
6
|
+
require 'kramdown'
|
7
|
+
@config = config
|
8
|
+
rescue LoadError
|
9
|
+
STDERR.puts 'You are missing a library required for Markdown. Please run:'
|
10
|
+
STDERR.puts ' $ [sudo] gem install kramdown'
|
11
|
+
raise FatalException.new("Missing dependency: kramdown")
|
12
|
+
end
|
13
|
+
|
14
|
+
def convert(content)
|
15
|
+
# Check for use of coderay
|
16
|
+
kramdown_configs = if @config['kramdown']['use_coderay']
|
17
|
+
base_kramdown_configs.merge({
|
18
|
+
:coderay_wrap => @config['kramdown']['coderay']['coderay_wrap'],
|
19
|
+
:coderay_line_numbers => @config['kramdown']['coderay']['coderay_line_numbers'],
|
20
|
+
:coderay_line_number_start => @config['kramdown']['coderay']['coderay_line_number_start'],
|
21
|
+
:coderay_tab_width => @config['kramdown']['coderay']['coderay_tab_width'],
|
22
|
+
:coderay_bold_every => @config['kramdown']['coderay']['coderay_bold_every'],
|
23
|
+
:coderay_css => @config['kramdown']['coderay']['coderay_css']
|
24
|
+
})
|
25
|
+
else
|
26
|
+
# not using coderay
|
27
|
+
base_kramdown_configs
|
28
|
+
end
|
29
|
+
Kramdown::Document.new(content, kramdown_configs).to_html
|
30
|
+
end
|
31
|
+
|
32
|
+
def base_kramdown_configs
|
33
|
+
{
|
34
|
+
:auto_ids => @config['kramdown']['auto_ids'],
|
35
|
+
:footnote_nr => @config['kramdown']['footnote_nr'],
|
36
|
+
:entity_output => @config['kramdown']['entity_output'],
|
37
|
+
:toc_levels => @config['kramdown']['toc_levels'],
|
38
|
+
:smart_quotes => @config['kramdown']['smart_quotes']
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Moft
|
2
|
+
module Converters
|
3
|
+
class Markdown
|
4
|
+
class MarukuParser
|
5
|
+
def initialize(config)
|
6
|
+
require 'maruku'
|
7
|
+
@config = config
|
8
|
+
if @config['maruku']['use_divs']
|
9
|
+
load_divs_library
|
10
|
+
end
|
11
|
+
if @config['maruku']['use_tex']
|
12
|
+
load_blahtext_library
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
STDERR.puts 'You are missing a library required for Markdown. Please run:'
|
16
|
+
STDERR.puts ' $ [sudo] gem install maruku'
|
17
|
+
raise FatalException.new("Missing dependency: maruku")
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_divs_library
|
21
|
+
require 'maruku/ext/div'
|
22
|
+
STDERR.puts 'Maruku: Using extended syntax for div elements.'
|
23
|
+
end
|
24
|
+
|
25
|
+
def load_blahtext_library
|
26
|
+
require 'maruku/ext/math'
|
27
|
+
STDERR.puts "Maruku: Using LaTeX extension. Images in `#{@config['maruku']['png_dir']}`."
|
28
|
+
|
29
|
+
# Switch off MathML output
|
30
|
+
MaRuKu::Globals[:html_math_output_mathml] = false
|
31
|
+
MaRuKu::Globals[:html_math_engine] = 'none'
|
32
|
+
|
33
|
+
# Turn on math to PNG support with blahtex
|
34
|
+
# Resulting PNGs stored in `images/latex`
|
35
|
+
MaRuKu::Globals[:html_math_output_png] = true
|
36
|
+
MaRuKu::Globals[:html_png_engine] = @config['maruku']['png_engine']
|
37
|
+
MaRuKu::Globals[:html_png_dir] = @config['maruku']['png_dir']
|
38
|
+
MaRuKu::Globals[:html_png_url] = @config['maruku']['png_url']
|
39
|
+
end
|
40
|
+
|
41
|
+
def convert(content)
|
42
|
+
Maruku.new(content).to_html
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Moft
|
2
|
+
module Converters
|
3
|
+
class Markdown
|
4
|
+
class RDiscountParser
|
5
|
+
def initialize(config)
|
6
|
+
require 'rdiscount'
|
7
|
+
@config = config
|
8
|
+
@rdiscount_extensions = @config['rdiscount']['extensions'].map { |e| e.to_sym }
|
9
|
+
rescue LoadError
|
10
|
+
STDERR.puts 'You are missing a library required for Markdown. Please run:'
|
11
|
+
STDERR.puts ' $ [sudo] gem install rdiscount'
|
12
|
+
raise FatalException.new("Missing dependency: rdiscount")
|
13
|
+
end
|
14
|
+
|
15
|
+
def convert(content)
|
16
|
+
rd = RDiscount.new(content, *@rdiscount_extensions)
|
17
|
+
html = rd.to_html
|
18
|
+
if rd.generate_toc and html.include?(@config['rdiscount']['toc_token'])
|
19
|
+
html.gsub!(@config['rdiscount']['toc_token'], rd.toc_content.force_encoding('utf-8'))
|
20
|
+
end
|
21
|
+
html
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Moft
|
2
|
+
module Converters
|
3
|
+
class Markdown
|
4
|
+
class RedcarpetParser
|
5
|
+
def initialize(config)
|
6
|
+
require 'redcarpet'
|
7
|
+
@config = config
|
8
|
+
@redcarpet_extensions = {}
|
9
|
+
@config['redcarpet']['extensions'].each { |e| @redcarpet_extensions[e.to_sym] = true }
|
10
|
+
|
11
|
+
@renderer ||= Class.new(Redcarpet::Render::HTML) do
|
12
|
+
def block_code(code, lang)
|
13
|
+
lang = lang && lang.split.first || "text"
|
14
|
+
output = add_code_tags(
|
15
|
+
Pygments.highlight(code, :lexer => lang, :options => { :encoding => 'utf-8' }),
|
16
|
+
lang
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_code_tags(code, lang)
|
21
|
+
code = code.sub(/<pre>/,'<pre><code class="' + lang + '">')
|
22
|
+
code = code.sub(/<\/pre>/,"</code></pre>")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
rescue LoadError
|
26
|
+
STDERR.puts 'You are missing a library required for Markdown. Please run:'
|
27
|
+
STDERR.puts ' $ [sudo] gem install redcarpet'
|
28
|
+
raise FatalException.new("Missing dependency: redcarpet")
|
29
|
+
end
|
30
|
+
|
31
|
+
def convert(content)
|
32
|
+
@redcarpet_extensions[:fenced_code_blocks] = !@redcarpet_extensions[:no_fenced_code_blocks]
|
33
|
+
@renderer.send :include, Redcarpet::Render::SmartyPants if @redcarpet_extensions[:smart]
|
34
|
+
markdown = Redcarpet::Markdown.new(@renderer.new(@redcarpet_extensions), @redcarpet_extensions)
|
35
|
+
markdown.render(content)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Moft
|
2
|
+
module Converters
|
3
|
+
class Textile < Converter
|
4
|
+
safe true
|
5
|
+
|
6
|
+
pygments_prefix '<notextile>'
|
7
|
+
pygments_suffix '</notextile>'
|
8
|
+
|
9
|
+
def setup
|
10
|
+
return if @setup
|
11
|
+
require 'redcloth'
|
12
|
+
@setup = true
|
13
|
+
rescue LoadError
|
14
|
+
STDERR.puts 'You are missing a library required for Textile. Please run:'
|
15
|
+
STDERR.puts ' $ [sudo] gem install RedCloth'
|
16
|
+
raise FatalException.new("Missing dependency: RedCloth")
|
17
|
+
end
|
18
|
+
|
19
|
+
def matches(ext)
|
20
|
+
rgx = '(' + @config['textile_ext'].gsub(',','|') +')'
|
21
|
+
ext =~ Regexp.new(rgx, Regexp::IGNORECASE)
|
22
|
+
end
|
23
|
+
|
24
|
+
def output_ext(ext)
|
25
|
+
".html"
|
26
|
+
end
|
27
|
+
|
28
|
+
def convert(content)
|
29
|
+
setup
|
30
|
+
|
31
|
+
# Shortcut if config doesn't contain RedCloth section
|
32
|
+
return RedCloth.new(content).to_html if @config['redcloth'].nil?
|
33
|
+
|
34
|
+
# List of attributes defined on RedCloth
|
35
|
+
# (from http://redcloth.rubyforge.org/classes/RedCloth/TextileDoc.html)
|
36
|
+
attrs = ['filter_classes', 'filter_html', 'filter_ids', 'filter_styles',
|
37
|
+
'hard_breaks', 'lite_mode', 'no_span_caps', 'sanitize_html']
|
38
|
+
|
39
|
+
r = RedCloth.new(content)
|
40
|
+
|
41
|
+
# Set attributes in r if they are NOT nil in the config
|
42
|
+
attrs.each do |attr|
|
43
|
+
r.instance_variable_set("@#{attr}".to_sym, @config['redcloth'][attr]) unless @config['redcloth'][attr].nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
r.to_html
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
# Convertible provides methods for converting a pagelike item
|
6
|
+
# from a certain type of markup into actual content
|
7
|
+
#
|
8
|
+
# Requires
|
9
|
+
# self.site -> Moft::Site
|
10
|
+
# self.content
|
11
|
+
# self.content=
|
12
|
+
# self.data=
|
13
|
+
# self.ext=
|
14
|
+
# self.output=
|
15
|
+
# self.name
|
16
|
+
module Moft
|
17
|
+
module Convertible
|
18
|
+
# Returns the contents as a String.
|
19
|
+
def to_s
|
20
|
+
self.content || ''
|
21
|
+
end
|
22
|
+
|
23
|
+
# Read the YAML frontmatter.
|
24
|
+
#
|
25
|
+
# base - The String path to the dir containing the file.
|
26
|
+
# name - The String filename of the file.
|
27
|
+
#
|
28
|
+
# Returns nothing.
|
29
|
+
def read_yaml(base, name)
|
30
|
+
begin
|
31
|
+
self.content = File.read(File.join(base, name))
|
32
|
+
|
33
|
+
if self.content =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
34
|
+
self.content = $POSTMATCH
|
35
|
+
self.data = YAML.safe_load($1)
|
36
|
+
end
|
37
|
+
rescue SyntaxError => e
|
38
|
+
puts "YAML Exception reading #{File.join(base, name)}: #{e.message}"
|
39
|
+
rescue Exception => e
|
40
|
+
puts "Error reading file #{File.join(base, name)}: #{e.message}"
|
41
|
+
end
|
42
|
+
|
43
|
+
self.data ||= {}
|
44
|
+
end
|
45
|
+
|
46
|
+
# Transform the contents based on the content type.
|
47
|
+
#
|
48
|
+
# Returns nothing.
|
49
|
+
def transform
|
50
|
+
self.content = converter.convert(self.content)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Determine the extension depending on content_type.
|
54
|
+
#
|
55
|
+
# Returns the String extension for the output file.
|
56
|
+
# e.g. ".html" for an HTML output file.
|
57
|
+
def output_ext
|
58
|
+
converter.output_ext(self.ext)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Determine which converter to use based on this convertible's
|
62
|
+
# extension.
|
63
|
+
#
|
64
|
+
# Returns the Converter instance.
|
65
|
+
def converter
|
66
|
+
@converter ||= self.site.converters.find { |c| c.matches(self.ext) }
|
67
|
+
end
|
68
|
+
|
69
|
+
# Render Liquid in the content
|
70
|
+
#
|
71
|
+
# content - the raw Liquid content to render
|
72
|
+
# payload - the payload for Liquid
|
73
|
+
# info - the info for Liquid
|
74
|
+
#
|
75
|
+
# Returns the converted content
|
76
|
+
def render_liquid(content, payload, info)
|
77
|
+
Liquid::Template.parse(content).render!(payload, info)
|
78
|
+
rescue Exception => e
|
79
|
+
Moft::Logger.error "Liquid Exception:", "#{e.message} in #{payload[:file]}"
|
80
|
+
e.backtrace.each do |backtrace|
|
81
|
+
puts backtrace
|
82
|
+
end
|
83
|
+
abort("Build Failed")
|
84
|
+
end
|
85
|
+
|
86
|
+
# Recursively render layouts
|
87
|
+
#
|
88
|
+
# layouts - a list of the layouts
|
89
|
+
# payload - the payload for Liquid
|
90
|
+
# info - the info for Liquid
|
91
|
+
#
|
92
|
+
# Returns nothing
|
93
|
+
def render_all_layouts(layouts, payload, info)
|
94
|
+
# recursively render layouts
|
95
|
+
layout = layouts[self.data["layout"]]
|
96
|
+
used = Set.new([layout])
|
97
|
+
|
98
|
+
while layout
|
99
|
+
payload = payload.deep_merge({"content" => self.output, "page" => layout.data})
|
100
|
+
|
101
|
+
self.output = self.render_liquid(layout.content,
|
102
|
+
payload.merge({:file => self.data["layout"]}),
|
103
|
+
info)
|
104
|
+
|
105
|
+
if layout = layouts[layout.data["layout"]]
|
106
|
+
if used.include?(layout)
|
107
|
+
layout = nil # avoid recursive chain
|
108
|
+
else
|
109
|
+
used << layout
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Add any necessary layouts to this convertible document.
|
116
|
+
#
|
117
|
+
# payload - The site payload Hash.
|
118
|
+
# layouts - A Hash of {"name" => "layout"}.
|
119
|
+
#
|
120
|
+
# Returns nothing.
|
121
|
+
def do_layout(payload, layouts)
|
122
|
+
info = { :filters => [Moft::Filters], :registers => { :site => self.site, :page => payload['page'] } }
|
123
|
+
|
124
|
+
# render and transform content (this becomes the final content of the object)
|
125
|
+
payload["pygments_prefix"] = converter.pygments_prefix
|
126
|
+
payload["pygments_suffix"] = converter.pygments_suffix
|
127
|
+
|
128
|
+
self.content = self.render_liquid(self.content,
|
129
|
+
payload.merge({:file => self.name}),
|
130
|
+
info)
|
131
|
+
self.transform
|
132
|
+
|
133
|
+
# output keeps track of what will finally be written
|
134
|
+
self.output = self.content
|
135
|
+
|
136
|
+
self.render_all_layouts(layouts, payload, info)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Write the generated page file to the destination directory.
|
140
|
+
#
|
141
|
+
# dest - The String path to the destination dir.
|
142
|
+
#
|
143
|
+
# Returns nothing.
|
144
|
+
def write(dest)
|
145
|
+
path = destination(dest)
|
146
|
+
FileUtils.mkdir_p(File.dirname(path))
|
147
|
+
File.open(path, 'w') do |f|
|
148
|
+
f.write(self.output)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|