jekyll 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of jekyll might be problematic. Click here for more details.

@@ -1,3 +1,17 @@
1
+ == 0.3.0 / 2008-12-24
2
+ * Major Enhancements
3
+ * Added --server option to start a simple WEBrick server on destination directory [github.com/johnreilly and github.com/mchung]
4
+ * Minor Enhancements
5
+ * Added post categories based on directories containing _posts [github.com/mreid]
6
+ * Added post topics based on directories underneath _posts
7
+ * Added new date filter that shows the full month name [github.com/mreid]
8
+ * Merge Post's YAML front matter into its to_liquid payload [github.com/remi]
9
+ * Restrict includes to regular files underneath _includes
10
+ * Bug Fixes
11
+ * Change YAML delimiter matcher so as to not chew up 2nd level markdown headers [github.com/mreid]
12
+ * Fix bug that meant page data (such as the date) was not available in templates [github.com/mreid]
13
+ * Properly reject directories in _layouts
14
+
1
15
  == 0.2.1 / 2008-12-15
2
16
  * Major Changes
3
17
  * Use Maruku (pure Ruby) for Markdown by default [github.com/mreid]
@@ -8,7 +8,10 @@ lib/jekyll.rb
8
8
  lib/jekyll/albino.rb
9
9
  lib/jekyll/converters/csv.rb
10
10
  lib/jekyll/converters/mephisto.rb
11
+ lib/jekyll/converters/mt.rb
12
+ lib/jekyll/converters/wordpress.rb
11
13
  lib/jekyll/convertible.rb
14
+ lib/jekyll/core_ext.rb
12
15
  lib/jekyll/filters.rb
13
16
  lib/jekyll/layout.rb
14
17
  lib/jekyll/page.rb
@@ -22,11 +25,12 @@ test/source/_layouts/default.html
22
25
  test/source/_layouts/simple.html
23
26
  test/source/_posts/2008-10-18-foo-bar.textile
24
27
  test/source/_posts/2008-11-21-complex.textile
28
+ test/source/_posts/2008-12-03-permalinked-post.textile
25
29
  test/source/_posts/2008-12-13-include.markdown
26
30
  test/source/css/screen.css
27
31
  test/source/index.html
28
- test/source/posts/2008-12-03-permalinked-post.textile
29
32
  test/suite.rb
33
+ test/test_generated_site.rb
30
34
  test/test_jekyll.rb
31
35
  test/test_post.rb
32
36
  test/test_site.rb
@@ -31,9 +31,10 @@ various data about my site. A reverse chronological list of all my blog posts
31
31
  can be found in <code>site.posts</code>. Each post, in turn, contains various
32
32
  fields such as <code>title</code> and <code>date</code>.
33
33
 
34
- Jekyll gets the list of blog posts by parsing the files in the
35
- "_posts":http://github.com/mojombo/tpw/tree/master/_posts directory. Each
36
- post's filename contains the publishing date and slug (what shows up in the
34
+ Jekyll gets the list of blog posts by parsing the files in any
35
+ "_posts":http://github.com/mojombo/tpw/tree/master/_posts directory found in
36
+ subdirectories below the root.
37
+ Each post's filename contains the publishing date and slug (what shows up in the
37
38
  URL) that the final HTML file should have. Open up the file corresponding to a
38
39
  blog post:
39
40
  "2008-11-17-blogging-like-a-hacker.textile":http://github.com/mojombo/tpw/tree/master/_posts/2008-11-17-blogging-like-a-hacker.textile.
@@ -52,6 +53,12 @@ filename is used to construct the URL in the generated site. The example post,
52
53
  for instance, ends up at
53
54
  <code>http://tom.preston-werner.com/2008/11/17/blogging-like-a-hacker.html</code>.
54
55
 
56
+ Categories for posts are derived from the directory structure the posts were
57
+ found within. A post that appears in the directory foo/bar/_posts is placed in
58
+ the categories 'foo' and 'bar'. By selecting posts from particular categories
59
+ in your Liquid templates, you will be able to host multiple blogs within a
60
+ site.
61
+
55
62
  Files that do not reside in directories prefixed with an underscore are
56
63
  mirrored into a corresponding directory structure in the generated site. If a
57
64
  file does not have a YAML preface, it is not run through the Liquid
@@ -129,6 +136,14 @@ compilation), you must install it (gem install rdiscount) and then you can
129
136
  have it used instead:
130
137
 
131
138
  $ jekyll --rdiscount
139
+
140
+ When previewing complex sites locally, simply opening the site in a web
141
+ browser (using file://) can cause problems with links that are relative to
142
+ the site root (e.g., "/stylesheets/style.css"). To get around this, Jekyll
143
+ can launch a simple WEBrick server (works well in conjunction with --auto).
144
+ Default port is 4000:
145
+
146
+ $ jekyll --server [PORT]
132
147
 
133
148
  h2. Data
134
149
 
@@ -149,7 +164,7 @@ h3. Global
149
164
 
150
165
  content
151
166
  In layout files, this contains the content of the subview(s). In Posts or
152
- pages, this is undefined.
167
+ Pages, this is undefined.
153
168
 
154
169
  h3. Site
155
170
 
@@ -165,6 +180,9 @@ h3. Site
165
180
  high quality but slow to compute results, run the jekyll command with the
166
181
  --lsi (latent semantic indexing) option.
167
182
 
183
+ site.categories.CATEGORY
184
+ The list of all Posts in category CATEGORY.
185
+
168
186
  h3. Post
169
187
 
170
188
  post.title
@@ -181,6 +199,18 @@ h3. Post
181
199
  An identifier unique to the Post (useful in RSS feeds).
182
200
  e.g. /2008/12/14/my-post
183
201
 
202
+ post.categories
203
+ The list of categories to which this post belongs. Categories are
204
+ derived from the directory structure above the _posts directory. For
205
+ example, a post at /work/code/_posts/2008-12-24-closures.textile
206
+ would have this field set to ['work', 'code'].
207
+
208
+ post.topics
209
+ The list of topics for this Post. Topics are derived from the directory
210
+ structure beneath the _posts directory. For example, a post at
211
+ /_posts/music/metal/2008-12-24-metalocalypse.textile would have this field
212
+ set to ['music', 'metal'].
213
+
184
214
  post.content
185
215
  The content of the Post.
186
216
 
@@ -258,7 +288,8 @@ becomes
258
288
 
259
289
  h3. Include (Tag)
260
290
 
261
- If you have small page fragments that you wish to include in multiple places on your site, you can use the <code>include</code> tag.
291
+ If you have small page fragments that you wish to include in multiple places
292
+ on your site, you can use the <code>include</code> tag.
262
293
 
263
294
  <pre>{% include sig.textile %}</pre>
264
295
 
@@ -296,6 +327,22 @@ highlighting stylesheet. For an example stylesheet you can look at
296
327
  are the same styles as used by GitHub and you are free to use them for your
297
328
  own site.
298
329
 
330
+ h2. Categories
331
+
332
+ Posts are placed into categories based on the directory structure they are found
333
+ within (see above for an example). The categories can be accessed from within
334
+ a Liquid template as follows:
335
+
336
+ <pre>
337
+ {% for post in site.categories.foo %}
338
+ <li><span>{{ post.date | date_to_string }}</span> - {{ post.title }}</li>
339
+ {% endfor %}
340
+ </pre>
341
+
342
+ This would list all the posts in the category 'foo' by date and title.
343
+
344
+ The posts within each category are sorted in reverse chronological order.
345
+
299
346
  h2. Contribute
300
347
 
301
348
  If you'd like to hack on Jekyll, grab the source from GitHub. To get
@@ -314,6 +361,24 @@ The best way to get your changes merged back into core is as follows:
314
361
  # Push the branch up to GitHub
315
362
  # Send me (mojombo) a pull request for your branch
316
363
 
364
+ h2. Blog migrations
365
+
366
+ h3. Movable Type
367
+
368
+ To migrate your MT blog into Jekyll, you'll need read access to the database.
369
+ The lib/jekyll/converters/mt.rb module provides a simple convert to create
370
+ .markdown files in a _posts directory based on the entries contained therein.
371
+
372
+ $ export DB=my_mtdb
373
+ $ export USER=dbuser
374
+ $ export PASS=dbpass
375
+ $ ruby -r './lib/jekyll/converters/mt' -e 'Jekyll::MT.process( \
376
+ "#{ENV["DB"]}", "#{ENV["USER"]}", "#{ENV["PASS"]}")'
377
+
378
+ You may need to adjust the SQL query used to retrieve MT entries. Left alone,
379
+ it will attempt to pull all entries across all blogs regardless of status.
380
+ Please check the results and verify the posts before publishing.
381
+
317
382
  h2. License
318
383
 
319
384
  (The MIT License)
data/Rakefile CHANGED
@@ -18,4 +18,7 @@ namespace :convert do
18
18
  task :mephisto do
19
19
  sh %q(ruby -r './lib/jekyll/converters/mephisto' -e 'Jekyll::Mephisto.postgres(:database => "#{ENV["DB"]}")')
20
20
  end
21
+ task :mt do
22
+ sh %q(ruby -r './lib/jekyll/converters/mt' -e 'Jekyll::MT.process("#{ENV["DB"]}", "#{ENV["USER"]}", "#{ENV["PASS"]}")')
23
+ end
21
24
  end
data/bin/jekyll CHANGED
@@ -25,6 +25,11 @@ opts = OptionParser.new do |opts|
25
25
  options[:auto] = true
26
26
  end
27
27
 
28
+ opts.on("--server [PORT]", "Start web server (default port 4000)") do |port|
29
+ options[:server] = true
30
+ options[:server_port] = port || 4000
31
+ end
32
+
28
33
  opts.on("--lsi", "Use LSI for better related posts") do
29
34
  Jekyll.lsi = true
30
35
  end
@@ -95,7 +100,28 @@ if options[:auto]
95
100
 
96
101
  dw.start
97
102
 
98
- loop { sleep 1000 }
103
+ unless options[:server]
104
+ loop { sleep 1000 }
105
+ end
99
106
  else
100
107
  Jekyll.process(source, destination)
108
+ puts "Successfully generated site in #{destination}"
109
+ end
110
+
111
+ if options[:server]
112
+ require 'webrick'
113
+ include WEBrick
114
+
115
+ FileUtils.mkdir_p(destination)
116
+
117
+ s = HTTPServer.new(
118
+ :Port => options[:server_port],
119
+ :DocumentRoot => destination
120
+ )
121
+ t = Thread.new {
122
+ s.start
123
+ }
124
+
125
+ trap("INT") { s.shutdown }
126
+ t.join()
101
127
  end
@@ -1,22 +1,22 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{jekyll}
3
- s.version = "0.2.1"
3
+ s.version = "0.3.0"
4
4
 
5
5
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
6
  s.authors = ["Tom Preston-Werner"]
7
- s.date = %q{2008-12-15}
7
+ s.date = %q{2008-12-24}
8
8
  s.default_executable = %q{jekyll}
9
9
  s.email = ["tom@mojombo.com"]
10
10
  s.executables = ["jekyll"]
11
11
  s.extra_rdoc_files = ["History.txt", "Manifest.txt"]
12
- s.files = ["History.txt", "Manifest.txt", "README.textile", "Rakefile", "bin/jekyll", "jekyll.gemspec", "lib/jekyll.rb", "lib/jekyll/albino.rb", "lib/jekyll/converters/csv.rb", "lib/jekyll/converters/mephisto.rb", "lib/jekyll/convertible.rb", "lib/jekyll/filters.rb", "lib/jekyll/layout.rb", "lib/jekyll/page.rb", "lib/jekyll/post.rb", "lib/jekyll/site.rb", "lib/jekyll/tags/highlight.rb", "lib/jekyll/tags/include.rb", "test/helper.rb", "test/source/_includes/sig.markdown", "test/source/_layouts/default.html", "test/source/_layouts/simple.html", "test/source/_posts/2008-10-18-foo-bar.textile", "test/source/_posts/2008-11-21-complex.textile", "test/source/_posts/2008-12-13-include.markdown", "test/source/css/screen.css", "test/source/index.html", "test/source/posts/2008-12-03-permalinked-post.textile", "test/suite.rb", "test/test_jekyll.rb", "test/test_post.rb", "test/test_site.rb"]
12
+ s.files = ["History.txt", "Manifest.txt", "README.textile", "Rakefile", "bin/jekyll", "jekyll.gemspec", "lib/jekyll.rb", "lib/jekyll/albino.rb", "lib/jekyll/converters/csv.rb", "lib/jekyll/converters/mephisto.rb", "lib/jekyll/converters/mt.rb", "lib/jekyll/converters/wordpress.rb", "lib/jekyll/convertible.rb", "lib/jekyll/core_ext.rb", "lib/jekyll/filters.rb", "lib/jekyll/layout.rb", "lib/jekyll/page.rb", "lib/jekyll/post.rb", "lib/jekyll/site.rb", "lib/jekyll/tags/highlight.rb", "lib/jekyll/tags/include.rb", "test/helper.rb", "test/source/_includes/sig.markdown", "test/source/_layouts/default.html", "test/source/_layouts/simple.html", "test/source/_posts/2008-10-18-foo-bar.textile", "test/source/_posts/2008-11-21-complex.textile", "test/source/_posts/2008-12-03-permalinked-post.textile", "test/source/_posts/2008-12-13-include.markdown", "test/source/css/screen.css", "test/source/index.html", "test/suite.rb", "test/test_generated_site.rb", "test/test_jekyll.rb", "test/test_post.rb", "test/test_site.rb"]
13
13
  s.has_rdoc = true
14
14
  s.rdoc_options = ["--main", "README.txt"]
15
15
  s.require_paths = ["lib"]
16
16
  s.rubyforge_project = %q{jekyll}
17
17
  s.rubygems_version = %q{1.3.0}
18
18
  s.summary = %q{Jekyll is a simple, blog aware, static site generator.}
19
- s.test_files = ["test/test_jekyll.rb", "test/test_post.rb", "test/test_site.rb"]
19
+ s.test_files = ["test/test_generated_site.rb", "test/test_jekyll.rb", "test/test_post.rb", "test/test_site.rb"]
20
20
 
21
21
  if s.respond_to? :specification_version then
22
22
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
@@ -6,6 +6,7 @@ require 'rubygems'
6
6
  # core
7
7
  require 'fileutils'
8
8
  require 'time'
9
+ require 'yaml'
9
10
 
10
11
  # stdlib
11
12
 
@@ -30,6 +31,7 @@ rescue LoadError
30
31
  end
31
32
 
32
33
  # internal requires
34
+ require 'jekyll/core_ext'
33
35
  require 'jekyll/site'
34
36
  require 'jekyll/convertible'
35
37
  require 'jekyll/layout'
@@ -41,7 +43,7 @@ require 'jekyll/tags/include'
41
43
  require 'jekyll/albino'
42
44
 
43
45
  module Jekyll
44
- VERSION = '0.2.1'
46
+ VERSION = '0.3.0'
45
47
 
46
48
  class << self
47
49
  attr_accessor :source, :dest, :lsi, :pygments, :markdown_proc
@@ -0,0 +1,59 @@
1
+ # Created by Nick Gerakines, open source and publically available under the
2
+ # MIT license. Use this module at your own risk.
3
+ # I'm an Erlang/Perl/C++ guy so please forgive my dirty ruby.
4
+
5
+ require 'rubygems'
6
+ require 'sequel'
7
+ require 'fileutils'
8
+
9
+ # NOTE: This converter requires Sequel and the MySQL gems.
10
+ # The MySQL gem can be difficult to install on OS X. Once you have MySQL
11
+ # installed, running the following commands should work:
12
+ # $ sudo gem install sequel
13
+ # $ sudo gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
14
+
15
+ module Jekyll
16
+ module MT
17
+ # This query will pull blog posts from all entries across all blogs. If
18
+ # you've got unpublished, deleted or otherwise hidden posts please sift
19
+ # through the created posts to make sure nothing is accidently published.
20
+ QUERY = "SELECT entry_id, entry_basename, entry_text, entry_text_more, entry_created_on, entry_title FROM mt_entry"
21
+
22
+ def self.process(dbname, user, pass, host = 'localhost')
23
+ db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host)
24
+
25
+ FileUtils.mkdir_p "_posts"
26
+
27
+ db[QUERY].each do |post|
28
+ title = post[:entry_title]
29
+ slug = post[:entry_basename]
30
+ date = post[:entry_created_on]
31
+ content = post[:entry_text]
32
+ more_content = post[:entry_text_more]
33
+
34
+ # Be sure to include the body and extended body.
35
+ if more_content != nil
36
+ conent = content + " \n" + more_content
37
+ end
38
+
39
+ # Ideally, this script would determine the post format (markdown, html
40
+ # , etc) and create files with proper extensions. At this point it
41
+ # just assumes that markdown will be acceptable.
42
+ name = [date.year, date.month, date.day, slug].join('-') + ".markdown"
43
+
44
+ data = {
45
+ 'layout' => 'post',
46
+ 'title' => title.to_s,
47
+ 'mt_id' => post[:entry_id],
48
+ }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
49
+
50
+ File.open("_posts/#{name}", "w") do |f|
51
+ f.puts data
52
+ f.puts "---"
53
+ f.puts content
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -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
@@ -13,13 +13,13 @@ module Jekyll
13
13
  def read_yaml(base, name)
14
14
  self.content = File.read(File.join(base, name))
15
15
 
16
- if self.content =~ /^(---.*\n.*?)\n---.*\n/m
16
+ if self.content =~ /^(---\s*\n.*?)\n---\s*\n/m
17
17
  self.content = self.content[($1.size + 5)..-1]
18
18
 
19
19
  self.data = YAML.load($1)
20
20
  end
21
21
  end
22
-
22
+
23
23
  # Transform the contents based on the file extension.
24
24
  #
25
25
  # Returns nothing
@@ -39,10 +39,8 @@ module Jekyll
39
39
  # +site_payload+ is the site payload hash
40
40
  #
41
41
  # Returns nothing
42
- def do_layout(payload, layouts, site_payload)
43
- # construct payload
44
- payload = payload.merge(site_payload)
45
- # render content
42
+ def do_layout(payload, layouts)
43
+ # render and transform content (this becomes the final content of the object)
46
44
  self.content = Liquid::Template.parse(self.content).render(payload, [Jekyll::Filters])
47
45
  self.transform
48
46
 
@@ -52,7 +50,7 @@ module Jekyll
52
50
  # recursively render layouts
53
51
  layout = layouts[self.data["layout"]]
54
52
  while layout
55
- payload = payload.merge({"content" => self.output, "page" => self.data})
53
+ payload = payload.deep_merge({"content" => self.output, "page" => layout.data})
56
54
  self.output = Liquid::Template.parse(layout.content).render(payload, [Jekyll::Filters])
57
55
 
58
56
  layout = layouts[layout.data["layout"]]
@@ -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
@@ -4,6 +4,10 @@ module Jekyll
4
4
  def date_to_string(date)
5
5
  date.strftime("%d %b %Y")
6
6
  end
7
+
8
+ def date_to_long_string(date)
9
+ date.strftime("%d %B %Y")
10
+ end
7
11
 
8
12
  def date_to_xmlschema(date)
9
13
  date.xmlschema
@@ -15,7 +19,6 @@ module Jekyll
15
19
 
16
20
  def number_of_words(input)
17
21
  input.split.length
18
- end
19
- end
20
-
22
+ end
23
+ end
21
24
  end
@@ -28,21 +28,6 @@ module Jekyll
28
28
  def process(name)
29
29
  self.ext = File.extname(name)
30
30
  end
31
-
32
- # Add any necessary layouts to this post
33
- # +layouts+ is a Hash of {"name" => "layout"}
34
- # +site_payload+ is the site payload hash
35
- #
36
- # Returns nothing
37
- def add_layout(layouts, site_payload)
38
- payload = {"page" => self.data}.merge(site_payload)
39
- self.content = Liquid::Template.parse(self.content).render(payload, [Jekyll::Filters])
40
-
41
- layout = layouts[self.data["layout"]] || self.content
42
- payload = {"content" => self.content, "page" => self.data}
43
-
44
- self.content = Liquid::Template.parse(layout).render(payload, [Jekyll::Filters])
45
- end
46
31
  end
47
32
 
48
33
  end
@@ -37,9 +37,9 @@ module Jekyll
37
37
  # +site_payload+ is the site payload hash
38
38
  #
39
39
  # Returns nothing
40
- def add_layout(layouts, site_payload)
41
- payload = {"page" => self.data}
42
- do_layout(payload, layouts, site_payload)
40
+ def render(layouts, site_payload)
41
+ payload = {"page" => self.data}.deep_merge(site_payload)
42
+ do_layout(payload, layouts)
43
43
  end
44
44
 
45
45
  # Write the generated page file to the destination directory.
@@ -3,12 +3,12 @@ module Jekyll
3
3
  class Post
4
4
  include Comparable
5
5
  include Convertible
6
-
6
+
7
7
  class << self
8
8
  attr_accessor :lsi
9
9
  end
10
10
 
11
- MATCHER = /^(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
11
+ MATCHER = /^(.+\/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
12
12
 
13
13
  # Post name validator. Post filenames must be like:
14
14
  # 2008-11-05-my-awesome-post.textile
@@ -18,22 +18,26 @@ module Jekyll
18
18
  name =~ MATCHER
19
19
  end
20
20
 
21
- attr_accessor :date, :slug, :ext
21
+ attr_accessor :date, :slug, :ext, :categories, :topics
22
22
  attr_accessor :data, :content, :output
23
23
 
24
24
  # Initialize this Post instance.
25
25
  # +base+ is the String path to the dir containing the post file
26
26
  # +name+ is the String filename of the post file
27
+ # +categories+ is an Array of Strings for the categories for this post
27
28
  #
28
29
  # Returns <Post>
29
- def initialize(base, name)
30
- @base = base
30
+ def initialize(source, dir, name)
31
+ @base = File.join(source, dir, '_posts')
31
32
  @name = name
32
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
+
33
39
  self.process(name)
34
- self.read_yaml(base, name)
35
- #Removed to avoid munging of liquid tags, replaced in convertible.rb#48
36
- #self.transform
40
+ self.read_yaml(@base, name)
37
41
  end
38
42
 
39
43
  # Spaceship is based on Post#date
@@ -48,7 +52,7 @@ module Jekyll
48
52
  #
49
53
  # Returns nothing
50
54
  def process(name)
51
- m, date, slug, ext = *name.match(MATCHER)
55
+ m, cats, date, slug, ext = *name.match(MATCHER)
52
56
  self.date = Time.parse(date)
53
57
  self.slug = slug
54
58
  self.ext = ext
@@ -61,9 +65,12 @@ module Jekyll
61
65
  #
62
66
  # Returns <String>
63
67
  def dir
64
- permalink ?
65
- permalink.to_s.split("/")[0..-2].join("/") :
66
- date.strftime("/%Y/%m/%d/")
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
67
74
  end
68
75
 
69
76
  # The full path and filename of the post.
@@ -90,7 +97,7 @@ module Jekyll
90
97
  def id
91
98
  self.dir + self.slug
92
99
  end
93
-
100
+
94
101
  # Calculate related posts.
95
102
  #
96
103
  # Returns [<Post>]
@@ -118,11 +125,16 @@ module Jekyll
118
125
  # +site_payload+ is the site payload hash
119
126
  #
120
127
  # Returns nothing
121
- def add_layout(layouts, site_payload)
122
- # construct post payload
123
- related = related_posts(site_payload["site"]["posts"])
124
- payload = {"page" => self.to_liquid.merge(self.data)}
125
- do_layout(payload, layouts, site_payload.merge({"site" => {"related_posts" => related}}))
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)
126
138
  end
127
139
 
128
140
  # Write the generated post file to the destination directory.
@@ -146,7 +158,12 @@ module Jekyll
146
158
  "url" => self.url,
147
159
  "date" => self.date,
148
160
  "id" => self.id,
149
- "content" => self.content }
161
+ "topics" => self.topics,
162
+ "content" => self.content }.deep_merge(self.data)
163
+ end
164
+
165
+ def inspect
166
+ "<Post: #{self.id}>"
150
167
  end
151
168
  end
152
169
 
@@ -24,19 +24,19 @@ module Jekyll
24
24
  # Returns nothing
25
25
  def process
26
26
  self.read_layouts
27
- self.read_posts
28
- self.write_posts
29
27
  self.transform_pages
28
+ self.write_posts
30
29
  end
31
30
 
32
- # Read all the files in <source>/_layouts into memory for
33
- # later use.
31
+ # Read all the files in <source>/_layouts except backup files
32
+ # (end with "~") into memory for later use.
34
33
  #
35
34
  # Returns nothing
36
35
  def read_layouts
37
36
  base = File.join(self.source, "_layouts")
38
37
  entries = Dir.entries(base)
39
- entries = entries.reject { |e| File.directory?(e) }
38
+ entries = entries.reject { |e| e[-1..-1] == '~' }
39
+ entries = entries.reject { |e| File.directory?(File.join(base, e)) }
40
40
 
41
41
  entries.each do |f|
42
42
  name = f.split(".")[0..-2].join(".")
@@ -46,17 +46,29 @@ module Jekyll
46
46
  # ignore missing layout dir
47
47
  end
48
48
 
49
- # Read all the files in <source>/posts and create a new Post
50
- # object with each one.
49
+ # Read all the files in <base>/_posts except backup files (end with "~")
50
+ # and create a new Post object with each one.
51
51
  #
52
52
  # Returns nothing
53
- def read_posts
54
- base = File.join(self.source, "_posts")
55
- entries = Dir.entries(base)
56
- entries = entries.reject { |e| File.directory?(e) }
53
+ def read_posts(dir)
54
+ base = File.join(self.source, dir, '_posts')
55
+
56
+ entries = []
57
+ Dir.chdir(base) { entries = Dir['**/*'] }
58
+ entries = entries.reject { |e| e[-1..-1] == '~' }
59
+ entries = entries.reject { |e| File.directory?(File.join(base, e)) }
57
60
 
61
+ # first pass processes, but does not yet render post content
58
62
  entries.each do |f|
59
- self.posts << Post.new(base, f) if Post.valid?(f)
63
+ if Post.valid?(f)
64
+ post = Post.new(self.source, dir, f)
65
+ self.posts << post
66
+ end
67
+ end
68
+
69
+ # second pass renders each post now that full site payload is available
70
+ self.posts.each do |post|
71
+ post.render(self.layouts, site_payload)
60
72
  end
61
73
 
62
74
  self.posts.sort!
@@ -69,14 +81,14 @@ module Jekyll
69
81
  # Returns nothing
70
82
  def write_posts
71
83
  self.posts.each do |post|
72
- post.add_layout(self.layouts, site_payload)
73
84
  post.write(self.dest)
74
85
  end
75
86
  end
76
87
 
77
88
  # Copy all regular files from <source> to <dest>/ ignoring
78
- # any files/directories that are hidden (start with ".") or contain
79
- # site content (start with "_")
89
+ # any files/directories that are hidden or backup files (start
90
+ # with "." or end with "~") or contain site content (start with "_")
91
+ # unless they are "_posts" directories
80
92
  # The +dir+ String is a relative path used to call this method
81
93
  # recursively as it descends through directories
82
94
  #
@@ -84,7 +96,17 @@ module Jekyll
84
96
  def transform_pages(dir = '')
85
97
  base = File.join(self.source, dir)
86
98
  entries = Dir.entries(base)
87
- entries = entries.reject { |e| ['.', '_'].include?(e[0..0]) }
99
+ entries = entries.reject { |e| e[-1..-1] == '~' }
100
+ entries = entries.reject do |e|
101
+ (e != '_posts') and ['.', '_'].include?(e[0..0])
102
+ end
103
+
104
+ # we need to make sure to process _posts *first* otherwise they
105
+ # might not be available yet to other templates as {{ site.posts }}
106
+ if entries.include?('_posts')
107
+ entries.delete('_posts')
108
+ read_posts(dir)
109
+ end
88
110
 
89
111
  entries.each do |f|
90
112
  if File.directory?(File.join(base, f))
@@ -93,13 +115,13 @@ module Jekyll
93
115
  else
94
116
  first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
95
117
 
96
- # if the file appears to have a YAML header then process it as a page
97
118
  if first3 == "---"
119
+ # file appears to have a YAML header so process it as a page
98
120
  page = Page.new(self.source, dir, f)
99
- page.add_layout(self.layouts, site_payload)
121
+ page.render(self.layouts, site_payload)
100
122
  page.write(self.dest)
101
- # otherwise copy the file without transforming it
102
123
  else
124
+ # otherwise copy the file without transforming it
103
125
  FileUtils.mkdir_p(File.join(self.dest, dir))
104
126
  FileUtils.cp(File.join(self.source, dir, f), File.join(self.dest, dir, f))
105
127
  end
@@ -111,7 +133,17 @@ module Jekyll
111
133
  #
112
134
  # Returns {"site" => {"time" => <Time>, "posts" => [<Post>]}}
113
135
  def site_payload
114
- {"site" => {"time" => Time.now, "posts" => self.posts.sort.reverse}}
136
+ # Build the category hash map of category ( names => arrays of posts )
137
+ # then sort each array in reverse order
138
+ categories = Hash.new { |hash, key| hash[key] = Array.new }
139
+ self.posts.each { |p| p.categories.each { |c| categories[c] << p } }
140
+ categories.values.map { |cats| cats.sort! { |a, b| b <=> a} }
141
+
142
+ {"site" => {
143
+ "time" => Time.now,
144
+ "posts" => self.posts.sort { |a,b| b <=> a },
145
+ "categories" => categories
146
+ }}
115
147
  end
116
148
  end
117
149
 
@@ -7,7 +7,18 @@ module Jekyll
7
7
  end
8
8
 
9
9
  def render(context)
10
- File.read(File.join(Jekyll.source, '_includes', @file))
10
+ if @file !~ /^[a-zA-Z0-9_\/\.-]+$/ || @file =~ /\.\// || @file =~ /\/\./
11
+ return "Include file '#{@file}' contains invalid characters or sequences"
12
+ end
13
+
14
+ Dir.chdir(File.join(Jekyll.source, '_includes')) do
15
+ choices = Dir['**/*'].reject { |x| File.symlink?(x) }
16
+ if choices.include?(@file)
17
+ File.read(@file)
18
+ else
19
+ "Included file '#{@file}' not found in _includes directory"
20
+ end
21
+ end
11
22
  end
12
23
  end
13
24
 
@@ -5,8 +5,18 @@ title: Tom Preston-Werner
5
5
 
6
6
  h1. Welcome to my site
7
7
 
8
+ h2. Please read our {{ site.posts | size }} Posts
9
+
8
10
  <ul>
9
11
  {% for post in site.posts %}
10
12
  <li>{{ post.date }} <a href="{{ post.url }}">{{ post.title }}</a></li>
11
13
  {% endfor %}
12
- </ul>
14
+ </ul>
15
+
16
+ {% assign first_post = site.posts.first %}
17
+ <div id="first_post">
18
+ <h1>{{ first_post.title }}</h1>
19
+ <div>
20
+ {{ first_post.content }}
21
+ </div>
22
+ </div>
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestGeneratedSite < Test::Unit::TestCase
4
+ def setup
5
+ clear_dest
6
+ source = File.join(File.dirname(__FILE__), *%w[source])
7
+ @s = Site.new(source, dest_dir)
8
+ @s.process
9
+ @index = File.read(File.join(dest_dir, 'index.html'))
10
+ end
11
+
12
+ def test_site_posts_in_index
13
+ # confirm that {{ site.posts }} is working
14
+ assert @index.include?("#{@s.posts.size} Posts")
15
+ end
16
+
17
+ def test_post_content_in_index
18
+ # confirm that the {{ post.content }} is rendered OK
19
+ assert @index.include?('<p>This <em>is</em> cool</p>')
20
+ end
21
+ end
@@ -7,6 +7,9 @@ class TestPost < Test::Unit::TestCase
7
7
 
8
8
  def test_valid
9
9
  assert Post.valid?("2008-10-19-foo-bar.textile")
10
+ assert Post.valid?("foo/bar/2008-10-19-foo-bar.textile")
11
+
12
+ assert !Post.valid?("lol2008-10-19-foo-bar.textile")
10
13
  assert !Post.valid?("blah")
11
14
  end
12
15
 
@@ -21,6 +24,7 @@ class TestPost < Test::Unit::TestCase
21
24
 
22
25
  def test_url
23
26
  p = Post.allocate
27
+ p.categories = []
24
28
  p.process("2008-10-19-foo-bar.textile")
25
29
 
26
30
  assert_equal "/2008/10/19/foo-bar.html", p.url
@@ -29,7 +33,7 @@ class TestPost < Test::Unit::TestCase
29
33
  def test_permalink
30
34
  p = Post.allocate
31
35
  p.process("2008-12-03-permalinked-post.textile")
32
- p.read_yaml(File.join(File.dirname(__FILE__), *%w[source posts]), "2008-12-03-permalinked-post.textile")
36
+ p.read_yaml(File.join(File.dirname(__FILE__), *%w[source _posts]), "2008-12-03-permalinked-post.textile")
33
37
 
34
38
  assert_equal "my_category/permalinked-post", p.permalink
35
39
  end
@@ -37,7 +41,7 @@ class TestPost < Test::Unit::TestCase
37
41
  def test_dir_respects_permalink
38
42
  p = Post.allocate
39
43
  p.process("2008-12-03-permalinked-post.textile")
40
- p.read_yaml(File.join(File.dirname(__FILE__), *%w[source posts]), "2008-12-03-permalinked-post.textile")
44
+ p.read_yaml(File.join(File.dirname(__FILE__), *%w[source _posts]), "2008-12-03-permalinked-post.textile")
41
45
 
42
46
  assert_equal "my_category", p.dir
43
47
  end
@@ -59,10 +63,10 @@ class TestPost < Test::Unit::TestCase
59
63
  assert_equal "<h1>{{ page.title }}</h1>\n<p>Best <strong>post</strong> ever</p>", p.content
60
64
  end
61
65
 
62
- def test_add_layout
63
- p = Post.new(File.join(File.dirname(__FILE__), *%w[source _posts]), "2008-10-18-foo-bar.textile")
66
+ def test_render
67
+ p = Post.new(File.join(File.dirname(__FILE__), *%w[source]), '', "2008-10-18-foo-bar.textile")
64
68
  layouts = {"default" => Layout.new(File.join(File.dirname(__FILE__), *%w[source _layouts]), "simple.html")}
65
- p.add_layout(layouts, {"site" => {"posts" => []}})
69
+ p.render(layouts, {"site" => {"posts" => []}})
66
70
 
67
71
  assert_equal "<<< <h1>Foo Bar</h1>\n<p>Best <strong>post</strong> ever</p> >>>", p.output
68
72
  end
@@ -70,25 +74,25 @@ class TestPost < Test::Unit::TestCase
70
74
  def test_write
71
75
  clear_dest
72
76
 
73
- p = Post.new(File.join(File.dirname(__FILE__), *%w[source _posts]), "2008-10-18-foo-bar.textile")
77
+ p = Post.new(File.join(File.dirname(__FILE__), *%w[source]), '', "2008-10-18-foo-bar.textile")
74
78
  layouts = {"default" => Layout.new(File.join(File.dirname(__FILE__), *%w[source _layouts]), "simple.html")}
75
- p.add_layout(layouts, {"site" => {"posts" => []}})
79
+ p.render(layouts, {"site" => {"posts" => []}})
76
80
  p.write(dest_dir)
77
81
  end
78
82
 
79
83
  def test_data
80
- p = Post.new(File.join(File.dirname(__FILE__), *%w[source _posts]), "2008-11-21-complex.textile")
84
+ p = Post.new(File.join(File.dirname(__FILE__), *%w[source]), '', "2008-11-21-complex.textile")
81
85
  layouts = {"default" => Layout.new(File.join(File.dirname(__FILE__), *%w[source _layouts]), "simple.html")}
82
- p.add_layout(layouts, {"site" => {"posts" => []}})
86
+ p.render(layouts, {"site" => {"posts" => []}})
83
87
 
84
88
  assert_equal "<<< <p>url: /2008/11/21/complex.html<br />\ndate: #{Time.parse("2008-11-21")}<br />\nid: /2008/11/21/complex</p> >>>", p.output
85
89
  end
86
90
 
87
91
  def test_include
88
92
  Jekyll.source = File.join(File.dirname(__FILE__), *%w[source])
89
- p = Post.new(File.join(File.dirname(__FILE__), *%w[source _posts]), "2008-12-13-include.markdown")
93
+ p = Post.new(File.join(File.dirname(__FILE__), *%w[source]), '', "2008-12-13-include.markdown")
90
94
  layouts = {"default" => Layout.new(File.join(File.dirname(__FILE__), *%w[source _layouts]), "simple.html")}
91
- p.add_layout(layouts, {"site" => {"posts" => []}})
95
+ p.render(layouts, {"site" => {"posts" => []}})
92
96
 
93
97
  assert_equal "<<< <hr />\n<p>Tom Preston-Werner github.com/mojombo</p>\n\n<p>This <em>is</em> cool</p> >>>", p.output
94
98
  end
@@ -15,11 +15,11 @@ class TestSite < Test::Unit::TestCase
15
15
 
16
16
  assert_equal ["default", "simple"].sort, @s.layouts.keys.sort
17
17
  end
18
-
18
+
19
19
  def test_read_posts
20
- @s.read_posts
20
+ @s.read_posts('')
21
21
 
22
- assert_equal 3, @s.posts.size
22
+ assert_equal 4, @s.posts.size
23
23
  end
24
24
 
25
25
  def test_write_posts
@@ -27,4 +27,4 @@ class TestSite < Test::Unit::TestCase
27
27
 
28
28
  @s.process
29
29
  end
30
- end
30
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Preston-Werner
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-12-15 00:00:00 -08:00
12
+ date: 2008-12-24 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -103,7 +103,10 @@ files:
103
103
  - lib/jekyll/albino.rb
104
104
  - lib/jekyll/converters/csv.rb
105
105
  - lib/jekyll/converters/mephisto.rb
106
+ - lib/jekyll/converters/mt.rb
107
+ - lib/jekyll/converters/wordpress.rb
106
108
  - lib/jekyll/convertible.rb
109
+ - lib/jekyll/core_ext.rb
107
110
  - lib/jekyll/filters.rb
108
111
  - lib/jekyll/layout.rb
109
112
  - lib/jekyll/page.rb
@@ -117,11 +120,12 @@ files:
117
120
  - test/source/_layouts/simple.html
118
121
  - test/source/_posts/2008-10-18-foo-bar.textile
119
122
  - test/source/_posts/2008-11-21-complex.textile
123
+ - test/source/_posts/2008-12-03-permalinked-post.textile
120
124
  - test/source/_posts/2008-12-13-include.markdown
121
125
  - test/source/css/screen.css
122
126
  - test/source/index.html
123
- - test/source/posts/2008-12-03-permalinked-post.textile
124
127
  - test/suite.rb
128
+ - test/test_generated_site.rb
125
129
  - test/test_jekyll.rb
126
130
  - test/test_post.rb
127
131
  - test/test_site.rb
@@ -153,6 +157,7 @@ signing_key:
153
157
  specification_version: 2
154
158
  summary: Jekyll is a simple, blog aware, static site generator.
155
159
  test_files:
160
+ - test/test_generated_site.rb
156
161
  - test/test_jekyll.rb
157
162
  - test/test_post.rb
158
163
  - test/test_site.rb