lanonna/pam/lib/issue_parser.ml
2024-04-30 12:01:58 +02:00

110 lines
4.3 KiB
OCaml

type when_ = | Before | After | SameDay
type alert_requestp = {
date_offset: int;
exact_time: int * int;
when_: when_;
}
type alert_request = {
date_offset: int;
exact_time: int * int;
due_date: Ptime.date;
when_: when_;
}
let of_alert_request ar =
let h, m = ar.exact_time in
let d = ar.due_date |> Ptime.of_date |> Option.map Ptime.to_rfc3339 |> function | None -> "none" | Some d -> d in
[%string "alert_request[date_offset=%{ar.date_offset#Int};exact_time=(%{h#Int}, %{m#Int});due_date=%{d}"]
let to_exact_date offset ar =
let src = ar.due_date |> Ptime.of_date in
let secs_in_day = 24 * 60 * 60 in
let span = ar.date_offset * secs_in_day |> Ptime.Span.of_int_s in
let add_span = match ar.when_ with | Before -> Ptime.sub_span | _ -> Ptime.add_span in
let date = Option.bind src (fun s -> add_span s span) |> Option.map (Ptime.to_date ~tz_offset_s:offset) in
let h, m = ar.exact_time in
let ptime_time = (h, m, 0), offset in
Option.bind date (fun date -> Ptime.of_date_time (date, ptime_time))
let parse_body str =
let prefix = "@alert: " in
let idx = String.length prefix in
let lines = String.split_on_char '\n' str in
let rec find_alert acc = function
| line::rest ->
if line |> String.starts_with ~prefix:"@alert: " then
let until = String.length line - idx in
let alert_rest = String.sub line idx until |> String.split_on_char ' ' in
find_alert (alert_rest@acc) rest
else
find_alert acc rest
| [] -> acc
in
find_alert [] lines
let to_alert_request str =
let is_number str = Str.string_match (Str.regexp "[0-9]+") str 0 in
let rec count n str = function
| "-" ->
if String.starts_with ~prefix:"-" str then
let rest = (String.sub str 1 (String.length str-1)) in
count (n+1) rest "-"
else if String.starts_with ~prefix:"+" str then Error "Malformed alert string"
else Ok (n, str)
| "+" ->
if String.starts_with ~prefix:"+" str then
let rest = (String.sub str 1 (String.length str-1)) in
count (n+1) rest "+"
else if String.starts_with ~prefix:"-" str then Error "Malformed alert string"
else Ok (n, str)
| _ -> Ok (n, str)
in
let od, when_ =
if String.starts_with ~prefix:"-" str then (count 0 str "-", Before)
else if String.starts_with ~prefix:"+" str then (count 0 str "+", After)
else (Ok (0, str), SameDay)
in
Result.bind od (fun (offset, date_str) ->
match String.split_on_char ':' date_str with
| h::m::[] when is_number h && is_number m -> Ok {date_offset=offset; exact_time=(int_of_string h, int_of_string m); when_=when_}
| _ -> Error [%string "Malformed time: %{str}"])
let to_datetime: Datatypes.forgejo_issue_data -> (Ptime.t list option, string) result = function
| {due_date=None; _} -> Ok None
| {due_date=Some due_date; body=body; _} ->
let rec all acc = function [] -> Ok acc | Ok x::xs -> all (x::acc) xs | Error e::_ -> Error e in
let rec all' acc = function [] -> Some acc | Some x::xs -> all' (x::acc) xs | None::_ -> None in
let alerts =
body
|> parse_body
|> List.map to_alert_request
|> all []
in
let issue_due_date =
Ptime.of_rfc3339 due_date
|> Ptime.rfc3339_string_error
|> Result.map (fun (timestamp, utc_offset, _) ->
let offset = Option.value ~default:0 utc_offset in
(Ptime.to_date timestamp ~tz_offset_s:offset, offset))
in
match (issue_due_date, alerts) with
| Error e, _ -> Error e
| _, Error e -> Error e
| Ok (date, offset), Ok alerts ->
let convert = (fun (a: alert_requestp) -> {date_offset=a.date_offset; exact_time=a.exact_time; due_date=date; when_=a.when_}) in
List.map (fun ap -> ap |> convert |> to_exact_date offset) alerts
|> all' []
|> Result.ok
let should_alert alerts now =
let is_noon_in_seconds hour tz_offset minute =
let h = hour * 60 * 60 in
let m = minute * 60 in
let total = tz_offset + h + m in
let _ = total |> string_of_int |> print_endline in
(12 * 60 * 60) = total
in
let is_noon t = Ptime.to_date_time t |> function (_date, ((h, m, _s), tz_offset)) -> is_noon_in_seconds h tz_offset m in
(List.exists (Ptime.equal now) alerts) ||
(List.for_all (Ptime.is_earlier ~than:now) alerts && is_noon now)