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.
Files changed (48) hide show
  1. data/Gemfile +2 -0
  2. data/LICENSE +21 -0
  3. data/bin/moft +83 -0
  4. data/lib/moft.rb +89 -0
  5. data/lib/moft/command.rb +27 -0
  6. data/lib/moft/commands/build.rb +64 -0
  7. data/lib/moft/commands/new.rb +50 -0
  8. data/lib/moft/commands/serve.rb +33 -0
  9. data/lib/moft/configuration.rb +171 -0
  10. data/lib/moft/converter.rb +48 -0
  11. data/lib/moft/converters/identity.rb +21 -0
  12. data/lib/moft/converters/markdown.rb +43 -0
  13. data/lib/moft/converters/markdown/kramdown_parser.rb +44 -0
  14. data/lib/moft/converters/markdown/maruku_parser.rb +47 -0
  15. data/lib/moft/converters/markdown/rdiscount_parser.rb +26 -0
  16. data/lib/moft/converters/markdown/redcarpet_parser.rb +40 -0
  17. data/lib/moft/converters/textile.rb +50 -0
  18. data/lib/moft/convertible.rb +152 -0
  19. data/lib/moft/core_ext.rb +68 -0
  20. data/lib/moft/deprecator.rb +34 -0
  21. data/lib/moft/draft.rb +35 -0
  22. data/lib/moft/errors.rb +4 -0
  23. data/lib/moft/filters.rb +141 -0
  24. data/lib/moft/generator.rb +4 -0
  25. data/lib/moft/generators/pagination.rb +131 -0
  26. data/lib/moft/layout.rb +42 -0
  27. data/lib/moft/logger.rb +52 -0
  28. data/lib/moft/mime.types +85 -0
  29. data/lib/moft/page.rb +147 -0
  30. data/lib/moft/plugin.rb +75 -0
  31. data/lib/moft/post.rb +377 -0
  32. data/lib/moft/site.rb +422 -0
  33. data/lib/moft/static_file.rb +70 -0
  34. data/lib/moft/tags/gist.rb +30 -0
  35. data/lib/moft/tags/highlight.rb +83 -0
  36. data/lib/moft/tags/include.rb +37 -0
  37. data/lib/moft/tags/post_url.rb +46 -0
  38. data/lib/site_template/_config.yml +1 -0
  39. data/lib/site_template/_layouts/default.html +38 -0
  40. data/lib/site_template/_layouts/post.html +6 -0
  41. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +24 -0
  42. data/lib/site_template/css/screen.css +189 -0
  43. data/lib/site_template/css/syntax.css +60 -0
  44. data/lib/site_template/images/.gitkeep +0 -0
  45. data/lib/site_template/images/rss.png +0 -0
  46. data/lib/site_template/index.html +13 -0
  47. data/moft.gemspec +100 -0
  48. 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,21 @@
1
+ module Moft
2
+ module Converters
3
+ class Identity < Converter
4
+ safe true
5
+
6
+ priority :lowest
7
+
8
+ def matches(ext)
9
+ true
10
+ end
11
+
12
+ def output_ext(ext)
13
+ ext
14
+ end
15
+
16
+ def convert(content)
17
+ content
18
+ end
19
+ end
20
+ end
21
+ 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