diff --git a/lib/jekyll.rb b/lib/jekyll.rb index 5ff25deb..986f19f3 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -33,6 +33,7 @@ require 'jekyll/deprecator' require 'jekyll/configuration' require 'jekyll/site' require 'jekyll/convertible' +require 'jekyll/url' require 'jekyll/layout' require 'jekyll/page' require 'jekyll/post' diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb index 56119e07..bbdeb868 100644 --- a/lib/jekyll/page.rb +++ b/lib/jekyll/page.rb @@ -37,7 +37,12 @@ module Jekyll # # 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. @@ -61,29 +66,21 @@ module Jekyll # # Returns the String url. def url - return @url if @url + @url ||= URL.new({ + :template => template, + :placeholders => url_placeholders, + :permalink => permalink + }).to_s + end - 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 - - # 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. diff --git a/lib/jekyll/post.rb b/lib/jekyll/post.rb index bea50b29..54e9fa47 100644 --- a/lib/jekyll/post.rb +++ b/lib/jekyll/post.rb @@ -195,36 +195,31 @@ module Jekyll 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 ||= URL.new({ + :template => template, + :placeholders => url_placeholders, + :permalink => permalink + }).to_s + end - 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 - - # 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). diff --git a/lib/jekyll/url.rb b/lib/jekyll/url.rb new file mode 100644 index 00000000..10e1cb5d --- /dev/null +++ b/lib/jekyll/url.rb @@ -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 diff --git a/test/test_url.rb b/test/test_url.rb new file mode 100644 index 00000000..0576eda2 --- /dev/null +++ b/test/test_url.rb @@ -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