summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/jekyll-4.4.1/lib/jekyll/document.rb
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/bundle/ruby/3.4.0/gems/jekyll-4.4.1/lib/jekyll/document.rb')
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/jekyll-4.4.1/lib/jekyll/document.rb543
1 files changed, 543 insertions, 0 deletions
diff --git a/vendor/bundle/ruby/3.4.0/gems/jekyll-4.4.1/lib/jekyll/document.rb b/vendor/bundle/ruby/3.4.0/gems/jekyll-4.4.1/lib/jekyll/document.rb
new file mode 100644
index 0000000..2e39024
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/jekyll-4.4.1/lib/jekyll/document.rb
@@ -0,0 +1,543 @@
+# frozen_string_literal: true
+
+module Jekyll
+ class Document
+ include Comparable
+ extend Forwardable
+
+ attr_reader :path, :site, :extname, :collection, :type
+ attr_accessor :content, :output
+
+ def_delegator :self, :read_post_data, :post_read
+
+ YAML_FRONT_MATTER_REGEXP = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m.freeze
+ DATELESS_FILENAME_MATCHER = %r!^(?:.+/)*(.*)(\.[^.]+)$!.freeze
+ DATE_FILENAME_MATCHER = %r!^(?>.+/)*?(\d{2,4}-\d{1,2}-\d{1,2})-([^/]*)(\.[^.]+)$!.freeze
+
+ SASS_FILE_EXTS = %w(.sass .scss).freeze
+ YAML_FILE_EXTS = %w(.yaml .yml).freeze
+
+ #
+
+ # Class-wide cache to stash and retrieve regexp to detect "super-directories"
+ # of a particular Jekyll::Document object.
+ #
+ # dirname - The *special directory* for the Document.
+ # e.g. "_posts" or "_drafts" for Documents from the `site.posts` collection.
+ def self.superdirs_regex(dirname)
+ @superdirs_regex ||= {}
+ @superdirs_regex[dirname] ||= %r!#{dirname}.*!
+ end
+
+ #
+
+ # Create a new Document.
+ #
+ # path - the path to the file
+ # relations - a hash with keys :site and :collection, the values of which
+ # are the Jekyll::Site and Jekyll::Collection to which this
+ # Document belong.
+ #
+ # Returns nothing.
+ def initialize(path, relations = {})
+ @site = relations[:site]
+ @path = path
+ @extname = File.extname(path)
+ @collection = relations[:collection]
+ @type = @collection.label.to_sym
+
+ @has_yaml_header = nil
+
+ if draft?
+ categories_from_path("_drafts")
+ else
+ categories_from_path(collection.relative_directory)
+ end
+
+ data.default_proc = proc do |_, key|
+ site.frontmatter_defaults.find(relative_path, type, key)
+ end
+
+ trigger_hooks(:post_init)
+ end
+
+ # Fetch the Document's data.
+ #
+ # Returns a Hash containing the data. An empty hash is returned if
+ # no data was read.
+ def data
+ @data ||= {}
+ end
+
+ # Merge some data in with this document's data.
+ #
+ # Returns the merged data.
+ def merge_data!(other, source: "YAML front matter")
+ merge_categories!(other)
+ Utils.deep_merge_hashes!(data, other)
+ merge_date!(source)
+ data
+ end
+
+ # Returns the document date. If metadata is not present then calculates it
+ # based on Jekyll::Site#time or the document file modification time.
+ #
+ # Return document date string.
+ def date
+ data["date"] ||= (draft? ? source_file_mtime : site.time)
+ end
+
+ # Return document file modification time in the form of a Time object.
+ #
+ # Return document file modification Time object.
+ def source_file_mtime
+ File.mtime(path)
+ end
+
+ # Returns whether the document is a draft. This is only the case if
+ # the document is in the 'posts' collection but in a different
+ # directory than '_posts'.
+ #
+ # Returns whether the document is a draft.
+ def draft?
+ data["draft"] ||= relative_path.index(collection.relative_directory).nil? &&
+ collection.label == "posts"
+ end
+
+ # The path to the document, relative to the collections_dir.
+ #
+ # Returns a String path which represents the relative path from the collections_dir
+ # to this document.
+ def relative_path
+ @relative_path ||= path.sub("#{site.collections_path}/", "")
+ end
+
+ # The output extension of the document.
+ #
+ # Returns the output extension
+ def output_ext
+ renderer.output_ext
+ end
+
+ # The base filename of the document, without the file extname.
+ #
+ # Returns the basename without the file extname.
+ def basename_without_ext
+ @basename_without_ext ||= File.basename(path, ".*")
+ end
+
+ # The base filename of the document.
+ #
+ # Returns the base filename of the document.
+ def basename
+ @basename ||= File.basename(path)
+ end
+
+ def renderer
+ @renderer ||= Jekyll::Renderer.new(site, self)
+ end
+
+ # Produces a "cleaned" relative path.
+ # The "cleaned" relative path is the relative path without the extname
+ # and with the collection's directory removed as well.
+ # This method is useful when building the URL of the document.
+ #
+ # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`)
+ #
+ # Examples:
+ # When relative_path is "_methods/site/generate...md":
+ # cleaned_relative_path
+ # # => "/site/generate"
+ #
+ # Returns the cleaned relative path of the document.
+ def cleaned_relative_path
+ @cleaned_relative_path ||=
+ relative_path[0..-extname.length - 1]
+ .sub(collection.relative_directory, "")
+ .gsub(%r!\.*\z!, "")
+ end
+
+ # Determine whether the document is a YAML file.
+ #
+ # Returns true if the extname is either .yml or .yaml, false otherwise.
+ def yaml_file?
+ YAML_FILE_EXTS.include?(extname)
+ end
+
+ # Determine whether the document is an asset file.
+ # Asset files include CoffeeScript files and Sass/SCSS files.
+ #
+ # Returns true if the extname belongs to the set of extensions
+ # that asset files use.
+ def asset_file?
+ sass_file? || coffeescript_file?
+ end
+
+ # Determine whether the document is a Sass file.
+ #
+ # Returns true if extname == .sass or .scss, false otherwise.
+ def sass_file?
+ SASS_FILE_EXTS.include?(extname)
+ end
+
+ # Determine whether the document is a CoffeeScript file.
+ #
+ # Returns true if extname == .coffee, false otherwise.
+ def coffeescript_file?
+ extname == ".coffee"
+ end
+
+ # Determine whether the file should be rendered with Liquid.
+ #
+ # Returns false if the document is either an asset file or a yaml file,
+ # or if the document doesn't contain any Liquid Tags or Variables,
+ # true otherwise.
+ def render_with_liquid?
+ return false if data["render_with_liquid"] == false
+
+ !(coffeescript_file? || yaml_file? || !Utils.has_liquid_construct?(content))
+ end
+
+ # Determine whether the file should be rendered with a layout.
+ #
+ # Returns true if the Front Matter specifies that `layout` is set to `none`.
+ def no_layout?
+ data["layout"] == "none"
+ end
+
+ # Determine whether the file should be placed into layouts.
+ #
+ # Returns false if the document is set to `layouts: none`, or is either an
+ # asset file or a yaml file. Returns true otherwise.
+ def place_in_layout?
+ !(asset_file? || yaml_file? || no_layout?)
+ end
+
+ # The URL template where the document would be accessible.
+ #
+ # Returns the URL template for the document.
+ def url_template
+ collection.url_template
+ end
+
+ # Construct a Hash of key-value pairs which contain a mapping between
+ # a key in the URL template and the corresponding value for this document.
+ #
+ # Returns the Hash of key-value pairs for replacement in the URL.
+ def url_placeholders
+ @url_placeholders ||= Drops::UrlDrop.new(self)
+ end
+
+ # The permalink for this Document.
+ # Permalink is set via the data Hash.
+ #
+ # Returns the permalink or nil if no permalink was set in the data.
+ def permalink
+ data && data.is_a?(Hash) && data["permalink"]
+ end
+
+ # The computed URL for the document. See `Jekyll::URL#to_s` for more details.
+ #
+ # Returns the computed URL for the document.
+ def url
+ @url ||= URL.new(
+ :template => url_template,
+ :placeholders => url_placeholders,
+ :permalink => permalink
+ ).to_s
+ end
+
+ def [](key)
+ data[key]
+ end
+
+ # The full path to the output file.
+ #
+ # base_directory - the base path of the output directory
+ #
+ # Returns the full path to the output file of this document.
+ def destination(base_directory)
+ @destination ||= {}
+ @destination[base_directory] ||= begin
+ path = site.in_dest_dir(base_directory, URL.unescape_path(url))
+ if url.end_with? "/"
+ path = File.join(path, "index.html")
+ else
+ path << output_ext unless path.end_with? output_ext
+ end
+ path
+ end
+ end
+
+ # Write the generated Document file to the destination directory.
+ #
+ # dest - The String path to the destination dir.
+ #
+ # Returns nothing.
+ def write(dest)
+ path = destination(dest)
+ FileUtils.mkdir_p(File.dirname(path))
+ Jekyll.logger.debug "Writing:", path
+ File.write(path, output, :mode => "wb")
+
+ trigger_hooks(:post_write)
+ end
+
+ # Whether the file is published or not, as indicated in YAML front-matter
+ #
+ # Returns 'false' if the 'published' key is specified in the
+ # YAML front-matter and is 'false'. Otherwise returns 'true'.
+ def published?
+ !(data.key?("published") && data["published"] == false)
+ end
+
+ # Read in the file and assign the content and data based on the file contents.
+ # Merge the frontmatter of the file with the frontmatter default
+ # values
+ #
+ # Returns nothing.
+ def read(opts = {})
+ Jekyll.logger.debug "Reading:", relative_path
+
+ if yaml_file?
+ @data = SafeYAML.load_file(path)
+ else
+ begin
+ merge_defaults
+ read_content(**opts)
+ read_post_data
+ rescue StandardError => e
+ handle_read_error(e)
+ end
+ end
+ end
+
+ # Create a Liquid-understandable version of this Document.
+ #
+ # Returns a Hash representing this Document's data.
+ def to_liquid
+ @to_liquid ||= Drops::DocumentDrop.new(self)
+ end
+
+ # The inspect string for this document.
+ # Includes the relative path and the collection label.
+ #
+ # Returns the inspect string for this document.
+ def inspect
+ "#<#{self.class} #{relative_path} collection=#{collection.label}>"
+ end
+
+ # The string representation for this document.
+ #
+ # Returns the content of the document
+ def to_s
+ output || content || "NO CONTENT"
+ end
+
+ # Compare this document against another document.
+ # Comparison is a comparison between the 2 paths of the documents.
+ #
+ # Returns -1, 0, +1 or nil depending on whether this doc's path is less than,
+ # equal or greater than the other doc's path. See String#<=> for more details.
+ def <=>(other)
+ return nil unless other.respond_to?(:data)
+
+ cmp = data["date"] <=> other.data["date"]
+ cmp = path <=> other.path if cmp.nil? || cmp.zero?
+ cmp
+ end
+
+ # Determine whether this document should be written.
+ # Based on the Collection to which it belongs.
+ #
+ # True if the document has a collection and if that collection's #write?
+ # method returns true, and if the site's Publisher will publish the document.
+ # False otherwise.
+ #
+ # rubocop:disable Naming/MemoizedInstanceVariableName
+ def write?
+ return @write_p if defined?(@write_p)
+
+ @write_p = collection&.write? && site.publisher.publish?(self)
+ end
+ # rubocop:enable Naming/MemoizedInstanceVariableName
+
+ # The Document excerpt_separator, from the YAML Front-Matter or site
+ # default excerpt_separator value
+ #
+ # Returns the document excerpt_separator
+ def excerpt_separator
+ @excerpt_separator ||= (data["excerpt_separator"] || site.config["excerpt_separator"]).to_s
+ end
+
+ # Whether to generate an excerpt
+ #
+ # Returns true if the excerpt separator is configured.
+ def generate_excerpt?
+ !excerpt_separator.empty?
+ end
+
+ def next_doc
+ pos = collection.docs.index { |post| post.equal?(self) }
+ collection.docs[pos + 1] if pos && pos < collection.docs.length - 1
+ end
+
+ def previous_doc
+ pos = collection.docs.index { |post| post.equal?(self) }
+ collection.docs[pos - 1] if pos && pos.positive?
+ end
+
+ def trigger_hooks(hook_name, *args)
+ Jekyll::Hooks.trigger collection.label.to_sym, hook_name, self, *args if collection
+ Jekyll::Hooks.trigger :documents, hook_name, self, *args
+ end
+
+ def id
+ @id ||= File.join(File.dirname(url), (data["slug"] || basename_without_ext).to_s)
+ end
+
+ # Calculate related posts.
+ #
+ # Returns an Array of related Posts.
+ def related_posts
+ @related_posts ||= Jekyll::RelatedPosts.new(self).build
+ end
+
+ # Override of method_missing to check in @data for the key.
+ def method_missing(method, *args, &blck)
+ if data.key?(method.to_s)
+ Jekyll::Deprecator.deprecation_message "Document##{method} is now a key in the #data hash."
+ Jekyll.logger.warn "", "Called by #{caller(1..1)[0]}."
+ data[method.to_s]
+ else
+ super
+ end
+ end
+
+ def respond_to_missing?(method, *)
+ data.key?(method.to_s) || super
+ end
+
+ # Add superdirectories of the special_dir to categories.
+ # In the case of es/_posts, 'es' is added as a category.
+ # In the case of _posts/es, 'es' is NOT added as a category.
+ #
+ # Returns nothing.
+ def categories_from_path(special_dir)
+ if relative_path.start_with?(special_dir)
+ superdirs = []
+ else
+ superdirs = relative_path.sub(Document.superdirs_regex(special_dir), "")
+ superdirs = superdirs.split(File::SEPARATOR)
+ superdirs.reject! { |c| c.empty? || c == special_dir || c == basename }
+ end
+
+ merge_data!({ "categories" => superdirs }, :source => "file path")
+ end
+
+ def populate_categories
+ categories = Array(data["categories"]) + Utils.pluralized_array_from_hash(
+ data, "category", "categories"
+ )
+ categories.map!(&:to_s)
+ categories.flatten!
+ categories.uniq!
+
+ merge_data!({ "categories" => categories })
+ end
+
+ def populate_tags
+ tags = Utils.pluralized_array_from_hash(data, "tag", "tags")
+ tags.flatten!
+
+ merge_data!({ "tags" => tags })
+ end
+
+ private
+
+ def merge_categories!(other)
+ if other.key?("categories") && !other["categories"].nil?
+ other["categories"] = other["categories"].split if other["categories"].is_a?(String)
+
+ if data["categories"].is_a?(Array)
+ other["categories"] = data["categories"] | other["categories"]
+ end
+ end
+ end
+
+ def merge_date!(source)
+ if data.key?("date")
+ data["date"] = Utils.parse_date(
+ data["date"].to_s,
+ "Document '#{relative_path}' does not have a valid date in the #{source}."
+ )
+ end
+ end
+
+ def merge_defaults
+ defaults = @site.frontmatter_defaults.all(relative_path, type)
+ merge_data!(defaults, :source => "front matter defaults") unless defaults.empty?
+ end
+
+ def read_content(**opts)
+ self.content = File.read(path, **Utils.merged_file_read_opts(site, opts))
+ if content =~ YAML_FRONT_MATTER_REGEXP
+ self.content = Regexp.last_match.post_match
+ data_file = SafeYAML.load(Regexp.last_match(1))
+ merge_data!(data_file, :source => "YAML front matter") if data_file
+ end
+ end
+
+ def read_post_data
+ populate_title
+ populate_categories
+ populate_tags
+ generate_excerpt
+ end
+
+ def handle_read_error(error)
+ if error.is_a? Psych::SyntaxError
+ Jekyll.logger.error "Error:", "YAML Exception reading #{path}: #{error.message}"
+ else
+ Jekyll.logger.error "Error:", "could not read file #{path}: #{error.message}"
+ end
+
+ if site.config["strict_front_matter"] || error.is_a?(Jekyll::Errors::FatalException)
+ raise error
+ end
+ end
+
+ def populate_title
+ if relative_path =~ DATE_FILENAME_MATCHER
+ date, slug, ext = Regexp.last_match.captures
+ modify_date(date)
+ elsif relative_path =~ DATELESS_FILENAME_MATCHER
+ slug, ext = Regexp.last_match.captures
+ end
+ # `slug` will be nil for documents without an extension since the regex patterns
+ # above tests for an extension as well.
+ # In such cases, assign `basename_without_ext` as the slug.
+ slug ||= basename_without_ext
+
+ # slugs shouldn't end with a period
+ # `String#gsub!` removes all trailing periods (in comparison to `String#chomp!`)
+ slug.gsub!(%r!\.*\z!, "")
+
+ # Try to ensure the user gets a title.
+ data["title"] ||= Utils.titleize_slug(slug)
+ # Only overwrite slug & ext if they aren't specified.
+ data["slug"] ||= slug
+ data["ext"] ||= ext
+ end
+
+ def modify_date(date)
+ if !data["date"] || data["date"].to_i == site.time.to_i
+ merge_data!({ "date" => date }, :source => "filename")
+ end
+ end
+
+ def generate_excerpt
+ data["excerpt"] ||= Jekyll::Excerpt.new(self) if generate_excerpt?
+ end
+ end
+end