From cfcbe1f83011f1ee0887cf8e420827b1c95afd07 Mon Sep 17 00:00:00 2001 From: Lucas Jenss Date: Tue, 9 Apr 2013 23:53:46 +0200 Subject: [PATCH 1/5] Refactor URL processing/generation into separate module This is done to prepare for improved permalink generation for URLs containing special characters, as proposed in issue #782 --- lib/jekyll.rb | 1 + lib/jekyll/page.rb | 30 +++++++------------------- lib/jekyll/post.rb | 45 +++++++++++++-------------------------- lib/jekyll/url.rb | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 52 deletions(-) create mode 100644 lib/jekyll/url.rb diff --git a/lib/jekyll.rb b/lib/jekyll.rb index ab35a3b2..3357185b 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -30,6 +30,7 @@ require 'pygments' require 'jekyll/core_ext' 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 0de5e254..33294e5b 100644 --- a/lib/jekyll/page.rb +++ b/lib/jekyll/page.rb @@ -1,6 +1,7 @@ module Jekyll class Page include Convertible + include URL attr_writer :dir attr_accessor :site, :pager @@ -69,28 +70,13 @@ module Jekyll end end - # The generated relative url of this page. e.g. /about.html. - # - # Returns the String url. - def url - return @url if @url - - url = if permalink - permalink - 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 + # See url.rb for an explanation + 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 50fd983c..c3c3d9be 100644 --- a/lib/jekyll/post.rb +++ b/lib/jekyll/post.rb @@ -2,6 +2,7 @@ module Jekyll class Post include Comparable include Convertible + include URL class << self attr_accessor :lsi @@ -153,36 +154,20 @@ module Jekyll end end - # The generated relative url of this post. - # e.g. /2008/11/05/my-awesome-post.html - # - # 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 - - # sanitize url - @url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/') - @url += "/" if url =~ /\/$/ - @url + # See url.rb for an explanation + 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..93d381fb --- /dev/null +++ b/lib/jekyll/url.rb @@ -0,0 +1,53 @@ +# The URL module provides methods that generate a URL for a resource in which they're +# included, such as a Post or a Page. +# +# Requires +# +# self.permalink - If a permalink is set in the included instance, that permalink +# will be returned instead of any URL that might've been generated +# +# self.url_placeholders - Placeholders that may be used in the URL, which will be replaced +# with the values when the URL is generated. Must return a Hash +# mapping placeholder names to their values. For example, if this +# method returned +# +# { "year" => Time.now.strftime("%Y") } +# +# Every occurrence of ":year" (note the colon) would be replaced with +# the current year. +# +# + +module Jekyll + module URL + + # The generated relative url of this page. e.g. /about.html. + # + # Returns the String url. + def url + @url ||= sanitize_url(permalink || generate_url) + end + + # Generate the URL by replacing all placeholders with their respective values + # + # Returns the _unsanitizied_ String URL + def generate_url + url_placeholders.inject(template) { |result, token| + result.gsub(/:#{token.first}/, token.last) + } + 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 =~ /\/$/ + url + end + end +end From f5d0be96602d831f39c755bf4d7f49e95c09e32b Mon Sep 17 00:00:00 2001 From: Lucas Jenss Date: Thu, 25 Jul 2013 22:44:27 +0200 Subject: [PATCH 2/5] Move URL generation to own class instead of a module As suggested by @parkr in #944 --- lib/jekyll/page.rb | 12 ++++++++- lib/jekyll/post.rb | 14 +++++++++-- lib/jekyll/url.rb | 61 +++++++++++++++++++++++++++------------------- test/test_url.rb | 28 +++++++++++++++++++++ 4 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 test/test_url.rb diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb index 2041e4c9..10069f6c 100644 --- a/lib/jekyll/page.rb +++ b/lib/jekyll/page.rb @@ -1,7 +1,6 @@ module Jekyll class Page include Convertible - include URL attr_writer :dir attr_accessor :site, :pager @@ -63,6 +62,17 @@ module Jekyll end end + # The generated relative url of this page. e.g. /about.html. + # + # Returns the String url. + def url + @url ||= URL.new({ + :template => template, + :placeholders => url_placeholders, + :permalink => permalink + }).to_s + end + # See url.rb for an explanation def url_placeholders { diff --git a/lib/jekyll/post.rb b/lib/jekyll/post.rb index 79e751fe..6b5c38d5 100644 --- a/lib/jekyll/post.rb +++ b/lib/jekyll/post.rb @@ -2,7 +2,6 @@ module Jekyll class Post include Comparable include Convertible - include URL class << self attr_accessor :lsi @@ -195,6 +194,17 @@ module Jekyll end end + # The generated relative url of this post. + # + # Returns the String url. + def url + @url ||= URL.new({ + :template => template, + :placeholders => url_placeholders, + :permalink => permalink + }).to_s + end + # See url.rb for an explanation def url_placeholders { @@ -204,7 +214,7 @@ module Jekyll "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('/'), + "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 diff --git a/lib/jekyll/url.rb b/lib/jekyll/url.rb index fb9f8ee0..383cebaa 100644 --- a/lib/jekyll/url.rb +++ b/lib/jekyll/url.rb @@ -1,38 +1,49 @@ -# The URL module provides methods that generate a URL for a resource in which they're -# included, such as a Post or a Page. +# Public: Methods that generate a URL for a resource such as a Post or a Page. # -# Requires +# Examples # -# self.permalink - If a permalink is set in the included instance, that permalink -# will be returned instead of any URL that might've been generated +# URL.new({ +# :template => /:categories/:title.html", +# :placeholders => {:categories => "ruby", :title => "something"} +# }).to_s # -# self.url_placeholders - Placeholders that may be used in the URL, which will be replaced -# with the values when the URL is generated. Must return a Hash -# mapping placeholder names to their values. For example, if this -# method returned -# -# { "year" => Time.now.strftime("%Y") } -# -# Every occurrence of ":year" (note the colon) would be replaced with -# the current year. -# -# - module Jekyll - module URL + class URL - # The generated relative url of this page. e.g. /about.html. - # - # Returns the String url. - def url - @url ||= sanitize_url(permalink || generate_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 - # Generate the URL by replacing all placeholders with their respective values + # 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 - url_placeholders.inject(template) { |result, token| + @placeholders.inject(@template) { |result, token| result.gsub(/:#{token.first}/, token.last) } 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 From cce58159ce05e74f4c8ab47ebeb0b8b8ec8d2c75 Mon Sep 17 00:00:00 2001 From: Lucas Jenss Date: Wed, 31 Jul 2013 23:11:51 +0200 Subject: [PATCH 3/5] Use symbols for all placeholders See https://github.com/mojombo/jekyll/pull/944#discussion_r5443105 for a discussion. --- lib/jekyll/page.rb | 6 +++--- lib/jekyll/post.rb | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb index 10069f6c..032fe265 100644 --- a/lib/jekyll/page.rb +++ b/lib/jekyll/page.rb @@ -76,9 +76,9 @@ module Jekyll # See url.rb for an explanation def url_placeholders { - "path" => @dir, - "basename" => self.basename, - "output_ext" => self.output_ext + :path => @dir, + :basename => self.basename, + :output_ext => self.output_ext } end diff --git a/lib/jekyll/post.rb b/lib/jekyll/post.rb index 6b5c38d5..8a52efc3 100644 --- a/lib/jekyll/post.rb +++ b/lib/jekyll/post.rb @@ -208,16 +208,16 @@ module Jekyll # See url.rb for an explanation 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 + :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 From a23e94ad489d210e79f8d1b09edf69d1698dbbef Mon Sep 17 00:00:00 2001 From: Lucas Jenss Date: Wed, 31 Jul 2013 23:17:06 +0200 Subject: [PATCH 4/5] Improve comment for url_placeholders method --- lib/jekyll/page.rb | 3 ++- lib/jekyll/post.rb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb index 032fe265..bbdeb868 100644 --- a/lib/jekyll/page.rb +++ b/lib/jekyll/page.rb @@ -73,7 +73,8 @@ module Jekyll }).to_s end - # See url.rb for an explanation + # 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, diff --git a/lib/jekyll/post.rb b/lib/jekyll/post.rb index 8a52efc3..6ecc8b2b 100644 --- a/lib/jekyll/post.rb +++ b/lib/jekyll/post.rb @@ -205,7 +205,8 @@ module Jekyll }).to_s end - # See url.rb for an explanation + # 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"), From 362fcf28f0a0d1a32d71d16359aeab6b2972e090 Mon Sep 17 00:00:00 2001 From: Lucas Jenss Date: Wed, 31 Jul 2013 23:17:56 +0200 Subject: [PATCH 5/5] Use do ... end for multiline block --- lib/jekyll/url.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/jekyll/url.rb b/lib/jekyll/url.rb index 383cebaa..10e1cb5d 100644 --- a/lib/jekyll/url.rb +++ b/lib/jekyll/url.rb @@ -43,9 +43,9 @@ module Jekyll # # Returns the _unsanitizied_ String URL def generate_url - @placeholders.inject(@template) { |result, token| + @placeholders.inject(@template) do |result, token| result.gsub(/:#{token.first}/, token.last) - } + end end # Returns a sanitized String URL