# frozen_string_literal: true module Jekyll module Filters module Arrays # Returns one or several random items from an Array. # # @example # {{ site.posts | sample }} # {{ site.posts | sample: 3 }} # @param input [Array] # @param amount [Integer] # @return [Any] def sample(input, amount = 1) input = [] unless array? input input.sample(amount) end # Join arrays or append item to array # # @example # {% assign things = site.posts | where: "layout", "thing" %} # {% assign being = site.posts | find: "layout", "being" %} # {% assign dread = things | concat: being %} # # @param input [Array] # @param concatenable [Array,any] def join(input, concatenable) input = [] unless array? input if concatenable.is_a? Array input + concatenable else input.dup << concatenable end end # Finds the next posts in a collection, starting from current # post and restarts from the beginning to always return posts. # # @example # {{ page | infinite_next: site.posts }} # @param input [Jekyll::Document,Drop] The current post (page) # @param posts [Array] An array of posts (ie. results from where filter) # @param amount [Integer] Amount of posts next to current, max is posts size # @return [Array] Array of posts def infinite_next(input, posts, amount = 1) posts = [] unless array? posts liquid_input = input.to_liquid liquid_posts = posts.map(&:to_liquid) index = find_in_stack(liquid_input, liquid_posts) liquid_posts.rotate(index).slice(1, amount) if index end # Finds the previous posts in a collection, starting from current # post and restarts from the beginning to always return posts. # # @example # {{ page | infinite_next: site.posts }} # @param input [Jekyll::Document,Drop] The current post (page) # @param posts [Array] An array of posts (ie. results from where filter) # @param amount [Integer] Amount of posts previous to current, max is posts size # @return [Array] Array of posts def infinite_prev(input, posts, amount = 1) posts = [] unless array? posts liquid_input = input.to_liquid liquid_posts = posts.map(&:to_liquid) index = find_in_stack(liquid_input, liquid_posts) liquid_posts.rotate(index).reverse.slice(0, amount).reverse if index end # Return next post from an array or nothing if post is last. # # @example # {{ page | next: site.posts }} # @param input [Jekyll::Document,Drop] # @param posts [Array] # @return [Drop,nil] def next(input, posts) posts = [] unless array? posts liquid_input = input.to_liquid liquid_posts = posts.map(&:to_liquid) index = find_in_stack(liquid_input, liquid_posts) liquid_posts[index + 1] if index end # Return previous post from an array, or nothing if post is the # first item. # # @example # {{ page | prev: site.posts }} # {{ page | previous: site.posts }} # @param input [Jekyll::Document,Drop] # @param array [Array] # @return [Drop,nil] def prev(input, posts) posts = [] unless array? posts liquid_input = input.to_liquid liquid_posts = posts.map(&:to_liquid) index = find_in_stack(liquid_input, liquid_posts) liquid_posts[index - 1] if index&.positive? end alias previous prev private # Detects if param is an array or raise an error if strict filters # is enabled # # @param posts [Array] # @return [Boolean] def array?(posts) posts.is_a?(Array).tap do |a| next if a next unless @context.strict_filters raise Liquid::ArgumentError, 'needs an array argument' end end # Find post in posts, raise an error if strict_filters is enabled # # @param input [Drop] # @param posts [Array] array of drops # @return [Integer] def find_in_stack(input, posts) posts.index(input).tap do |n| next if n next unless @context.strict_filters title = input['title'] || input['id'] || 'no title' titles = posts.map do |p| p['title'] || p['id'] || 'no title' end raise Liquid::ArgumentError, "can't find #{title} in #{titles.join(', ')}" end end end end end Liquid::Template.register_filter(Jekyll::Filters::Arrays)