From aa97f1025dc3c8b331189182cbab6fc3ccfb7d30 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Wed, 3 Sep 2014 15:43:51 -0700 Subject: [PATCH 01/10] Support a new `relative_include` tag --- lib/jekyll/tags/include.rb | 26 ++++++++++---- .../2014-09-02-relative-includes.markdown | 25 +++++++++++++ .../_posts/relative_includes/params.html | 7 ++++ .../_posts/relative_includes/rel_include.html | 1 + test/test_generated_site.rb | 2 +- test/test_site.rb | 2 +- test/test_tags.rb | 35 +++++++++++++++++++ 7 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 test/source/_posts/2014-09-02-relative-includes.markdown create mode 100644 test/source/_posts/relative_includes/params.html create mode 100644 test/source/_posts/relative_includes/rel_include.html diff --git a/lib/jekyll/tags/include.rb b/lib/jekyll/tags/include.rb index f90f8e8a..6d9e31cf 100644 --- a/lib/jekyll/tags/include.rb +++ b/lib/jekyll/tags/include.rb @@ -18,9 +18,15 @@ module Jekyll VALID_SYNTAX = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/ VARIABLE_SYNTAX = /(?[^{]*\{\{\s*(?[\w\-\.]+)\s*(\|.*)?\}\}[^\s}]*)(?.*)/ - INCLUDES_DIR = '_includes' - def initialize(tag_name, markup, tokens) + @tag_name = tag_name + case tag_name + when 'include' + @includes_dir = '_includes' + when 'relative_include' + @includes_dir = '' + end + super matched = markup.strip.match(VARIABLE_SYNTAX) if matched @@ -97,8 +103,12 @@ eos end def render(context) - dir = File.join(File.realpath(context.registers[:site].source), INCLUDES_DIR) - + case @tag_name + when 'include' + dir = File.join(File.realpath(context.registers[:site].source), @includes_dir) + when 'relative_include' + dir = File.join(File.realpath(context.registers[:site].source), File.dirname(context.registers[:page]["path"])) + end file = render_variable(context) || @file validate_file_name(file) @@ -113,7 +123,7 @@ eos partial.render!(context) end rescue => e - raise IncludeTagError.new e.message, File.join(INCLUDES_DIR, @file) + raise IncludeTagError.new e.message, File.join(@includes_dir, @file) end end @@ -126,7 +136,7 @@ eos end def path_relative_to_source(dir, path) - File.join(INCLUDES_DIR, path.sub(Regexp.new("^#{dir}"), "")) + File.join(@includes_dir, path.sub(Regexp.new("^#{dir}"), "")) end def realpath_prefixed_with?(path, dir) @@ -138,7 +148,11 @@ eos File.read(file, file_read_opts(context)) end end + + class RelativeIncludeTag < IncludeTag + end end end Liquid::Template.register_tag('include', Jekyll::Tags::IncludeTag) +Liquid::Template.register_tag('relative_include', Jekyll::Tags::RelativeIncludeTag) diff --git a/test/source/_posts/2014-09-02-relative-includes.markdown b/test/source/_posts/2014-09-02-relative-includes.markdown new file mode 100644 index 00000000..4afeffbc --- /dev/null +++ b/test/source/_posts/2014-09-02-relative-includes.markdown @@ -0,0 +1,25 @@ +--- +title: Post +layout: post +include1: rel_include.html +include2: relative_includes/rel_include +include3: rel_INCLUDE +include4: params +include5: clude +--- + +Liquid tests +- 1 {% relative_include relative_includes/{{ page.include1 }} %} +- 2 {% relative_include {{ page.include2 | append: '.html' }} %} +- 3 {% relative_include relative_includes/{{ page.include3 | downcase | append: '.html' }} %} + +Whitespace tests +- 4 {% relative_include relative_includes/{{page.include1}} %} +- 5 {% relative_include relative_includes/{{ page.include1}} %} +- 6 {% relative_include relative_includes/{{ page.include3 | downcase | append: '.html'}} %} + +Parameters test +- 7 {% relative_include relative_includes/{{ page.include4 | append: '.html' }} var1='foo' var2='bar' %} + +Partial variable test +- 8 {% relative_include relative_includes/rel_in{{ page.include5 }}.html %} diff --git a/test/source/_posts/relative_includes/params.html b/test/source/_posts/relative_includes/params.html new file mode 100644 index 00000000..ae4c6cfa --- /dev/null +++ b/test/source/_posts/relative_includes/params.html @@ -0,0 +1,7 @@ +{{include.param}} + +
    + {% for param in include %} +
  • {{param[0]}} = {{param[1]}}
  • + {% endfor %} +
diff --git a/test/source/_posts/relative_includes/rel_include.html b/test/source/_posts/relative_includes/rel_include.html new file mode 100644 index 00000000..5fb677b5 --- /dev/null +++ b/test/source/_posts/relative_includes/rel_include.html @@ -0,0 +1 @@ +relative_included diff --git a/test/test_generated_site.rb b/test/test_generated_site.rb index ca3124c5..37818726 100644 --- a/test/test_generated_site.rb +++ b/test/test_generated_site.rb @@ -14,7 +14,7 @@ class TestGeneratedSite < Test::Unit::TestCase end should "ensure post count is as expected" do - assert_equal 42, @site.posts.size + assert_equal 43, @site.posts.size end should "insert site.posts into the index" do diff --git a/test/test_site.rb b/test/test_site.rb index 97111971..7c42862a 100644 --- a/test/test_site.rb +++ b/test/test_site.rb @@ -48,7 +48,7 @@ class TestSite < Test::Unit::TestCase Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir}) end @site = Site.new(Jekyll.configuration) - @num_invalid_posts = 2 + @num_invalid_posts = 4 end should "have an empty tag hash by default" do diff --git a/test/test_tags.rb b/test/test_tags.rb index 12402c7f..37920853 100644 --- a/test/test_tags.rb +++ b/test/test_tags.rb @@ -509,5 +509,40 @@ CONTENT assert_match %r{8 included}, @content end end + + context "relative include tag with variable and liquid filters" do + setup do + stub(Jekyll).configuration do + site_configuration({'pygments' => true}) + end + + site = Site.new(Jekyll.configuration) + post = Post.new(site, source_dir, '', "2014-09-02-relative-includes.markdown") + layouts = { "default" => Layout.new(site, source_dir('_layouts'), "simple.html")} + post.render(layouts, {"site" => {"posts" => []}}) + @content = post.content + end + + should "include file as variable with liquid filters" do + assert_match %r{1 relative_include}, @content + assert_match %r{2 relative_include}, @content + assert_match %r{3 relative_include}, @content + end + + should "include file as variable and liquid filters with arbitrary whitespace" do + assert_match %r{4 relative_include}, @content + assert_match %r{5 relative_include}, @content + assert_match %r{6 relative_include}, @content + end + + should "include file as variable and filters with additional parameters" do + assert_match '
  • var1 = foo
  • ', @content + assert_match '
  • var2 = bar
  • ', @content + end + + should "include file as partial variable" do + assert_match %r{8 relative_include}, @content + end + end end end From 787bb582da8073bde04393c3a8adce52ae59b4ba Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Sat, 6 Sep 2014 21:48:00 -0700 Subject: [PATCH 02/10] Trash the subclass --- lib/jekyll/tags/include.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/jekyll/tags/include.rb b/lib/jekyll/tags/include.rb index 6d9e31cf..26fedb0e 100644 --- a/lib/jekyll/tags/include.rb +++ b/lib/jekyll/tags/include.rb @@ -148,9 +148,6 @@ eos File.read(file, file_read_opts(context)) end end - - class RelativeIncludeTag < IncludeTag - end end end From 9f558d1ceccc052967321f1d46bd6733a0511846 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Sat, 6 Sep 2014 21:48:15 -0700 Subject: [PATCH 03/10] Rename tag to `include_relative` --- lib/jekyll/tags/include.rb | 6 +++--- .../2014-09-02-relative-includes.markdown | 18 +++++++++--------- .../params.html | 0 .../rel_include.html | 0 4 files changed, 12 insertions(+), 12 deletions(-) rename test/source/_posts/{relative_includes => include_relative}/params.html (100%) rename test/source/_posts/{relative_includes => include_relative}/rel_include.html (100%) diff --git a/lib/jekyll/tags/include.rb b/lib/jekyll/tags/include.rb index 26fedb0e..9efab578 100644 --- a/lib/jekyll/tags/include.rb +++ b/lib/jekyll/tags/include.rb @@ -23,7 +23,7 @@ module Jekyll case tag_name when 'include' @includes_dir = '_includes' - when 'relative_include' + when 'include_relative' @includes_dir = '' end @@ -106,7 +106,7 @@ eos case @tag_name when 'include' dir = File.join(File.realpath(context.registers[:site].source), @includes_dir) - when 'relative_include' + when 'include_relative' dir = File.join(File.realpath(context.registers[:site].source), File.dirname(context.registers[:page]["path"])) end file = render_variable(context) || @file @@ -152,4 +152,4 @@ eos end Liquid::Template.register_tag('include', Jekyll::Tags::IncludeTag) -Liquid::Template.register_tag('relative_include', Jekyll::Tags::RelativeIncludeTag) +Liquid::Template.register_tag('include_relative', Jekyll::Tags::IncludeTag) diff --git a/test/source/_posts/2014-09-02-relative-includes.markdown b/test/source/_posts/2014-09-02-relative-includes.markdown index 4afeffbc..748e47f2 100644 --- a/test/source/_posts/2014-09-02-relative-includes.markdown +++ b/test/source/_posts/2014-09-02-relative-includes.markdown @@ -2,24 +2,24 @@ title: Post layout: post include1: rel_include.html -include2: relative_includes/rel_include +include2: include_relative/rel_include include3: rel_INCLUDE include4: params include5: clude --- Liquid tests -- 1 {% relative_include relative_includes/{{ page.include1 }} %} -- 2 {% relative_include {{ page.include2 | append: '.html' }} %} -- 3 {% relative_include relative_includes/{{ page.include3 | downcase | append: '.html' }} %} +- 1 {% include_relative include_relative/{{ page.include1 }} %} +- 2 {% include_relative {{ page.include2 | append: '.html' }} %} +- 3 {% include_relative include_relative/{{ page.include3 | downcase | append: '.html' }} %} Whitespace tests -- 4 {% relative_include relative_includes/{{page.include1}} %} -- 5 {% relative_include relative_includes/{{ page.include1}} %} -- 6 {% relative_include relative_includes/{{ page.include3 | downcase | append: '.html'}} %} +- 4 {% include_relative include_relative/{{page.include1}} %} +- 5 {% include_relative include_relative/{{ page.include1}} %} +- 6 {% include_relative include_relative/{{ page.include3 | downcase | append: '.html'}} %} Parameters test -- 7 {% relative_include relative_includes/{{ page.include4 | append: '.html' }} var1='foo' var2='bar' %} +- 7 {% include_relative include_relative/{{ page.include4 | append: '.html' }} var1='foo' var2='bar' %} Partial variable test -- 8 {% relative_include relative_includes/rel_in{{ page.include5 }}.html %} +- 8 {% include_relative include_relative/rel_in{{ page.include5 }}.html %} diff --git a/test/source/_posts/relative_includes/params.html b/test/source/_posts/include_relative/params.html similarity index 100% rename from test/source/_posts/relative_includes/params.html rename to test/source/_posts/include_relative/params.html diff --git a/test/source/_posts/relative_includes/rel_include.html b/test/source/_posts/include_relative/rel_include.html similarity index 100% rename from test/source/_posts/relative_includes/rel_include.html rename to test/source/_posts/include_relative/rel_include.html From 0e4549013d03edfa8911d380ec188831b7bba6d7 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Sun, 7 Sep 2014 11:21:40 -0700 Subject: [PATCH 04/10] Define methods for shared vars between include & include_relative --- lib/jekyll/tags/include.rb | 40 ++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/lib/jekyll/tags/include.rb b/lib/jekyll/tags/include.rb index 9efab578..43bf3dfc 100644 --- a/lib/jekyll/tags/include.rb +++ b/lib/jekyll/tags/include.rb @@ -19,14 +19,6 @@ module Jekyll VARIABLE_SYNTAX = /(?[^{]*\{\{\s*(?[\w\-\.]+)\s*(\|.*)?\}\}[^\s}]*)(?.*)/ def initialize(tag_name, markup, tokens) - @tag_name = tag_name - case tag_name - when 'include' - @includes_dir = '_includes' - when 'include_relative' - @includes_dir = '' - end - super matched = markup.strip.match(VARIABLE_SYNTAX) if matched @@ -102,13 +94,13 @@ eos end end + def includes_dir + '_includes' + end + def render(context) - case @tag_name - when 'include' - dir = File.join(File.realpath(context.registers[:site].source), @includes_dir) - when 'include_relative' - dir = File.join(File.realpath(context.registers[:site].source), File.dirname(context.registers[:page]["path"])) - end + dir = dir_to_include(context) + file = render_variable(context) || @file validate_file_name(file) @@ -123,10 +115,14 @@ eos partial.render!(context) end rescue => e - raise IncludeTagError.new e.message, File.join(@includes_dir, @file) + raise IncludeTagError.new e.message, File.join(includes_dir, @file) end end + def dir_to_include(context) + File.join(File.realpath(context.registers[:site].source), includes_dir) + end + def validate_path(path, dir, safe) if safe && !realpath_prefixed_with?(path, dir) raise IOError.new "The included file '#{path}' should exist and should not be a symlink" @@ -136,7 +132,7 @@ eos end def path_relative_to_source(dir, path) - File.join(@includes_dir, path.sub(Regexp.new("^#{dir}"), "")) + File.join(includes_dir, path.sub(Regexp.new("^#{dir}"), "")) end def realpath_prefixed_with?(path, dir) @@ -148,8 +144,18 @@ eos File.read(file, file_read_opts(context)) end end + + class IncludeRelativeTag < IncludeTag + def includes_dir + '.' + end + + def dir_to_include(context) + File.join(File.realpath(context.registers[:site].source), File.dirname(context.registers[:page]["path"])) + end + end end end Liquid::Template.register_tag('include', Jekyll::Tags::IncludeTag) -Liquid::Template.register_tag('include_relative', Jekyll::Tags::IncludeTag) +Liquid::Template.register_tag('include_relative', Jekyll::Tags::IncludeRelativeTag) From 934c37b57847cb0d95a5347bf628be6b23b862a1 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Sun, 7 Sep 2014 11:21:50 -0700 Subject: [PATCH 05/10] Separate include_relative test out into its own context --- test/test_tags.rb | 56 +++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/test/test_tags.rb b/test/test_tags.rb index 37920853..f32200fd 100644 --- a/test/test_tags.rb +++ b/test/test_tags.rb @@ -509,40 +509,40 @@ CONTENT assert_match %r{8 included}, @content end end + end - context "relative include tag with variable and liquid filters" do - setup do - stub(Jekyll).configuration do - site_configuration({'pygments' => true}) - end - - site = Site.new(Jekyll.configuration) - post = Post.new(site, source_dir, '', "2014-09-02-relative-includes.markdown") - layouts = { "default" => Layout.new(site, source_dir('_layouts'), "simple.html")} - post.render(layouts, {"site" => {"posts" => []}}) - @content = post.content + context "relative include tag with variable and liquid filters" do + setup do + stub(Jekyll).configuration do + site_configuration({'pygments' => true}) end - should "include file as variable with liquid filters" do - assert_match %r{1 relative_include}, @content - assert_match %r{2 relative_include}, @content - assert_match %r{3 relative_include}, @content - end + site = Site.new(Jekyll.configuration) + post = Post.new(site, source_dir, '', "2014-09-02-relative-includes.markdown") + layouts = { "default" => Layout.new(site, source_dir('_layouts'), "simple.html")} + post.render(layouts, {"site" => {"posts" => []}}) + @content = post.content + end - should "include file as variable and liquid filters with arbitrary whitespace" do - assert_match %r{4 relative_include}, @content - assert_match %r{5 relative_include}, @content - assert_match %r{6 relative_include}, @content - end + should "include file as variable with liquid filters" do + assert_match %r{1 relative_include}, @content + assert_match %r{2 relative_include}, @content + assert_match %r{3 relative_include}, @content + end - should "include file as variable and filters with additional parameters" do - assert_match '
  • var1 = foo
  • ', @content - assert_match '
  • var2 = bar
  • ', @content - end + should "include file as variable and liquid filters with arbitrary whitespace" do + assert_match %r{4 relative_include}, @content + assert_match %r{5 relative_include}, @content + assert_match %r{6 relative_include}, @content + end - should "include file as partial variable" do - assert_match %r{8 relative_include}, @content - end + should "include file as variable and filters with additional parameters" do + assert_match '
  • var1 = foo
  • ', @content + assert_match '
  • var2 = bar
  • ', @content + end + + should "include file as partial variable" do + assert_match %r{8 relative_include}, @content end end end From e7c8bbf5df60d2846234316bffd1621f5d1c5560 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Sun, 7 Sep 2014 11:24:44 -0700 Subject: [PATCH 06/10] Test that includes relative to self are included --- test/source/_posts/2014-09-02-relative-includes.markdown | 4 ++++ test/test_tags.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/test/source/_posts/2014-09-02-relative-includes.markdown b/test/source/_posts/2014-09-02-relative-includes.markdown index 748e47f2..ab451b63 100644 --- a/test/source/_posts/2014-09-02-relative-includes.markdown +++ b/test/source/_posts/2014-09-02-relative-includes.markdown @@ -23,3 +23,7 @@ Parameters test Partial variable test - 8 {% include_relative include_relative/rel_in{{ page.include5 }}.html %} + +Relative to self test: + +- 9 {% include_relative 2014-03-03-yaml-with-dots.md %} diff --git a/test/test_tags.rb b/test/test_tags.rb index f32200fd..75abf849 100644 --- a/test/test_tags.rb +++ b/test/test_tags.rb @@ -544,5 +544,9 @@ CONTENT should "include file as partial variable" do assert_match %r{8 relative_include}, @content end + + should "include files relative to self" do + assert_match %r{9 —\ntitle: Test Post Where YAML}, @content + end end end From 4da07bb2c35b07d65322155f3aac96775700c712 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Sun, 7 Sep 2014 12:00:37 -0700 Subject: [PATCH 07/10] Add tests around nasty include attempts --- lib/jekyll/tags/include.rb | 3 ++- test/test_tags.rb | 55 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/lib/jekyll/tags/include.rb b/lib/jekyll/tags/include.rb index 43bf3dfc..644297d4 100644 --- a/lib/jekyll/tags/include.rb +++ b/lib/jekyll/tags/include.rb @@ -151,7 +151,8 @@ eos end def dir_to_include(context) - File.join(File.realpath(context.registers[:site].source), File.dirname(context.registers[:page]["path"])) + page_path = context.registers[:page].nil? ? includes_dir : File.dirname(context.registers[:page]["path"]) + File.join(File.realpath(context.registers[:site].source), page_path) end end end diff --git a/test/test_tags.rb b/test/test_tags.rb index 75abf849..756bdcb5 100644 --- a/test/test_tags.rb +++ b/test/test_tags.rb @@ -548,5 +548,60 @@ CONTENT should "include files relative to self" do assert_match %r{9 —\ntitle: Test Post Where YAML}, @content end + + context "trying to do bad stuff" do + context "include missing file" do + setup do + @content = < 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true}) + end + assert_equal 'Included file \'./missing.html\' not found', exception.message + end + end + end + + context "with symlink'd include" do + + should "not allow symlink includes" do + File.open("/tmp/pages-test", 'w') { |file| file.write("SYMLINK TEST") } + assert_raise IOError do + content = < 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true, 'safe' => true }) + end + assert_no_match /SYMLINK TEST/, @result + end + + should "not expose the existence of symlinked files" do + ex = assert_raise IOError do + content = < 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true, 'safe' => true }) + end + assert_match /should exist and should not be a symlink/, ex.message + end + end end end From afd30b0a9bab61ad093b463a35e6a316099498c0 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Sun, 7 Sep 2014 12:14:19 -0700 Subject: [PATCH 08/10] Add documentation on the include_relative tag --- site/_docs/templates.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/site/_docs/templates.md b/site/_docs/templates.md index 5ace04fc..75336eb2 100644 --- a/site/_docs/templates.md +++ b/site/_docs/templates.md @@ -281,6 +281,17 @@ These parameters are available via Liquid in the include: {% raw %}{{ include.param }}{% endraw %} {% endhighlight %} +### Including files relative to another file + +You can also choose to include files relative to the current file: + +{% highlight ruby %} +{% raw %}{% include_relative somedir/footer.html %}{% endraw %} +{% endhighlight %} + +You won't need to place your included content within the `_includes` directory. +All the other capaibilities of the `include` tag are available to the `include_relative` tag. + ### Code snippet highlighting Jekyll has built in support for syntax highlighting of [over 100 From 3668437c96579a10fa49ffce6bb1973ee17e0464 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Sun, 7 Sep 2014 12:18:35 -0700 Subject: [PATCH 09/10] Rename method to `resolved_includes_dir` --- lib/jekyll/tags/include.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/jekyll/tags/include.rb b/lib/jekyll/tags/include.rb index 644297d4..41d3699b 100644 --- a/lib/jekyll/tags/include.rb +++ b/lib/jekyll/tags/include.rb @@ -99,7 +99,7 @@ eos end def render(context) - dir = dir_to_include(context) + dir = resolved_includes_dir(context) file = render_variable(context) || @file validate_file_name(file) @@ -119,7 +119,7 @@ eos end end - def dir_to_include(context) + def resolved_includes_dir(context) File.join(File.realpath(context.registers[:site].source), includes_dir) end @@ -150,7 +150,7 @@ eos '.' end - def dir_to_include(context) + def resolved_includes_dir(context) page_path = context.registers[:page].nil? ? includes_dir : File.dirname(context.registers[:page]["path"]) File.join(File.realpath(context.registers[:site].source), page_path) end From a569799ffca0075d43a203f4faff37b123bcd25c Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Sun, 7 Sep 2014 12:18:54 -0700 Subject: [PATCH 10/10] Sanitize the resolved includes path --- lib/jekyll/tags/include.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jekyll/tags/include.rb b/lib/jekyll/tags/include.rb index 41d3699b..724b33c0 100644 --- a/lib/jekyll/tags/include.rb +++ b/lib/jekyll/tags/include.rb @@ -152,7 +152,7 @@ eos def resolved_includes_dir(context) page_path = context.registers[:page].nil? ? includes_dir : File.dirname(context.registers[:page]["path"]) - File.join(File.realpath(context.registers[:site].source), page_path) + Jekyll.sanitized_path(context.registers[:site].source, page_path) end end end