Skip to content

Commit

Permalink
Merge pull request jekyll#944 from x3ro/permalink-special-characters
Browse files Browse the repository at this point in the history
WIP - Improve permalink generation for URLs with special characters
  • Loading branch information
parkr committed Aug 28, 2013
2 parents 5033437 + 362fcf2 commit 0d890e4
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 50 deletions.
1 change: 1 addition & 0 deletions lib/jekyll.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def require_all(path)
require 'jekyll/configuration'
require 'jekyll/site'
require 'jekyll/convertible'
require 'jekyll/url'
require 'jekyll/layout'
require 'jekyll/page'
require 'jekyll/post'
Expand Down
43 changes: 20 additions & 23 deletions lib/jekyll/page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ def dir
#
# Returns the String permalink or nil if none has been set.
def permalink
self.data && self.data['permalink']
return nil if self.data.nil? || self.data['permalink'].nil?
if site.config['relative_permalinks']
File.join(@dir, self.data['permalink'])
else
self.data['permalink']
end
end

# The template of the permalink.
Expand All @@ -61,29 +66,21 @@ def template
#
# Returns the String url.
def url
return @url if @url

url = if permalink
if site.config['relative_permalinks']
File.join(@dir, permalink)
else
permalink
end
else
{
"path" => @dir,
"basename" => self.basename,
"output_ext" => self.output_ext,
}.inject(template) { |result, token|
result.gsub(/:#{token.first}/, token.last)
}.gsub(/\/\//, "/")
end
@url ||= URL.new({
:template => template,
:placeholders => url_placeholders,
:permalink => permalink
}).to_s
end

# sanitize url
@url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/')
@url += "/" if url =~ /\/$/
@url.gsub!(/\A([^\/])/, '/\1')
@url
# Returns a hash of URL placeholder names (as symbols) mapping to the
# desired placeholder replacements. For details see "url.rb"
def url_placeholders
{
:path => @dir,
:basename => self.basename,
:output_ext => self.output_ext
}
end

# Extract information from the page filename.
Expand Down
49 changes: 22 additions & 27 deletions lib/jekyll/post.rb
Original file line number Diff line number Diff line change
Expand Up @@ -195,36 +195,31 @@ def template
end

# The generated relative url of this post.
# e.g. /2008/11/05/my-awesome-post.html
#
# Returns the String URL.
# Returns the String url.
def url
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.map { |c| URI.escape(c.to_s) }.join('/'),
"short_month" => date.strftime("%b"),
"y_day" => date.strftime("%j"),
"output_ext" => self.output_ext
}.inject(template) { |result, token|
result.gsub(/:#{Regexp.escape token.first}/, token.last)
}.gsub(/\/\//, "/")
end
@url ||= URL.new({
:template => template,
:placeholders => url_placeholders,
:permalink => permalink
}).to_s
end

# sanitize url
@url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/')
@url += "/" if url =~ /\/$/
@url.gsub!(/\A([^\/])/, '/\1')
@url
# Returns a hash of URL placeholder names (as symbols) mapping to the
# desired placeholder replacements. For details see "url.rb"
def url_placeholders
{
: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 || []).map { |c| URI.escape(c.to_s) }.join('/'),
:short_month => date.strftime("%b"),
:y_day => date.strftime("%j"),
:output_ext => self.output_ext
}
end

# The UID for this post (useful in feeds).
Expand Down
67 changes: 67 additions & 0 deletions lib/jekyll/url.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Public: Methods that generate a URL for a resource such as a Post or a Page.
#
# Examples
#
# URL.new({
# :template => /:categories/:title.html",
# :placeholders => {:categories => "ruby", :title => "something"}
# }).to_s
#
module Jekyll
class URL

# options - One of :permalink or :template must be supplied.
# :template - The String used as template for URL generation,
# for example "/:path/:basename:output_ext", where
# a placeholder is prefixed with a colon.
# :placeholders - A hash containing the placeholders which will be
# replaced when used inside the template. E.g.
# { "year" => Time.now.strftime("%Y") } would replace
# the placeholder ":year" with the current year.
# :permalink - If supplied, no URL will be generated from the
# template. Instead, the given permalink will be
# used as URL.
def initialize(options)
@template = options[:template]
@placeholders = options[:placeholders] || {}
@permalink = options[:permalink]

if (@template || @permalink).nil?
raise ArgumentError, "One of :template or :permalink must be supplied."
end
end

# The generated relative URL of the resource
#
# Returns the String URL
def to_s
sanitize_url(@permalink || generate_url)
end

# Internal: Generate the URL by replacing all placeholders with their
# respective values
#
# Returns the _unsanitizied_ String URL
def generate_url
@placeholders.inject(@template) do |result, token|
result.gsub(/:#{token.first}/, token.last)
end
end

# Returns a sanitized String URL
def sanitize_url(in_url)
# Remove all double slashes
url = in_url.gsub(/\/\//, "/")

# Remove every URL segment that consists solely of dots
url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/')

# Append a trailing slash to the URL if the unsanitized URL had one
url += "/" if in_url =~ /\/$/

# Always add a leading slash
url.gsub!(/\A([^\/])/, '/\1')
url
end
end
end
28 changes: 28 additions & 0 deletions test/test_url.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require 'helper'

class TestURL < Test::Unit::TestCase
context "The URL class" do

should "throw an exception if neither permalink or template is specified" do
assert_raises ArgumentError do
URL.new(:placeholders => {})
end
end

should "replace placeholders in templates" do
assert_equal "/foo/bar", URL.new(
:template => "/:x/:y",
:placeholders => {:x => "foo", :y => "bar"}
).to_s
end

should "return permalink if given" do
assert_equal "/le/perma/link", URL.new(
:template => "/:x/:y",
:placeholders => {:x => "foo", :y => "bar"},
:permalink => "/le/perma/link"
).to_s
end

end
end

0 comments on commit 0d890e4

Please sign in to comment.