Skip to content

Commit 9456572

Browse files
committed
Release systemdkit 0.1.4
1 parent 7f8d902 commit 9456572

4 files changed

Lines changed: 116 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## v0.1.4
4+
5+
- Added `Systemd.Calendar` helpers for typed daily, weekly, and monthly timer calendar expressions.
6+
37
## v0.1.3
48

59
- Added `Systemd.UnitFile.equivalent?/2` for semantic unit file comparison after normalization.

lib/systemd/calendar.ex

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
defmodule Systemd.Calendar do
2+
@moduledoc "Helpers for building systemd calendar expressions."
3+
4+
@weekdays %{
5+
monday: "Mon",
6+
mon: "Mon",
7+
tuesday: "Tue",
8+
tue: "Tue",
9+
wednesday: "Wed",
10+
wed: "Wed",
11+
thursday: "Thu",
12+
thu: "Thu",
13+
friday: "Fri",
14+
fri: "Fri",
15+
saturday: "Sat",
16+
sat: "Sat",
17+
sunday: "Sun",
18+
sun: "Sun"
19+
}
20+
21+
@doc "Builds a daily calendar expression at a time of day."
22+
@spec daily_at(String.t() | Time.t()) :: String.t()
23+
def daily_at(time), do: "*-*-* #{time!(time)}"
24+
25+
@doc "Builds a weekly calendar expression for a weekday and time of day."
26+
@spec weekly_at(atom() | String.t(), String.t() | Time.t()) :: String.t()
27+
def weekly_at(day, time), do: "#{weekday!(day)} *-*-* #{time!(time)}"
28+
29+
@doc "Builds a monthly calendar expression for a day of month and time of day."
30+
@spec monthly_at(pos_integer(), String.t() | Time.t()) :: String.t()
31+
def monthly_at(day, time) when is_integer(day) and day in 1..31 do
32+
"*-*-#{String.pad_leading(to_string(day), 2, "0")} #{time!(time)}"
33+
end
34+
35+
def monthly_at(day, _time) do
36+
raise ArgumentError, "monthly timer day must be an integer from 1 to 31, got: #{inspect(day)}"
37+
end
38+
39+
defp time!(%Time{} = time) do
40+
time
41+
|> Time.truncate(:second)
42+
|> Time.to_iso8601()
43+
end
44+
45+
defp time!(value) when is_binary(value) do
46+
value
47+
|> normalize_time_string()
48+
|> Time.from_iso8601()
49+
|> case do
50+
{:ok, time} ->
51+
time!(time)
52+
53+
{:error, _reason} ->
54+
raise ArgumentError,
55+
"expected timer time as HH:MM or HH:MM:SS, got: #{inspect(value)}"
56+
end
57+
end
58+
59+
defp time!(value) do
60+
raise ArgumentError, "expected timer time as HH:MM, HH:MM:SS, or Time, got: #{inspect(value)}"
61+
end
62+
63+
defp normalize_time_string(<<_h1, _h2, ?:, _m1, _m2>> = time), do: time <> ":00"
64+
defp normalize_time_string(time), do: time
65+
66+
defp weekday!(day) when is_atom(day) do
67+
case Map.fetch(@weekdays, day) do
68+
{:ok, weekday} -> weekday
69+
:error -> raise ArgumentError, "unknown weekday #{inspect(day)}"
70+
end
71+
end
72+
73+
defp weekday!(day) when is_binary(day) do
74+
weekday = String.downcase(day)
75+
76+
@weekdays
77+
|> Enum.find_value(fn {key, value} -> if Atom.to_string(key) == weekday, do: value end)
78+
|> case do
79+
nil -> raise ArgumentError, "unknown weekday #{inspect(day)}"
80+
value -> value
81+
end
82+
end
83+
end

mix.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Systemd.MixProject do
22
use Mix.Project
33

4-
@version "0.1.3"
4+
@version "0.1.4"
55
@source_url "https://github.com/elixir-vibe/systemdkit"
66

77
def project do
@@ -76,6 +76,7 @@ defmodule Systemd.MixProject do
7676
Systemd.Properties
7777
],
7878
"Unit files": [
79+
Systemd.Calendar,
7980
Systemd.UnitName,
8081
Systemd.UnitFile,
8182
Systemd.UnitFile.Blank,

test/systemd/calendar_test.exs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
defmodule Systemd.CalendarTest do
2+
use ExUnit.Case, async: true
3+
4+
alias Systemd.Calendar
5+
6+
test "builds daily calendar expressions" do
7+
assert Calendar.daily_at("02:30") == "*-*-* 02:30:00"
8+
assert Calendar.daily_at("02:30:15") == "*-*-* 02:30:15"
9+
assert Calendar.daily_at(~T[02:30:00]) == "*-*-* 02:30:00"
10+
end
11+
12+
test "builds weekly calendar expressions" do
13+
assert Calendar.weekly_at(:monday, "02:30") == "Mon *-*-* 02:30:00"
14+
assert Calendar.weekly_at("sun", ~T[03:15:00]) == "Sun *-*-* 03:15:00"
15+
end
16+
17+
test "builds monthly calendar expressions" do
18+
assert Calendar.monthly_at(1, "02:30") == "*-*-01 02:30:00"
19+
assert Calendar.monthly_at(31, ~T[23:59:59]) == "*-*-31 23:59:59"
20+
end
21+
22+
test "rejects invalid values" do
23+
assert_raise ArgumentError, ~r/expected timer time/, fn -> Calendar.daily_at("2:30") end
24+
assert_raise ArgumentError, ~r/unknown weekday/, fn -> Calendar.weekly_at(:noday, "02:30") end
25+
assert_raise ArgumentError, ~r/monthly timer day/, fn -> Calendar.monthly_at(0, "02:30") end
26+
end
27+
end

0 commit comments

Comments
 (0)