qrush-jekyll 0.3.0.1

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.
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'sequel'
3
+ require 'fileutils'
4
+
5
+ # NOTE: This converter requires Sequel and the MySQL gems.
6
+ # The MySQL gem can be difficult to install on OS X. Once you have MySQL
7
+ # installed, running the following commands should work:
8
+ # $ sudo gem install sequel
9
+ # $ sudo gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
10
+
11
+ module Jekyll
12
+ module WordPress
13
+
14
+ # Reads a MySQL database via Sequel and creates a post file for each
15
+ # post in wp_posts that has post_status = 'publish'.
16
+ # This restriction is made because 'draft' posts are not guaranteed to
17
+ # have valid dates.
18
+ QUERY = "select * from wp_posts where post_status = 'publish' and post_type = 'post'"
19
+
20
+ def self.process(dbname, user, pass, host = 'localhost')
21
+ db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host)
22
+
23
+ FileUtils.mkdir_p "_posts"
24
+
25
+ db[QUERY].each do |post|
26
+ # Get required fields and construct Jekyll compatible name
27
+ title = post[:post_title]
28
+ slug = post[:post_name]
29
+ date = post[:post_date]
30
+ content = post[:post_content]
31
+
32
+ name = [date.year, date.month, date.day, slug].join('-') + ".markdown"
33
+
34
+ # Get the relevant fields as a hash, delete empty fields and convert
35
+ # to YAML for the header
36
+ data = {
37
+ 'layout' => 'post',
38
+ 'title' => title.to_s,
39
+ 'excerpt' => post[:post_excerpt].to_s,
40
+ 'wordpress_id' => post[:ID],
41
+ 'wordpress_url' => post[:guid]
42
+ }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
43
+
44
+ # Write out the data and content to file
45
+ File.open("_posts/#{name}", "w") do |f|
46
+ f.puts data
47
+ f.puts "---"
48
+ f.puts content
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,60 @@
1
+ module Jekyll
2
+ module Convertible
3
+ # Return the contents as a string
4
+ def to_s
5
+ self.content || ''
6
+ end
7
+
8
+ # Read the YAML frontmatter
9
+ # +base+ is the String path to the dir containing the file
10
+ # +name+ is the String filename of the file
11
+ #
12
+ # Returns nothing
13
+ def read_yaml(base, name)
14
+ self.content = File.read(File.join(base, name))
15
+
16
+ if self.content =~ /^(---\s*\n.*?)\n---\s*\n/m
17
+ self.content = self.content[($1.size + 5)..-1]
18
+
19
+ self.data = YAML.load($1)
20
+ end
21
+ end
22
+
23
+ # Transform the contents based on the file extension.
24
+ #
25
+ # Returns nothing
26
+ def transform
27
+ case self.ext[1..-1]
28
+ when /textile/i
29
+ self.ext = ".html"
30
+ self.content = RedCloth.new(self.content).to_html
31
+ when /markdown/i, /mkdn/i, /md/i
32
+ self.ext = ".html"
33
+ self.content = Jekyll.markdown_proc.call(self.content)
34
+ end
35
+ end
36
+
37
+ # Add any necessary layouts to this convertible document
38
+ # +layouts+ is a Hash of {"name" => "layout"}
39
+ # +site_payload+ is the site payload hash
40
+ #
41
+ # Returns nothing
42
+ def do_layout(payload, layouts)
43
+ # render and transform content (this becomes the final content of the object)
44
+ self.content = Liquid::Template.parse(self.content).render(payload, [Jekyll::Filters])
45
+ self.transform
46
+
47
+ # output keeps track of what will finally be written
48
+ self.output = self.content
49
+
50
+ # recursively render layouts
51
+ layout = layouts[self.data["layout"]]
52
+ while layout
53
+ payload = payload.deep_merge({"content" => self.output, "page" => layout.data})
54
+ self.output = Liquid::Template.parse(layout.content).render(payload, [Jekyll::Filters])
55
+
56
+ layout = layouts[layout.data["layout"]]
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,22 @@
1
+ class Hash
2
+ # Merges self with another hash, recursively.
3
+ #
4
+ # This code was lovingly stolen from some random gem:
5
+ # http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
6
+ #
7
+ # Thanks to whoever made it.
8
+ def deep_merge(hash)
9
+ target = dup
10
+
11
+ hash.keys.each do |key|
12
+ if hash[key].is_a? Hash and self[key].is_a? Hash
13
+ target[key] = target[key].deep_merge(hash[key])
14
+ next
15
+ end
16
+
17
+ target[key] = hash[key]
18
+ end
19
+
20
+ target
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ module Jekyll
2
+
3
+ module Filters
4
+ def date_to_string(date)
5
+ date.strftime("%d %b %Y")
6
+ end
7
+
8
+ def date_to_long_string(date)
9
+ date.strftime("%d %B %Y")
10
+ end
11
+
12
+ def date_to_xmlschema(date)
13
+ date.xmlschema
14
+ end
15
+
16
+ def xml_escape(input)
17
+ input.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
18
+ end
19
+
20
+ def number_of_words(input)
21
+ input.split.length
22
+ end
23
+
24
+ def array_to_sentence_string(array)
25
+ connector = "and"
26
+ case array.length
27
+ when 0
28
+ ""
29
+ when 1
30
+ array[0].to_s
31
+ when 2
32
+ "#{array[0]} #{connector} #{array[1]}"
33
+ else
34
+ "#{array[0...-1].join(', ')}, #{connector} #{array[-1]}"
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,33 @@
1
+ module Jekyll
2
+
3
+ class Layout
4
+ include Convertible
5
+
6
+ attr_accessor :ext
7
+ attr_accessor :data, :content
8
+
9
+ # Initialize a new Layout.
10
+ # +base+ is the String path to the <source>
11
+ # +name+ is the String filename of the post file
12
+ #
13
+ # Returns <Page>
14
+ def initialize(base, name)
15
+ @base = base
16
+ @name = name
17
+
18
+ self.data = {}
19
+
20
+ self.process(name)
21
+ self.read_yaml(base, name)
22
+ end
23
+
24
+ # Extract information from the layout filename
25
+ # +name+ is the String filename of the layout file
26
+ #
27
+ # Returns nothing
28
+ def process(name)
29
+ self.ext = File.extname(name)
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,64 @@
1
+ module Jekyll
2
+
3
+ class Page
4
+ include Convertible
5
+
6
+ attr_accessor :ext
7
+ attr_accessor :data, :content, :output
8
+
9
+ # Initialize a new Page.
10
+ # +base+ is the String path to the <source>
11
+ # +dir+ is the String path between <source> and the file
12
+ # +name+ is the String filename of the file
13
+ #
14
+ # Returns <Page>
15
+ def initialize(base, dir, name)
16
+ @base = base
17
+ @dir = dir
18
+ @name = name
19
+
20
+ self.data = {}
21
+
22
+ self.process(name)
23
+ self.read_yaml(File.join(base, dir), name)
24
+ #self.transform
25
+ end
26
+
27
+ # Extract information from the page filename
28
+ # +name+ is the String filename of the page file
29
+ #
30
+ # Returns nothing
31
+ def process(name)
32
+ self.ext = File.extname(name)
33
+ end
34
+
35
+ # Add any necessary layouts to this post
36
+ # +layouts+ is a Hash of {"name" => "layout"}
37
+ # +site_payload+ is the site payload hash
38
+ #
39
+ # Returns nothing
40
+ def render(layouts, site_payload)
41
+ payload = {"page" => self.data}.deep_merge(site_payload)
42
+ do_layout(payload, layouts)
43
+ end
44
+
45
+ # Write the generated page file to the destination directory.
46
+ # +dest+ is the String path to the destination dir
47
+ #
48
+ # Returns nothing
49
+ def write(dest)
50
+ FileUtils.mkdir_p(File.join(dest, @dir))
51
+
52
+ name = @name
53
+ if self.ext != ""
54
+ name = @name.split(".")[0..-2].join('.') + self.ext
55
+ end
56
+
57
+ path = File.join(dest, @dir, name)
58
+ File.open(path, 'w') do |f|
59
+ f.write(self.output)
60
+ end
61
+ end
62
+ end
63
+
64
+ end
@@ -0,0 +1,170 @@
1
+ module Jekyll
2
+
3
+ class Post
4
+ include Comparable
5
+ include Convertible
6
+
7
+ class << self
8
+ attr_accessor :lsi
9
+ end
10
+
11
+ MATCHER = /^(.+\/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
12
+
13
+ # Post name validator. Post filenames must be like:
14
+ # 2008-11-05-my-awesome-post.textile
15
+ #
16
+ # Returns <Bool>
17
+ def self.valid?(name)
18
+ name =~ MATCHER
19
+ end
20
+
21
+ attr_accessor :date, :slug, :ext, :categories, :topics
22
+ attr_accessor :data, :content, :output
23
+
24
+ # Initialize this Post instance.
25
+ # +base+ is the String path to the dir containing the post file
26
+ # +name+ is the String filename of the post file
27
+ # +categories+ is an Array of Strings for the categories for this post
28
+ #
29
+ # Returns <Post>
30
+ def initialize(source, dir, name)
31
+ @base = File.join(source, dir, '_posts')
32
+ @name = name
33
+
34
+ self.categories = dir.split('/').reject { |x| x.empty? }
35
+
36
+ parts = name.split('/')
37
+ self.topics = parts.size > 1 ? parts[0..-2] : []
38
+
39
+ self.process(name)
40
+ self.read_yaml(@base, name)
41
+ end
42
+
43
+ # Spaceship is based on Post#date
44
+ #
45
+ # Returns -1, 0, 1
46
+ def <=>(other)
47
+ self.date <=> other.date
48
+ end
49
+
50
+ # Extract information from the post filename
51
+ # +name+ is the String filename of the post file
52
+ #
53
+ # Returns nothing
54
+ def process(name)
55
+ m, cats, date, slug, ext = *name.match(MATCHER)
56
+ self.date = Time.parse(date)
57
+ self.slug = slug
58
+ self.ext = ext
59
+ end
60
+
61
+ # The generated directory into which the post will be placed
62
+ # upon generation. This is derived from the permalink or, if
63
+ # permalink is absent, set to the default date
64
+ # e.g. "/2008/11/05/"
65
+ #
66
+ # Returns <String>
67
+ def dir
68
+ if permalink
69
+ permalink.to_s.split("/")[0..-2].join("/")
70
+ else
71
+ prefix = self.categories.empty? ? '' : '/' + self.categories.join('/')
72
+ prefix + date.strftime("/%Y/%m/%d/")
73
+ end
74
+ end
75
+
76
+ # The full path and filename of the post.
77
+ # Defined in the YAML of the post body
78
+ # (Optional)
79
+ #
80
+ # Returns <String>
81
+ def permalink
82
+ self.data && self.data['permalink']
83
+ end
84
+
85
+ # The generated relative url of this post
86
+ # e.g. /2008/11/05/my-awesome-post.html
87
+ #
88
+ # Returns <String>
89
+ def url
90
+ self.dir + self.slug + ".html"
91
+ end
92
+
93
+ # The UID for this post (useful in feeds)
94
+ # e.g. /2008/11/05/my-awesome-post
95
+ #
96
+ # Returns <String>
97
+ def id
98
+ self.dir + self.slug
99
+ end
100
+
101
+ # Calculate related posts.
102
+ #
103
+ # Returns [<Post>]
104
+ def related_posts(posts)
105
+ return [] unless posts.size > 1
106
+
107
+ if Jekyll.lsi
108
+ self.class.lsi ||= begin
109
+ puts "Running the classifier... this could take a while."
110
+ lsi = Classifier::LSI.new
111
+ posts.each { |x| $stdout.print(".");$stdout.flush;lsi.add_item(x) }
112
+ puts ""
113
+ lsi
114
+ end
115
+
116
+ related = self.class.lsi.find_related(self.content, 11)
117
+ related - [self]
118
+ else
119
+ (posts - [self])[0..9]
120
+ end
121
+ end
122
+
123
+ # Add any necessary layouts to this post
124
+ # +layouts+ is a Hash of {"name" => "layout"}
125
+ # +site_payload+ is the site payload hash
126
+ #
127
+ # Returns nothing
128
+ def render(layouts, site_payload)
129
+ # construct payload
130
+ payload =
131
+ {
132
+ "site" => { "related_posts" => related_posts(site_payload["site"]["posts"]) },
133
+ "page" => self.to_liquid
134
+ }
135
+ payload = payload.deep_merge(site_payload)
136
+
137
+ do_layout(payload, layouts)
138
+ end
139
+
140
+ # Write the generated post file to the destination directory.
141
+ # +dest+ is the String path to the destination dir
142
+ #
143
+ # Returns nothing
144
+ def write(dest)
145
+ FileUtils.mkdir_p(File.join(dest, dir))
146
+
147
+ path = File.join(dest, self.url)
148
+ File.open(path, 'w') do |f|
149
+ f.write(self.output)
150
+ end
151
+ end
152
+
153
+ # Convert this post into a Hash for use in Liquid templates.
154
+ #
155
+ # Returns <Hash>
156
+ def to_liquid
157
+ { "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
158
+ "url" => self.url,
159
+ "date" => self.date,
160
+ "id" => self.id,
161
+ "topics" => self.topics,
162
+ "content" => self.content }.deep_merge(self.data)
163
+ end
164
+
165
+ def inspect
166
+ "<Post: #{self.id}>"
167
+ end
168
+ end
169
+
170
+ end