Packages
expression
2.45.0
2.49.1
2.49.0
2.48.0
2.47.5
2.47.4
2.47.3
2.47.2
2.47.1
2.47.0
2.46.0
2.45.1
2.45.0
2.44.1
2.44.0
2.43.0
2.42.1
2.41.4
2.41.3
2.41.2
2.41.0
2.40.0
2.38.0
2.37.0
2.36.2
2.36.1
2.36.0
2.35.0
2.34.0
2.33.1
2.33.0
2.32.1
2.31.6
2.31.3
2.31.0
2.30.0
2.28.0
2.27.3
2.27.2
2.27.1
2.27.0
2.26.1
2.26.0
2.25.0
2.24.2
2.24.1
2.24.0
2.23.9
2.23.8
2.23.7
2.23.6
2.23.4
2.23.3
2.23.2
2.23.1
2.23.0
2.22.2
2.22.0
2.21.0
2.20.0
2.19.0
2.18.0
2.17.0
2.16.0
2.14.0
2.13.0
2.12.0
2.11.0
2.10.1
2.10.0
2.9.0
2.8.0
2.6.0
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.4.1
2.4.0
2.3.2
2.3.1
2.3.0
2.2.1
2.2.0
2.1.1
2.1.0
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.1.1
1.1.0
1.0.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.0
0.4.3
0.4.2
0.4.1
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.1.0
A Excel like expression parser, compatible with FLOIP Expression language.
Current section
Files
Jump to
Current section
Files
lib/expression/context.ex
defmodule Expression.Context do
@moduledoc """
A helper module for creating a context that can be
used with Expression.Eval
# Example
iex> Expression.Context.new(%{foo: "bar"})
%{"foo" => "bar"}
iex> Expression.Context.new(%{FOO: "bar"})
%{"foo" => "bar"}
iex> Expression.Context.new(%{foo: %{bar: "baz"}})
%{"foo" => %{"bar" => "baz"}}
iex> Expression.Context.new(%{Foo: %{Bar: "baz"}})
%{"foo" => %{"bar" => "baz"}}
iex> Expression.Context.new(%{foo: %{bar: 1}})
%{"foo" => %{"bar" => 1}}
iex> Expression.Context.new(%{date: "2020-12-13T23:34:45"})
%{"date" => ~U[2020-12-13 23:34:45.0Z]}
iex> Expression.Context.new(%{boolean: "true"})
%{"boolean" => true}
iex> Expression.Context.new(%{float: 1.234})
%{"float" => 1.234}
iex> now = DateTime.utc_now()
iex> ctx = Expression.Context.new(%{float: "1.234", nested: %{date: now}})
iex> ctx["float"]
1.234
iex> now == ctx["nested"]["date"]
true
iex> Expression.Context.new(%{mixed: ["2020-12-13T23:34:45", 1, "true", "binary"]})
%{"mixed" => [~U[2020-12-13 23:34:45.0Z], 1, true, "binary"]}
"""
@type t :: map
@spec new(map, Keyword.t() | nil) :: t
def new(ctx, opts \\ []) when is_map(ctx) do
ctx
# Ensure all keys are lower case strings
|> Enum.map(&downcase_string_key/1)
|> Enum.map(&iterate(&1, opts))
|> Enum.into(%{})
end
defp downcase_string_key({key, value}), do: {String.downcase(to_string(key)), value}
defp iterate({key, value}, opts) when is_map(value) or is_list(value) do
{key, evaluate!(value, opts)}
end
# Implictly convert the string "0" as a number
defp iterate({key, "0"}, _opts), do: {key, 0}
defp iterate({key, value}, opts) when is_binary(value) do
cond do
# Prevent implicitly converting numbers starting with a zero
# Only allows strings fully made of digits or decimals
String.starts_with?(value, "0") and String.match?(value, ~r/^\d+(\.\d+)?$/) -> {key, value}
Keyword.get(opts, :skip_context_evaluation?, false) -> {key, value}
true -> {key, evaluate!(value, opts)}
end
end
defp iterate({key, value}, _), do: {key, value}
defp evaluate!(ctx, opts) when is_map(ctx) and not is_struct(ctx) do
new(ctx, opts)
end
defp evaluate!(ctx, opts) when is_list(ctx) do
Enum.map(ctx, &evaluate!(&1, opts))
end
defp evaluate!(binary, _) when is_binary(binary) do
case Expression.Parser.literal(binary) do
{:ok, [{:literal, literal}], "", _, _, _} -> literal
# when we're not parsing the full literal
{:ok, [{:literal, _literal}], _, _, _, _} -> binary
# when we're getting something entirely unexpected
{:error, _reason, _, _, _, _} -> binary
end
rescue
ArgumentError -> binary
end
defp evaluate!(value, _), do: value
end