dysinger-jekyll 0.4.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 (42) hide show
  1. data/History.txt +91 -0
  2. data/README.textile +475 -0
  3. data/VERSION.yml +4 -0
  4. data/bin/jekyll +136 -0
  5. data/lib/jekyll.rb +67 -0
  6. data/lib/jekyll/albino.rb +116 -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 +54 -0
  13. data/lib/jekyll/convertible.rb +71 -0
  14. data/lib/jekyll/core_ext.rb +22 -0
  15. data/lib/jekyll/filters.rb +39 -0
  16. data/lib/jekyll/layout.rb +33 -0
  17. data/lib/jekyll/page.rb +64 -0
  18. data/lib/jekyll/post.rb +174 -0
  19. data/lib/jekyll/site.rb +167 -0
  20. data/lib/jekyll/tags/highlight.rb +53 -0
  21. data/lib/jekyll/tags/include.rb +31 -0
  22. data/test/helper.rb +13 -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 +37 -0
  37. data/test/test_generated_site.rb +22 -0
  38. data/test/test_jekyll.rb +0 -0
  39. data/test/test_post.rb +113 -0
  40. data/test/test_site.rb +33 -0
  41. data/test/test_tags.rb +31 -0
  42. metadata +205 -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,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,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,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,174 @@
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/" if the permalink style is :date, otherwise nothing
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
+ if Jekyll.permalink_style == :date
73
+ prefix + date.strftime("/%Y/%m/%d/")
74
+ else
75
+ prefix + '/'
76
+ end
77
+ end
78
+ end
79
+
80
+ # The full path and filename of the post.
81
+ # Defined in the YAML of the post body
82
+ # (Optional)
83
+ #
84
+ # Returns <String>
85
+ def permalink
86
+ self.data && self.data['permalink']
87
+ end
88
+
89
+ # The generated relative url of this post
90
+ # e.g. /2008/11/05/my-awesome-post.html
91
+ #
92
+ # Returns <String>
93
+ def url
94
+ permalink || self.dir + self.slug + ".html"
95
+ end
96
+
97
+ # The UID for this post (useful in feeds)
98
+ # e.g. /2008/11/05/my-awesome-post
99
+ #
100
+ # Returns <String>
101
+ def id
102
+ self.dir + self.slug
103
+ end
104
+
105
+ # Calculate related posts.
106
+ #
107
+ # Returns [<Post>]
108
+ def related_posts(posts)
109
+ return [] unless posts.size > 1
110
+
111
+ if Jekyll.lsi
112
+ self.class.lsi ||= begin
113
+ puts "Running the classifier... this could take a while."
114
+ lsi = Classifier::LSI.new
115
+ posts.each { |x| $stdout.print(".");$stdout.flush;lsi.add_item(x) }
116
+ puts ""
117
+ lsi
118
+ end
119
+
120
+ related = self.class.lsi.find_related(self.content, 11)
121
+ related - [self]
122
+ else
123
+ (posts - [self])[0..9]
124
+ end
125
+ end
126
+
127
+ # Add any necessary layouts to this post
128
+ # +layouts+ is a Hash of {"name" => "layout"}
129
+ # +site_payload+ is the site payload hash
130
+ #
131
+ # Returns nothing
132
+ def render(layouts, site_payload)
133
+ # construct payload
134
+ payload =
135
+ {
136
+ "site" => { "related_posts" => related_posts(site_payload["site"]["posts"]) },
137
+ "page" => self.to_liquid
138
+ }
139
+ payload = payload.deep_merge(site_payload)
140
+
141
+ do_layout(payload, layouts)
142
+ end
143
+
144
+ # Write the generated post file to the destination directory.
145
+ # +dest+ is the String path to the destination dir
146
+ #
147
+ # Returns nothing
148
+ def write(dest)
149
+ FileUtils.mkdir_p(File.join(dest, dir))
150
+
151
+ path = File.join(dest, self.url)
152
+ File.open(path, 'w') do |f|
153
+ f.write(self.output)
154
+ end
155
+ end
156
+
157
+ # Convert this post into a Hash for use in Liquid templates.
158
+ #
159
+ # Returns <Hash>
160
+ def to_liquid
161
+ { "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
162
+ "url" => self.url,
163
+ "date" => self.date,
164
+ "id" => self.id,
165
+ "topics" => self.topics,
166
+ "content" => self.content }.deep_merge(self.data)
167
+ end
168
+
169
+ def inspect
170
+ "<Post: #{self.id}>"
171
+ end
172
+ end
173
+
174
+ end