class JekyllFeed::Generator

def collections

in the form of { collection_name => { categories = [...], path = "..." } }
Returns a hash representing all collections to be processed and their metadata
def collections
  return @collections if defined?(@collections)
  @collections = case config["collections"]
                 when Array
                   config["collections"].map { |c| [c, {}] }.to_h
                 when Hash
                   config["collections"]
                 else
                   {}
                 end
  @collections = normalize_posts_meta(@collections)
  @collections.each_value do |meta|
    meta["categories"] = (meta["categories"] || []).to_set
  end
  @collections
end

def config

Returns the plugin's config or an empty hash if not set
def config
  @config ||= @site.config["feed"] || {}
end

def disabled_in_development?

def disabled_in_development?
  config && config["disable_in_development"] && Jekyll.env == "development"
end

def feed_path(collection: "posts", category: nil)

Will return `/feed/collection/category.xml` for other collection categories
WIll return `/feed/collection.xml` for other collections
Will return `/feed/category.xml` for post categories
Will return "/feed.xml", or the config-specified default feed for posts

category - a category within that collection, e.g., "news"
collection - the name of a collection, e.g., "posts"

Determines the destination path of a given feed
def feed_path(collection: "posts", category: nil)
  prefix = collection == "posts" ? "/feed" : "/feed/#{collection}"
  return "#{prefix}/#{category}.xml" if category
  collections.dig(collection, "path") || "#{prefix}.xml"
end

def feed_source_path

Path to feed.xml template file
def feed_source_path
  @feed_source_path ||= File.expand_path "feed.xml", __dir__
end

def feed_template

def feed_template
  @feed_template ||= File.read(feed_source_path).gsub(MINIFY_REGEX, "")
end

def file_exists?(file_path)

Checks if a file already exists in the site source
def file_exists?(file_path)
  File.exist? @site.in_source_dir(file_path)
end

def generate(site)

Main plugin action, called by Jekyll-core
def generate(site)
  @site = site
  if disabled_in_development?
    Jekyll.logger.info "Jekyll Feed:", "Skipping feed generation in development"
    return
  end
  collections.each do |name, meta|
    Jekyll.logger.info "Jekyll Feed:", "Generating feed for #{name}"
    (meta["categories"] + [nil]).each do |category|
      path = feed_path(:collection => name, :category => category)
      next if file_exists?(path)
      @site.pages << make_page(path, :collection => name, :category => category)
    end
  end
  generate_feed_by_tag if config["tags"] && !@site.tags.empty?
end

def generate_feed_by_tag

def generate_feed_by_tag
  tags_config = config["tags"]
  tags_config = {} unless tags_config.is_a?(Hash)
  except    = tags_config["except"] || []
  only      = tags_config["only"] || @site.tags.keys
  tags_pool = only - except
  tags_path = tags_config["path"] || "/feed/by_tag/"
  generate_tag_feed(tags_pool, tags_path)
end

def generate_tag_feed(tags_pool, tags_path)

def generate_tag_feed(tags_pool, tags_path)
  tags_pool.each do |tag|
    # allow only tags with basic alphanumeric characters and underscore to keep
    # feed path simple.
    next if %r![^a-zA-Z0-9_]!.match?(tag)
    Jekyll.logger.info "Jekyll Feed:", "Generating feed for posts tagged #{tag}"
    path = "#{tags_path}#{tag}.xml"
    next if file_exists?(path)
    @site.pages << make_page(path, :tags => tag)
  end
end

def make_page(file_path, collection: "posts", category: nil, tags: nil)

def make_page(file_path, collection: "posts", category: nil, tags: nil)
  PageWithoutAFile.new(@site, __dir__, "", file_path).tap do |file|
    file.content = feed_template
    file.data.merge!(
      "layout"     => nil,
      "sitemap"    => false,
      "xsl"        => file_exists?("feed.xslt.xml"),
      "collection" => collection,
      "category"   => category,
      "tags"       => tags
    )
    file.output
  end
end

def normalize_posts_meta(hash)

compatability, can be configured via top-level keys or directly as a collection
Special case the "posts" collection, which, for ease of use and backwards
def normalize_posts_meta(hash)
  hash["posts"] ||= {}
  hash["posts"]["path"] ||= config["path"]
  hash["posts"]["categories"] ||= config["categories"]
  config["path"] ||= hash["posts"]["path"]
  hash
end