Skip to content

Commit

Permalink
Support @reboot in parser & composer. Raise Error in Scheduler & Date…
Browse files Browse the repository at this point in the history
…Checker. (#30)
  • Loading branch information
maennchen authored Jan 20, 2017
1 parent 6ab926d commit ff08c66
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 22 deletions.
4 changes: 3 additions & 1 deletion lib/crontab/cron_expression.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule Crontab.CronExpression do

@type t :: %Crontab.CronExpression{
extended: boolean,
reboot: boolean,
second: value,
minute: value,
hour: value,
Expand Down Expand Up @@ -44,7 +45,8 @@ defmodule Crontab.CronExpression do
The :extended attribute defines if the second is taken into account.
"""
defstruct extended: false, second: [:*], minute: [:*], hour: [:*], day: [:*], month: [:*], weekday: [:*], year: [:*]
defstruct extended: false, reboot: false, second: [:*], minute: [:*], hour: [:*], day: [:*], month: [:*],
weekday: [:*], year: [:*]

@doc """
Create a `%Crontab.CronExpression{}` via sigil.
Expand Down
7 changes: 7 additions & 0 deletions lib/crontab/cron_expression/composer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,21 @@ defmodule Crontab.CronExpression.Composer do
iex> Crontab.CronExpression.Composer.compose %Crontab.CronExpression{minute: [9, {:-, 4, 6}, {:/, :*, 9}]}
"9,4-6,*/9 * * * * *"
iex> Crontab.CronExpression.Composer.compose %Crontab.CronExpression{reboot: true}
"@reboot"
"""
@spec compose(CronExpression.t) :: binary
def compose(%CronExpression{reboot: true}) do
"@reboot"
end
def compose(cron_expression = %CronExpression{}) do
cron_expression
|> CronExpression.to_condition_list
|> compose_interval
|> Enum.join(" ")
end

@spec compose_interval(CronExpression.condition_list) :: [binary]
defp compose_interval([{_, conditions} | tail]) do
part = conditions
Expand Down
4 changes: 2 additions & 2 deletions lib/crontab/cron_expression/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule Crontab.CronExpression.Parser do
@type result :: {:ok, CronExpression.t} | {:error, binary}

@specials %{
reboot: %CronExpression{reboot: true},
yearly: %CronExpression{minute: [0], hour: [0], day: [1], month: [1]},
annually: %CronExpression{minute: [0], hour: [0], day: [1], month: [1]},
monthly: %CronExpression{minute: [0], hour: [0], day: [1]},
Expand Down Expand Up @@ -77,7 +78,7 @@ defmodule Crontab.CronExpression.Parser do
@spec parse(binary, boolean) :: result
def parse(cron_expression, extended \\ false)
def parse("@" <> identifier, _) do
special(String.to_atom(identifier))
special(String.to_atom(String.downcase(identifier)))
end
def parse(cron_expression, true) do
interpret(String.split(cron_expression, " "), @extended_intervals, %CronExpression{extended: true})
Expand Down Expand Up @@ -235,7 +236,6 @@ defmodule Crontab.CronExpression.Parser do
end

@spec special(atom) :: result
defp special(:reboot), do: {:error, "Special identifier @reboot is not supported."}
defp special(identifier) do
if Map.has_key?(@specials, identifier) do
{:ok, Map.fetch!(@specials, identifier)}
Expand Down
24 changes: 11 additions & 13 deletions lib/crontab/date_checker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,30 @@ defmodule Crontab.DateChecker do
alias Crontab.CronExpression

@doc """
Check a CronExpression against a given date.
Check a condition list against a given date.
### Examples
iex> Crontab.DateChecker.matches_date? %CronExpression{minute: [{:"/", :*, 8}]}, ~N[2004-04-16 04:08:08]
iex> Crontab.DateChecker.matches_date? %Crontab.CronExpression{minute: [{:"/", :*, 8}]}, ~N[2004-04-16 04:08:08]
true
iex> Crontab.DateChecker.matches_date? %CronExpression{minute: [{:"/", :*, 9}]}, ~N[2004-04-16 04:07:08]
iex> Crontab.DateChecker.matches_date? %Crontab.CronExpression{minute: [{:"/", :*, 9}]}, ~N[2004-04-16 04:07:08]
false
iex> Crontab.DateChecker.matches_date? %Crontab.CronExpression{reboot: true}, ~N[2004-04-16 04:07:08]
** (RuntimeError) Special identifier @reboot is not supported.
iex> Crontab.DateChecker.matches_date? [{:hour, [{:"/", :*, 4}, 7]}], ~N[2004-04-16 04:07:08]
true
"""
@spec matches_date?(CronExpression.t, NaiveDateTime.t) :: boolean
@spec matches_date?(CronExpression.t, NaiveDateTime.t) :: boolean | no_return
def matches_date?(%CronExpression{reboot: true}, _), do: raise "Special identifier @reboot is not supported."
def matches_date?(cron_expression = %CronExpression{}, execution_date) do
cron_expression
|> CronExpression.to_condition_list
|> matches_date?(execution_date)
end

@doc """
Check a condition list against a given date.
### Examples
iex> Crontab.DateChecker.matches_date? [{:hour, [{:"/", :*, 4}, 7]}], ~N[2004-04-16 04:07:08]
true
"""
@spec matches_date?(CronExpression.condition_list, NaiveDateTime.t) :: boolean
def matches_date?(condition_list, date)
def matches_date?([], _), do: true
Expand Down
34 changes: 30 additions & 4 deletions lib/crontab/scheduler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ defmodule Crontab.Scheduler do
iex> Crontab.Scheduler.get_next_run_date(%Crontab.CronExpression{year: [{:/, :*, 9}]}, ~N[2002-01-13 23:00:07])
{:ok, ~N[2007-01-01 00:00:00]}
iex> Crontab.Scheduler.get_next_run_date %Crontab.CronExpression{reboot: true}
** (RuntimeError) Special identifier @reboot is not supported.
"""
@spec get_next_run_date(CronExpression.t, NaiveDateTime.t, integer) :: result
def get_next_run_date(cron_expression, date, max_runs \\ @max_runs)
def get_next_run_date(cron_expression, date \\ DateTime.to_naive(DateTime.utc_now), max_runs \\ @max_runs)
def get_next_run_date(%CronExpression{reboot: true}, _, _), do: raise "Special identifier @reboot is not supported."
def get_next_run_date(cron_expression = %CronExpression{extended: false}, date, max_runs) do
case get_run_date(cron_expression, clean_date(date, :seconds), max_runs, :increment) do
{:ok, date} -> {:ok, date}
Expand All @@ -52,9 +56,12 @@ defmodule Crontab.Scheduler do
iex> Crontab.Scheduler.get_next_run_date!(%Crontab.CronExpression{year: [{:/, :*, 9}]}, ~N[2002-01-13 23:00:07])
~N[2007-01-01 00:00:00]
iex> Crontab.Scheduler.get_next_run_date! %Crontab.CronExpression{reboot: true}
** (RuntimeError) Special identifier @reboot is not supported.
"""
@spec get_next_run_date!(CronExpression.t, NaiveDateTime.t, integer) :: NaiveDateTime.t | no_return
def get_next_run_date!(cron_expression, date, max_runs \\ @max_runs) do
def get_next_run_date!(cron_expression, date \\ DateTime.to_naive(DateTime.utc_now), max_runs \\ @max_runs) do
case get_next_run_date(cron_expression, date, max_runs) do
{:ok, result} -> result
{:error, error} -> raise error
Expand Down Expand Up @@ -84,6 +91,9 @@ defmodule Crontab.Scheduler do
...> %Crontab.CronExpression{year: [2017], month: [1], day: [1], hour: [0], minute: [1]}, ~N[2016-12-17 00:00:00])
{:error, [~N[2017-01-01 00:01:00]], "No compliant date was found for your interval."}
iex> Crontab.Scheduler.get_next_run_dates(3, %Crontab.CronExpression{reboot: true})
** (RuntimeError) Special identifier @reboot is not supported.
"""
@spec get_next_run_dates(pos_integer, CronExpression.t, NaiveDateTime.t) :: {:ok | :error, [NaiveDateTime.t], binary}
def get_next_run_dates(n, cron_expression, date \\ DateTime.to_naive(DateTime.utc_now))
Expand Down Expand Up @@ -113,6 +123,9 @@ defmodule Crontab.Scheduler do
...> %Crontab.CronExpression{year: [2017], month: [1], day: [1], hour: [0], minute: [1]}, ~N[2016-12-17 00:00:00])
** (RuntimeError) No compliant date was found for your interval.
iex> Crontab.Scheduler.get_next_run_dates!(3, %Crontab.CronExpression{reboot: true})
** (RuntimeError) Special identifier @reboot is not supported.
"""
@spec get_next_run_dates!(pos_integer, CronExpression.t, NaiveDateTime.t) :: [NaiveDateTime.t] | no_return
def get_next_run_dates!(n, cron_expression, date \\ DateTime.to_naive(DateTime.utc_now))
Expand Down Expand Up @@ -153,9 +166,13 @@ defmodule Crontab.Scheduler do
...> year: [{:/, :*, 9}]}, ~N[2002-01-13 23:00:07]
{:ok, ~N[1998-12-31 23:59:00]}
iex> Crontab.Scheduler.get_previous_run_date %Crontab.CronExpression{reboot: true}
** (RuntimeError) Special identifier @reboot is not supported.
"""
@spec get_previous_run_date(CronExpression.t, NaiveDateTime.t, integer) :: result
def get_previous_run_date(cron_expression, date, max_runs \\ @max_runs)
def get_previous_run_date(cron_expression, date \\ DateTime.to_naive(DateTime.utc_now), max_runs \\ @max_runs)
def get_previous_run_date(%CronExpression{reboot: true}, _, _), do: raise "Special identifier @reboot is not supported."
def get_previous_run_date(cron_expression = %CronExpression{extended: false}, date, max_runs) do
case get_run_date(cron_expression, date, max_runs, :decrement) do
{:ok, date} -> {:ok, reset(date, :seconds)}
Expand Down Expand Up @@ -183,10 +200,13 @@ defmodule Crontab.Scheduler do
...> year: [{:/, :*, 9}]}, ~N[2002-01-13 23:00:07]
~N[1998-12-31 23:59:00]
iex> Crontab.Scheduler.get_previous_run_date! %Crontab.CronExpression{reboot: true}
** (RuntimeError) Special identifier @reboot is not supported.
"""
@spec get_previous_run_date!(CronExpression.t, NaiveDateTime.t, integer) :: NaiveDateTime.t | no_return
def get_previous_run_date!(cron_expression, date, max_runs \\ @max_runs) do
def get_previous_run_date!(cron_expression, date \\ DateTime.to_naive(DateTime.utc_now), max_runs \\ @max_runs) do
case get_previous_run_date(cron_expression, date, max_runs) do
{:ok, result} -> result
{:error, error} -> raise error
Expand Down Expand Up @@ -216,6 +236,9 @@ defmodule Crontab.Scheduler do
...> %Crontab.CronExpression{year: [2016], month: [1], day: [1], hour: [0], minute: [1]}, ~N[2016-12-17 00:00:00])
{:error, [~N[2016-01-01 00:01:00]], "No compliant date was found for your interval."}
iex> Crontab.Scheduler.get_previous_run_dates(3, %Crontab.CronExpression{reboot: true})
** (RuntimeError) Special identifier @reboot is not supported.
"""
@spec get_previous_run_dates(pos_integer, CronExpression.t, NaiveDateTime.t) ::
{:ok | :error, [NaiveDateTime.t], binary}
Expand Down Expand Up @@ -247,6 +270,9 @@ defmodule Crontab.Scheduler do
...> %Crontab.CronExpression{year: [2017], month: [1], day: [1], hour: [0], minute: [1]}, ~N[2016-12-17 00:00:00])
** (RuntimeError) No compliant date was found for your interval.
iex> Crontab.Scheduler.get_previous_run_dates!(3, %Crontab.CronExpression{reboot: true})
** (RuntimeError) Special identifier @reboot is not supported.
"""
@spec get_previous_run_dates!(pos_integer, CronExpression.t, NaiveDateTime.t) :: [NaiveDateTime.t] | no_return
def get_previous_run_dates!(n, cron_expression, date \\ DateTime.to_naive(DateTime.utc_now))
Expand Down
8 changes: 6 additions & 2 deletions test/crontab/cron_expression/parser_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ defmodule Crontab.CronExpression.ParserTest do
doctest Crontab.CronExpression.Parser
import Crontab.CronExpression.Parser

test "parse \"@reboot\" gives error" do
assert parse("@reboot") == {:error, "Special identifier @reboot is not supported."}
test "parse \"@reboot\" gives reboot" do
assert parse("@reboot") == {:ok, %Crontab.CronExpression{reboot: true, extended: false, minute: [:*], hour: [:*], day: [:*], month: [:*], weekday: [:*], year: [:*]}}
end

test "parse \"@REBOOT\" gives reboot" do
assert parse("@REBOOT") == {:ok, %Crontab.CronExpression{reboot: true, extended: false, minute: [:*], hour: [:*], day: [:*], month: [:*], weekday: [:*], year: [:*]}}
end

test "parse \"@unknown\" gives error" do
Expand Down
7 changes: 7 additions & 0 deletions test/crontab/date_checker_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,11 @@ defmodule Crontab.DateCheckerTest do
assert matches_date?(:weekday, [{:"/", :*, 3}], base_date) == false
assert matches_date?(:year, [{:"/", :*, 3}], base_date) == false
end

test "fail on @reboot" do
base_date = ~N[2003-04-17 04:04:08]
assert_raise RuntimeError, "Special identifier @reboot is not supported.", fn ->
matches_date?(%Crontab.CronExpression{reboot: true}, base_date)
end
end
end

0 comments on commit ff08c66

Please sign in to comment.