From 33255e3ac383709c0f69c2413801b591a884872c Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Fri, 25 Mar 2016 17:44:29 -0700 Subject: [PATCH] IncludeTag: implement multiple load paths --- lib/jekyll/site.rb | 6 +++- lib/jekyll/tags/include.rb | 64 ++++++++++++++++++++------------------ test/test_tags.rb | 8 ++--- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index e13a6bda..9ceda0a0 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -11,7 +11,7 @@ module Jekyll :gems, :plugin_manager, :theme attr_accessor :converters, :generators, :reader - attr_reader :regenerator, :liquid_renderer + attr_reader :regenerator, :liquid_renderer, :includes_load_paths # Public: Initialize a new Site. # @@ -52,8 +52,12 @@ module Jekyll self.plugin_manager = Jekyll::PluginManager.new(self) self.plugins = plugin_manager.plugins_path + self.theme = nil self.theme = Jekyll::Theme.new(config["theme"]) if config["theme"] + @includes_load_paths = Array(in_source_dir(config["includes_dir"].to_s)) + @includes_load_paths << theme.includes_path if self.theme + self.file_read_opts = {} self.file_read_opts[:encoding] = config['encoding'] if config['encoding'] diff --git a/lib/jekyll/tags/include.rb b/lib/jekyll/tags/include.rb index 847e6638..d4f31f2a 100644 --- a/lib/jekyll/tags/include.rb +++ b/lib/jekyll/tags/include.rb @@ -12,8 +12,6 @@ module Jekyll end class IncludeTag < Liquid::Tag - attr_reader :includes_dir - VALID_SYNTAX = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/ VARIABLE_SYNTAX = /(?[^{]*(\{\{\s*[\w\-\.]+\s*(\|.*)?\}\}[^\s{}]*)+)(?.*)/ @@ -98,20 +96,29 @@ eos end end - def tag_includes_dir(context) - context.registers[:site].config['includes_dir'].freeze + def tag_includes_dirs(context) + context.registers[:site].includes_load_paths.freeze + end + + def locate_include_file(context, file, safe) + includes_dirs = tag_includes_dirs(context) + includes_dirs.each do |dir| + path = File.join(dir, file) + return path if valid_include_file?(path, dir, safe) + end + raise IOError, "Could not locate the included file '#{file}' in any of #{includes_dirs}." \ + " Ensure it exists in one of those directories and, if it is a symlink, " \ + "does not point outside your site source." end def render(context) site = context.registers[:site] - @includes_dir = tag_includes_dir(context) - dir = resolved_includes_dir(context) file = render_variable(context) || @file validate_file_name(file) - path = File.join(dir, file) - validate_path(path, dir, site.safe) + path = locate_include_file(context, file, site.safe) + return unless path # Add include to dependency tree if context.registers[:page] && context.registers[:page].key?("path") @@ -121,16 +128,16 @@ eos ) end - begin + #begin partial = load_cached_partial(path, context) context.stack do context['include'] = parse_params(context) if @params partial.render!(context) end - rescue => e - raise IncludeTagError.new e.message, File.join(@includes_dir, @file) - end + #rescue => e + #raise IncludeTagError.new e.message, path + #end end def load_cached_partial(path, context) @@ -144,24 +151,18 @@ eos end end - def resolved_includes_dir(context) - context.registers[:site].in_source_dir(@includes_dir) + def valid_include_file?(path, dir, safe) + !(outside_site_source?(path, dir, safe) || !File.exist?(path)) 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" - elsif !File.exist?(path) - raise IOError.new "Included file '#{path_relative_to_source(dir, path)}' not found" - end - end - - def path_relative_to_source(dir, path) - File.join(@includes_dir, path.sub(Regexp.new("^#{dir}"), "")) + def outside_site_source?(path, dir, safe) + safe && !realpath_prefixed_with?(path, dir) end def realpath_prefixed_with?(path, dir) File.exist?(path) && File.realpath(path).start_with?(dir) + rescue + false end # This method allows to modify the file content by inheriting from the class. @@ -171,16 +172,17 @@ eos end class IncludeRelativeTag < IncludeTag - def tag_includes_dir(context) - '.'.freeze + def tag_includes_dirs(context) + Array(page_path(context)).freeze end def page_path(context) - context.registers[:page].nil? ? includes_dir : File.dirname(context.registers[:page]["path"]) - end - - def resolved_includes_dir(context) - context.registers[:site].in_source_dir(page_path(context)) + if context.registers[:page].nil? + context.registers[:site].source + else + current_doc_dir = File.dirname(context.registers[:page]["path"]) + context.registers[:site].in_source_dir current_doc_dir + end end end end diff --git a/test/test_tags.rb b/test/test_tags.rb index bd481265..81089dab 100644 --- a/test/test_tags.rb +++ b/test/test_tags.rb @@ -615,7 +615,7 @@ title: Include symlink CONTENT create_post(content, {'permalink' => '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 + assert_match "Could not locate the included file 'tmp/pages-test-does-not-exist' in any of [\"/Users/parkr/jekyll/jekyll/test/source/_includes\"].", ex.message end end @@ -756,7 +756,7 @@ CONTENT exception = assert_raises IOError do create_post(@content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true}) end - assert_equal 'Included file \'_includes/missing.html\' not found', exception.message + assert_match "Could not locate the included file 'missing.html' in any of [\"#{source_dir}/_includes\"].", exception.message end end @@ -838,7 +838,7 @@ CONTENT exception = assert_raises IOError do create_post(@content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true}) end - assert_equal 'Included file \'./missing.html\' not found', exception.message + assert_match "Could not locate the included file 'missing.html' in any of [\"#{source_dir}\"].", exception.message end end @@ -892,7 +892,7 @@ title: Include symlink CONTENT create_post(content, {'permalink' => '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 + assert_match /Ensure it exists in one of those directories and, if it is a symlink, does not point outside your site source./, ex.message end end end