Skip to content
This repository

New Liquid tag for doing simple image galleries. #295

Closed
wants to merge 56 commits into from

9 participants

Robert Bruce Park Tom Preston-Werner Higor Eurípedes Matt Hall Aaron Beckerman Ben Chatelain Elia Schito Ryan Davis Aman Gupta
Robert Bruce Park
robru commented March 03, 2011

Here's a basic usage:

 {% gallery name:your_gallery_name %}
 <img src="{{ file.path }}" />
 {% endgallery %}

If you want to arrange the images into a grid, you can do this:

 <table><tr>
 {% gallery name:your_gallery_name %}
 <td><img src="{{ file.path }}" alt="{{ file.title }}"/>
 <p>Taken on {{ file.date | date: "%F" }}</p></td>
 {% cycle '', '', '', '</tr><tr>' %}
 {% endgallery %}
 </tr></table>

It provides this data to the templates:

file.url        # absolute path to file
file.path       # relative path to file
file.htmlurl    # absolute path to html file
file.htmlpath   # relative path to html file
file.name       # File.basename(filename)
file.date       # date extracted from beginning of filename, if present (optional)
file.slug       # basename with the date and file extension stripped off
file.title      # file.slug with hyphens converted to spaces, and put into Title Case

As well as the familiar forloop variable.

Also, none of my code is actually specific to image files. This plugin could easily be used to glob anything out of the filesystem, including a list of available downloads:

 <ul>
 {% gallery name:downloads_page dir:downloads format:tar.gz reverse:no %}
 <li><a href="{{ file.path }}">{{ file.title }}</a></li>
 {% endgallery %}
 </ul>

The above example assumes that it's located inside /downloads_page/index.html and that your .tar.gz files are in /downloads_page/downloads/*.tar.gz. I've added some input checking that prevents the user passing in any periods or slashes to prevent them being able to glob outside of the jekyll directory.

and others added some commits December 15, 2010
Higor Eurípedes updated/fixed wordpress.com migration script 9e0eb75
Matt Hall Adding Posterous Importer c1f0e07
Matt Hall Updating CLI for importing 84c1a72
Aaron Beckerman fix typo in history: site.ports -> site.posts 033333f
Ben Chatelain Add Jekyll::WordPress.TABLE_PREFIX and inclusion in QUERY 42f63f9
Ben Chatelain Change TABLE_PREFIX back to default e902bb9
Ben Chatelain Change TABLE_PREFIX from class member to 4th parameter of process met…
…hod (now lowercase)

Move QUERY into process method
bc3771a
Ben Chatelain Fix compile error by making QUERY lowercase (local instead of const) d61c1e9
Elia Schito The Wordpress.com migrator now works and gathers categories as tags. f68bbcb
Elia Schito Merged wordpress.com migrator fix from 'heuripedes/jekyll' ca48ea9
Elia Schito Take permalink name directly from worpress export file. c70dac3
Elia Schito Remove double directory creation. 034b064
Ryan Davis Cleaned up unnecessary string munging bd01e64
Robert Bruce Park Added liquid tag for doing simple image galleries. 787bb67
Robert Bruce Park Allow formats other than just ".jpg", and provide 'title' to the temp…
…lates.

'title' is just the image filename with the extension removed, hyphens and underscores converted to spaces, and the remaining words converted to Title Case.
fc8940d
Robert Bruce Park Less regex for better readability. 3cab419
Robert Bruce Park Add option to let the user reverse the sort. b18ccdd
Robert Bruce Park Simplify Title Casing by adding a method to String class. 69597bf
Robert Bruce Park Provide more data (including dates) to the templates. 8e8497f
Robert Bruce Park Do some input sanity checking. f996dbe
Robert Bruce Park Better input sanitation.
Should be pretty solid now. Nothing nasty is going to make it from user-supplied template data into the glob method now.
e8d3102
Robert Bruce Park Simplify titleize. eeb18e4
Robert Bruce Park Underscores too! D'oh! d07a9df
Aman Gupta Gemfile to help install the dependencies a04c270
Aman Gupta fix "4.2.1" versioned dev dependencies, and cleanup syntax 16ea326
Aman Gupta use the new albino gem 08725eb
Aman Gupta open4 is not required 4b5a4e8
Aman Gupta work around cucumber issue (closes #296) 8cc7f06
Aman Gupta rdiscount is a dev dependency 9da714d
Aman Gupta make kramdown a runtime dep so people can use it instead of maruku edef025
Aman Gupta speed up cleanup be8b771
Aman Gupta sanitize urls and ignore symlinks 13cc44f
Tom Preston-Werner Update history. f82c51d
Tom Preston-Werner Merge remote-tracking branch 'zenspider/master' into devel 5f4dfe3
Tom Preston-Werner Move require to jekyll.rb and update history. a31780a
Tom Preston-Werner Merge remote-tracking branch 'ab9/master' into devel 38844cd
Tom Preston-Werner Merge remote-tracking branch 'MattHall/posterous' into devel f58a821
Tom Preston-Werner Merge remote-tracking branch 'elia/master' into devel d2814cf
Tom Preston-Werner Merge remote-tracking branch 'phatblat/master' into test 01a9090
Tom Preston-Werner Merge remote-tracking branch 'MattHall/cli' into test 68eaadd
Tom Preston-Werner TomDoc convertible.rb. 6c94db1
Tom Preston-Werner
Owner

This looks really nice. Could you hook up a few test cases and cucumber features for it?

Robert Bruce Park
robru commented March 11, 2011

This isn't the first time I've ever written a test case, but it's the first time I've written one in Ruby (all my previous experience is in python), and i've never used Cucumber before. Please let me know if these are satisfactory.

Thanks!

added some commits March 11, 2011
Robert Bruce Park More consistent absolute/relative path generation, and better cucumbe…
…r test of this.
cad5dde
Robert Bruce Park Provide contents of YAML front matter of iterated files to parent tem…
…plate.

As per discussion at jekyll#299

Since images contain no YAML, this is harmlessly ignored by image files, but does make the plugin more useful to people using gallery for lists of files other than images.
11c86f4
Robert Bruce Park Add Cucumber Scenario for new ability to read YAML from globbed files. d96f226
Robert Bruce Park Make name attribute default to '.' if left unspecified.
This allows /index.html to contain a gallery of files at the top level instead of forcing everything to be one subdirectory below the sourcedir.
80aa862
Robert Bruce Park robru commented on the diff March 12, 2011
lib/jekyll/tags/gallery.rb
((47 lines not shown))
1
Robert Bruce Park
robru added a note March 12, 2011

i've changed this so that if name is not specified, it defaults to a period, which is equivalent to saying "look in the root of the sourcedir for the gallery files". This is slightly nicer than it was before, but I'm still looking for a way to say "the gallery should be located in the same directory as the file that contained {% gallery %}."

Any thoughts on this, Tom?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Robert Bruce Park
robru commented March 18, 2011

Hmmm, I seem to have made a mess of things by merging. Should I start over with a new fork and a new pull request, or is this ok as is? Sorry, github newbie ;-)

Robert Bruce Park
robru commented March 18, 2011

Yeah, starting this one over. Derp de derp derp...

Robert Bruce Park robru closed this March 18, 2011
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 56 unique commits by 9 authors.

Dec 15, 2010
Higor Eurípedes updated/fixed wordpress.com migration script 9e0eb75
Dec 19, 2010
Matt Hall Adding Posterous Importer c1f0e07
Matt Hall Updating CLI for importing 84c1a72
Jan 23, 2011
Aaron Beckerman fix typo in history: site.ports -> site.posts 033333f
Ben Chatelain Add Jekyll::WordPress.TABLE_PREFIX and inclusion in QUERY 42f63f9
Ben Chatelain Change TABLE_PREFIX back to default e902bb9
Ben Chatelain Change TABLE_PREFIX from class member to 4th parameter of process met…
…hod (now lowercase)

Move QUERY into process method
bc3771a
Ben Chatelain Fix compile error by making QUERY lowercase (local instead of const) d61c1e9
Jan 27, 2011
Elia Schito The Wordpress.com migrator now works and gathers categories as tags. f68bbcb
Elia Schito Merged wordpress.com migrator fix from 'heuripedes/jekyll' ca48ea9
Elia Schito Take permalink name directly from worpress export file. c70dac3
Elia Schito Remove double directory creation. 034b064
Mar 02, 2011
Ryan Davis Cleaned up unnecessary string munging bd01e64
Mar 03, 2011
Robert Bruce Park Added liquid tag for doing simple image galleries. 787bb67
Mar 04, 2011
Robert Bruce Park Allow formats other than just ".jpg", and provide 'title' to the temp…
…lates.

'title' is just the image filename with the extension removed, hyphens and underscores converted to spaces, and the remaining words converted to Title Case.
fc8940d
Robert Bruce Park Less regex for better readability. 3cab419
Robert Bruce Park Add option to let the user reverse the sort. b18ccdd
Robert Bruce Park Simplify Title Casing by adding a method to String class. 69597bf
Robert Bruce Park Provide more data (including dates) to the templates. 8e8497f
Robert Bruce Park Do some input sanity checking. f996dbe
Mar 05, 2011
Robert Bruce Park Better input sanitation.
Should be pretty solid now. Nothing nasty is going to make it from user-supplied template data into the glob method now.
e8d3102
Mar 06, 2011
Robert Bruce Park Simplify titleize. eeb18e4
Robert Bruce Park Underscores too! D'oh! d07a9df
Aman Gupta Gemfile to help install the dependencies a04c270
Aman Gupta fix "4.2.1" versioned dev dependencies, and cleanup syntax 16ea326
Aman Gupta use the new albino gem 08725eb
Aman Gupta open4 is not required 4b5a4e8
Mar 07, 2011
Aman Gupta work around cucumber issue (closes #296) 8cc7f06
Aman Gupta rdiscount is a dev dependency 9da714d
Aman Gupta make kramdown a runtime dep so people can use it instead of maruku edef025
Mar 10, 2011
Aman Gupta speed up cleanup be8b771
Aman Gupta sanitize urls and ignore symlinks 13cc44f
Tom Preston-Werner Update history. f82c51d
Tom Preston-Werner Merge remote-tracking branch 'zenspider/master' into devel 5f4dfe3
Tom Preston-Werner Move require to jekyll.rb and update history. a31780a
Tom Preston-Werner Merge remote-tracking branch 'ab9/master' into devel 38844cd
Tom Preston-Werner Merge remote-tracking branch 'MattHall/posterous' into devel f58a821
Tom Preston-Werner Merge remote-tracking branch 'elia/master' into devel d2814cf
Tom Preston-Werner Merge remote-tracking branch 'phatblat/master' into test 01a9090
Tom Preston-Werner Merge remote-tracking branch 'MattHall/cli' into test 68eaadd
Mar 11, 2011
Tom Preston-Werner TomDoc convertible.rb. 6c94db1
Tom Preston-Werner Better error message for invalid post date. dce4ccc
Robert Bruce Park Add Cucumber feature test for gallery tag. 23fad5e
Robert Bruce Park Start a test case for the gallery tag. ba8ee75
Robert Bruce Park More consistent absolute/relative path generation, and better cucumbe…
…r test of this.
cad5dde
Mar 12, 2011
Robert Bruce Park Provide contents of YAML front matter of iterated files to parent tem…
…plate.

As per discussion at jekyll#299

Since images contain no YAML, this is harmlessly ignored by image files, but does make the plugin more useful to people using gallery for lists of files other than images.
11c86f4
Robert Bruce Park Add Cucumber Scenario for new ability to read YAML from globbed files. d96f226
Robert Bruce Park Make name attribute default to '.' if left unspecified.
This allows /index.html to contain a gallery of files at the top level instead of forcing everything to be one subdirectory below the sourcedir.
80aa862
Robert Bruce Park Change cucumber feature to show the simplest possible use case of gal…
…lery tag.
3ebcc45
Mar 13, 2011
Robert Bruce Park Code style cleanup.
Moved the String.titleize method definition into core_ext.rb. Also deleted the copious usage documentation comments because a) it was a bit out of date and b) that belongs on the wiki anyway.
5e87d9c
Robert Bruce Park Add file.htmlpath to make it easier to link to htmlized files. 16d8590
Robert Bruce Park Add file.htmlurl to compliment file.htmlpath 2b03d21
Mar 17, 2011
Robert Bruce Park Create gallery.feature, moving the three existing Cucumber Scenarios …
…there, and add two more.
ae5ba76
Robert Bruce Park Minor expansion of YAML use in Cucumber test. 592e4f6
Mar 18, 2011
Robert Bruce Park Merge remote-tracking branch 'mojombo/devel' 227fc4f
Robert Bruce Park Merge branch 'master' of github.com:robru/jekyll 967106a
This page is out of date. Refresh to see the latest.
1  .gitignore
... ...
@@ -1,3 +1,4 @@
  1
+Gemfile.lock
1 2
 test/dest
2 3
 *.gem
3 4
 pkg/
2  Gemfile
... ...
@@ -0,0 +1,2 @@
  1
+source :rubygems
  2
+gemspec
16  History.txt
... ...
@@ -1,3 +1,17 @@
  1
+== HEAD
  2
+  * Major Enhancements
  3
+    * Add command line importer functionality (#253)
  4
+  * Minor Enhancements
  5
+    * Switch to Albino gem
  6
+    * Bundler support
  7
+    * Use English library to avoid hoops (#292)
  8
+    * Add Posterous importer (#254)
  9
+    * Fixes for Wordpress importer (#274, #252, #271)
  10
+    * Better error message for invalid post date (#291)
  11
+    * Print formatted fatal exceptions to stdout on build failure
  12
+  * Bug Fixes
  13
+    * Secure additional path exploits
  14
+
1 15
 == 0.10.0 / 2010-12-16
2 16
   * Bug Fixes
3 17
     * Add --no-server option.
@@ -75,7 +89,7 @@
75 89
     * Empty tags causes error in read_posts (#84)
76 90
     * Fix pagination to adhere to read/render/write paradigm
77 91
   * Test Enhancement
78  
-    * cucumber features no longer use site.ports.first where a better
  92
+    * cucumber features no longer use site.posts.first where a better
79 93
       alternative is available
80 94
 
81 95
 == 0.5.6 / 2010-01-08
1  README.textile
Source Rendered
@@ -27,7 +27,6 @@ h2. Runtime Dependencies
27 27
 * Classifier: Generating related posts (Ruby)
28 28
 * Maruku: Default markdown engine (Ruby)
29 29
 * Directory Watcher: Auto-regeneration of sites (Ruby)
30  
-* Open4: Talking to pygments for syntax highlighting (Ruby)
31 30
 * Pygments: Syntax highlighting (Python)
32 31
 
33 32
 h2. Developer Dependencies
88  bin/jekyll
@@ -9,7 +9,8 @@ Basic Command Line Usage:
9 9
   jekyll                                                   # . -> ./_site
10 10
   jekyll <path to write generated site>                    # . -> <path>
11 11
   jekyll <path to source> <path to write generated site>   # <path> -> <path>
12  
-
  12
+  jekyll import <importer name> <options>                  # imports posts using named import script
  13
+  
13 14
   Configuration is read from '<source>/_config.yml' but can be overriden
14 15
   using the following options:
15 16
 
@@ -18,11 +19,37 @@ HELP
18 19
 require 'optparse'
19 20
 require 'jekyll'
20 21
 
  22
+
21 23
 exec = {}
22 24
 options = {}
23 25
 opts = OptionParser.new do |opts|
24 26
   opts.banner = help
25 27
 
  28
+  opts.on("--file [PATH]", "File to import from") do |import_file|
  29
+    options['file'] = import_file
  30
+  end
  31
+  
  32
+  opts.on("--dbname [TEXT]", "DB to import from") do |import_dbname|
  33
+    options['dbname'] = import_dbname
  34
+  end
  35
+  
  36
+  opts.on("--user [TEXT]", "Username to use when importing") do |import_user|
  37
+    options['user'] = import_user
  38
+  end
  39
+  
  40
+  opts.on("--pass [TEXT]", "Password to use when importing") do |import_pass|
  41
+    options['pass'] = import_pass
  42
+  end
  43
+  
  44
+  opts.on("--host [HOST ADDRESS]", "Host to import from") do |import_host|
  45
+    options['host'] = import_host
  46
+  end
  47
+  
  48
+  opts.on("--site [SITE NAME]", "Site to import from") do |import_site|
  49
+    options['site'] = import_site
  50
+  end
  51
+  
  52
+
26 53
   opts.on("--[no-]safe", "Safe mode (default unsafe)") do |safe|
27 54
     options['safe'] = safe
28 55
   end
@@ -105,6 +132,59 @@ end
105 132
 # Read command line options into `options` hash
106 133
 opts.parse!
107 134
 
  135
+
  136
+# Check for import stuff
  137
+if ARGV.size > 0
  138
+  if ARGV[0] == 'import'
  139
+    migrator = ARGV[1]
  140
+
  141
+    if migrator.nil?
  142
+      puts "Invalid options. Run `jekyll --help` for assistance."
  143
+      exit(1)
  144
+    else
  145
+      migrator = migrator.downcase
  146
+    end
  147
+    
  148
+    cmd_options = []
  149
+    ['file', 'dbname', 'user', 'pass', 'host', 'site'].each do |p|
  150
+      cmd_options << "\"#{options[p]}\"" unless options[p].nil?
  151
+    end
  152
+    
  153
+    # It's import time
  154
+    puts "Importing..."
  155
+    
  156
+    # Ideally, this shouldn't be necessary. Maybe parse the actual
  157
+    # src files for the migrator name?
  158
+    migrators = {
  159
+      :posterous => 'Posterous',
  160
+      :wordpressdotcom => 'WordpressDotCom',
  161
+      :wordpress => 'Wordpress',
  162
+      :csv => 'CSV',
  163
+      :drupal => 'Drupal',
  164
+      :mephisto => 'Mephisto',
  165
+      :mt => 'MT',
  166
+      :textpattern => 'TextPattern',
  167
+      :typo => 'Typo'
  168
+    }
  169
+    
  170
+    app_root = File.join(File.dirname(__FILE__), '..')
  171
+    
  172
+    require "#{app_root}/lib/jekyll/migrators/#{migrator}"
  173
+    
  174
+    if Jekyll.const_defined?(migrators[migrator.to_sym])
  175
+      migrator_class = Jekyll.const_get(migrators[migrator.to_sym])
  176
+      migrator_class.process(*cmd_options)
  177
+    else
  178
+      puts "Invalid migrator. Run `jekyll --help` for assistance."
  179
+      exit(1)
  180
+    end
  181
+    
  182
+    exit(0)
  183
+  end
  184
+end
  185
+
  186
+
  187
+
108 188
 # Get source and destintation from command line
109 189
 case ARGV.size
110 190
   when 0
@@ -162,7 +242,11 @@ else
162 242
   puts "Building site: #{source} -> #{destination}"
163 243
   begin
164 244
     site.process
165  
-  rescue Jekyll::FatalException
  245
+  rescue Jekyll::FatalException => e
  246
+    puts
  247
+    puts "ERROR: YOUR SITE COULD NOT BE BUILT:"
  248
+    puts "------------------------------------"
  249
+    puts e.message
166 250
     exit(1)
167 251
   end
168 252
   puts "Successfully generated site: #{source} -> #{destination}"
54  features/gallery.feature
... ...
@@ -0,0 +1,54 @@
3  features/support/env.rb
@@ -14,3 +14,6 @@ def run_jekyll(opts = {})
14 14
   command << " >> /dev/null 2>&1" if opts[:debug].nil?
15 15
   system command
16 16
 end
  17
+
  18
+# work around "invalid option: --format" cucumber bug (see #296)
  19
+Test::Unit.run = true
22  jekyll.gemspec
@@ -23,17 +23,19 @@ Gem::Specification.new do |s|
23 23
   s.rdoc_options = ["--charset=UTF-8"]
24 24
   s.extra_rdoc_files = %w[README.textile LICENSE]
25 25
 
26  
-  s.add_runtime_dependency('liquid', [">= 1.9.0"])
27  
-  s.add_runtime_dependency('classifier', [">= 1.3.1"])
28  
-  s.add_runtime_dependency('directory_watcher', [">= 1.1.1"])
29  
-  s.add_runtime_dependency('maruku', [">= 0.5.9"])
  26
+  s.add_runtime_dependency('liquid', ">= 1.9.0")
  27
+  s.add_runtime_dependency('classifier', ">= 1.3.1")
  28
+  s.add_runtime_dependency('directory_watcher', ">= 1.1.1")
  29
+  s.add_runtime_dependency('maruku', ">= 0.5.9")
  30
+  s.add_runtime_dependency('kramdown', ">= 0.13.2")
  31
+  s.add_runtime_dependency('albino', ">= 1.3.2")
30 32
 
31  
-  s.add_development_dependency('redgreen', [">= 4.2.1"])
32  
-  s.add_development_dependency('shoulda', [">= 4.2.1"])
33  
-  s.add_development_dependency('rr', [">= 4.2.1"])
34  
-  s.add_development_dependency('cucumber', [">= 4.2.1"])
35  
-  s.add_development_dependency('RedCloth', [">= 4.2.1"])
36  
-  s.add_development_dependency('kramdown', [">= 0.12.0"])
  33
+  s.add_development_dependency('redgreen', ">= 1.2.2")
  34
+  s.add_development_dependency('shoulda', ">= 2.11.3")
  35
+  s.add_development_dependency('rr', ">= 1.0.2")
  36
+  s.add_development_dependency('cucumber', ">= 0.10.0")
  37
+  s.add_development_dependency('RedCloth', ">= 4.2.1")
  38
+  s.add_development_dependency('rdiscount', ">= 1.6.5")
37 39
 
38 40
   # = MANIFEST =
39 41
   s.files = %w[
3  lib/jekyll.rb
@@ -19,10 +19,12 @@ def require_all(path)
19 19
 require 'fileutils'
20 20
 require 'time'
21 21
 require 'yaml'
  22
+require 'English'
22 23
 
23 24
 # 3rd party
24 25
 require 'liquid'
25 26
 require 'maruku'
  27
+require 'albino'
26 28
 
27 29
 # internal requires
28 30
 require 'jekyll/core_ext'
@@ -32,7 +34,6 @@ def require_all(path)
32 34
 require 'jekyll/page'
33 35
 require 'jekyll/post'
34 36
 require 'jekyll/filters'
35  
-require 'jekyll/albino'
36 37
 require 'jekyll/static_file'
37 38
 require 'jekyll/errors'
38 39
 
120  lib/jekyll/albino.rb
... ...
@@ -1,120 +0,0 @@
1  
-##
2  
-# Wrapper for the Pygments command line tool, pygmentize.
3  
-#
4  
-# Pygments: http://pygments.org/
5  
-#
6  
-# Assumes pygmentize is in the path.  If not, set its location
7  
-# with Albino.bin = '/path/to/pygmentize'
8  
-#
9  
-# Use like so:
10  
-#
11  
-#   @syntaxer = Albino.new('/some/file.rb', :ruby)
12  
-#   puts @syntaxer.colorize
13  
-#
14  
-# This'll print out an HTMLized, Ruby-highlighted version
15  
-# of '/some/file.rb'.
16  
-#
17  
-# To use another formatter, pass it as the third argument:
18  
-#
19  
-#   @syntaxer = Albino.new('/some/file.rb', :ruby, :bbcode)
20  
-#   puts @syntaxer.colorize
21  
-#
22  
-# You can also use the #colorize class method:
23  
-#
24  
-#   puts Albino.colorize('/some/file.rb', :ruby)
25  
-#
26  
-# Another also: you get a #to_s, for somewhat nicer use in Rails views.
27  
-#
28  
-#   ... helper file ...
29  
-#   def highlight(text)
30  
-#     Albino.new(text, :ruby)
31  
-#   end
32  
-#
33  
-#   ... view file ...
34  
-#   <%= highlight text %>
35  
-#
36  
-# The default lexer is 'text'.  You need to specify a lexer yourself;
37  
-# because we are using STDIN there is no auto-detect.
38  
-#
39  
-# To see all lexers and formatters available, run `pygmentize -L`.
40  
-#
41  
-# Chris Wanstrath // chris@ozmm.org
42  
-#         GitHub // http://github.com
43  
-#
44  
-
45  
-class Albino
46  
-  @@bin = Rails.development? ? 'pygmentize' : '/usr/bin/pygmentize' rescue 'pygmentize'
47  
-
48  
-  def self.bin=(path)
49  
-    @@bin = path
50  
-  end
51  
-
52  
-  def self.colorize(*args)
53  
-    new(*args).colorize
54  
-  end
55  
-
56  
-  def initialize(target, lexer = :text, format = :html)
57  
-    @target  = target
58  
-    @options = { :l => lexer, :f => format, :O => 'encoding=utf-8' }
59  
-  end
60  
-
61  
-  def execute(command)
62  
-    output = ''
63  
-    IO.popen(command, mode='r+') do |p|
64  
-      p.write @target
65  
-      p.close_write
66  
-      output = p.read.strip
67  
-    end
68  
-    output
69  
-  end
70  
-
71  
-  def colorize(options = {})
72  
-    html = execute(@@bin + convert_options(options))
73  
-    # Work around an RDiscount bug: http://gist.github.com/97682
74  
-    html.to_s.sub(%r{</pre></div>\Z}, "</pre>\n</div>")
75  
-  end
76  
-  alias_method :to_s, :colorize
77  
-
78  
-  def convert_options(options = {})
79  
-    @options.merge(options).inject('') do |string, (flag, value)|
80  
-      string + " -#{flag} #{value}"
81  
-    end
82  
-  end
83  
-end
84  
-
85  
-if $0 == __FILE__
86  
-  require 'rubygems'
87  
-  require 'test/spec'
88  
-  require 'mocha'
89  
-  begin require 'redgreen'; rescue LoadError; end
90  
-
91  
-  context "Albino" do
92  
-    setup do
93  
-      @syntaxer = Albino.new(__FILE__, :ruby)
94  
-    end
95  
-
96  
-    specify "defaults to text" do
97  
-      syntaxer = Albino.new(__FILE__)
98  
-      syntaxer.expects(:execute).with('pygmentize -f html -l text').returns(true)
99  
-      syntaxer.colorize
100  
-    end
101  
-
102  
-    specify "accepts options" do
103  
-      @syntaxer.expects(:execute).with('pygmentize -f html -l ruby').returns(true)
104  
-      @syntaxer.colorize
105  
-    end
106  
-
107  
-    specify "works with strings" do
108  
-      syntaxer = Albino.new('class New; end', :ruby)
109  
-      assert_match %r(highlight), syntaxer.colorize
110  
-    end
111  
-
112  
-    specify "aliases to_s" do
113  
-      assert_equal @syntaxer.colorize, @syntaxer.to_s
114  
-    end
115  
-
116  
-    specify "class method colorize" do
117  
-      assert_equal @syntaxer.colorize, Albino.colorize(__FILE__, :ruby)
118  
-    end
119  
-  end
120  
-end
37  lib/jekyll/convertible.rb
@@ -10,21 +10,22 @@
10 10
 #   self.output=
11 11
 module Jekyll
12 12
   module Convertible
13  
-    # Return the contents as a string
  13
+    # Returns the contents as a String.
14 14
     def to_s
15 15
       self.content || ''
16 16
     end
17 17
 
18  
-    # Read the YAML frontmatter
19  
-    #   +base+ is the String path to the dir containing the file
20  
-    #   +name+ is the String filename of the file
  18
+    # Read the YAML frontmatter.
21 19
     #
22  
-    # Returns nothing
  20
+    # base - The String path to the dir containing the file.
  21
+    # name - The String filename of the file.
  22
+    #
  23
+    # Returns nothing.
23 24
     def read_yaml(base, name)
24 25
       self.content = File.read(File.join(base, name))
25 26
 
26 27
       if self.content =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
27  
-        self.content = self.content[($1.size + $2.size)..-1]
  28
+        self.content = $POSTMATCH
28 29
 
29 30
         begin
30 31
           self.data = YAML.load($1)
@@ -38,42 +39,46 @@ def read_yaml(base, name)
38 39
 
39 40
     # Transform the contents based on the content type.
40 41
     #
41  
-    # Returns nothing
  42
+    # Returns nothing.
42 43
     def transform
43 44
       self.content = converter.convert(self.content)
44 45
     end
45 46
 
46  
-    # Determine the extension depending on content_type
  47
+    # Determine the extension depending on content_type.
47 48
     #
48  
-    # Returns the extensions for the output file
  49
+    # Returns the String extension for the output file.
  50
+    #   e.g. ".html" for an HTML output file.
49 51
     def output_ext
50 52
       converter.output_ext(self.ext)
51 53
     end
52 54
 
53 55
     # Determine which converter to use based on this convertible's
54  
-    # extension
  56
+    # extension.
  57
+    #
  58
+    # Returns the Converter instance.
55 59
     def converter
56 60
       @converter ||= self.site.converters.find { |c| c.matches(self.ext) }
57 61
     end
58 62
 
59  
-    # Add any necessary layouts to this convertible document
60  
-    #   +layouts+ is a Hash of {"name" => "layout"}
61  
-    #   +site_payload+ is the site payload hash
  63
+    # Add any necessary layouts to this convertible document.
  64
+    #
  65
+    # payload - The site payload Hash.
  66
+    # layouts - A Hash of {"name" => "layout"}.
62 67
     #
63  
-    # Returns nothing
  68
+    # Returns nothing.
64 69
     def do_layout(payload, layouts)
65 70
       info = { :filters => [Jekyll::Filters], :registers => { :site => self.site } }
66 71
 
67 72
       # render and transform content (this becomes the final content of the object)
68 73
       payload["pygments_prefix"] = converter.pygments_prefix
69 74
       payload["pygments_suffix"] = converter.pygments_suffix
70  
-      
  75
+
71 76
       begin
72 77
         self.content = Liquid::Template.parse(self.content).render(payload, info)
73 78
       rescue => e
74 79
         puts "Liquid Exception: #{e.message} in #{self.data["layout"]}"
75 80
       end
76  
-      
  81
+
77 82
       self.transform
78 83
 
79 84
       # output keeps track of what will finally be written
6  lib/jekyll/core_ext.rb
@@ -50,3 +50,9 @@ def xmlschema
50 50
     strftime("%Y-%m-%dT%H:%M:%S%Z")
51 51
   end if RUBY_VERSION < '1.9'
52 52
 end
  53
+
  54
+class String
  55
+  def titleize
  56
+    split(/[\W_]+/).map(&:capitalize).join ' '
  57
+  end
  58
+end
73  lib/jekyll/migrators/posterous.rb
... ...
@@ -0,0 +1,73 @@
  1
+require 'rubygems'
  2
+require 'jekyll'
  3
+require 'fileutils'
  4
+require 'net/http'
  5
+require 'uri'
  6
+require "json"
  7
+
  8
+# ruby -r './lib/jekyll/migrators/posterous.rb' -e 'Jekyll::Posterous.process(email, pass, blog)'
  9
+
  10
+module Jekyll
  11
+  module Posterous
  12
+    
  13
+    def self.fetch(uri_str, limit = 10)
  14
+      # You should choose better exception.
  15
+      raise ArgumentError, 'Stuck in a redirect loop. Please double check your email and password' if limit == 0
  16
+      
  17
+      response = nil
  18
+      Net::HTTP.start('posterous.com') {|http|
  19
+        req = Net::HTTP::Get.new(uri_str)
  20
+        req.basic_auth @email, @pass
  21
+        response = http.request(req)
  22
+      }
  23
+
  24
+      case response
  25
+        when Net::HTTPSuccess     then response
  26
+        when Net::HTTPRedirection then fetch(response['location'], limit - 1)
  27
+        else response.error!
  28
+      end
  29
+    end
  30
+        
  31
+        
  32
+    def self.process(email, pass, blog = 'primary')
  33
+      @email, @pass = email, pass
  34
+      @api_token = JSON.parse(self.fetch("/api/2/auth/token").body)['api_token']
  35
+      FileUtils.mkdir_p "_posts"
  36
+      
  37
+      posts = JSON.parse(self.fetch("/api/v2/users/me/sites/#{blog}/posts?api_token=#{@api_token}").body)
  38
+      page = 1
  39
+      
  40
+      while posts.any?
  41
+      
  42
+        posts.each do |post|
  43
+          title = post["title"]
  44
+          slug = title.gsub(/[^[:alnum:]]+/, '-').downcase  
  45
+          date = Date.parse(post["display_date"])
  46
+          content = post["body_html"]
  47
+          published = !post["is_private"]
  48
+          name = "%02d-%02d-%02d-%s.html" % [date.year, date.month, date.day, slug]
  49
+        
  50
+          # Get the relevant fields as a hash, delete empty fields and convert
  51
+          # to YAML for the header
  52
+          data = {
  53
+             'layout' => 'post',
  54
+             'title' => title.to_s,
  55
+             'published' => published
  56
+           }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
  57
+
  58
+          # Write out the data and content to file
  59
+          File.open("_posts/#{name}", "w") do |f|
  60
+            f.puts data
  61
+            f.puts "---"
  62
+            f.puts content
  63
+          end
  64
+        
  65
+        end
  66
+        
  67
+        page += 1
  68
+        posts = JSON.parse(self.fetch("/api/v2/users/me/sites/#{blog}/posts?api_token=#{@api_token}&page=#{page}").body)        
  69
+      end
  70
+      
  71
+    end
  72
+  end
  73
+end
38  lib/jekyll/migrators/wordpress.com.rb
... ...
@@ -1,38 +0,0 @@
1  
-require 'rubygems'
2  
-require 'hpricot'
3  
-require 'fileutils'
4  
-
5  
-# This importer takes a wordpress.xml file,
6  
-# which can be exported from your 
7  
-# wordpress.com blog (/wp-admin/export.php)
8  
-
9  
-module Jekyll
10  
-  module WordpressDotCom
11  
-    def self.process(filename = "wordpress.xml")
12  
-      FileUtils.mkdir_p "_posts"
13  
-      posts = 0
14  
-
15  
-			doc = Hpricot::XML(File.read(filename))
16  
-			
17  
-			(doc/:channel/:item).each do |item|
18  
-				title = item.at(:title).inner_text
19  
-				name = "#{Date.parse((doc/:channel/:item).first.at(:pubDate).inner_text).to_s("%Y-%m-%d")}-#{title.downcase.gsub('[^a-z0-9]', '-')}.html"
20  
-				
21  
-				File.open("_posts/#{name}", "w") do |f|
22  
-          f.puts <<-HEADER
23  
----
24  
-layout: post
25  
-title: #{title}
26  
----
27  
- 
28  
-HEADER
29  
-          f.puts item.at('content:encoded').inner_text
30  
-        end
31  
-
32  
-				posts += 1
33  
-			end
34  
-
35  
-			"Imported #{posts} posts"
36  
-    end
37  
-  end
38  
-end
18  lib/jekyll/migrators/wordpress.rb
@@ -12,18 +12,18 @@
12 12
 module Jekyll
13 13
   module WordPress
14 14
 
15  
-    # Reads a MySQL database via Sequel and creates a post file for each
16  
-    # post in wp_posts that has post_status = 'publish'.
17  
-    # This restriction is made because 'draft' posts are not guaranteed to
18  
-    # have valid dates.
19  
-    QUERY = "select post_title, post_name, post_date, post_content, post_excerpt, ID, guid from wp_posts where post_status = 'publish' and post_type = 'post'"
20  
-
21  
-    def self.process(dbname, user, pass, host = 'localhost')
  15
+    def self.process(dbname, user, pass, host = 'localhost', table_prefix = 'wp_')
22 16
       db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
23 17
 
24 18
       FileUtils.mkdir_p "_posts"
25  
-
26  
-      db[QUERY].each do |post|
  19
+      
  20
+      # Reads a MySQL database via Sequel and creates a post file for each
  21
+      # post in wp_posts that has post_status = 'publish'.
  22
+      # This restriction is made because 'draft' posts are not guaranteed to
  23
+      # have valid dates.
  24
+      query = "select post_title, post_name, post_date, post_content, post_excerpt, ID, guid from #{table_prefix}posts where post_status = 'publish' and post_type = 'post'"
  25
+
  26
+      db[query].each do |post|
27 27
         # Get required fields and construct Jekyll compatible name
28 28
         title = post[:post_title]
29 29
         slug = post[:post_name]
45  lib/jekyll/migrators/wordpressdotcom.rb
... ...
@@ -0,0 +1,45 @@
  1
+# coding: utf-8
  2
+
  3
+require 'rubygems'
  4
+require 'hpricot'
  5
+require 'fileutils'
  6
+require 'yaml'
  7
+
  8
+module Jekyll
  9
+  
  10
+  # This importer takes a wordpress.xml file,
  11
+  # which can be exported from your 
  12
+  # wordpress.com blog (/wp-admin/export.php)
  13
+  module WordpressDotCom
  14
+    def self.process(filename = "wordpress.xml")
  15
+      FileUtils.mkdir_p "_posts"
  16
+      posts = 0
  17
+
  18
+			doc = Hpricot::XML(File.read(filename))
  19
+			
  20
+			(doc/:channel/:item).each do |item|
  21
+				title = item.at(:title).inner_text.strip
  22
+				permalink_title = item.at('wp:post_name').inner_text
  23
+				date = Time.parse(item.at(:pubDate).inner_text)
  24
+				tags = (item/:category).map{|c| c.inner_text}.reject{|c| c == 'Uncategorized'}.uniq
  25
+				name = "#{date.strftime('%Y-%m-%d')}-#{permalink_title}.html"
  26
+			  header = {
  27
+			    'layout' => 'post', 
  28
+			    'title'  => title, 
  29
+			    'tags'   => tags
  30
+			  }
  31
+			  
  32
+				File.open("_posts/#{name}", "w") do |f|
  33
+				  f.puts header.to_yaml
  34
+				  f.puts '---'
  35
+          f.puts item.at('content:encoded').inner_text
  36
+        end
  37
+
  38
+				posts += 1
  39
+			end
  40
+
  41
+			puts "Imported #{posts} posts"
  42
+    end
  43
+  end
  44
+  
  45
+end
25  lib/jekyll/page.rb
@@ -55,14 +55,23 @@ def template
55 55
     #
56 56
     # Returns <String>
57 57
     def url
58  
-      return permalink if permalink
59  
-
60  
-      @url ||= {
61  
-        "basename"   => self.basename,
62  
-        "output_ext" => self.output_ext,
63  
-      }.inject(template) { |result, token|
64  
-        result.gsub(/:#{token.first}/, token.last)
65  
-      }.gsub(/\/\//, "/")
  58
+      return @url if @url
  59
+
  60
+      url = if permalink
  61
+        permalink
  62
+      else
  63
+        {
  64
+          "basename"   => self.basename,
  65
+          "output_ext" => self.output_ext,
  66
+        }.inject(template) { |result, token|
  67
+          result.gsub(/:#{token.first}/, token.last)
  68
+        }.gsub(/\/\//, "/")
  69
+      end
  70
+
  71
+      # sanitize url
  72
+      @url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/')
  73
+      @url += "/" if url =~ /\/$/
  74
+      @url
66 75
     end
67 76
 
68 77
     # Extract information from the page filename
39  lib/jekyll/post.rb
@@ -78,6 +78,8 @@ def process(name)
78 78
       self.date = Time.parse(date)
79 79
       self.slug = slug
80 80
       self.ext = ext
  81
+    rescue ArgumentError
  82
+      raise FatalException.new("Post #{name} does not have a valid date.")
81 83
     end
82 84
 
83 85
     # The generated directory into which the post will be placed
@@ -117,20 +119,29 @@ def template
117 119
     #
118 120
     # Returns <String>
119 121
     def url
120  
-      return permalink if permalink
121  
-
122  
-      @url ||= {
123  
-        "year"       => date.strftime("%Y"),
124  
-        "month"      => date.strftime("%m"),
125  
-        "day"        => date.strftime("%d"),
126  
-        "title"      => CGI.escape(slug),
127  
-        "i_day"      => date.strftime("%d").to_i.to_s,
128  
-        "i_month"    => date.strftime("%m").to_i.to_s,
129  
-        "categories" => categories.join('/'),
130  
-        "output_ext" => self.output_ext
131  
-      }.inject(template) { |result, token|
132  
-        result.gsub(/:#{Regexp.escape token.first}/, token.last)
133  
-      }.gsub(/\/\//, "/")
  122
+      return @url if @url
  123
+
  124
+      url = if permalink
  125
+        permalink
  126
+      else
  127
+        {
  128
+          "year"       => date.strftime("%Y"),
  129
+          "month"      => date.strftime("%m"),
  130
+          "day"        => date.strftime("%d"),
  131
+          "title"      => CGI.escape(slug),
  132
+          "i_day"      => date.strftime("%d").to_i.to_s,
  133
+          "i_month"    => date.strftime("%m").to_i.to_s,
  134
+          "categories" => categories.join('/'),
  135
+          "output_ext" => self.output_ext
  136
+        }.inject(template) { |result, token|
  137
+          result.gsub(/:#{Regexp.escape token.first}/, token.last)
  138
+        }.gsub(/\/\//, "/")
  139
+      end
  140
+
  141
+      # sanitize url
  142
+      @url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/')
  143
+      @url += "/" if url =~ /\/$/
  144
+      @url
134 145
     end
135 146
 
136 147
     # The UID for this post (useful in feeds)
19  lib/jekyll/site.rb
... ...
@@ -1,3 +1,5 @@
  1
+require 'set'
  2
+
1 3
 module Jekyll
2 4
 
3 5
   class Site
@@ -158,13 +160,13 @@ def render
158 160
     # Returns nothing
159 161
     def cleanup
160 162
       # all files and directories in destination, including hidden ones
161  
-      dest_files = []
  163
+      dest_files = Set.new
162 164
       Dir.glob(File.join(self.dest, "**", "*"), File::FNM_DOTMATCH) do |file|
163 165
         dest_files << file unless file =~ /\/\.{1,2}$/
164 166
       end
165 167
 
166 168
       # files to be written
167  
-      files = []
  169
+      files = Set.new
168 170
       self.posts.each do |post|
169 171
         files << post.destination(self.dest)
170 172
       end
@@ -176,11 +178,13 @@ def cleanup
176 178
       end
177 179
       
178 180
       # adding files' parent directories
179  
-      files.each { |file| files << File.dirname(file) unless files.include? File.dirname(file) }
  181
+      dirs = Set.new
  182
+      files.each { |file| dirs << File.dirname(file) }
  183
+      files.merge(dirs)
180 184
       
181 185
       obsolete_files = dest_files - files
182 186
       
183  
-      FileUtils.rm_rf(obsolete_files)
  187
+      FileUtils.rm_rf(obsolete_files.to_a)
184 188
     end
185 189
 
186 190
     # Write static files, pages and posts
@@ -206,7 +210,7 @@ def write
206 210
     # Returns nothing
207 211
     def read_directories(dir = '')
208 212
       base = File.join(self.source, dir)
209  
-      entries = filter_entries(Dir.entries(base))
  213
+      entries = Dir.chdir(base){ filter_entries(Dir['*']) }
210 214
 
211 215
       self.read_posts(dir)
212 216
 
@@ -264,7 +268,10 @@ def site_payload
264 268
     def filter_entries(entries)
265 269
       entries = entries.reject do |e|
266 270
         unless ['.htaccess'].include?(e)
267  
-          ['.', '_', '#'].include?(e[0..0]) || e[-1..-1] == '~' || self.exclude.include?(e)
  271
+          ['.', '_', '#'].include?(e[0..0]) ||
  272
+          e[-1..-1] == '~' ||
  273
+          self.exclude.include?(e) ||
  274
+          File.symlink?(e)
268 275
         end
269 276
       end
270 277
     end
90  lib/jekyll/tags/gallery.rb
... ...
@@ -0,0 +1,90 @@