# Brimstone Conditional
<!-- INTRODUCTION START -->
Evaluate conditions defined in a logical structure.
This module allows to evaluate complex tree conditional structures and digest
them to get a final output. The main function is `evaluate/2` which will
digest the struct and return the values with the conditional switches
resolved.
The struct itself can be converted to a string to be stored, using
`:erlang.term_to_binary/1` and `Base.encode64/1` under the hood. The struct
can be retrieved later using `from_string/1`, which performs the mirror
operation. These strings include a version at the begining to acomodate the
possibility of altering this struct in the future and allowing migrations
from previous stringified conditionals.
<!-- INTRODUCTION END -->
<!-- USAGE START -->
The struct uses recursion to evaluate its parameters. A
plain boolean will return itself, a list will be assumed
to be an `and` structure, and a map or keyword list will
traverse itself as a list of key/value tuples, using the
key as the operation and the value as parameters.
Known map operators are the logic gates `and`, `or`, `xor`, `not`, `nor` and
`xnor`, the comparison operators `eq`, `neq`, `gt`, `ge`, `lt` and `le`, the
check operators `in` and `match`, the disambiguator `cond`, the scape hatch
`fn`, and the utility operators `var`, `count`, `cat`, `interpolate`, `each`
and `sum`.
The comparison operators assume that the first element is the topic that we
are comparing, and any other element is what are we comparing it to,
therefore is possible to ask if something is greather that "this thing" and
also to "that other thing" in the same step, or if "this thing" if different
to "this other thing" and also to "that other thing".
The check operator `match` will check if the string provided as the first
element of the argument list matches all regex and strings provided as the
rest of arguments. Strings will be compiled to regex, and other data types
will try to convert themselves to a string, then surround themselves with the
regex operators `^` and `$` and compile the resulting string to a regex, thus
if you try to match "22" and "2" this will return true, as "2" is a string
contained in "22", but if you try to match "22" and 2 it will return false,
as the integer 2 will become the regex "^2$". This is intentional. If you
require to pass aditional options to the regex you wish to compile, you may
do so with a tuple of size two where each element correspond to the arguments
of `Regex.compile!/2`.
The check operator `in` will test membership of elements on the first element
provided. It works on Lists, Strings and Maps. Maps are a special case, as it
will check if the specified map arguments are a subset of the topic map.
The disambiguator operator `cond` will operate exactly as an elixir `cond`.
It expects to receive a list of tuples, where each tuple is a pair with a
condition that will be evaluated with this very same function, and a value.
It will substitute itself with the first element of this list that return
`true` after checking its condition.
The utility operators will perform common basic tasks, usually on the state
provided (an empty map as default) to fetch data. All these tasks could be
handled as functions, but they are so common that including them make the
struct way more usable.
`var` will perform a `Map.get/2` using the provided atom or string as key.
`each` will turn itself into a list containing all values of the state map
which had a key begining with the atom or string provided, followed with an
index inside brackets (like the accessor syntax).
`count` will return the size of the provided list.
`sum` will asume the provided list contains numbers and will add them up.
`cat` will try to catenate the elements of the provided argument list
following a DWIM approach. To do so it will run a reduce function on the list
and will take into account the data types of both the accumulator that is
being built and the element we are trying to add. I.E: catenating two
strings, two lists or two tuples together will simply join them, catenating a
keyword list to a map will add the keywords to the map, but catenating a map
to a list (keyword or not) will simply append the map as the last element of
the list, and so on. If there is not a known approach to what we are trying
to catenate, this operator will try to cast both elements to string before
joining them.
`interpolate` expects a list where the first element is a string and the rest
are interpolation arguments. Interpolation arguments may be key-value pairs
or simple values. The string will be reduced searching for each element in
the argument list following gettext conventions. If the pattern `%{keyname}`
is fount, it will be replaced by `value`. For values that do not have a key,
the reduce function will match the ordinal (starting at one) (I.E: `%{1}`,
`%{2}`, etc.) of a list composed of all arguments that are not key-value
pairs.
Any other operator that receives a list as a parameter will be handled as an
`and` operation of the result of applying the specfied operator to each
element in the list, with the exception of the scape hatch `fn`, which will
assume that the first element of the provided list is a function and will
apply using the rest of the list as arguments. The `fn` operator also can
acceps a different syntax using tuples, where you may specify `{module,
function_name, arg_list}` or `{function, arg_list}`. In any case, it will
check the arity of the relevant function. If the arity is equal to the
arguments provided it will call it only with the provided arguments, and
prepend the entire state to the argument list otherwise. If the first element
of the provided list is a string, this operator will wrap it around the `fn
... end` syntax for anonymous functions, enabling you to pass a string
encoded function begining with `arg1, arg2, ..., argn -> `, or simply `-> `
if the function does not have arguments. Please be very careful with this
operator, as it may execute arbitrary code in the context of the erlang VM.
<!-- USAGE END -->
## Installation
It is [available in Hex](https://hexdocs.pm/brimstone_conditional), and the
package can be installed by adding `brimstone_conditional` to your list of
dependencies in `mix.exs`:
```elixir
def deps do
[
{:brimstone_conditional, "~> 0.1.4"}
]
end
```
The docs can be found at <https://hexdocs.pm/brimstone_conditional>.