jekyll-reloaded 0.12
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/History.txt +321 -0
- data/LICENSE +21 -0
- data/README.textile +41 -0
- data/Rakefile +161 -0
- data/bin/jekyll +289 -0
- data/cucumber.yml +1 -0
- data/features/create_sites.feature +112 -0
- data/features/embed_filters.feature +60 -0
- data/features/markdown.feature +30 -0
- data/features/pagination.feature +27 -0
- data/features/permalinks.feature +65 -0
- data/features/post_data.feature +153 -0
- data/features/site_configuration.feature +145 -0
- data/features/site_data.feature +82 -0
- data/features/step_definitions/jekyll_steps.rb +145 -0
- data/features/support/env.rb +19 -0
- data/jekyll.gemspec +146 -0
- data/lib/guard/jekyll.rb +57 -0
- data/lib/jekyll/converter.rb +50 -0
- data/lib/jekyll/converters/identity.rb +22 -0
- data/lib/jekyll/converters/markdown.rb +125 -0
- data/lib/jekyll/converters/textile.rb +50 -0
- data/lib/jekyll/convertible.rb +116 -0
- data/lib/jekyll/core_ext.rb +52 -0
- data/lib/jekyll/errors.rb +6 -0
- data/lib/jekyll/filters.rb +118 -0
- data/lib/jekyll/generator.rb +7 -0
- data/lib/jekyll/generators/pagination.rb +113 -0
- data/lib/jekyll/layout.rb +51 -0
- data/lib/jekyll/live_site.rb +216 -0
- data/lib/jekyll/migrators/csv.rb +26 -0
- data/lib/jekyll/migrators/drupal.rb +103 -0
- data/lib/jekyll/migrators/enki.rb +49 -0
- data/lib/jekyll/migrators/joomla.rb +53 -0
- data/lib/jekyll/migrators/marley.rb +52 -0
- data/lib/jekyll/migrators/mephisto.rb +84 -0
- data/lib/jekyll/migrators/mt.rb +86 -0
- data/lib/jekyll/migrators/posterous.rb +67 -0
- data/lib/jekyll/migrators/rss.rb +47 -0
- data/lib/jekyll/migrators/textpattern.rb +58 -0
- data/lib/jekyll/migrators/tumblr.rb +195 -0
- data/lib/jekyll/migrators/typo.rb +51 -0
- data/lib/jekyll/migrators/wordpress.rb +294 -0
- data/lib/jekyll/migrators/wordpressdotcom.rb +70 -0
- data/lib/jekyll/page.rb +160 -0
- data/lib/jekyll/plugin.rb +77 -0
- data/lib/jekyll/post.rb +262 -0
- data/lib/jekyll/site.rb +339 -0
- data/lib/jekyll/static_file.rb +77 -0
- data/lib/jekyll/tags/highlight.rb +118 -0
- data/lib/jekyll/tags/include.rb +37 -0
- data/lib/jekyll/tags/post_url.rb +38 -0
- data/lib/jekyll.rb +134 -0
- data/test/helper.rb +34 -0
- data/test/source/.htaccess +8 -0
- data/test/source/_includes/sig.markdown +3 -0
- data/test/source/_layouts/default.html +27 -0
- data/test/source/_layouts/simple.html +1 -0
- data/test/source/_posts/2008-02-02-not-published.textile +8 -0
- data/test/source/_posts/2008-02-02-published.textile +8 -0
- data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
- data/test/source/_posts/2008-11-21-complex.textile +8 -0
- data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
- data/test/source/_posts/2008-12-13-include.markdown +8 -0
- data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
- data/test/source/_posts/2009-01-27-categories.textile +7 -0
- data/test/source/_posts/2009-01-27-category.textile +7 -0
- data/test/source/_posts/2009-01-27-empty-categories.textile +7 -0
- data/test/source/_posts/2009-01-27-empty-category.textile +7 -0
- data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
- data/test/source/_posts/2009-05-18-empty-tag.textile +6 -0
- data/test/source/_posts/2009-05-18-empty-tags.textile +6 -0
- data/test/source/_posts/2009-05-18-tag.textile +6 -0
- data/test/source/_posts/2009-05-18-tags.textile +9 -0
- data/test/source/_posts/2009-06-22-empty-yaml.textile +3 -0
- data/test/source/_posts/2009-06-22-no-yaml.textile +1 -0
- data/test/source/_posts/2010-01-08-triple-dash.markdown +5 -0
- data/test/source/_posts/2010-01-09-date-override.textile +7 -0
- data/test/source/_posts/2010-01-09-time-override.textile +7 -0
- data/test/source/_posts/2010-01-09-timezone-override.textile +7 -0
- data/test/source/_posts/2010-01-16-override-data.textile +4 -0
- data/test/source/_posts/2011-04-12-md-extension.md +7 -0
- data/test/source/_posts/2011-04-12-text-extension.text +0 -0
- data/test/source/about.html +6 -0
- data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
- data/test/source/contacts.html +5 -0
- data/test/source/css/screen.css +76 -0
- data/test/source/deal.with.dots.html +7 -0
- data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
- data/test/source/index.html +22 -0
- data/test/source/sitemap.xml +32 -0
- data/test/source/win/_posts/2009-05-24-yaml-linebreak.markdown +7 -0
- data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
- data/test/suite.rb +11 -0
- data/test/test_configuration.rb +29 -0
- data/test/test_core_ext.rb +66 -0
- data/test/test_filters.rb +62 -0
- data/test/test_generated_site.rb +72 -0
- data/test/test_kramdown.rb +23 -0
- data/test/test_page.rb +117 -0
- data/test/test_pager.rb +113 -0
- data/test/test_post.rb +450 -0
- data/test/test_rdiscount.rb +18 -0
- data/test/test_redcarpet.rb +21 -0
- data/test/test_redcloth.rb +86 -0
- data/test/test_site.rb +220 -0
- data/test/test_tags.rb +201 -0
- metadata +332 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
|
|
5
|
+
module Filters
|
|
6
|
+
# Convert a Textile string into HTML output.
|
|
7
|
+
#
|
|
8
|
+
# input - The Textile String to convert.
|
|
9
|
+
#
|
|
10
|
+
# Returns the HTML formatted String.
|
|
11
|
+
def textilize(input)
|
|
12
|
+
site = @context.registers[:site]
|
|
13
|
+
converter = site.getConverterImpl(Jekyll::TextileConverter)
|
|
14
|
+
converter.convert(input)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Convert a Markdown string into HTML output.
|
|
18
|
+
#
|
|
19
|
+
# input - The Markdown String to convert.
|
|
20
|
+
#
|
|
21
|
+
# Returns the HTML formatted String.
|
|
22
|
+
def markdownify(input)
|
|
23
|
+
site = @context.registers[:site]
|
|
24
|
+
converter = site.getConverterImpl(Jekyll::MarkdownConverter)
|
|
25
|
+
converter.convert(input)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Format a date in short format e.g. "27 Jan 2011".
|
|
29
|
+
#
|
|
30
|
+
# date - the Time to format.
|
|
31
|
+
#
|
|
32
|
+
# Returns the formatting String.
|
|
33
|
+
def date_to_string(date)
|
|
34
|
+
date.strftime("%d %b %Y")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Format a date in long format e.g. "27 January 2011".
|
|
38
|
+
#
|
|
39
|
+
# date - The Time to format.
|
|
40
|
+
#
|
|
41
|
+
# Returns the formatted String.
|
|
42
|
+
def date_to_long_string(date)
|
|
43
|
+
date.strftime("%d %B %Y")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Format a date for use in XML.
|
|
47
|
+
#
|
|
48
|
+
# date - The Time to format.
|
|
49
|
+
#
|
|
50
|
+
# Examples
|
|
51
|
+
#
|
|
52
|
+
# date_to_xmlschema(Time.now)
|
|
53
|
+
# # => "2011-04-24T20:34:46+08:00"
|
|
54
|
+
#
|
|
55
|
+
# Returns the formatted String.
|
|
56
|
+
def date_to_xmlschema(date)
|
|
57
|
+
date.xmlschema
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def xml_escape(input)
|
|
61
|
+
CGI.escapeHTML(input)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# CGI escape a string for use in a URL. Replaces any special characters
|
|
65
|
+
# with appropriate %XX replacements.
|
|
66
|
+
#
|
|
67
|
+
# input - The String to escape.
|
|
68
|
+
#
|
|
69
|
+
# Examples
|
|
70
|
+
#
|
|
71
|
+
# cgi_escape('foo,bar;baz?')
|
|
72
|
+
# # => "foo%2Cbar%3Bbaz%3F"
|
|
73
|
+
#
|
|
74
|
+
# Returns the escaped String.
|
|
75
|
+
def cgi_escape(input)
|
|
76
|
+
CGI::escape(input)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def uri_escape(input)
|
|
80
|
+
URI.escape(input)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Count the number of words in the input string.
|
|
84
|
+
#
|
|
85
|
+
# input - The String on which to operate.
|
|
86
|
+
#
|
|
87
|
+
# Returns the Integer word count.
|
|
88
|
+
def number_of_words(input)
|
|
89
|
+
input.split.length
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Join an array of things into a string by separating with commes and the
|
|
93
|
+
# word "and" for the last one.
|
|
94
|
+
#
|
|
95
|
+
# array - The Array of Strings to join.
|
|
96
|
+
#
|
|
97
|
+
# Examples
|
|
98
|
+
#
|
|
99
|
+
# array_to_sentence_string(["apples", "oranges", "grapes"])
|
|
100
|
+
# # => "apples, oranges, and grapes"
|
|
101
|
+
#
|
|
102
|
+
# Returns the formatted String.
|
|
103
|
+
def array_to_sentence_string(array)
|
|
104
|
+
connector = "and"
|
|
105
|
+
case array.length
|
|
106
|
+
when 0
|
|
107
|
+
""
|
|
108
|
+
when 1
|
|
109
|
+
array[0].to_s
|
|
110
|
+
when 2
|
|
111
|
+
"#{array[0]} #{connector} #{array[1]}"
|
|
112
|
+
else
|
|
113
|
+
"#{array[0...-1].join(', ')}, #{connector} #{array[-1]}"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
module Jekyll
|
|
2
|
+
|
|
3
|
+
class Pagination < Generator
|
|
4
|
+
# This generator is safe from arbitrary code execution.
|
|
5
|
+
safe true
|
|
6
|
+
|
|
7
|
+
# Generate paginated pages if necessary.
|
|
8
|
+
#
|
|
9
|
+
# site - The Site.
|
|
10
|
+
#
|
|
11
|
+
# Returns nothing.
|
|
12
|
+
def generate(site)
|
|
13
|
+
site.pages.dup.each do |page|
|
|
14
|
+
paginate(site, page) if Pager.pagination_enabled?(site.config, page.name)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Paginates the blog's posts. Renders the index.html file into paginated
|
|
19
|
+
# directories, e.g.: page2/index.html, page3/index.html, etc and adds more
|
|
20
|
+
# site-wide data.
|
|
21
|
+
#
|
|
22
|
+
# site - The Site.
|
|
23
|
+
# page - The index.html Page that requires pagination.
|
|
24
|
+
#
|
|
25
|
+
# {"paginator" => { "page" => <Number>,
|
|
26
|
+
# "per_page" => <Number>,
|
|
27
|
+
# "posts" => [<Post>],
|
|
28
|
+
# "total_posts" => <Number>,
|
|
29
|
+
# "total_pages" => <Number>,
|
|
30
|
+
# "previous_page" => <Number>,
|
|
31
|
+
# "next_page" => <Number> }}
|
|
32
|
+
def paginate(site, page)
|
|
33
|
+
all_posts = site.site_payload['site']['posts']
|
|
34
|
+
pages = Pager.calculate_pages(all_posts, site.config['paginate'].to_i)
|
|
35
|
+
(1..pages).each do |num_page|
|
|
36
|
+
pager = Pager.new(site.config, num_page, all_posts, pages)
|
|
37
|
+
if num_page > 1
|
|
38
|
+
newpage = Page.new(site, site.source, page.dir, page.name)
|
|
39
|
+
newpage.pager = pager
|
|
40
|
+
newpage.dir = File.join(page.dir, "page#{num_page}")
|
|
41
|
+
site.pages << newpage
|
|
42
|
+
else
|
|
43
|
+
page.pager = pager
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class Pager
|
|
50
|
+
attr_reader :page, :per_page, :posts, :total_posts, :total_pages, :previous_page, :next_page
|
|
51
|
+
|
|
52
|
+
# Calculate the number of pages.
|
|
53
|
+
#
|
|
54
|
+
# all_posts - The Array of all Posts.
|
|
55
|
+
# per_page - The Integer of entries per page.
|
|
56
|
+
#
|
|
57
|
+
# Returns the Integer number of pages.
|
|
58
|
+
def self.calculate_pages(all_posts, per_page)
|
|
59
|
+
(all_posts.size.to_f / per_page.to_i).ceil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Determine if pagination is enabled for a given file.
|
|
63
|
+
#
|
|
64
|
+
# config - The configuration Hash.
|
|
65
|
+
# file - The String filename of the file.
|
|
66
|
+
#
|
|
67
|
+
# Returns true if pagination is enabled, false otherwise.
|
|
68
|
+
def self.pagination_enabled?(config, file)
|
|
69
|
+
file == 'index.html' && !config['paginate'].nil?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Initialize a new Pager.
|
|
73
|
+
#
|
|
74
|
+
# config - The Hash configuration of the site.
|
|
75
|
+
# page - The Integer page number.
|
|
76
|
+
# all_posts - The Array of all the site's Posts.
|
|
77
|
+
# num_pages - The Integer number of pages or nil if you'd like the number
|
|
78
|
+
# of pages calculated.
|
|
79
|
+
def initialize(config, page, all_posts, num_pages = nil)
|
|
80
|
+
@page = page
|
|
81
|
+
@per_page = config['paginate'].to_i
|
|
82
|
+
@total_pages = num_pages || Pager.calculate_pages(all_posts, @per_page)
|
|
83
|
+
|
|
84
|
+
if @page > @total_pages
|
|
85
|
+
raise RuntimeError, "page number can't be greater than total pages: #{@page} > #{@total_pages}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
init = (@page - 1) * @per_page
|
|
89
|
+
offset = (init + @per_page - 1) >= all_posts.size ? all_posts.size : (init + @per_page - 1)
|
|
90
|
+
|
|
91
|
+
@total_posts = all_posts.size
|
|
92
|
+
@posts = all_posts[init..offset]
|
|
93
|
+
@previous_page = @page != 1 ? @page - 1 : nil
|
|
94
|
+
@next_page = @page != @total_pages ? @page + 1 : nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Convert this Pager's data to a Hash suitable for use by Liquid.
|
|
98
|
+
#
|
|
99
|
+
# Returns the Hash representation of this Pager.
|
|
100
|
+
def to_liquid
|
|
101
|
+
{
|
|
102
|
+
'page' => page,
|
|
103
|
+
'per_page' => per_page,
|
|
104
|
+
'posts' => posts,
|
|
105
|
+
'total_posts' => total_posts,
|
|
106
|
+
'total_pages' => total_pages,
|
|
107
|
+
'previous_page' => previous_page,
|
|
108
|
+
'next_page' => next_page
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Jekyll
|
|
2
|
+
|
|
3
|
+
class Layout
|
|
4
|
+
include Convertible
|
|
5
|
+
|
|
6
|
+
# Gets the Site object.
|
|
7
|
+
attr_reader :site
|
|
8
|
+
|
|
9
|
+
# Gets/Sets the extension of this layout.
|
|
10
|
+
attr_accessor :ext
|
|
11
|
+
|
|
12
|
+
# Gets/Sets the Hash that holds the metadata for this layout.
|
|
13
|
+
attr_accessor :data
|
|
14
|
+
|
|
15
|
+
# Gets/Sets the content of this layout.
|
|
16
|
+
attr_accessor :content
|
|
17
|
+
|
|
18
|
+
# Initialize a new Layout.
|
|
19
|
+
#
|
|
20
|
+
# site - The Site.
|
|
21
|
+
# base - The String path to the source.
|
|
22
|
+
# name - The String filename of the post file.
|
|
23
|
+
def initialize(site, base, name)
|
|
24
|
+
@site = site
|
|
25
|
+
@base = base
|
|
26
|
+
@name = name
|
|
27
|
+
|
|
28
|
+
self.process(name)
|
|
29
|
+
self.read_yaml(base, name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The source filename for this layout.
|
|
33
|
+
def filename
|
|
34
|
+
File.join(@base, @name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def inspect
|
|
38
|
+
"<Layout: @name=#{@name.inspect}>"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Extract information from the layout filename.
|
|
42
|
+
#
|
|
43
|
+
# name - The String filename of the layout file.
|
|
44
|
+
#
|
|
45
|
+
# Returns nothing.
|
|
46
|
+
def process(name)
|
|
47
|
+
self.ext = File.extname(name)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
require 'jekyll'
|
|
2
|
+
require 'jekyll/site'
|
|
3
|
+
|
|
4
|
+
module Jekyll
|
|
5
|
+
|
|
6
|
+
class LiveSite < Site
|
|
7
|
+
def process_files files
|
|
8
|
+
pages = []
|
|
9
|
+
Array(files).each do |filename|
|
|
10
|
+
resolve_file(filename) { |page| pages << page }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
return false if pages.empty?
|
|
14
|
+
|
|
15
|
+
payload = nil
|
|
16
|
+
|
|
17
|
+
for page in pages
|
|
18
|
+
if page.respond_to? :render
|
|
19
|
+
payload ||= self.site_payload
|
|
20
|
+
page.read_yaml('', page.filename)
|
|
21
|
+
# TODO: remove post if stopped being published?
|
|
22
|
+
page.render(self.layouts, payload)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if page.write(self.dest)
|
|
26
|
+
yield page.destination(self.dest)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def resolve_file filename
|
|
32
|
+
path = File.expand_path filename, self.source
|
|
33
|
+
file = ContentFile.new path, self.source, self.dest, self.method(:allow_file?)
|
|
34
|
+
unless file.valid?
|
|
35
|
+
warn "invalid file: #{file.path}"
|
|
36
|
+
return
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
case file.type
|
|
40
|
+
when :post
|
|
41
|
+
page = self.posts.find { |p| p.filename == file.path }
|
|
42
|
+
page ||= create_post(file.relative_dir, file.name)
|
|
43
|
+
yield page
|
|
44
|
+
when :layout
|
|
45
|
+
if pair = self.layouts.find { |_, p| p.filename == file.path }
|
|
46
|
+
layout_name = pair[0]
|
|
47
|
+
else
|
|
48
|
+
layout_name = file.name.sub(/\.[^.]+$/, '')
|
|
49
|
+
self.layouts[layout_name] = Layout.new(self, file.dir, file.name)
|
|
50
|
+
end
|
|
51
|
+
[self.posts, self.pages].each do |group|
|
|
52
|
+
group.each { |p| yield p if using_layout?(layout_name, p) }
|
|
53
|
+
end
|
|
54
|
+
when :file
|
|
55
|
+
page = nil
|
|
56
|
+
[self.pages, self.static_files].each do |group|
|
|
57
|
+
if page = group.find { |p| p.filename == file.path }
|
|
58
|
+
break
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
page ||= if has_yaml_header? file.path
|
|
63
|
+
new_page = Page.new(self, self.source, file.dir, file.name)
|
|
64
|
+
self.pages << new_page
|
|
65
|
+
new_page
|
|
66
|
+
else
|
|
67
|
+
new_file = StaticFile.new(self, self.source, file.dir, file.name)
|
|
68
|
+
self.static_files << new_file
|
|
69
|
+
new_file
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
yield page
|
|
73
|
+
else
|
|
74
|
+
raise "unknown file type: #{file.type}"
|
|
75
|
+
end
|
|
76
|
+
rescue InvalidPost => e
|
|
77
|
+
warn e.message
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class ContentFile
|
|
81
|
+
attr_reader :path, :dir, :name
|
|
82
|
+
alias to_s path
|
|
83
|
+
|
|
84
|
+
TYPE_MAPPING = {
|
|
85
|
+
'_layouts' => :layout,
|
|
86
|
+
'_includes' => :include,
|
|
87
|
+
'_posts' => :post
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
def initialize path, source, dest, allowed_checker = nil
|
|
91
|
+
@path = path
|
|
92
|
+
@source = source
|
|
93
|
+
@destination = dest
|
|
94
|
+
@allowed_checker = allowed_checker || lambda {|f| true }
|
|
95
|
+
@dir, @name = File.split @path
|
|
96
|
+
@type = :file
|
|
97
|
+
@valid_dir = nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def type
|
|
101
|
+
deep_check
|
|
102
|
+
@type
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def relative_dir
|
|
106
|
+
@relative_dir ||= self.dir.sub(File.join(@source, ''), '')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def valid?
|
|
110
|
+
!File.symlink? path and
|
|
111
|
+
@allowed_checker.call(name) and
|
|
112
|
+
deep_check
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def deep_check
|
|
116
|
+
return @valid_dir unless @valid_dir.nil?
|
|
117
|
+
@valid_dir = valid_dir? self.dir
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def valid_dir? path
|
|
121
|
+
if path == '/'
|
|
122
|
+
false
|
|
123
|
+
elsif path == @destination
|
|
124
|
+
false
|
|
125
|
+
elsif path == @source
|
|
126
|
+
true
|
|
127
|
+
elsif File.symlink? path
|
|
128
|
+
false
|
|
129
|
+
else
|
|
130
|
+
dir, name = File.split path
|
|
131
|
+
if type = TYPE_MAPPING[name]
|
|
132
|
+
@type = type
|
|
133
|
+
elsif !@allowed_checker.call(name)
|
|
134
|
+
return false
|
|
135
|
+
end
|
|
136
|
+
valid_dir? dir
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class InvalidPost < StandardError; end
|
|
142
|
+
|
|
143
|
+
def create_post dir, name
|
|
144
|
+
path = File.join dir, name
|
|
145
|
+
# TODO: hack
|
|
146
|
+
dir, name = path.split('/_posts/', 2)
|
|
147
|
+
|
|
148
|
+
if Post.valid? name
|
|
149
|
+
post = Post.new(self, self.source, dir, name)
|
|
150
|
+
|
|
151
|
+
if publish_post? post
|
|
152
|
+
self.posts << post
|
|
153
|
+
post.categories.each { |c| self.categories[c] << post }
|
|
154
|
+
post.tags.each { |c| self.tags[c] << post }
|
|
155
|
+
post
|
|
156
|
+
else
|
|
157
|
+
raise InvalidPost, "post not publishable: #{post.inspect}"
|
|
158
|
+
end
|
|
159
|
+
else
|
|
160
|
+
raise InvalidPost, "not a valid post: #{name.inspect}"
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def publish_post? post
|
|
165
|
+
post.published && (self.future || post.date <= self.time)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def using_layout? name, page, seen = nil
|
|
169
|
+
if using = page.data['layout'] and (seen.nil? or !seen.include?(using))
|
|
170
|
+
using == name or using_layout?(name, self.layouts[using], (seen || []) << using)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def has_yaml_header? file
|
|
175
|
+
'---' == File.open(file) { |fd| fd.read(3) }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
FILTER_PREFIXES = %w[ . _ # ]
|
|
179
|
+
FILTER_SUFFIXES = %w[ ~ ]
|
|
180
|
+
|
|
181
|
+
# Filter out any files/directories that are hidden or backup files (start
|
|
182
|
+
# with ".", "_", or "#", or end with "~"), or are excluded by the site
|
|
183
|
+
# configuration, unless they are whitelisted by the site configuration.
|
|
184
|
+
#
|
|
185
|
+
# To be able to check symlinks, this method expects to be run from the
|
|
186
|
+
# directory where entries originate.
|
|
187
|
+
#
|
|
188
|
+
# entries - The Array of file/directory entries to filter.
|
|
189
|
+
#
|
|
190
|
+
# Returns the Array of filtered entries.
|
|
191
|
+
def filter_entries(entries)
|
|
192
|
+
entries.select { |entry|
|
|
193
|
+
allow_file? entry and !File.symlink? entry
|
|
194
|
+
}
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def allow_file? entry
|
|
198
|
+
whitelisted_file? entry or
|
|
199
|
+
(allow_file_name? entry and !blacklisted_file? entry)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def allow_file_name? entry
|
|
203
|
+
!(FILTER_PREFIXES.include? entry[0,1] or
|
|
204
|
+
FILTER_SUFFIXES.include? entry[-1,1])
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def whitelisted_file? entry
|
|
208
|
+
self.include.include? entry
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def blacklisted_file? entry
|
|
212
|
+
self.exclude.include? entry
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Jekyll
|
|
2
|
+
module CSV
|
|
3
|
+
# Reads a csv with title, permalink, body, published_at, and filter.
|
|
4
|
+
# It creates a post file for each row in the csv
|
|
5
|
+
def self.process(file = "posts.csv")
|
|
6
|
+
FileUtils.mkdir_p "_posts"
|
|
7
|
+
posts = 0
|
|
8
|
+
FasterCSV.foreach(file) do |row|
|
|
9
|
+
next if row[0] == "title"
|
|
10
|
+
posts += 1
|
|
11
|
+
name = row[3].split(" ")[0]+"-"+row[1]+(row[4] =~ /markdown/ ? ".markdown" : ".textile")
|
|
12
|
+
File.open("_posts/#{name}", "w") do |f|
|
|
13
|
+
f.puts <<-HEADER
|
|
14
|
+
---
|
|
15
|
+
layout: post
|
|
16
|
+
title: #{row[0]}
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
HEADER
|
|
20
|
+
f.puts row[2]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
"Created #{posts} posts!"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'sequel'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
|
|
6
|
+
# NOTE: This converter requires Sequel and the MySQL gems.
|
|
7
|
+
# The MySQL gem can be difficult to install on OS X. Once you have MySQL
|
|
8
|
+
# installed, running the following commands should work:
|
|
9
|
+
# $ sudo gem install sequel
|
|
10
|
+
# $ sudo gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
|
|
11
|
+
|
|
12
|
+
module Jekyll
|
|
13
|
+
module Drupal
|
|
14
|
+
# Reads a MySQL database via Sequel and creates a post file for each post
|
|
15
|
+
# in wp_posts that has post_status = 'publish'. This restriction is made
|
|
16
|
+
# because 'draft' posts are not guaranteed to have valid dates.
|
|
17
|
+
QUERY = "SELECT n.nid, \
|
|
18
|
+
n.title, \
|
|
19
|
+
nr.body, \
|
|
20
|
+
n.created, \
|
|
21
|
+
n.status \
|
|
22
|
+
FROM node AS n, \
|
|
23
|
+
node_revisions AS nr \
|
|
24
|
+
WHERE (n.type = 'blog' OR n.type = 'story') \
|
|
25
|
+
AND n.vid = nr.vid"
|
|
26
|
+
|
|
27
|
+
def self.process(dbname, user, pass, host = 'localhost', prefix = '')
|
|
28
|
+
db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
|
|
29
|
+
|
|
30
|
+
if prefix != ''
|
|
31
|
+
QUERY[" node "] = " " + prefix + "node "
|
|
32
|
+
QUERY[" node_revisions "] = " " + prefix + "node_revisions "
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
FileUtils.mkdir_p "_posts"
|
|
36
|
+
FileUtils.mkdir_p "_drafts"
|
|
37
|
+
|
|
38
|
+
# Create the refresh layout
|
|
39
|
+
# Change the refresh url if you customized your permalink config
|
|
40
|
+
File.open("_layouts/refresh.html", "w") do |f|
|
|
41
|
+
f.puts <<EOF
|
|
42
|
+
<!DOCTYPE html>
|
|
43
|
+
<html>
|
|
44
|
+
<head>
|
|
45
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
|
46
|
+
<meta http-equiv="refresh" content="0;url={{ page.refresh_to_post_id }}.html" />
|
|
47
|
+
</head>
|
|
48
|
+
</html>
|
|
49
|
+
EOF
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
db[QUERY].each do |post|
|
|
53
|
+
# Get required fields and construct Jekyll compatible name
|
|
54
|
+
node_id = post[:nid]
|
|
55
|
+
title = post[:title]
|
|
56
|
+
content = post[:body]
|
|
57
|
+
created = post[:created]
|
|
58
|
+
time = Time.at(created)
|
|
59
|
+
is_published = post[:status] == 1
|
|
60
|
+
dir = is_published ? "_posts" : "_drafts"
|
|
61
|
+
slug = title.strip.downcase.gsub(/(&|&)/, ' and ').gsub(/[\s\.\/\\]/, '-').gsub(/[^\w-]/, '').gsub(/[-_]{2,}/, '-').gsub(/^[-_]/, '').gsub(/[-_]$/, '')
|
|
62
|
+
name = time.strftime("%Y-%m-%d-") + slug + '.md'
|
|
63
|
+
|
|
64
|
+
# Get the relevant fields as a hash, delete empty fields and convert
|
|
65
|
+
# to YAML for the header
|
|
66
|
+
data = {
|
|
67
|
+
'layout' => 'post',
|
|
68
|
+
'title' => title.to_s,
|
|
69
|
+
'created' => created,
|
|
70
|
+
}.delete_if { |k,v| v.nil? || v == ''}.to_yaml
|
|
71
|
+
|
|
72
|
+
# Write out the data and content to file
|
|
73
|
+
File.open("#{dir}/#{name}", "w") do |f|
|
|
74
|
+
f.puts data
|
|
75
|
+
f.puts "---"
|
|
76
|
+
f.puts content
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Make a file to redirect from the old Drupal URL
|
|
80
|
+
if is_published
|
|
81
|
+
aliases = db["SELECT dst FROM #{prefix}url_alias WHERE src = ?", "node/#{node_id}"].all
|
|
82
|
+
|
|
83
|
+
aliases.push(:dst => "node/#{node_id}")
|
|
84
|
+
|
|
85
|
+
aliases.each do |url_alias|
|
|
86
|
+
FileUtils.mkdir_p url_alias[:dst]
|
|
87
|
+
File.open("#{url_alias[:dst]}/index.md", "w") do |f|
|
|
88
|
+
f.puts "---"
|
|
89
|
+
f.puts "layout: refresh"
|
|
90
|
+
f.puts "refresh_to_post_id: /#{time.strftime("%Y/%m/%d/") + slug}"
|
|
91
|
+
f.puts "---"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# TODO: Make dirs & files for nodes of type 'page'
|
|
98
|
+
# Make refresh pages for these as well
|
|
99
|
+
|
|
100
|
+
# TODO: Make refresh dirs & files according to entries in url_alias table
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Adapted by Rodrigo Pinto <rodrigopqn@gmail.com>
|
|
2
|
+
# Based on typo.rb by Toby DiPasquale
|
|
3
|
+
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'rubygems'
|
|
6
|
+
require 'sequel'
|
|
7
|
+
|
|
8
|
+
module Jekyll
|
|
9
|
+
module Enki
|
|
10
|
+
SQL = <<-EOS
|
|
11
|
+
SELECT p.id,
|
|
12
|
+
p.title,
|
|
13
|
+
p.slug,
|
|
14
|
+
p.body,
|
|
15
|
+
p.published_at as date,
|
|
16
|
+
p.cached_tag_list as tags
|
|
17
|
+
FROM posts p
|
|
18
|
+
EOS
|
|
19
|
+
|
|
20
|
+
# Just working with postgres, but can be easily adapted
|
|
21
|
+
# to work with both mysql and postgres.
|
|
22
|
+
def self.process(dbname, user, pass, host = 'localhost')
|
|
23
|
+
FileUtils.mkdir_p('_posts')
|
|
24
|
+
db = Sequel.postgres(:database => dbname,
|
|
25
|
+
:user => user,
|
|
26
|
+
:password => pass,
|
|
27
|
+
:host => host,
|
|
28
|
+
:encoding => 'utf8')
|
|
29
|
+
|
|
30
|
+
db[SQL].each do |post|
|
|
31
|
+
name = [ sprintf("%.04d", post[:date].year),
|
|
32
|
+
sprintf("%.02d", post[:date].month),
|
|
33
|
+
sprintf("%.02d", post[:date].day),
|
|
34
|
+
post[:slug].strip ].join('-')
|
|
35
|
+
name += '.textile'
|
|
36
|
+
|
|
37
|
+
File.open("_posts/#{name}", 'w') do |f|
|
|
38
|
+
f.puts({ 'layout' => 'post',
|
|
39
|
+
'title' => post[:title].to_s,
|
|
40
|
+
'enki_id' => post[:id],
|
|
41
|
+
'categories' => post[:tags]
|
|
42
|
+
}.delete_if { |k, v| v.nil? || v == '' }.to_yaml)
|
|
43
|
+
f.puts '---'
|
|
44
|
+
f.puts post[:body].delete("\r")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|