Current section

Files

Jump to
automata src automata@cron.erl
Raw

src/automata@cron.erl

-module(automata@cron).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/automata/cron.gleam").
-export([parse/1, validate/1, normalize/1, to_string/1, matches_plan/2, matches/2, iterator_after_plan/2, iterator_after/2, next_after_plan/2, next_after/2, builder/0, any/0, at/1, between/2, every/1, every_from/2, every_between/3, one_of/1, item_exact/1, item_range/2, item_step_any/1, item_step_from/2, item_step_between/3, with_minute/2, with_hour/2, with_day_of_month/2, with_month/2, with_day_of_week/2, build/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" UNIX 5-field cron stack: parse a cron string, validate it,\n"
" normalise it into a fast-evaluation `CronPlan`, and ask whether a\n"
" given `ValidDateTime` matches or what the next match is. This\n"
" module is the user-facing facade; the typed phases live in the\n"
" `automata/cron/{ast,parser,validator,normalize,evaluator,iterator,\n"
" next,builder}` submodules.\n"
).
-file("src/automata/cron.gleam", 23).
?DOC(
" Parse a UNIX 5-field cron expression (`minute hour day-of-month\n"
" month day-of-week`) into a `RawCron` AST. Returns a `ParseError`\n"
" for syntactic problems such as wrong field count or empty fields.\n"
" Range and alias validation is done separately by `validate/1`.\n"
).
-spec parse(binary()) -> {ok, automata@cron@ast:raw_cron()} |
{error, automata@cron@parser:parse_error()}.
parse(Input) ->
automata@cron@parser:parse(Input).
-file("src/automata/cron.gleam", 30).
?DOC(
" Validate a `RawCron` and return a `ValidCron` (opaque) when every\n"
" field is in range and consistent. The returned value is the only\n"
" shape the rest of the pipeline accepts.\n"
).
-spec validate(automata@cron@ast:raw_cron()) -> {ok,
automata@cron@validator:valid_cron()} |
{error, automata@cron@validator:validation_error()}.
validate(Raw) ->
automata@cron@validator:validate(Raw).
-file("src/automata/cron.gleam", 39).
?DOC(
" Pre-compute the lookup tables `evaluator`/`iterator`/`next` need.\n"
" Reuse the resulting `CronPlan` across calls if you evaluate the\n"
" same spec many times — see the `*_plan` variants.\n"
).
-spec normalize(automata@cron@validator:valid_cron()) -> automata@cron@normalize:cron_plan().
normalize(Spec) ->
automata@cron@normalize:normalize(Spec).
-file("src/automata/cron.gleam", 44).
?DOC(" Render a `ValidCron` back to its canonical 5-field string form.\n").
-spec to_string(automata@cron@validator:valid_cron()) -> binary().
to_string(Spec) ->
automata@cron@validator:to_string(Spec).
-file("src/automata/cron.gleam", 99).
?DOC(
" Same as `matches/2` but takes an already-`normalize`d `CronPlan`,\n"
" so callers that evaluate the same spec many times can pay the\n"
" normalisation cost once.\n"
).
-spec matches_plan(
automata@cron@normalize:cron_plan(),
automata@schedule@ast:valid_date_time()
) -> boolean().
matches_plan(Plan, At) ->
automata@cron@evaluator:matches(
Plan,
automata@schedule@ast:valid_datetime_value(At)
).
-file("src/automata/cron.gleam", 48).
-spec matches(
automata@cron@validator:valid_cron(),
automata@schedule@ast:valid_date_time()
) -> boolean().
matches(Spec, At) ->
_pipe = Spec,
_pipe@1 = automata@cron@normalize:normalize(_pipe),
matches_plan(_pipe@1, At).
-file("src/automata/cron.gleam", 107).
?DOC(" Plan-reuse counterpart to `iterator_after/2`.\n").
-spec iterator_after_plan(
automata@cron@normalize:cron_plan(),
automata@schedule@ast:boundary()
) -> automata@cron@iterator:cron_iterator().
iterator_after_plan(Plan, Boundary) ->
automata@cron@iterator:'after'(Plan, Boundary).
-file("src/automata/cron.gleam", 54).
-spec iterator_after(
automata@cron@validator:valid_cron(),
automata@schedule@ast:boundary()
) -> automata@cron@iterator:cron_iterator().
iterator_after(Spec, Boundary) ->
_pipe = Spec,
_pipe@1 = automata@cron@normalize:normalize(_pipe),
iterator_after_plan(_pipe@1, Boundary).
-file("src/automata/cron.gleam", 118).
?DOC(
" Plan-reuse counterpart to `next_after/2`. Same `None` semantics:\n"
" see the `next_after/2` doc-comment for the (very narrow) cases\n"
" where this can return `None` for a `ValidCron` that came out of\n"
" `validate`.\n"
).
-spec next_after_plan(
automata@cron@normalize:cron_plan(),
automata@schedule@ast:valid_date_time()
) -> gleam@option:option(automata@schedule@ast:valid_date_time()).
next_after_plan(Plan, After) ->
_pipe = automata@cron@next:next_after(
Plan,
automata@schedule@ast:valid_datetime_value(After)
),
gleam@option:map(_pipe, fun automata@schedule@ast:unsafe_assume_valid/1).
-file("src/automata/cron.gleam", 87).
?DOC(
" Return the next `ValidDateTime` after `after` that the cron spec\n"
" fires at, or `None` when no such occurrence exists.\n"
"\n"
" UNIX cron is non-terminating — every well-formed `ValidCron`\n"
" specification has infinitely many future firings — so for typical\n"
" inputs `None` is **unreachable in practice**. The `Option` shape\n"
" is preserved for two narrow cases:\n"
"\n"
" 1. Internal recursion guard. The next-firing search bounds itself\n"
" at ~5,000,000 candidate steps (≈ 9.5 years of minute granularity)\n"
" so a misbehaving plan cannot loop forever. A sufficiently\n"
" pathological combination of selectors that has no firing within\n"
" that horizon is treated as \"no next occurrence\".\n"
" 2. The maximum representable `ValidDateTime` boundary. If the\n"
" search would have to advance past the calendar's representable\n"
" range to find the next firing, it returns `None` rather than\n"
" fabricating a value.\n"
"\n"
" Callers that compose `next_after` into a pipeline can safely treat\n"
" `None` as \"no next time\" without distinguishing the two reasons —\n"
" both are degenerate cases that should never be reached for cron\n"
" specs that came out of `validate`. RRULE's `next_after` returns\n"
" `Option` for a different reason (a `COUNT=N` rule legitimately\n"
" exhausts); the cron variant has no such terminating clause.\n"
).
-spec next_after(
automata@cron@validator:valid_cron(),
automata@schedule@ast:valid_date_time()
) -> gleam@option:option(automata@schedule@ast:valid_date_time()).
next_after(Spec, After) ->
_pipe = Spec,
_pipe@1 = automata@cron@normalize:normalize(_pipe),
next_after_plan(_pipe@1, After).
-file("src/automata/cron.gleam", 126).
-spec builder() -> automata@cron@builder:builder().
builder() ->
automata@cron@builder:builder().
-file("src/automata/cron.gleam", 130).
-spec any() -> automata@cron@validator:selector().
any() ->
automata@cron@builder:any().
-file("src/automata/cron.gleam", 134).
-spec at(integer()) -> automata@cron@validator:selector().
at(Value) ->
automata@cron@builder:at(Value).
-file("src/automata/cron.gleam", 138).
-spec between(integer(), integer()) -> automata@cron@validator:selector().
between(Start, End) ->
automata@cron@builder:between(Start, End).
-file("src/automata/cron.gleam", 142).
-spec every(integer()) -> automata@cron@validator:selector().
every(Step) ->
automata@cron@builder:every(Step).
-file("src/automata/cron.gleam", 146).
-spec every_from(integer(), integer()) -> automata@cron@validator:selector().
every_from(Start, Step) ->
automata@cron@builder:every_from(Start, Step).
-file("src/automata/cron.gleam", 150).
-spec every_between(integer(), integer(), integer()) -> automata@cron@validator:selector().
every_between(Start, End, Step) ->
automata@cron@builder:every_between(Start, End, Step).
-file("src/automata/cron.gleam", 158).
-spec one_of(list(automata@cron@validator:item())) -> automata@cron@validator:selector().
one_of(Items) ->
automata@cron@builder:one_of(Items).
-file("src/automata/cron.gleam", 163).
?DOC(" Build an `Item` matching a single value (for use inside `one_of`).\n").
-spec item_exact(integer()) -> automata@cron@validator:item().
item_exact(Value) ->
{exact, Value}.
-file("src/automata/cron.gleam", 168).
?DOC(" Build an `Item` matching every value in `[from, to]` inclusive.\n").
-spec item_range(integer(), integer()) -> automata@cron@validator:item().
item_range(Start, End) ->
{range, Start, End}.
-file("src/automata/cron.gleam", 174).
?DOC(
" Build an `Item` of the form `*/step` (every `step`-th value across\n"
" the field's full range).\n"
).
-spec item_step_any(integer()) -> automata@cron@validator:item().
item_step_any(Step) ->
{step, step_any, Step}.
-file("src/automata/cron.gleam", 179).
?DOC(" Build an `Item` of the form `start/step`.\n").
-spec item_step_from(integer(), integer()) -> automata@cron@validator:item().
item_step_from(Start, Step) ->
{step, {step_exact, Start}, Step}.
-file("src/automata/cron.gleam", 184).
?DOC(" Build an `Item` of the form `from-to/step`.\n").
-spec item_step_between(integer(), integer(), integer()) -> automata@cron@validator:item().
item_step_between(Start, End, Step) ->
{step, {step_range, Start, End}, Step}.
-file("src/automata/cron.gleam", 192).
-spec with_minute(
automata@cron@builder:builder(),
automata@cron@validator:selector()
) -> automata@cron@builder:builder().
with_minute(Builder, Minute) ->
automata@cron@builder:with_minute(Builder, Minute).
-file("src/automata/cron.gleam", 199).
-spec with_hour(
automata@cron@builder:builder(),
automata@cron@validator:selector()
) -> automata@cron@builder:builder().
with_hour(Builder, Hour) ->
automata@cron@builder:with_hour(Builder, Hour).
-file("src/automata/cron.gleam", 206).
-spec with_day_of_month(
automata@cron@builder:builder(),
automata@cron@validator:selector()
) -> automata@cron@builder:builder().
with_day_of_month(Builder, Day_of_month) ->
automata@cron@builder:with_day_of_month(Builder, Day_of_month).
-file("src/automata/cron.gleam", 213).
-spec with_month(
automata@cron@builder:builder(),
automata@cron@validator:selector()
) -> automata@cron@builder:builder().
with_month(Builder, Month) ->
automata@cron@builder:with_month(Builder, Month).
-file("src/automata/cron.gleam", 220).
-spec with_day_of_week(
automata@cron@builder:builder(),
automata@cron@validator:selector()
) -> automata@cron@builder:builder().
with_day_of_week(Builder, Day_of_week) ->
automata@cron@builder:with_day_of_week(Builder, Day_of_week).
-file("src/automata/cron.gleam", 227).
-spec build(automata@cron@builder:builder()) -> {ok,
automata@cron@validator:valid_cron()} |
{error, automata@cron@validator:validation_error()}.
build(Builder) ->
automata@cron@builder:build(Builder).