Packages
quantum
1.0.0
3.5.3
3.5.2
retired
3.5.1
retired
3.5.0
3.4.0
3.3.0
3.2.0
3.1.0
3.0.2
3.0.1
3.0.0
3.0.0-rc.3
3.0.0-rc.2
3.0.0-rc.1
2.4.0
2.3.4
2.3.3
2.3.2
2.3.1
retired
2.3.0
retired
2.2.7
2.2.6
2.2.5
retired
2.2.4
retired
2.2.3
retired
2.2.2
retired
2.2.1
retired
2.2.0
retired
2.1.3
retired
2.1.2
retired
2.1.1
retired
2.1.0
retired
2.1.0-beta.1
retired
2.0.4
retired
2.0.3
retired
2.0.2
retired
2.0.1
retired
2.0.0
retired
2.0.0-beta.2
retired
2.0.0-beta.1
retired
1.9.3
retired
1.9.2
retired
1.9.1
retired
1.9.0
retired
1.8.1
retired
1.8.0
retired
1.7.1
retired
1.7.0
retired
1.6.1
retired
1.6.0
retired
1.5.0
retired
1.4.0
retired
1.3.2
retired
1.3.1
retired
1.3.0
retired
1.2.4
retired
1.2.3
retired
1.2.2
retired
1.2.1
retired
1.2.0
retired
1.1.0
retired
1.0.4
retired
1.0.3
retired
1.0.2
retired
1.0.1
retired
1.0.0
retired
Cron-like job scheduler for Elixir.
Retired package: Security issue - v1 is no longer supported
Current section
Files
Jump to
Current section
Files
lib/quantum.ex
defmodule Quantum do
use GenServer
import Process, only: [send_after: 3]
@days ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]
@months ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]
def start_link(options \\ []) do
GenServer.start_link(__MODULE__, %{}, [name: __MODULE__] ++ options)
end
# Public functions ------------------------------------------------------------------------------
def cron("@yearly", fun), do: GenServer.cast(__MODULE__, {"0 0 1 1 *", fun})
def cron("@monthly", fun), do: GenServer.cast(__MODULE__, {"0 0 1 * *", fun})
def cron("@weekly", fun), do: GenServer.cast(__MODULE__, {"0 0 * * 0", fun})
def cron("@daily", fun), do: GenServer.cast(__MODULE__, {"0 0 * * *", fun})
def cron("@hourly", fun), do: GenServer.cast(__MODULE__, {"0 * * * *", fun})
def cron(expression, fun), do: GenServer.cast(__MODULE__, {expression, fun})
def reset, do: GenServer.cast(__MODULE__, :reset)
# Private functions -----------------------------------------------------------------------------
def init(_) do
send_after(self, :tick, 1000)
{:ok, %{jobs: [], d: nil, h: nil, m: nil, w: nil}}
end
def handle_info(:tick, state) do
send_after(self, :tick, 1000)
{d, {h, m, _}} = :calendar.now_to_universal_time(:os.timestamp)
if state.d != d do
state = %{state | w: rem(:calendar.day_of_the_week(d), 7)}
end
if state.m != m do
state = %{state | d: d, h: h, m: m}
Enum.each(state.jobs, fn({e, fun}) -> Task.start(__MODULE__, :execute, [e, fun, state]) end)
end
{:noreply, state}
end
def handle_info(_, state), do: {:noreply, state}
def handle_cast(:reset, state) do
{:noreply, %{state | jobs: []}}
end
def handle_cast({ e, fun }, state) do
{:noreply, %{state | jobs: ["#{e |> String.downcase |> translate}": fun] ++ state.jobs}}
end
def execute("* * * * *", fun, _), do: fun.()
def execute("0 * * * *", fun, %{m: 0}), do: fun.()
def execute("0 0 * * *", fun, %{m: 0, h: 0}), do: fun.()
def execute("0 0 1 * *", fun, %{m: 0, h: 0, d: {_, _, 1}}), do: fun.()
def execute("0 0 1 1 *", fun, %{m: 0, h: 0, d: {_, 1, 1}}), do: fun.()
def execute(e, fun, state) do
[m, h, d, n, w] = e |> String.split(" ")
{_, cur_mon, cur_day} = state.d
cond do
!match(m, state.m, 0, 59) -> false
!match(h, state.h, 0, 59) -> false
!match(d, cur_day, 1, 31) -> false
!match(n, cur_mon, 1, 12) -> false
!match(w, state.w, 0, 6) -> false
true -> fun.()
end
end
defp translate(e) do
{e,_} = List.foldl(@days, {e,0}, fn(x, acc) -> translate(acc, x) end)
{e,_} = List.foldl(@months, {e,1}, fn(x, acc) -> translate(acc, x) end)
e
end
defp translate({e, i}, term), do: {String.replace(e, term, "#{i}"), i+1}
defp match("*", _, _, _), do: true
defp match([], _, _, _), do: false
defp match([e|t], v, min, max), do: Enum.any?(parse(e, min, max), &(&1 == v)) or match(t, v, min, max)
defp match(e, v, min, max), do: match(e |> String.split(","), v, min, max)
defp parse("*/" <> _ = e, min, max) do
[_,i] = e |> String.split("/")
{x,_} = i |> Integer.parse
Enum.reject(min..max, &(rem(&1, x) != 0))
end
defp parse(e, min, max) do
[r|i] = e |> String.split("/")
[x|y] = r |> String.split("-")
{v,_} = x |> Integer.parse
parse(v, y, i, min, max) |> Enum.reject(&((&1 < min) or (&1 > max)))
end
defp parse(v, [], [], _, _), do: [v]
defp parse(v, [], i, _, _) do
{x,_} = i |> Integer.parse
[rem(v,i)]
end
defp parse(v, y, [], min, max) do
{t,_} = y |> Integer.parse
if v < t do
Enum.to_list(v..t)
else
Enum.to_list(v..max) ++ Enum.to_list(min..t)
end
end
defp parse(v, y, i, min, max) do
{x, _} = i |> Integer.parse
parse(v, y, [], min, max) |> Enum.reject(&(rem(&1, x) != 0))
end
end