elq-jekyll 0.4.2

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 (42) hide show
  1. data/History.txt +105 -0
  2. data/README.textile +490 -0
  3. data/VERSION.yml +4 -0
  4. data/bin/jekyll +137 -0
  5. data/lib/jekyll.rb +67 -0
  6. data/lib/jekyll/albino.rb +119 -0
  7. data/lib/jekyll/converters/csv.rb +26 -0
  8. data/lib/jekyll/converters/mephisto.rb +79 -0
  9. data/lib/jekyll/converters/mt.rb +59 -0
  10. data/lib/jekyll/converters/textpattern.rb +50 -0
  11. data/lib/jekyll/converters/typo.rb +49 -0
  12. data/lib/jekyll/converters/wordpress.rb +56 -0
  13. data/lib/jekyll/convertible.rb +71 -0
  14. data/lib/jekyll/core_ext.rb +22 -0
  15. data/lib/jekyll/filters.rb +43 -0
  16. data/lib/jekyll/layout.rb +33 -0
  17. data/lib/jekyll/page.rb +64 -0
  18. data/lib/jekyll/post.rb +188 -0
  19. data/lib/jekyll/site.rb +170 -0
  20. data/lib/jekyll/tags/highlight.rb +56 -0
  21. data/lib/jekyll/tags/include.rb +31 -0
  22. data/test/helper.rb +14 -0
  23. data/test/source/_includes/sig.markdown +3 -0
  24. data/test/source/_layouts/default.html +27 -0
  25. data/test/source/_layouts/simple.html +1 -0
  26. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  27. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  28. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  29. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  30. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  31. data/test/source/css/screen.css +76 -0
  32. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  33. data/test/source/index.html +22 -0
  34. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  35. data/test/suite.rb +9 -0
  36. data/test/test_filters.rb +41 -0
  37. data/test/test_generated_site.rb +32 -0
  38. data/test/test_jekyll.rb +0 -0
  39. data/test/test_post.rb +135 -0
  40. data/test/test_site.rb +36 -0
  41. data/test/test_tags.rb +31 -0
  42. metadata +211 -0
@@ -0,0 +1,50 @@
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 TextPattern
13
+ # Reads a MySQL database via Sequel and creates a post file for each post.
14
+ # The only posts selected are those with a status of 4 or 5, which means "live"
15
+ # and "sticky" respectively.
16
+ # Other statuses is 1 => draft, 2 => hidden and 3 => pending
17
+ QUERY = "select Title, url_title, Posted, Body, Keywords from textpattern where Status = '4' or Status = '5'"
18
+
19
+ def self.process(dbname, user, pass, host = 'localhost')
20
+ db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host)
21
+
22
+ FileUtils.mkdir_p "_posts"
23
+
24
+ db[QUERY].each do |post|
25
+ # Get required fields and construct Jekyll compatible name
26
+ title = post[:Title]
27
+ slug = post[:url_title]
28
+ date = post[:Posted]
29
+ content = post[:Body]
30
+
31
+ name = [date.strftime("%Y-%m-%d"), slug].join('-') + ".textile"
32
+
33
+ # Get the relevant fields as a hash, delete empty fields and convert
34
+ # to YAML for the header
35
+ data = {
36
+ 'layout' => 'post',
37
+ 'title' => title.to_s,
38
+ 'tags' => post[:Keywords].split(',')
39
+ }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
40
+
41
+ # Write out the data and content to file
42
+ File.open("_posts/#{name}", "w") do |f|
43
+ f.puts data
44
+ f.puts "---"
45
+ f.puts content
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,49 @@
1
+ # Author: Toby DiPasquale <toby@cbcg.net>
2
+ require 'fileutils'
3
+ require 'rubygems'
4
+ require 'sequel'
5
+
6
+ module Jekyll
7
+ module Typo
8
+ # this SQL *should* work for both MySQL and PostgreSQL, but I haven't
9
+ # tested PostgreSQL yet (as of 2008-12-16)
10
+ SQL = <<-EOS
11
+ SELECT c.id id,
12
+ c.title title,
13
+ c.permalink slug,
14
+ c.body body,
15
+ c.published_at date,
16
+ c.state state,
17
+ COALESCE(tf.name, 'html') filter
18
+ FROM contents c
19
+ LEFT OUTER JOIN text_filters tf
20
+ ON c.text_filter_id = tf.id
21
+ EOS
22
+
23
+ def self.process dbname, user, pass, host='localhost'
24
+ FileUtils.mkdir_p '_posts'
25
+ db = Sequel.mysql dbname, :user => user, :password => pass, :host => host
26
+ db[SQL].each do |post|
27
+ next unless post[:state] =~ /Published/
28
+
29
+ name = [ sprintf("%.04d", post[:date].year),
30
+ sprintf("%.02d", post[:date].month),
31
+ sprintf("%.02d", post[:date].day),
32
+ post[:slug].strip ].join('-')
33
+ # Can have more than one text filter in this field, but we just want
34
+ # the first one for this
35
+ name += '.' + post[:filter].split(' ')[0]
36
+
37
+ File.open("_posts/#{name}", 'w') do |f|
38
+ f.puts({ 'layout' => 'post',
39
+ 'title' => post[:title].to_s,
40
+ 'typo_id' => post[:id]
41
+ }.delete_if { |k, v| v.nil? || v == '' }.to_yaml)
42
+ f.puts '---'
43
+ f.puts post[:body].delete("\r")
44
+ end
45
+ end
46
+ end
47
+
48
+ end # module Typo
49
+ end # module Jekyll
@@ -0,0 +1,56 @@
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 "table" that has post_status = 'publish'.
16
+ # This restriction is made because 'draft' posts are not guaranteed to
17
+ # have valid dates.
18
+ def self.process(dbname, user, pass, host = 'localhost', table = "wp_posts")
19
+ db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host)
20
+
21
+ FileUtils.mkdir_p "_posts"
22
+
23
+ sql = "SELECT * FROM #{table} WHERE post_status = 'publish' and post_type = 'post'"
24
+
25
+ db[QUERY].each do |post|
26
+ # Get required fields and construct Jekyll compatible name
27
+ title = post[:post_title]
28
+ #sanitize
29
+ slug = post[:post_name].gsub(/\//,'_')
30
+ date = post[:post_date]
31
+ content = post[:post_content]
32
+
33
+ name = "%02d-%02d-%02d-%s.html" % [date.year, date.month, date.day,
34
+ slug]
35
+
36
+ # Get the relevant fields as a hash, delete empty fields and convert
37
+ # to YAML for the header
38
+ data = {
39
+ 'layout' => 'post',
40
+ 'title' => title.to_s,
41
+ 'excerpt' => post[:post_excerpt].to_s,
42
+ 'wordpress_id' => post[:ID],
43
+ 'wordpress_url' => post[:guid]
44
+ }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
45
+
46
+ # Write out the data and content to file
47
+ File.open("_posts/#{name}", "w") do |f|
48
+ f.puts data
49
+ f.puts "---"
50
+ f.puts content
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,71 @@
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 Jekyll.content_type
28
+ when :textile
29
+ self.ext = ".html"
30
+ self.content = RedCloth.new(self.content).to_html
31
+ when :markdown
32
+ self.ext = ".html"
33
+ self.content = Jekyll.markdown_proc.call(self.content)
34
+ end
35
+ end
36
+
37
+ def determine_content_type
38
+ case self.ext[1..-1]
39
+ when /textile/i
40
+ return :textile
41
+ when /markdown/i, /mkdn/i, /md/i
42
+ return :markdown
43
+ end
44
+ return :unknown
45
+ end
46
+
47
+ # Add any necessary layouts to this convertible document
48
+ # +layouts+ is a Hash of {"name" => "layout"}
49
+ # +site_payload+ is the site payload hash
50
+ #
51
+ # Returns nothing
52
+ def do_layout(payload, layouts)
53
+ # render and transform content (this becomes the final content of the object)
54
+ Jekyll.content_type = self.determine_content_type
55
+ self.content = Liquid::Template.parse(self.content).render(payload, [Jekyll::Filters])
56
+ self.transform
57
+
58
+ # output keeps track of what will finally be written
59
+ self.output = self.content
60
+
61
+ # recursively render layouts
62
+ layout = layouts[self.data["layout"]]
63
+ while layout
64
+ payload = payload.deep_merge({"content" => self.output, "page" => layout.data})
65
+ self.output = Liquid::Template.parse(layout.content).render(payload, [Jekyll::Filters])
66
+
67
+ layout = layouts[layout.data["layout"]]
68
+ end
69
+ end
70
+ end
71
+ 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,43 @@
1
+ module Jekyll
2
+
3
+ module Filters
4
+ def textilize(input)
5
+ RedCloth.new(input).to_html
6
+ end
7
+
8
+ def date_to_string(date)
9
+ date.strftime("%d %b %Y")
10
+ end
11
+
12
+ def date_to_long_string(date)
13
+ date.strftime("%d %B %Y")
14
+ end
15
+
16
+ def date_to_xmlschema(date)
17
+ date.xmlschema
18
+ end
19
+
20
+ def xml_escape(input)
21
+ input.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
22
+ end
23
+
24
+ def number_of_words(input)
25
+ input.split.length
26
+ end
27
+
28
+ def array_to_sentence_string(array)
29
+ connector = "and"
30
+ case array.length
31
+ when 0
32
+ ""
33
+ when 1
34
+ array[0].to_s
35
+ when 2
36
+ "#{array[0]} #{connector} #{array[1]}"
37
+ else
38
+ "#{array[0...-1].join(', ')}, #{connector} #{array[-1]}"
39
+ end
40
+ end
41
+
42
+ end
43
+ 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,188 @@
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, :published
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
+
42
+ if self.data.has_key?('published') && self.data['published'] == false
43
+ self.published = false
44
+ else
45
+ self.published = true
46
+ end
47
+
48
+ if self.categories.empty?
49
+ if self.data.has_key?('category')
50
+ self.categories << self.data['category']
51
+ elsif self.data.has_key?('categories')
52
+ self.categories = self.data['categories'].split
53
+ end
54
+ end
55
+ end
56
+
57
+ # Spaceship is based on Post#date
58
+ #
59
+ # Returns -1, 0, 1
60
+ def <=>(other)
61
+ self.date <=> other.date
62
+ end
63
+
64
+ # Extract information from the post filename
65
+ # +name+ is the String filename of the post file
66
+ #
67
+ # Returns nothing
68
+ def process(name)
69
+ m, cats, date, slug, ext = *name.match(MATCHER)
70
+ self.date = Time.parse(date)
71
+ self.slug = slug
72
+ self.ext = ext
73
+ end
74
+
75
+ # The generated directory into which the post will be placed
76
+ # upon generation. This is derived from the permalink or, if
77
+ # permalink is absent, set to the default date
78
+ # e.g. "/2008/11/05/" if the permalink style is :date, otherwise nothing
79
+ #
80
+ # Returns <String>
81
+ def dir
82
+ if permalink
83
+ permalink.to_s.split("/")[0..-2].join("/") + '/'
84
+ else
85
+ prefix = self.categories.empty? ? '' : '/' + self.categories.join('/')
86
+ if Jekyll.permalink_style == :date
87
+ prefix + date.strftime("/%Y/%m/%d/")
88
+ else
89
+ prefix + '/'
90
+ end
91
+ end
92
+ end
93
+
94
+ # The full path and filename of the post.
95
+ # Defined in the YAML of the post body
96
+ # (Optional)
97
+ #
98
+ # Returns <String>
99
+ def permalink
100
+ self.data && self.data['permalink']
101
+ end
102
+
103
+ # The generated relative url of this post
104
+ # e.g. /2008/11/05/my-awesome-post.html
105
+ #
106
+ # Returns <String>
107
+ def url
108
+ permalink || self.dir + self.slug + ".html"
109
+ end
110
+
111
+ # The UID for this post (useful in feeds)
112
+ # e.g. /2008/11/05/my-awesome-post
113
+ #
114
+ # Returns <String>
115
+ def id
116
+ self.dir + self.slug
117
+ end
118
+
119
+ # Calculate related posts.
120
+ #
121
+ # Returns [<Post>]
122
+ def related_posts(posts)
123
+ return [] unless posts.size > 1
124
+
125
+ if Jekyll.lsi
126
+ self.class.lsi ||= begin
127
+ puts "Running the classifier... this could take a while."
128
+ lsi = Classifier::LSI.new
129
+ posts.each { |x| $stdout.print(".");$stdout.flush;lsi.add_item(x) }
130
+ puts ""
131
+ lsi
132
+ end
133
+
134
+ related = self.class.lsi.find_related(self.content, 11)
135
+ related - [self]
136
+ else
137
+ (posts - [self])[0..9]
138
+ end
139
+ end
140
+
141
+ # Add any necessary layouts to this post
142
+ # +layouts+ is a Hash of {"name" => "layout"}
143
+ # +site_payload+ is the site payload hash
144
+ #
145
+ # Returns nothing
146
+ def render(layouts, site_payload)
147
+ # construct payload
148
+ payload =
149
+ {
150
+ "site" => { "related_posts" => related_posts(site_payload["site"]["posts"]) },
151
+ "page" => self.to_liquid
152
+ }
153
+ payload = payload.deep_merge(site_payload)
154
+
155
+ do_layout(payload, layouts)
156
+ end
157
+
158
+ # Write the generated post file to the destination directory.
159
+ # +dest+ is the String path to the destination dir
160
+ #
161
+ # Returns nothing
162
+ def write(dest)
163
+ FileUtils.mkdir_p(File.join(dest, dir))
164
+
165
+ path = File.join(dest, self.url)
166
+ File.open(path, 'w') do |f|
167
+ f.write(self.output)
168
+ end
169
+ end
170
+
171
+ # Convert this post into a Hash for use in Liquid templates.
172
+ #
173
+ # Returns <Hash>
174
+ def to_liquid
175
+ { "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
176
+ "url" => self.url,
177
+ "date" => self.date,
178
+ "id" => self.id,
179
+ "topics" => self.topics,
180
+ "content" => self.content }.deep_merge(self.data)
181
+ end
182
+
183
+ def inspect
184
+ "<Post: #{self.id}>"
185
+ end
186
+ end
187
+
188
+ end