Packages

Jq like thing for grabbing stuff out of json like objects

Current section

Files

Jump to
jqish lib jqish.ex
Raw

lib/jqish.ex

defmodule Jqish do
@moduledoc """
Documentation for Jqish.
"""
defmodule SyntaxError do
defstruct [:token, before: nil]
end
defmodule UnexpectedValueError do
defstruct [:target, :path]
end
defmodule TypeError do
defstruct [:target, :reason]
end
defp lex(rule) do
rule
|> to_charlist
|> :jqish_lexer.string
|> case do
{:ok, tokens, _} ->
{:ok, tokens}
{:error, {_, _, {_, token}}, _} ->
{:error, %SyntaxError{token: :erlang.list_to_binary(token)}}
end
end
def parse(rule) do
with {:ok, tokens} <- lex(rule) do
case :jqish_parser.parse(tokens) do
{:ok, _} = ok ->
ok
{:error, {_, :jqish_parser, ['syntax error before: ', before]}} ->
{:error, %SyntaxError{before: :erlang.list_to_binary(before)}}
end
end
end
defp e({:error, _} = err, _), do: err
defp e(t, {:compose, left, right}) do
case e(t, left) do
{:error, _} = e -> e
res ->
e(res, right)
end
end
defp e(t, :.), do: t
defp e(t, {:function, "map", subexpr}) when is_list(t) do
result = Enum.reduce_while(t, [], fn subvalue, acc ->
case e(subvalue, subexpr) do
{:error, _} = err -> {:halt, err}
:void -> {:cont, acc}
ok -> {:cont, [ok | acc]}
end
end)
with reversed when is_list(reversed) <- result do
Enum.reverse(reversed)
end
end
defp e(t, {:function, "map", _subexpr}) do
{:error, %TypeError{target: t, reason: "Cannot map over a non-list value"}}
end
defp e(t, {:function, "select", subexpr}) do
case e(t, subexpr) do
true -> t
false -> :void
end
end
defp e(t, {:operator, op, selector, lhs}) do
rhs = e(t, selector)
case op do
"==" -> rhs == lhs
">=" -> rhs >= lhs
"<=" -> rhs <= lhs
"!=" -> rhs != lhs
">" -> rhs > lhs
"<" -> rhs < lhs
end
end
defp e(t, {:pick, :iterator, rest}) when is_list(t) do
Enum.map(t, fn el -> e(el, rest) end)
end
defp e(t, {:pick, :iterator, _rest}) do
{:error, %TypeError{target: t, reason: "Cannot iterate over a non-list"}}
end
defp e(t, {:pick, tok, {:function, _, _} = f}), do: pick(e(t, f), tok)
defp e(t, {:pick, tok, rest}), do: e(pick(t, tok), rest)
defp e(t, nil), do: t
defp pick(t, nil), do: t
defp pick(t, {:optional, key}) when is_map(t), do: Map.get(t, key)
defp pick(_t, {:optional, _key}), do: nil
defp pick(t, {:slice, from, to}) when is_list(t), do: Enum.slice(t, from, to)
defp pick(t, index) when is_list(t) and is_integer(index), do: Enum.at(t, index)
defp pick(t, key) when is_map(t), do: Map.get(t, key)
defp pick(target, path) do
{:error, %UnexpectedValueError{target: target, path: path}}
end
def eval(target, expr) do
case e(target, expr) do
{:error, _} = err -> err
good -> {:ok, good}
end
end
@doc """
Run a rule on a target.
## Examples
iex> Jqish.run(%{"foo" => %{"bar" => [1, 2, 3, 4]}}, ".foo.bar.[2]")
{:ok, 3}
"""
def run(target, rule) do
with {:ok, expr} <- parse(rule) do
eval(target, expr)
end
end
end