From b55927e8f71b5d3cd6258f7f0d08962dd759cf7d Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Fri, 2 Aug 2019 01:51:00 +0530 Subject: [PATCH] Add PathManager class to cache interim paths (#7732) Merge pull request 7732 --- lib/jekyll.rb | 1 + lib/jekyll/entry_filter.rb | 4 ++-- lib/jekyll/page.rb | 2 +- lib/jekyll/path_manager.rb | 31 +++++++++++++++++++++++++++++++ lib/jekyll/reader.rb | 2 +- lib/jekyll/tags/include.rb | 2 +- 6 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 lib/jekyll/path_manager.rb diff --git a/lib/jekyll.rb b/lib/jekyll.rb index 2b0e6382..07304057 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -65,6 +65,7 @@ module Jekyll autoload :LogAdapter, "jekyll/log_adapter" autoload :Page, "jekyll/page" autoload :PageWithoutAFile, "jekyll/page_without_a_file" + autoload :PathManager, "jekyll/path_manager" autoload :PluginManager, "jekyll/plugin_manager" autoload :Publisher, "jekyll/publisher" autoload :Reader, "jekyll/reader" diff --git a/lib/jekyll/entry_filter.rb b/lib/jekyll/entry_filter.rb index 4a238036..b8f4e044 100644 --- a/lib/jekyll/entry_filter.rb +++ b/lib/jekyll/entry_filter.rb @@ -90,12 +90,12 @@ module Jekyll # Check if an entry matches a specific pattern. # Returns true if path matches against any glob pattern, else false. def glob_include?(enumerator, entry) - entry_with_source = File.join(site.source, entry) + entry_with_source = PathManager.join(site.source, entry) enumerator.any? do |pattern| case pattern when String - pattern_with_source = File.join(site.source, pattern) + pattern_with_source = PathManager.join(site.source, pattern) File.fnmatch?(pattern_with_source, entry_with_source) || entry_with_source.start_with?(pattern_with_source) diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb index e6d691c2..f353252d 100644 --- a/lib/jekyll/page.rb +++ b/lib/jekyll/page.rb @@ -47,7 +47,7 @@ module Jekyll end process(name) - read_yaml(File.join(base, dir), name) + read_yaml(PathManager.join(base, dir), name) data.default_proc = proc do |_, key| site.frontmatter_defaults.find(relative_path, type, key) diff --git a/lib/jekyll/path_manager.rb b/lib/jekyll/path_manager.rb new file mode 100644 index 00000000..eeed1f95 --- /dev/null +++ b/lib/jekyll/path_manager.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Jekyll + # A singleton class that caches frozen instances of path strings returned from its methods. + # + # NOTE: + # This class exists because `File.join` allocates an Array and returns a new String on every + # call using **the same arguments**. Caching the result means reduced memory usage. + # However, the caches are never flushed so that they can be used even when a site is + # regenerating. The results are frozen to deter mutation of the cached string. + # + # Therefore, employ this class only for situations where caching the result is necessary + # for performance reasons. + # + class PathManager + # This class cannot be initialized from outside + private_class_method :new + + # Wraps `File.join` to cache the frozen result. + # Reassigns `nil`, empty strings and empty arrays to a frozen empty string beforehand. + # + # Returns a frozen string. + def self.join(base, item) + base = "" if base.nil? || base.empty? + item = "" if item.nil? || item.empty? + @join ||= {} + @join[base] ||= {} + @join[base][item] ||= File.join(base, item).freeze + end + end +end diff --git a/lib/jekyll/reader.rb b/lib/jekyll/reader.rb index 797e4291..381d9d31 100644 --- a/lib/jekyll/reader.rb +++ b/lib/jekyll/reader.rb @@ -85,7 +85,7 @@ module Jekyll def retrieve_dirs(_base, dir, dot_dirs) dot_dirs.each do |file| dir_path = site.in_source_dir(dir, file) - rel_path = File.join(dir, file) + rel_path = PathManager.join(dir, file) @site.reader.read_directories(rel_path) unless @site.dest.chomp("/") == dir_path end end diff --git a/lib/jekyll/tags/include.rb b/lib/jekyll/tags/include.rb index 20a41bb7..7fabe25f 100644 --- a/lib/jekyll/tags/include.rb +++ b/lib/jekyll/tags/include.rb @@ -100,7 +100,7 @@ module Jekyll def locate_include_file(context, file, safe) includes_dirs = tag_includes_dirs(context) includes_dirs.each do |dir| - path = File.join(dir.to_s, file.to_s) + path = PathManager.join(dir, file) return path if valid_include_file?(path, dir.to_s, safe) end raise IOError, could_not_locate_message(file, includes_dirs, safe)