lib/jekyll-archives-v2.rb



# frozen_string_literal: true

require "jekyll"

module Jekyll
  module ArchivesV2
    # Internal requires
    autoload :Archive,  "jekyll-archives-v2/archive"
    autoload :PageDrop, "jekyll-archives-v2/page_drop"
    autoload :VERSION,  "jekyll-archives-v2/version"

    class Archives < Jekyll::Generator
      safe true

      def initialize(config = {})
        defaults = {}
        config.fetch("collections", {}).each do |name, collection|
          defaults[name] = {
            "layout"     => "archive",
            "enabled"    => [],
            "permalinks" => {
              "year"     => "/#{name}/:year/",
              "month"    => "/#{name}/:year/:month/",
              "day"      => "/#{name}/:year/:month/:day/",
              "tag"      => "/#{name}/tag/:name/",
              "category" => "/#{name}/category/:name/",
            },
          }
        end
        defaults.freeze
        archives_config = config.fetch("jekyll-archives", {})
        if archives_config.is_a?(Hash)
          @config = Utils.deep_merge_hashes(defaults, archives_config)
        else
          @config = nil
          Jekyll.logger.warn "Archives:", "Expected a hash but got #{archives_config.inspect}"
          Jekyll.logger.warn "", "Archives will not be generated for this site."
        end
      end

      def generate(site)
        return if @config.nil?

        @site = site
        @collections = site.collections
        @archives = []

        @site.config["jekyll-archives"] = @config

        # loop through collections keys and read them
        @config.each do |collection_name, collection_config|
          read(collection_name)
        end

        @site.pages.concat(@archives)
        @site.config["archives"] = @archives
      end

      # Read archive data from collection
      def read(collection_name)
        if enabled?(collection_name, "tags")
          read_tags(collection_name)
        end

        if enabled?(collection_name, "categories")
          read_categories(collection_name)
        end

        if enabled?(collection_name, "year") || enabled?(collection_name, "month") || enabled?(collection_name, "day")
          read_dates(collection_name)
        end
      end

      def read_tags(collection_name)
        tags(@collections[collection_name]).each do |title, documents|
          @archives << Archive.new(@site, title, "tag", collection_name, documents)
        end
      end

      def read_categories(collection_name)
        categories(@collections[collection_name]).each do |title, documents|
          @archives << Archive.new(@site, title, "category", collection_name, documents)
        end
      end

      def read_dates(collection_name)
        years(@collections[collection_name]).each do |year, y_documents|
          append_enabled_date_type({ :year => year }, "year", collection_name, y_documents)
          months(y_documents).each do |month, m_documents|
            append_enabled_date_type({ :year => year, :month => month }, "month", collection_name, m_documents)
            days(m_documents).each do |day, d_documents|
              append_enabled_date_type({ :year => year, :month => month, :day => day }, "day", collection_name, d_documents)
            end
          end
        end
      end

      # Checks if archive type is enabled in config
      def enabled?(collection_name, archive)
        @config[collection_name]["enabled"] == true || @config[collection_name]["enabled"] == "all" || (@config[collection_name]["enabled"].is_a?(Array) && @config[collection_name]["enabled"].include?(archive))
      end

      def tags(documents)
        doc_attr_hash(documents, "tags")
      end

      def categories(documents)
        doc_attr_hash(documents, "categories")
      end

      # Custom `post_attr_hash` method for years
      def years(documents)
        date_attr_hash(documents, "%Y")
      end

      # Custom `post_attr_hash` method for months
      def months(year_documents)
        date_attr_hash(year_documents, "%m")
      end

      # Custom `post_attr_hash` method for days
      def days(month_documents)
        date_attr_hash(month_documents, "%d")
      end

      private

      # Initialize a new Archive page and append to base array if the associated date `type`
      # has been enabled by configuration.
      #
      # meta            - A Hash of the year / month / day as applicable for date.
      # type            - The type of date archive.
      # collection_name - the name of the collection
      # documents       - The array of documents that belong in the date archive.
      def append_enabled_date_type(meta, type, collection_name, documents)
        @archives << Archive.new(@site, meta, type, collection_name, documents) if enabled?(collection_name, type)
      end

      # Custom `post_attr_hash` for date type archives.
      #
      # documents - Array of documents to be considered for archiving.
      # id        - String used to format post date via `Time.strptime` e.g. %Y, %m, etc.
      def date_attr_hash(documents, id)
        hash = Hash.new { |hsh, key| hsh[key] = [] }
        documents.each { |document| hash[document.date.strftime(id)] << document }
        hash.each_value { |documents| documents.sort!.reverse! }
        hash
      end

      # Custom `post_attr_hash` for any collection.
      #
      # documents - Array of documents to be considered for archiving.
      # doc_attr  - The String name of the Document attribute.
      def doc_attr_hash(documents, doc_attr)
        # Build a hash map based on the specified document attribute ( doc_attr =>
        # array of elements from collection ) then sort each array in reverse order.
        hash = Hash.new { |h, key| h[key] = [] }
        documents.docs.each { |document| document.data[doc_attr]&.each { |t| hash[t] << document } }
        hash.each_value { |documents| documents.sort!.reverse! }
        hash
      end
    end
  end
end