lonnon-jekyll 0.6.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 (75) hide show
  1. data/.gitignore +6 -0
  2. data/History.txt +151 -0
  3. data/README.textile +613 -0
  4. data/Rakefile +91 -0
  5. data/VERSION.yml +4 -0
  6. data/bin/jekyll +166 -0
  7. data/features/create_sites.feature +57 -0
  8. data/features/embed_filters.feature +74 -0
  9. data/features/pagination.feature +40 -0
  10. data/features/permalinks.feature +65 -0
  11. data/features/post_data.feature +153 -0
  12. data/features/site_configuration.feature +63 -0
  13. data/features/site_data.feature +82 -0
  14. data/features/step_definitions/jekyll_steps.rb +136 -0
  15. data/features/support/env.rb +16 -0
  16. data/lib/jekyll.rb +86 -0
  17. data/lib/jekyll/albino.rb +122 -0
  18. data/lib/jekyll/converters/csv.rb +26 -0
  19. data/lib/jekyll/converters/mephisto.rb +79 -0
  20. data/lib/jekyll/converters/mt.rb +59 -0
  21. data/lib/jekyll/converters/textpattern.rb +50 -0
  22. data/lib/jekyll/converters/typo.rb +49 -0
  23. data/lib/jekyll/converters/wordpress.rb +61 -0
  24. data/lib/jekyll/convertible.rb +139 -0
  25. data/lib/jekyll/core_ext.rb +37 -0
  26. data/lib/jekyll/filters.rb +56 -0
  27. data/lib/jekyll/haml_helpers.rb +15 -0
  28. data/lib/jekyll/layout.rb +37 -0
  29. data/lib/jekyll/page.rb +112 -0
  30. data/lib/jekyll/pager.rb +45 -0
  31. data/lib/jekyll/post.rb +316 -0
  32. data/lib/jekyll/site.rb +326 -0
  33. data/lib/jekyll/tags/highlight.rb +56 -0
  34. data/lib/jekyll/tags/include.rb +31 -0
  35. data/test/helper.rb +27 -0
  36. data/test/source/_includes/sig.markdown +3 -0
  37. data/test/source/_layouts/default.html +27 -0
  38. data/test/source/_layouts/simple.html +1 -0
  39. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  40. data/test/source/_posts/2008-02-02-published.textile +8 -0
  41. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  42. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  43. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  44. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  45. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  46. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  47. data/test/source/_posts/2009-01-27-category.textile +7 -0
  48. data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
  49. data/test/source/_posts/2009-05-18-tag.textile +6 -0
  50. data/test/source/_posts/2009-05-18-tags.textile +9 -0
  51. data/test/source/_posts/2009-06-22-empty-yaml.textile +3 -0
  52. data/test/source/_posts/2009-06-22-no-yaml.textile +1 -0
  53. data/test/source/_posts/2009-11-27-more-divider.markdown +5 -0
  54. data/test/source/about.html +6 -0
  55. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  56. data/test/source/contacts.html +5 -0
  57. data/test/source/css/screen.css +76 -0
  58. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  59. data/test/source/index.html +22 -0
  60. data/test/source/paginated/_posts/2009-11-29-one-page.markdown +3 -0
  61. data/test/source/paginated/_posts/2009-11-29-red-page.markdown +4 -0
  62. data/test/source/paginated/_posts/2009-11-29-two-page.markdown +3 -0
  63. data/test/source/sitemap.xml +23 -0
  64. data/test/source/win/_posts/2009-05-24-yaml-linebreak.markdown +7 -0
  65. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  66. data/test/suite.rb +9 -0
  67. data/test/test_configuration.rb +29 -0
  68. data/test/test_filters.rb +54 -0
  69. data/test/test_generated_site.rb +40 -0
  70. data/test/test_page.rb +98 -0
  71. data/test/test_pager.rb +47 -0
  72. data/test/test_post.rb +317 -0
  73. data/test/test_site.rb +85 -0
  74. data/test/test_tags.rb +115 -0
  75. metadata +196 -0
@@ -0,0 +1,61 @@
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
17
+ # to have valid dates.
18
+ QUERY = "select post_title, post_name, post_date, post_content, ID, guid from wp_posts where post_status = 'publish' and post_type = 'post'"
19
+
20
+ def self.process(dbname, user, pass, host = 'localhost', format = 'markdown')
21
+ db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host)
22
+
23
+ FileUtils.mkdir_p "_posts"
24
+
25
+ db[:wp_posts].where(:post_status => 'publish',
26
+ :post_type => 'post').each do |post|
27
+ # Get required fields and construct Jekyll compatible name
28
+ title = post[:post_title]
29
+ slug = post[:post_name]
30
+ date = post[:post_date]
31
+ content = post[:post_content]
32
+ excerpt = post[:post_excerpt]
33
+ name = "%02d-%02d-%02d-%s.#{format}" % [date.year, date.month,
34
+ date.day, slug]
35
+
36
+ tags = db[:wp_terms].join(:wp_term_taxonomy, :term_id => :term_id).join(:wp_term_relationships, :term_taxonomy_id => :term_taxonomy_id).where(:object_id => post[:ID], :taxonomy => 'post_tag').map(:name).join(' ').split
37
+
38
+ # Get the relevant fields as a hash, delete empty fields and
39
+ # convert to YAML for the header
40
+ data = {
41
+ 'layout' => 'post',
42
+ 'title' => title.to_s,
43
+ 'tags' => tags,
44
+ 'excerpt' => excerpt,
45
+ 'wordpress_id' => post[:ID],
46
+ 'wordpress_url' => post[:guid]
47
+ }.delete_if { |k,v| v.nil? ||
48
+ v == '' ||
49
+ v =~ /\A[[:space:]]+\z/}.to_yaml
50
+
51
+ # Write out the data and content to file
52
+ File.open("_posts/#{name}", "w") do |f|
53
+ f.puts data
54
+ f.puts "---"
55
+ f.puts content
56
+ end
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,139 @@
1
+ # Convertible provides methods for converting a pagelike item
2
+ # from a certain type of markup into actual content
3
+ #
4
+ # Requires
5
+ # self.site -> Jekyll::Site
6
+ module Jekyll
7
+ module Convertible
8
+ # Return the contents as a string
9
+ def to_s
10
+ self.content || ''
11
+ end
12
+
13
+ # Read the YAML frontmatter
14
+ # +base+ is the String path to the dir containing the file
15
+ # +name+ is the String filename of the file
16
+ #
17
+ # Returns nothing
18
+ def read_yaml(base, name)
19
+ self.content = File.read(File.join(base, name))
20
+
21
+ if self.content =~ /^(---\s*\n.*?\n?)(---.*?\n)/m
22
+ self.content = self.content[($1.size + $2.size)..-1]
23
+ self.data = YAML.load($1)
24
+ end
25
+ self.data ||= {}
26
+
27
+ if self.is_a? Jekyll::Post
28
+ divider = /<!--\s*[Mm][Oo][Rr][Ee]\s*-->\s*\n/
29
+ if self.content =~ divider
30
+ self.data['excerpt'], self.data['remainder'] = self.content.split($&, 2)
31
+ self.content = self.data['excerpt'] +
32
+ "\n" + '<div id="more"></div>' + "\n" +
33
+ self.data['remainder']
34
+ end
35
+ end
36
+ end
37
+
38
+ # Transform the contents based on the file extension.
39
+ #
40
+ # Returns nothing
41
+ def transform
42
+ case self.content_type
43
+ when 'textile'
44
+ self.ext = ".html"
45
+ self.content = self.site.textile(self.content)
46
+ if self.is_a? Jekyll::Post and self.data['excerpt']
47
+ self.data['excerpt'] = self.site.textile(self.data['excerpt'])
48
+ self.data['remainder'] = self.site.textile(self.data['remainder'])
49
+ end
50
+ when 'markdown'
51
+ self.ext = ".html"
52
+ self.content = self.site.markdown(self.content)
53
+ if self.is_a? Jekyll::Post and self.data['excerpt']
54
+ self.data['excerpt'] = self.site.markdown(self.data['excerpt'])
55
+ self.data['remainder'] = self.site.markdown(self.data['remainder'])
56
+ end
57
+ when 'haml'
58
+ self.ext = ".html"
59
+ # Actually rendered in do_layout.
60
+ self.content = Haml::Engine.new(self.content, :attr_wrapper => %{"})
61
+ end
62
+ end
63
+
64
+ # Determine which formatting engine to use based on this convertible's
65
+ # extension
66
+ #
67
+ # Returns one of :textile, :markdown, :haml, or :unknown
68
+ def content_type
69
+ case self.ext[1..-1]
70
+ when /textile/i
71
+ return 'textile'
72
+ when /markdown/i, /mkdn/i, /md/i
73
+ return 'markdown'
74
+ when /haml/i
75
+ return 'haml'
76
+ end
77
+ return 'unknown'
78
+ end
79
+
80
+ # Sets up a context for Haml and renders in it. The context has accessors
81
+ # matching the passed-in hash, e.g. "site", "page" and "content", and has
82
+ # helper modules mixed in.
83
+ #
84
+ # Returns String.
85
+ def render_haml_in_context(haml_engine, params={})
86
+ context = ClosedStruct.new(params)
87
+ context.extend(HamlHelpers)
88
+ context.extend(::Helpers) if defined?(::Helpers)
89
+ haml_engine.render(context)
90
+ end
91
+
92
+ # Add any necessary layouts to this convertible document
93
+ # +layouts+ is a Hash of {"name" => "layout"}
94
+ # +site_payload+ is the site payload hash
95
+ #
96
+ # Returns nothing
97
+ def do_layout(payload, layouts)
98
+ info = { :filters => [Jekyll::Filters], :registers => { :site => self.site } }
99
+
100
+ # render and transform content (this becomes the final content of the object)
101
+ payload["content_type"] = self.content_type
102
+
103
+ if self.content_type == "haml"
104
+ self.transform
105
+ self.content = render_haml_in_context(self.content,
106
+ :site => self.site,
107
+ :page => ClosedStruct.new(payload["page"]))
108
+ else
109
+ self.content = Liquid::Template.parse(self.content).render(payload, info)
110
+ self.transform
111
+ end
112
+
113
+ # output keeps track of what will finally be written
114
+ self.output = self.content
115
+ if self.is_a? Jekyll::Post
116
+ payload["page"].merge!({"content" => self.content,
117
+ "excerpt" => self.data['excerpt'],
118
+ "remainder" => self.data['remainder']})
119
+ end
120
+
121
+ # recursively render layouts
122
+ layout = layouts[self.data["layout"]]
123
+ while layout
124
+ payload = payload.deep_merge({"content" => self.output, "page" => layout.data})
125
+
126
+ if site.config['haml'] && layout.content.is_a?(Haml::Engine)
127
+ self.output = render_haml_in_context(layout.content,
128
+ :site => ClosedStruct.new(payload["site"]),
129
+ :page => ClosedStruct.new(payload["page"]),
130
+ :content => payload["content"])
131
+ else
132
+ self.output = Liquid::Template.parse(layout.content).render(payload, info)
133
+ end
134
+
135
+ layout = layouts[layout.data["layout"]]
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,37 @@
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
23
+
24
+ # Thanks, ActiveSupport!
25
+ class Date
26
+ # Converts datetime to an appropriate format for use in XML
27
+ def xmlschema
28
+ strftime("%Y-%m-%dT%H:%M:%S%Z")
29
+ end if RUBY_VERSION < '1.9'
30
+ end
31
+
32
+ require 'ostruct'
33
+ class ClosedStruct < OpenStruct
34
+ def method_missing(symbol, *args)
35
+ raise(NoMethodError, "undefined method `#{symbol}' for #{self}")
36
+ end
37
+ end
@@ -0,0 +1,56 @@
1
+ module Jekyll
2
+
3
+ module Filters
4
+ def textilize(input)
5
+ RedCloth.new(input).to_html
6
+ end
7
+
8
+ def markdownize(input)
9
+ require 'rdiscount'
10
+ RDiscount.new(input).to_html
11
+ end
12
+
13
+ def date_to_string(date)
14
+ date.strftime("%d %b %Y")
15
+ end
16
+
17
+ def date_to_long_string(date)
18
+ date.strftime("%d %B %Y")
19
+ end
20
+
21
+ def date_to_xmlschema(date)
22
+ date.xmlschema
23
+ end
24
+
25
+ def date_to_utc(date)
26
+ date.utc
27
+ end
28
+
29
+ def xml_escape(input)
30
+ CGI.escapeHTML(input)
31
+ end
32
+
33
+ def cgi_escape(input)
34
+ CGI::escape(input)
35
+ end
36
+
37
+ def number_of_words(input)
38
+ input.split.length
39
+ end
40
+
41
+ def array_to_sentence_string(array)
42
+ connector = "and"
43
+ case array.length
44
+ when 0
45
+ ""
46
+ when 1
47
+ array[0].to_s
48
+ when 2
49
+ "#{array[0]} #{connector} #{array[1]}"
50
+ else
51
+ "#{array[0...-1].join(', ')}, #{connector} #{array[-1]}"
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ require 'cgi'
2
+
3
+ module Jekyll
4
+ module HamlHelpers
5
+
6
+ def h(text)
7
+ CGI.escapeHTML(text)
8
+ end
9
+
10
+ def link_to(text, url)
11
+ %{<a href="#{h url}">#{text}</a>}
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ module Jekyll
2
+
3
+ class Layout
4
+ include Convertible
5
+
6
+ attr_accessor :site
7
+ attr_accessor :ext
8
+ attr_accessor :data, :content
9
+
10
+ # Initialize a new Layout.
11
+ # +site+ is the Site
12
+ # +base+ is the String path to the <source>
13
+ # +name+ is the String filename of the post file
14
+ #
15
+ # Returns <Page>
16
+ def initialize(site, base, name)
17
+ @site = site
18
+ @base = base
19
+ @name = name
20
+
21
+ self.data = {}
22
+
23
+ self.process(name)
24
+ self.read_yaml(base, name)
25
+ self.transform
26
+ end
27
+
28
+ # Extract information from the layout filename
29
+ # +name+ is the String filename of the layout file
30
+ #
31
+ # Returns nothing
32
+ def process(name)
33
+ self.ext = File.extname(name)
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,112 @@
1
+ module Jekyll
2
+
3
+ class Page
4
+ include Convertible
5
+
6
+ attr_accessor :site
7
+ attr_accessor :name, :ext, :basename
8
+ attr_accessor :data, :content, :output
9
+
10
+ # Initialize a new Page.
11
+ # +site+ is the Site
12
+ # +base+ is the String path to the <source>
13
+ # +dir+ is the String path between <source> and the file
14
+ # +name+ is the String filename of the file
15
+ #
16
+ # Returns <Page>
17
+ def initialize(site, base, dir, name)
18
+ @site = site
19
+ @base = base
20
+ @dir = dir
21
+ @name = name
22
+
23
+ self.process(name)
24
+ self.read_yaml(File.join(base, dir), name)
25
+ end
26
+
27
+ # The generated directory into which the page will be placed
28
+ # upon generation. This is derived from the permalink or, if
29
+ # permalink is absent, set to '/'
30
+ #
31
+ # Returns <String>
32
+ def dir
33
+ url[-1, 1] == '/' ? url : File.dirname(url)
34
+ end
35
+
36
+ # The full path and filename of the post.
37
+ # Defined in the YAML of the post body
38
+ # (Optional)
39
+ #
40
+ # Returns <String>
41
+ def permalink
42
+ self.data && self.data['permalink']
43
+ end
44
+
45
+ def template
46
+ if self.site.permalink_style == :pretty && !index?
47
+ "/:name/"
48
+ else
49
+ "/:name.html"
50
+ end
51
+ end
52
+
53
+ # The generated relative url of this page
54
+ # e.g. /about.html
55
+ #
56
+ # Returns <String>
57
+ def url
58
+ return permalink if permalink
59
+
60
+ @url ||= (ext == '.html') ? template.gsub(':name', basename) : "/#{name}"
61
+ end
62
+
63
+ # Extract information from the page filename
64
+ # +name+ is the String filename of the page file
65
+ #
66
+ # Returns nothing
67
+ def process(name)
68
+ self.ext = File.extname(name)
69
+ self.basename = name.split('.')[0..-2].first
70
+ end
71
+
72
+ # Add any necessary layouts to this post
73
+ # +layouts+ is a Hash of {"name" => "layout"}
74
+ # +site_payload+ is the site payload hash
75
+ #
76
+ # Returns nothing
77
+ def render(layouts, site_payload)
78
+ payload = {"page" => self.data}.deep_merge(site_payload)
79
+ do_layout(payload, layouts)
80
+ end
81
+
82
+ # Write the generated page file to the destination directory.
83
+ # +dest_prefix+ is the String path to the destination dir
84
+ # +dest_suffix+ is a suffix path to the destination dir
85
+ #
86
+ # Returns nothing
87
+ def write(dest_prefix, dest_suffix = nil)
88
+ dest = File.join(dest_prefix, @dir)
89
+ dest = File.join(dest, dest_suffix) if dest_suffix
90
+ FileUtils.mkdir_p(dest)
91
+
92
+ # The url needs to be unescaped in order to preserve the correct filename
93
+ path = File.join(dest, CGI.unescape(self.url))
94
+ if self.ext == '.html' && self.url[/\.html$/].nil?
95
+ FileUtils.mkdir_p(path)
96
+ path = File.join(path, "index.html")
97
+ end
98
+
99
+ File.open(path, 'w') do |f|
100
+ f.write(self.output)
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def index?
107
+ basename == 'index'
108
+ end
109
+
110
+ end
111
+
112
+ end