lib/liquex/tag/increment_tag.ex

defmodule Liquex.Tag.IncrementTag do
  @moduledoc """

  ## increment

  Creates and outputs a new number variable with initial value 0. On subsequent
  calls, it increases its value by one and outputs the new value.

  ### Input

      {% increment my_counter %}
      {% increment my_counter %}
      {% increment my_counter %}

  ### Output

    0
    1
    2

  Variables created using increment are independent from variables created using
  assign or capture.

  In the example below, a variable named “var” is created using assign. The
  increment tag is then used several times on a variable with the same name.
  Note that the increment tag does not affect the value of “var” that was
  created using assign.

  ### Input

      {% assign var = 10 %}
      {% increment var %}
      {% increment var %}
      {% increment var %}
      {{ var }}

  ### Output

      0
      1
      2
      10

  ## decrement

  Creates and outputs a new number variable with initial value -1. On subsequent
  calls, it decreases its value by one and outputs the new value.

  ### Input

      {% decrement variable %}
      {% decrement variable %}
      {% decrement variable %}

  ### Output

      -1
      -2
      -3

  Like increment, variables declared using decrement are independent from
  variables created using assign or capture.
  """

  @behaviour Liquex.Tag
  import NimbleParsec

  alias Liquex.Context
  alias Liquex.Indifferent

  alias Liquex.Parser.Field
  alias Liquex.Parser.Literal
  alias Liquex.Parser.Tag

  def parse do
    # Replace as {default, increment}
    increment = replace(string("increment"), {0, 1})
    decrement = replace(string("decrement"), {-1, -1})

    ignore(Tag.open_tag())
    |> unwrap_and_tag(choice([increment, decrement]), :by)
    |> ignore(Literal.whitespace(empty(), 1))
    |> optional(unwrap_and_tag(Field.identifier(), :identifier))
    |> ignore(Tag.close_tag())
    |> post_traverse({__MODULE__, :reverse_tags, []})
  end

  def parse_liquid_tag do
    # Replace as {default, increment}
    increment = replace(string("increment"), {0, 1})
    decrement = replace(string("decrement"), {-1, -1})

    choice([increment, decrement])
    |> unwrap_and_tag(:by)
    |> ignore(Literal.non_breaking_whitespace(empty(), 0))
    |> optional(unwrap_and_tag(Field.identifier(), :identifier))
    |> ignore(Tag.end_liquid_line())
    |> post_traverse({__MODULE__, :reverse_tags, []})
  end

  def reverse_tags(_rest, args, context, _line, _offset),
    do: {args |> Enum.reverse(), context}

  def render(
        [identifier: identifier, by: {default, increment}],
        %Context{environment: environment} = context
      ) do
    {value, environment} =
      Indifferent.get_and_update(
        environment,
        identifier,
        fn
          nil -> {default, default + increment}
          v -> {v, v + increment}
        end
      )

    {[Integer.to_string(value)], %{context | environment: environment}}
  end

  # Handle default identifier (nil)
  def render([by: increment], context), do: render([identifier: nil, by: increment], context)
end