lib/battle_standard.ex

defmodule BattleStandard do
  @doc """
  Battle Standard will implement two helpers for dealing with bitfield flag arrays delivered as integers.

  Lets run through an example.

  Your receive an integer from an external API and you were expecting an array of boolean values.

  ## Examples

      iex> #some_response
      user = %{
        first_name: "Stevejones",
        last_name: "Benson,
        flags: 123
      }
      123


  But I expected:

      iex> #some_response
      user = %{
        first_name: "Stevejones",
        last_name: "Benson,
        flags: %{
        CROSSPOSTED: true,
        EPHEMERAL: true,
        HAS_THREAD: true,
        IS_CROSSPOST: true,
        LOADING: false,
        SOURCE_MESSAGE_DELETED: true,
        SUPPRESS_EMBEDS: false,
        URGENT: true
        }
      }


  Use this package and set a module attribute describing your field. The example uses the discord message flags from [Here](https://discord.com/developers/docs/resources/channel#message-object-message-flags)

      iex> use BattleStandard

      @flag_bits [
        {:CROSSPOSTED, 1 <<< 0},
        {:IS_CROSSPOST, 1 <<< 1},
        {:SUPPRESS_EMBEDS, 1 <<< 2},
        {:SOURCE_MESSAGE_DELETED, 1 <<< 3},
        {:URGENT, 1 <<< 4},
        {:HAS_THREAD, 1 <<< 5},
        {:EPHEMERAL, 1 <<< 6},
        {:LOADING, 1 <<< 7}
      ]


  You can use the two callbacks from this this package that have been automatically inserted into your module. Put them into wherever you want, be it a changeset, or some other generic casting operation.

  ## Examples

      iex> MessageFlags.from_integer(123)
      %{
        CROSSPOSTED: true,
        EPHEMERAL: true,
        HAS_THREAD: true,
        IS_CROSSPOST: true,
        LOADING: false,
        SOURCE_MESSAGE_DELETED: true,
        SUPPRESS_EMBEDS: false,
        URGENT: true
      }

  """
  defmacro __using__(_opts) do
    quote do
      alias BattleStandard
      use Bitwise, only_operators: true

      @before_compile BattleStandard
    end
  end

  defmacro __before_compile__(_env) do
    quote do
      def from_integer(flag_value) do
        for {flag, value} <- @flag_bits, into: %{} do
          {flag, (flag_value &&& value) == value}
        end
      end

      def to_integer(flag_struct) do
        booleans =
          flag_struct
          |> Map.from_struct()
          |> Map.to_list()

        Enum.reduce(booleans, 0, fn {flag, enabled}, flag_value ->
          case enabled do
            true -> flag_value ||| @flag_bits[flag]
            false -> flag_value
          end
        end)
      end
    end
  end
end