Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

New Liquid tag for doing simple image galleries. #295

Closed
wants to merge 56 commits into from

9 participants

@robru

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.

heuripedes and others added some commits
@heuripedes heuripedes updated/fixed wordpress.com migration script
9e0eb75
@MattHall MattHall Adding Posterous Importer
c1f0e07
@MattHall MattHall Updating CLI for importing
84c1a72
@ab9 ab9 fix typo in history: site.ports -> site.posts
033333f
@phatblat phatblat Add Jekyll::WordPress.TABLE_PREFIX and inclusion in QUERY
42f63f9
@phatblat phatblat Change TABLE_PREFIX back to default
e902bb9
@phatblat phatblat Change TABLE_PREFIX from class member to 4th parameter of process met…
…hod (now lowercase)

Move QUERY into process method
bc3771a
@phatblat phatblat Fix compile error by making QUERY lowercase (local instead of const)
d61c1e9
@elia elia The Wordpress.com migrator now works and gathers categories as tags.
f68bbcb
@elia elia Merged wordpress.com migrator fix from 'heuripedes/jekyll'
ca48ea9
@elia elia Take permalink name directly from worpress export file.
c70dac3
@elia elia Remove double directory creation.
034b064
@zenspider zenspider 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
@tmm1 tmm1 Gemfile to help install the dependencies
a04c270
@tmm1 tmm1 fix "4.2.1" versioned dev dependencies, and cleanup syntax
16ea326
@tmm1 tmm1 use the new albino gem
08725eb
@tmm1 tmm1 open4 is not required
4b5a4e8
@tmm1 tmm1 work around cucumber issue (closes #296)
8cc7f06
@tmm1 tmm1 rdiscount is a dev dependency
9da714d
@tmm1 tmm1 make kramdown a runtime dep so people can use it instead of maruku
edef025
@tmm1 tmm1 speed up cleanup
be8b771
@tmm1 tmm1 sanitize urls and ignore symlinks
13cc44f
@mojombo mojombo Update history.
f82c51d
@mojombo mojombo Merge remote-tracking branch 'zenspider/master' into devel
5f4dfe3
@mojombo mojombo Move require to jekyll.rb and update history.
a31780a
@mojombo mojombo Merge remote-tracking branch 'ab9/master' into devel
38844cd
@mojombo mojombo Merge remote-tracking branch 'MattHall/posterous' into devel
f58a821
@mojombo mojombo Merge remote-tracking branch 'elia/master' into devel
d2814cf
@mojombo mojombo Merge remote-tracking branch 'phatblat/master' into test
01a9090
@mojombo mojombo Merge remote-tracking branch 'MattHall/cli' into test
68eaadd
@mojombo mojombo TomDoc convertible.rb.
6c94db1
@mojombo
Owner

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

@robru

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!

Robert Bruce... added some commits
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
@robru robru commented on the diff
lib/jekyll/tags/gallery.rb
((47 lines not shown))
+
+
+module Jekyll
+ class GalleryTag < Liquid::Block
+ include Convertible
+ attr_accessor :content, :data
+
+ def initialize(tag_name, markup, tokens)
+ attributes = {}
+
+ # Parse parameters
+ markup.scan(Liquid::TagAttributes) do |key, value|
+ attributes[key] = value
+ end
+
+ @name = attributes['name'] || '.'
@robru
robru added a note

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... added some commits
Robert Bruce Park Change cucumber feature to show the simplest possible use case of gal…
…lery tag.
3ebcc45
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
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
Robert Bruce Park Merge remote-tracking branch 'mojombo/devel'
227fc4f
Robert Bruce Park Merge branch 'master' of github.com:robru/jekyll
967106a
@robru

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 ;-)

@robru

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

@robru robru closed this
@parkr parkr referenced this pull request
Closed

List files in directory #2927

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 15, 2010
  1. @heuripedes
Commits on Dec 19, 2010
  1. @MattHall

    Adding Posterous Importer

    MattHall authored
  2. @MattHall

    Updating CLI for importing

    MattHall authored
Commits on Jan 22, 2011
  1. @ab9
Commits on Jan 23, 2011
  1. @phatblat
  2. @phatblat
  3. @phatblat

    Change TABLE_PREFIX from class member to 4th parameter of process met…

    phatblat authored
    …hod (now lowercase)
    
    Move QUERY into process method
  4. @phatblat
Commits on Jan 27, 2011
  1. @elia
  2. @elia
  3. @elia
  4. @elia

    Remove double directory creation.

    elia authored
Commits on Mar 2, 2011
  1. @zenspider
Commits on Mar 4, 2011
  1. Added liquid tag for doing simple image galleries.

    Robert Bruce Park authored
  2. Allow formats other than just ".jpg", and provide 'title' to the temp…

    Robert Bruce Park authored
    …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.
  3. Less regex for better readability.

    Robert Bruce Park authored
  4. Add option to let the user reverse the sort.

    Robert Bruce Park authored
  5. Simplify Title Casing by adding a method to String class.

    Robert Bruce Park authored
Commits on Mar 5, 2011
  1. Provide more data (including dates) to the templates.

    Robert Bruce Park authored
  2. Do some input sanity checking.

    Robert Bruce Park authored
Commits on Mar 6, 2011
  1. Better input sanitation.

    Robert Bruce Park authored
    Should be pretty solid now. Nothing nasty is going to make it from user-supplied template data into the glob method now.
  2. Simplify titleize.

    Robert Bruce Park authored
  3. Underscores too! D'oh!

    Robert Bruce Park authored
  4. @tmm1
  5. @tmm1
  6. @tmm1

    use the new albino gem

    tmm1 authored
Commits on Mar 7, 2011
  1. @tmm1

    open4 is not required

    tmm1 authored
Commits on Mar 8, 2011
  1. @tmm1
  2. @tmm1

    rdiscount is a dev dependency

    tmm1 authored
  3. @tmm1
Commits on Mar 11, 2011
  1. @tmm1

    speed up cleanup

    tmm1 authored
  2. @tmm1
  3. @mojombo

    Update history.

    mojombo authored
  4. @mojombo
  5. @mojombo
  6. @mojombo
  7. @mojombo
  8. @mojombo
  9. @mojombo
  10. @mojombo
Commits on Mar 12, 2011
  1. @mojombo

    TomDoc convertible.rb.

    mojombo authored
  2. @mojombo
  3. Add Cucumber feature test for gallery tag.

    Robert Bruce Park authored
  4. Start a test case for the gallery tag.

    Robert Bruce Park authored
  5. More consistent absolute/relative path generation, and better cucumbe…

    Robert Bruce Park authored
    …r test of this.
Commits on Mar 13, 2011
  1. Provide contents of YAML front matter of iterated files to parent tem…

    Robert Bruce Park authored
    …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.
  2. Make name attribute default to '.' if left unspecified.

    Robert Bruce Park authored
    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.
  3. Change cucumber feature to show the simplest possible use case of gal…

    Robert Bruce Park authored
    …lery tag.
  4. Code style cleanup.

    Robert Bruce Park authored
    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.
  5. Add file.htmlpath to make it easier to link to htmlized files.

    Robert Bruce Park authored
  6. Add file.htmlurl to compliment file.htmlpath

    Robert Bruce Park authored
Commits on Mar 17, 2011
  1. Create gallery.feature, moving the three existing Cucumber Scenarios …

    Robert Bruce Park authored
    …there, and add two more.
  2. Minor expansion of YAML use in Cucumber test.

    Robert Bruce Park authored
Commits on Mar 18, 2011
  1. Merge remote-tracking branch 'mojombo/devel'

    Robert Bruce Park authored
  2. Merge branch 'master' of github.com:robru/jekyll

    Robert Bruce Park authored
This page is out of date. Refresh to see the latest.
View
1  .gitignore
@@ -1,3 +1,4 @@
+Gemfile.lock
test/dest
*.gem
pkg/
View
2  Gemfile
@@ -0,0 +1,2 @@
+source :rubygems
+gemspec
View
16 History.txt
@@ -1,3 +1,17 @@
+== HEAD
+ * Major Enhancements
+ * Add command line importer functionality (#253)
+ * Minor Enhancements
+ * Switch to Albino gem
+ * Bundler support
+ * Use English library to avoid hoops (#292)
+ * Add Posterous importer (#254)
+ * Fixes for Wordpress importer (#274, #252, #271)
+ * Better error message for invalid post date (#291)
+ * Print formatted fatal exceptions to stdout on build failure
+ * Bug Fixes
+ * Secure additional path exploits
+
== 0.10.0 / 2010-12-16
* Bug Fixes
* Add --no-server option.
@@ -75,7 +89,7 @@
* Empty tags causes error in read_posts (#84)
* Fix pagination to adhere to read/render/write paradigm
* Test Enhancement
- * cucumber features no longer use site.ports.first where a better
+ * cucumber features no longer use site.posts.first where a better
alternative is available
== 0.5.6 / 2010-01-08
View
1  README.textile
@@ -27,7 +27,6 @@ h2. Runtime Dependencies
* Classifier: Generating related posts (Ruby)
* Maruku: Default markdown engine (Ruby)
* Directory Watcher: Auto-regeneration of sites (Ruby)
-* Open4: Talking to pygments for syntax highlighting (Ruby)
* Pygments: Syntax highlighting (Python)
h2. Developer Dependencies
View
88 bin/jekyll
@@ -9,7 +9,8 @@ Basic Command Line Usage:
jekyll # . -> ./_site
jekyll <path to write generated site> # . -> <path>
jekyll <path to source> <path to write generated site> # <path> -> <path>
-
+ jekyll import <importer name> <options> # imports posts using named import script
+
Configuration is read from '<source>/_config.yml' but can be overriden
using the following options:
@@ -18,11 +19,37 @@ HELP
require 'optparse'
require 'jekyll'
+
exec = {}
options = {}
opts = OptionParser.new do |opts|
opts.banner = help
+ opts.on("--file [PATH]", "File to import from") do |import_file|
+ options['file'] = import_file
+ end
+
+ opts.on("--dbname [TEXT]", "DB to import from") do |import_dbname|
+ options['dbname'] = import_dbname
+ end
+
+ opts.on("--user [TEXT]", "Username to use when importing") do |import_user|
+ options['user'] = import_user
+ end
+
+ opts.on("--pass [TEXT]", "Password to use when importing") do |import_pass|
+ options['pass'] = import_pass
+ end
+
+ opts.on("--host [HOST ADDRESS]", "Host to import from") do |import_host|
+ options['host'] = import_host
+ end
+
+ opts.on("--site [SITE NAME]", "Site to import from") do |import_site|
+ options['site'] = import_site
+ end
+
+
opts.on("--[no-]safe", "Safe mode (default unsafe)") do |safe|
options['safe'] = safe
end
@@ -105,6 +132,59 @@ end
# Read command line options into `options` hash
opts.parse!
+
+# Check for import stuff
+if ARGV.size > 0
+ if ARGV[0] == 'import'
+ migrator = ARGV[1]
+
+ if migrator.nil?
+ puts "Invalid options. Run `jekyll --help` for assistance."
+ exit(1)
+ else
+ migrator = migrator.downcase
+ end
+
+ cmd_options = []
+ ['file', 'dbname', 'user', 'pass', 'host', 'site'].each do |p|
+ cmd_options << "\"#{options[p]}\"" unless options[p].nil?
+ end
+
+ # It's import time
+ puts "Importing..."
+
+ # Ideally, this shouldn't be necessary. Maybe parse the actual
+ # src files for the migrator name?
+ migrators = {
+ :posterous => 'Posterous',
+ :wordpressdotcom => 'WordpressDotCom',
+ :wordpress => 'Wordpress',
+ :csv => 'CSV',
+ :drupal => 'Drupal',
+ :mephisto => 'Mephisto',
+ :mt => 'MT',
+ :textpattern => 'TextPattern',
+ :typo => 'Typo'
+ }
+
+ app_root = File.join(File.dirname(__FILE__), '..')
+
+ require "#{app_root}/lib/jekyll/migrators/#{migrator}"
+
+ if Jekyll.const_defined?(migrators[migrator.to_sym])
+ migrator_class = Jekyll.const_get(migrators[migrator.to_sym])
+ migrator_class.process(*cmd_options)
+ else
+ puts "Invalid migrator. Run `jekyll --help` for assistance."
+ exit(1)
+ end
+
+ exit(0)
+ end
+end
+
+
+
# Get source and destintation from command line
case ARGV.size
when 0
@@ -162,7 +242,11 @@ else
puts "Building site: #{source} -> #{destination}"
begin
site.process
- rescue Jekyll::FatalException
+ rescue Jekyll::FatalException => e
+ puts
+ puts "ERROR: YOUR SITE COULD NOT BE BUILT:"
+ puts "------------------------------------"
+ puts e.message
exit(1)
end
puts "Successfully generated site: #{source} -> #{destination}"
View
54 features/gallery.feature
@@ -0,0 +1,54 @@
+Feature: Image galleries
+ As a photographer who likes to hack
+ I want to be able to make image galleries
+ In order to share my awesome photos with the interwebs
+
+ Scenario: Images stored in sourcedir
+ Given I have a "20110310-foo-bar.jpg" file that contains " "
+ And I have a "20110311-slug.jpg" file that contains " "
+ And I have an "index.html" page that contains "{% gallery dir:. %}{{ file.url }} {% endgallery %}"
+ When I run jekyll
+ Then the _site directory should exist
+ And I should see "/20110311-slug.jpg /20110310-foo-bar.jpg" in "_site/index.html"
+
+ Scenario: Images in subdir
+ Given I have an img directory
+ And I have an "img/20110310-foo-bar.jpg" file that contains " "
+ And I have an "img/20110311-slug.jpg" file that contains " "
+ And I have an "index.html" page that contains "{% gallery %}{{ file.path }} {% endgallery %}"
+ When I run jekyll
+ Then the _site directory should exist
+ And I should see "img/20110311-slug.jpg img/20110310-foo-bar.jpg" in "_site/index.html"
+
+ Scenario: I want to have more than one distinct gallery on my site
+ Given I have a europe directory
+ And I have a europe/img directory
+ And I have a "europe/img/20110310-london-england.jpg" file that contains " "
+ And I have a "europe/img/20110311-paris-france.jpg" file that contains " "
+ And I have a "europe/img/20110312-berlin-germany.jpg" file that contains " "
+ And I have a "europe/index.html" page that contains "{% gallery name:europe reverse:no %}{{ file.title }}. {% endgallery %}"
+ When I run jekyll
+ Then the _site directory should exist
+ And I should see "London England. Paris France. Berlin Germany." in "_site/europe/index.html"
+
+ Scenario: I want to show a list of files to download instead of images
+ Given I have a downloads directory
+ And I have a downloads/files directory
+ And I have a "downloads/files/example-0.1.1.tar.gz" file that contains " "
+ And I have a "downloads/files/example-0.2.0.tar.gz" file that contains " "
+ And I have a "downloads/index.html" page that contains "{% gallery name:downloads dir:files format:tar.gz %}{{ file.slug }} {% endgallery %}"
+ When I run jekyll
+ Then the _site directory should exist
+ And I should see "example-0.2.0 example-0.1.1" in "_site/downloads/index.html"
+
+ Scenario: I want to show a list of documents that incorporate YAML Front Matter
+ Given I have a documentation directory
+ And I have a "documentation/introduction.textile" page with layout "chazwozzer" that contains "_Welcome!_"
+ And I have a "documentation/wonka-widgets.textile" page with layout "snozberries" that contains "*Golden Ticket!*"
+ And I have an "index.html" page that contains "{% gallery dir:documentation format:textile %}{{ file.htmlpath }} {{ file.layout }}{% endgallery %}"
+ When I run jekyll
+ Then the _site directory should exist
+ And I should see "<em>Welcome!</em>" in "_site/documentation/introduction.html"
+ And I should see "documentation/introduction.html chazwozzer" in "_site/index.html"
+ And I should see "<p><strong>Golden Ticket!</strong></p>" in "_site/documentation/wonka-widgets.html"
+ And I should see "documentation/wonka-widgets.html snozberries" in "_site/index.html"
View
3  features/support/env.rb
@@ -14,3 +14,6 @@ def run_jekyll(opts = {})
command << " >> /dev/null 2>&1" if opts[:debug].nil?
system command
end
+
+# work around "invalid option: --format" cucumber bug (see #296)
+Test::Unit.run = true
View
22 jekyll.gemspec
@@ -23,17 +23,19 @@ Gem::Specification.new do |s|
s.rdoc_options = ["--charset=UTF-8"]
s.extra_rdoc_files = %w[README.textile LICENSE]
- s.add_runtime_dependency('liquid', [">= 1.9.0"])
- s.add_runtime_dependency('classifier', [">= 1.3.1"])
- s.add_runtime_dependency('directory_watcher', [">= 1.1.1"])
- s.add_runtime_dependency('maruku', [">= 0.5.9"])
+ s.add_runtime_dependency('liquid', ">= 1.9.0")
+ s.add_runtime_dependency('classifier', ">= 1.3.1")
+ s.add_runtime_dependency('directory_watcher', ">= 1.1.1")
+ s.add_runtime_dependency('maruku', ">= 0.5.9")
+ s.add_runtime_dependency('kramdown', ">= 0.13.2")
+ s.add_runtime_dependency('albino', ">= 1.3.2")
- s.add_development_dependency('redgreen', [">= 4.2.1"])
- s.add_development_dependency('shoulda', [">= 4.2.1"])
- s.add_development_dependency('rr', [">= 4.2.1"])
- s.add_development_dependency('cucumber', [">= 4.2.1"])
- s.add_development_dependency('RedCloth', [">= 4.2.1"])
- s.add_development_dependency('kramdown', [">= 0.12.0"])
+ s.add_development_dependency('redgreen', ">= 1.2.2")
+ s.add_development_dependency('shoulda', ">= 2.11.3")
+ s.add_development_dependency('rr', ">= 1.0.2")
+ s.add_development_dependency('cucumber', ">= 0.10.0")
+ s.add_development_dependency('RedCloth', ">= 4.2.1")
+ s.add_development_dependency('rdiscount', ">= 1.6.5")
# = MANIFEST =
s.files = %w[
View
3  lib/jekyll.rb
@@ -19,10 +19,12 @@ def require_all(path)
require 'fileutils'
require 'time'
require 'yaml'
+require 'English'
# 3rd party
require 'liquid'
require 'maruku'
+require 'albino'
# internal requires
require 'jekyll/core_ext'
@@ -32,7 +34,6 @@ def require_all(path)
require 'jekyll/page'
require 'jekyll/post'
require 'jekyll/filters'
-require 'jekyll/albino'
require 'jekyll/static_file'
require 'jekyll/errors'
View
120 lib/jekyll/albino.rb
@@ -1,120 +0,0 @@
-##
-# Wrapper for the Pygments command line tool, pygmentize.
-#
-# Pygments: http://pygments.org/
-#
-# Assumes pygmentize is in the path. If not, set its location
-# with Albino.bin = '/path/to/pygmentize'
-#
-# Use like so:
-#
-# @syntaxer = Albino.new('/some/file.rb', :ruby)
-# puts @syntaxer.colorize
-#
-# This'll print out an HTMLized, Ruby-highlighted version
-# of '/some/file.rb'.
-#
-# To use another formatter, pass it as the third argument:
-#
-# @syntaxer = Albino.new('/some/file.rb', :ruby, :bbcode)
-# puts @syntaxer.colorize
-#
-# You can also use the #colorize class method:
-#
-# puts Albino.colorize('/some/file.rb', :ruby)
-#
-# Another also: you get a #to_s, for somewhat nicer use in Rails views.
-#
-# ... helper file ...
-# def highlight(text)
-# Albino.new(text, :ruby)
-# end
-#
-# ... view file ...
-# <%= highlight text %>
-#
-# The default lexer is 'text'. You need to specify a lexer yourself;
-# because we are using STDIN there is no auto-detect.
-#
-# To see all lexers and formatters available, run `pygmentize -L`.
-#
-# Chris Wanstrath // chris@ozmm.org
-# GitHub // http://github.com
-#
-
-class Albino
- @@bin = Rails.development? ? 'pygmentize' : '/usr/bin/pygmentize' rescue 'pygmentize'
-
- def self.bin=(path)
- @@bin = path
- end
-
- def self.colorize(*args)
- new(*args).colorize
- end
-
- def initialize(target, lexer = :text, format = :html)
- @target = target
- @options = { :l => lexer, :f => format, :O => 'encoding=utf-8' }
- end
-
- def execute(command)
- output = ''
- IO.popen(command, mode='r+') do |p|
- p.write @target
- p.close_write
- output = p.read.strip
- end
- output
- end
-
- def colorize(options = {})
- html = execute(@@bin + convert_options(options))
- # Work around an RDiscount bug: http://gist.github.com/97682
- html.to_s.sub(%r{</pre></div>\Z}, "</pre>\n</div>")
- end
- alias_method :to_s, :colorize
-
- def convert_options(options = {})
- @options.merge(options).inject('') do |string, (flag, value)|
- string + " -#{flag} #{value}"
- end
- end
-end
-
-if $0 == __FILE__
- require 'rubygems'
- require 'test/spec'
- require 'mocha'
- begin require 'redgreen'; rescue LoadError; end
-
- context "Albino" do
- setup do
- @syntaxer = Albino.new(__FILE__, :ruby)
- end
-
- specify "defaults to text" do
- syntaxer = Albino.new(__FILE__)
- syntaxer.expects(:execute).with('pygmentize -f html -l text').returns(true)
- syntaxer.colorize
- end
-
- specify "accepts options" do
- @syntaxer.expects(:execute).with('pygmentize -f html -l ruby').returns(true)
- @syntaxer.colorize
- end
-
- specify "works with strings" do
- syntaxer = Albino.new('class New; end', :ruby)
- assert_match %r(highlight), syntaxer.colorize
- end
-
- specify "aliases to_s" do
- assert_equal @syntaxer.colorize, @syntaxer.to_s
- end
-
- specify "class method colorize" do
- assert_equal @syntaxer.colorize, Albino.colorize(__FILE__, :ruby)
- end
- end
-end
View
37 lib/jekyll/convertible.rb
@@ -10,21 +10,22 @@
# self.output=
module Jekyll
module Convertible
- # Return the contents as a string
+ # Returns the contents as a String.
def to_s
self.content || ''
end
- # Read the YAML frontmatter
- # +base+ is the String path to the dir containing the file
- # +name+ is the String filename of the file
+ # Read the YAML frontmatter.
#
- # Returns nothing
+ # base - The String path to the dir containing the file.
+ # name - The String filename of the file.
+ #
+ # Returns nothing.
def read_yaml(base, name)
self.content = File.read(File.join(base, name))
if self.content =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
- self.content = self.content[($1.size + $2.size)..-1]
+ self.content = $POSTMATCH
begin
self.data = YAML.load($1)
@@ -38,42 +39,46 @@ def read_yaml(base, name)
# Transform the contents based on the content type.
#
- # Returns nothing
+ # Returns nothing.
def transform
self.content = converter.convert(self.content)
end
- # Determine the extension depending on content_type
+ # Determine the extension depending on content_type.
#
- # Returns the extensions for the output file
+ # Returns the String extension for the output file.
+ # e.g. ".html" for an HTML output file.
def output_ext
converter.output_ext(self.ext)
end
# Determine which converter to use based on this convertible's
- # extension
+ # extension.
+ #
+ # Returns the Converter instance.
def converter
@converter ||= self.site.converters.find { |c| c.matches(self.ext) }
end
- # Add any necessary layouts to this convertible document
- # +layouts+ is a Hash of {"name" => "layout"}
- # +site_payload+ is the site payload hash
+ # Add any necessary layouts to this convertible document.
+ #
+ # payload - The site payload Hash.
+ # layouts - A Hash of {"name" => "layout"}.
#
- # Returns nothing
+ # Returns nothing.
def do_layout(payload, layouts)
info = { :filters => [Jekyll::Filters], :registers => { :site => self.site } }
# render and transform content (this becomes the final content of the object)
payload["pygments_prefix"] = converter.pygments_prefix
payload["pygments_suffix"] = converter.pygments_suffix
-
+
begin
self.content = Liquid::Template.parse(self.content).render(payload, info)
rescue => e
puts "Liquid Exception: #{e.message} in #{self.data["layout"]}"
end
-
+
self.transform
# output keeps track of what will finally be written
View
6 lib/jekyll/core_ext.rb
@@ -50,3 +50,9 @@ def xmlschema
strftime("%Y-%m-%dT%H:%M:%S%Z")
end if RUBY_VERSION < '1.9'
end
+
+class String
+ def titleize
+ split(/[\W_]+/).map(&:capitalize).join ' '
+ end
+end
View
73 lib/jekyll/migrators/posterous.rb
@@ -0,0 +1,73 @@
+require 'rubygems'
+require 'jekyll'
+require 'fileutils'
+require 'net/http'
+require 'uri'
+require "json"
+
+# ruby -r './lib/jekyll/migrators/posterous.rb' -e 'Jekyll::Posterous.process(email, pass, blog)'
+
+module Jekyll
+ module Posterous
+
+ def self.fetch(uri_str, limit = 10)
+ # You should choose better exception.
+ raise ArgumentError, 'Stuck in a redirect loop. Please double check your email and password' if limit == 0
+
+ response = nil
+ Net::HTTP.start('posterous.com') {|http|
+ req = Net::HTTP::Get.new(uri_str)
+ req.basic_auth @email, @pass
+ response = http.request(req)
+ }
+
+ case response
+ when Net::HTTPSuccess then response
+ when Net::HTTPRedirection then fetch(response['location'], limit - 1)
+ else response.error!
+ end
+ end
+
+
+ def self.process(email, pass, blog = 'primary')
+ @email, @pass = email, pass
+ @api_token = JSON.parse(self.fetch("/api/2/auth/token").body)['api_token']
+ FileUtils.mkdir_p "_posts"
+
+ posts = JSON.parse(self.fetch("/api/v2/users/me/sites/#{blog}/posts?api_token=#{@api_token}").body)
+ page = 1
+
+ while posts.any?
+
+ posts.each do |post|
+ title = post["title"]
+ slug = title.gsub(/[^[:alnum:]]+/, '-').downcase
+ date = Date.parse(post["display_date"])
+ content = post["body_html"]
+ published = !post["is_private"]
+ name = "%02d-%02d-%02d-%s.html" % [date.year, date.month, date.day, slug]
+
+ # Get the relevant fields as a hash, delete empty fields and convert
+ # to YAML for the header
+ data = {
+ 'layout' => 'post',
+ 'title' => title.to_s,
+ 'published' => published
+ }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
+
+ # Write out the data and content to file
+ File.open("_posts/#{name}", "w") do |f|
+ f.puts data
+ f.puts "---"
+ f.puts content
+ end
+
+ end
+
+ page += 1
+ posts = JSON.parse(self.fetch("/api/v2/users/me/sites/#{blog}/posts?api_token=#{@api_token}&page=#{page}").body)
+ end
+
+ end
+ end
+end
View
38 lib/jekyll/migrators/wordpress.com.rb
@@ -1,38 +0,0 @@
-require 'rubygems'
-require 'hpricot'
-require 'fileutils'
-
-# This importer takes a wordpress.xml file,
-# which can be exported from your
-# wordpress.com blog (/wp-admin/export.php)
-
-module Jekyll
- module WordpressDotCom
- def self.process(filename = "wordpress.xml")
- FileUtils.mkdir_p "_posts"
- posts = 0
-
- doc = Hpricot::XML(File.read(filename))
-
- (doc/:channel/:item).each do |item|
- title = item.at(:title).inner_text
- name = "#{Date.parse((doc/:channel/:item).first.at(:pubDate).inner_text).to_s("%Y-%m-%d")}-#{title.downcase.gsub('[^a-z0-9]', '-')}.html"
-
- File.open("_posts/#{name}", "w") do |f|
- f.puts <<-HEADER
----
-layout: post
-title: #{title}
----
-
-HEADER
- f.puts item.at('content:encoded').inner_text
- end
-
- posts += 1
- end
-
- "Imported #{posts} posts"
- end
- end
-end
View
18 lib/jekyll/migrators/wordpress.rb
@@ -12,18 +12,18 @@
module Jekyll
module WordPress
- # Reads a MySQL database via Sequel and creates a post file for each
- # post in wp_posts that has post_status = 'publish'.
- # This restriction is made because 'draft' posts are not guaranteed to
- # have valid dates.
- 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'"
-
- def self.process(dbname, user, pass, host = 'localhost')
+ def self.process(dbname, user, pass, host = 'localhost', table_prefix = 'wp_')
db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
FileUtils.mkdir_p "_posts"
-
- db[QUERY].each do |post|
+
+ # Reads a MySQL database via Sequel and creates a post file for each
+ # post in wp_posts that has post_status = 'publish'.
+ # This restriction is made because 'draft' posts are not guaranteed to
+ # have valid dates.
+ 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'"
+
+ db[query].each do |post|
# Get required fields and construct Jekyll compatible name
title = post[:post_title]
slug = post[:post_name]
View
45 lib/jekyll/migrators/wordpressdotcom.rb
@@ -0,0 +1,45 @@
+# coding: utf-8
+
+require 'rubygems'
+require 'hpricot'
+require 'fileutils'
+require 'yaml'
+
+module Jekyll
+
+ # This importer takes a wordpress.xml file,
+ # which can be exported from your
+ # wordpress.com blog (/wp-admin/export.php)
+ module WordpressDotCom
+ def self.process(filename = "wordpress.xml")
+ FileUtils.mkdir_p "_posts"
+ posts = 0
+
+ doc = Hpricot::XML(File.read(filename))
+
+ (doc/:channel/:item).each do |item|
+ title = item.at(:title).inner_text.strip
+ permalink_title = item.at('wp:post_name').inner_text
+ date = Time.parse(item.at(:pubDate).inner_text)
+ tags = (item/:category).map{|c| c.inner_text}.reject{|c| c == 'Uncategorized'}.uniq
+ name = "#{date.strftime('%Y-%m-%d')}-#{permalink_title}.html"
+ header = {
+ 'layout' => 'post',
+ 'title' => title,
+ 'tags' => tags
+ }
+
+ File.open("_posts/#{name}", "w") do |f|
+ f.puts header.to_yaml
+ f.puts '---'
+ f.puts item.at('content:encoded').inner_text
+ end
+
+ posts += 1
+ end
+
+ puts "Imported #{posts} posts"
+ end
+ end
+
+end
View
25 lib/jekyll/page.rb
@@ -55,14 +55,23 @@ def template
#
# Returns <String>
def url
- return permalink if permalink
-
- @url ||= {
- "basename" => self.basename,
- "output_ext" => self.output_ext,
- }.inject(template) { |result, token|
- result.gsub(/:#{token.first}/, token.last)
- }.gsub(/\/\//, "/")
+ return @url if @url
+
+ url = if permalink
+ permalink
+ else
+ {
+ "basename" => self.basename,
+ "output_ext" => self.output_ext,
+ }.inject(template) { |result, token|
+ result.gsub(/:#{token.first}/, token.last)
+ }.gsub(/\/\//, "/")
+ end
+
+ # sanitize url
+ @url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/')
+ @url += "/" if url =~ /\/$/
+ @url
end
# Extract information from the page filename
View
39 lib/jekyll/post.rb
@@ -78,6 +78,8 @@ def process(name)
self.date = Time.parse(date)
self.slug = slug
self.ext = ext
+ rescue ArgumentError
+ raise FatalException.new("Post #{name} does not have a valid date.")
end
# The generated directory into which the post will be placed
@@ -117,20 +119,29 @@ def template
#
# Returns <String>
def url
- return permalink if permalink
-
- @url ||= {
- "year" => date.strftime("%Y"),
- "month" => date.strftime("%m"),
- "day" => date.strftime("%d"),
- "title" => CGI.escape(slug),
- "i_day" => date.strftime("%d").to_i.to_s,
- "i_month" => date.strftime("%m").to_i.to_s,
- "categories" => categories.join('/'),
- "output_ext" => self.output_ext
- }.inject(template) { |result, token|
- result.gsub(/:#{Regexp.escape token.first}/, token.last)
- }.gsub(/\/\//, "/")
+ return @url if @url
+
+ url = if permalink
+ permalink
+ else
+ {
+ "year" => date.strftime("%Y"),
+ "month" => date.strftime("%m"),
+ "day" => date.strftime("%d"),
+ "title" => CGI.escape(slug),
+ "i_day" => date.strftime("%d").to_i.to_s,
+ "i_month" => date.strftime("%m").to_i.to_s,
+ "categories" => categories.join('/'),
+ "output_ext" => self.output_ext
+ }.inject(template) { |result, token|
+ result.gsub(/:#{Regexp.escape token.first}/, token.last)
+ }.gsub(/\/\//, "/")
+ end
+
+ # sanitize url
+ @url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/')
+ @url += "/" if url =~ /\/$/
+ @url
end
# The UID for this post (useful in feeds)
View
19 lib/jekyll/site.rb
@@ -1,3 +1,5 @@
+require 'set'
+
module Jekyll
class Site
@@ -158,13 +160,13 @@ def render
# Returns nothing
def cleanup
# all files and directories in destination, including hidden ones
- dest_files = []
+ dest_files = Set.new
Dir.glob(File.join(self.dest, "**", "*"), File::FNM_DOTMATCH) do |file|
dest_files << file unless file =~ /\/\.{1,2}$/
end
# files to be written
- files = []
+ files = Set.new
self.posts.each do |post|
files << post.destination(self.dest)
end
@@ -176,11 +178,13 @@ def cleanup
end
# adding files' parent directories
- files.each { |file| files << File.dirname(file) unless files.include? File.dirname(file) }
+ dirs = Set.new
+ files.each { |file| dirs << File.dirname(file) }
+ files.merge(dirs)
obsolete_files = dest_files - files
- FileUtils.rm_rf(obsolete_files)
+ FileUtils.rm_rf(obsolete_files.to_a)
end
# Write static files, pages and posts
@@ -206,7 +210,7 @@ def write
# Returns nothing
def read_directories(dir = '')
base = File.join(self.source, dir)
- entries = filter_entries(Dir.entries(base))
+ entries = Dir.chdir(base){ filter_entries(Dir['*']) }
self.read_posts(dir)
@@ -264,7 +268,10 @@ def site_payload
def filter_entries(entries)
entries = entries.reject do |e|
unless ['.htaccess'].include?(e)
- ['.', '_', '#'].include?(e[0..0]) || e[-1..-1] == '~' || self.exclude.include?(e)
+ ['.', '_', '#'].include?(e[0..0]) ||
+ e[-1..-1] == '~' ||
+ self.exclude.include?(e) ||
+ File.symlink?(e)
end
end
end
View
90 lib/jekyll/tags/gallery.rb
@@ -0,0 +1,90 @@
+module Jekyll
+
+ class GalleryTag < Liquid::Block
+ include Convertible
+ attr_accessor :content, :data
+
+ def initialize(tag_name, markup, tokens)
+ attributes = {}
+
+ # Parse parameters
+ markup.scan(Liquid::TagAttributes) do |key, value|
+ attributes[key] = value
+ end
+
+ @name = attributes['name'] || '.'
@robru
robru added a note

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
+ @dir = attributes['dir'] || 'img'
+ @fmt = attributes['format'] || 'jpg'
+ @rev = attributes['reverse'].nil?
+
+ # Prevent template data from doing evil.
+ [@name, @dir, @fmt].each do |s|
+ s.delete! '/[]{}*?'
+ s.squeeze! '.'
+ end
+
+ super
+ end
+
+ def render(context)
+ context.registers[:gallery] ||= Hash.new(0)
+
+ files = Dir.glob(File.join(@name, @dir, "*.#{@fmt}"))
+ files.sort! {|x,y| @rev ? y <=> x : x <=> y }
+ length = files.length
+ result = []
+
+ context.stack do
+ files.each_with_index do |filename, index|
+ basename = File.basename(filename)
+
+ url = ['', @name, @dir, basename] - ['.']
+ path = url[-2..-1].join '/'
+ url = url.join '/'
+ html, hurl = *[path, url].collect {|s| s.sub /\.#{@fmt}$/, '.html'}
+
+ # This matches '1984-11-27-sluggy-slug.ext', with optional hyphens
+ # (so '19841127-slugger.ext' is also valid), and the date is optional.
+ m, date, year, month, day, slug, ext = *basename.match(
+ /((\d{4})-?(\d\d)-?(\d\d))?-?(.*)(\.#{@fmt})$/)
+
+ context['file'] = {
+ 'htmlpath' => html,
+ 'htmlurl' => hurl,
+ 'title' => slug.titleize,
+ 'date' => year.nil? ? nil : Time.local(year, month, day),
+ 'name' => basename,
+ 'slug' => slug,
+ 'path' => path,
+ 'url' => url
+ }
+
+ # Obviously images don't contain YAML but this bit is included
+ # on the off chance that somebody is using this tag for other files
+ # that do have YAML Front Matter. This is a harmless nop on images.
+ self.read_yaml(File.join(@name, @dir), basename)
+ context['file'].merge! self.data
+
+ context['forloop'] = {
+ 'name' => 'gallery',
+ 'length' => length,
+ 'index' => index + 1,
+ 'index0' => index,
+ 'rindex' => length - index,
+ 'rindex0' => length - index - 1,
+ 'first' => (index == 0),
+ 'last' => (index == length - 1)
+ }
+
+ result << render_all(@nodelist, context)
+ end
+ end
+ result
+ end
+
+ end
+
+end
+
+Liquid::Template.register_tag('gallery', Jekyll::GalleryTag)
+
View
6 test/test_post.rb
@@ -52,6 +52,12 @@ def do_render(post)
assert_equal "/2008/09/09/foo-bar.html", @post.url
end
+ should "raise a good error on invalid post date" do
+ assert_raise Jekyll::FatalException do
+ @post.process("2009-27-03-foo-bar.textile")
+ end
+ end
+
should "CGI escape urls" do
@post.categories = []
@post.process("2009-03-12-hash-#1.markdown")
View
35 test/test_tags.rb
@@ -124,4 +124,39 @@ def fill_post(code, override = {})
end
end
end
+
+ context "simple post with gallery" do
+ setup do
+ Dir.chdir dest_dir
+ FileUtils.mkdir_p 'gallery/img'
+ FileUtils.touch ['alpha-beta', 'delta-gamma'].collect {|img| "gallery/img/20110401-#{img}.jpg"}
+ FileUtils.touch "gallery/img/1984-01-01-wrong-format.gif"
+ @content = <<CONTENT
+---
+title: Super simple image gallery
+---
+
+My awesome photo gallery, jpgs only:
+{% gallery name:gallery %}
+<img title="{{ file.title }}, taken on {{ file.date | date: "%F" }}" src="{{ file.path }}" />
+{% endgallery %}
+
+Oh, I have a gif, too:
+{% gallery name:gallery format:gif %}
+<img title="This ugly gif is from {{ file.date | date: "%Y" }}" src="{{ file.path }}" />
+{% endgallery %}
+CONTENT
+ end
+
+ should "parse correctly" do
+ create_post(@content)
+ assert_match %r{title='Alpha Beta, taken on 2011-04-01'}, @result
+ assert_match %r{title='Delta Gamma, taken on 2011-04-01'}, @result
+ assert_match %r{src='img/20110401-alpha-beta.jpg'}, @result
+ assert_match %r{src='img/20110401-delta-gamma.jpg'}, @result
+ assert_match %r{title='This ugly gif is from 1984'}, @result
+ assert_no_match /I have a gif.*jpg/m, @result
+ assert_no_match /gif.*I have a gif/m, @result
+ end
+ end
end
Something went wrong with that request. Please try again.