From 9ce06e463952657ba72e8a65d67d33534ccc2d2a Mon Sep 17 00:00:00 2001 From: Benedetta Date: Wed, 1 May 2024 12:03:12 +0200 Subject: [PATCH] refactor issuelib --- pam/lib/issue_parser.ml | 110 ------------------------------ pam/lib/issuelib.ml | 147 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 110 deletions(-) delete mode 100644 pam/lib/issue_parser.ml create mode 100644 pam/lib/issuelib.ml diff --git a/pam/lib/issue_parser.ml b/pam/lib/issue_parser.ml deleted file mode 100644 index 600ccbc..0000000 --- a/pam/lib/issue_parser.ml +++ /dev/null @@ -1,110 +0,0 @@ -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) diff --git a/pam/lib/issuelib.ml b/pam/lib/issuelib.ml new file mode 100644 index 0000000..0beefe1 --- /dev/null +++ b/pam/lib/issuelib.ml @@ -0,0 +1,147 @@ +module Private = struct + + 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 option_bind f opt = Option.bind opt f + + let to_exact_date offset alert_data = + let alert_time = (alert_data.exact_time |> fst, alert_data.exact_time |> snd, 0), offset in + + let seconds_in_a_day = 24 * 60 * 60 in + let span = alert_data.date_offset * seconds_in_a_day |> Ptime.Span.of_int_s in + let add_span = match alert_data.when_ with | Before -> Ptime.sub_span | _ -> Ptime.add_span in + + alert_data.due_date + |> Ptime.of_date + |> option_bind (fun s -> add_span s span) + |> Option.map (Ptime.to_date ~tz_offset_s:offset) + |> option_bind (fun due_date -> Ptime.of_date_time (due_date, alert_time)) + + let parse_body str = + let prefix = "@alert: " in + let has_alert = String.starts_with ~prefix:prefix in + let idx = String.length prefix in + + let rec find_alert acc = function + | line::rest -> + if line |> has_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 + String.split_on_char '\n' str + |> find_alert [] + + + + 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 -> + let (h, m) = (h, m) |> BatTuple.Tuple2.map1 BatString.trim in + begin match int_of_string_opt h, int_of_string_opt m with + | Some h, Some m -> + Ok {date_offset=offset; exact_time=(h, m); when_=when_} + | None, _ -> Error [%string "Can't parse numbers from string hour: h=%{h}|m=%{m}|"] + | _, None -> Error [%string "Can't parse numbers from string minute: h=%{h}|m=%{m}|"] + end + | _ -> Error [%string "Malformed time: %{str}"]) +end + +open Private + +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 now alerts = + 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 + (12 * 60 * 60) = total + in + let remove_seconds (t: Ptime.t) = + let (date, ((h, m, _), offset)) = Ptime.to_date_time t in + let time' = ((h, m, 0), offset) in + Ptime.of_date_time (date, time') |> Option.get + in + let equal a b = Ptime.equal (remove_seconds a) (remove_seconds b) 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 (equal now) alerts) || + (List.for_all (Ptime.is_earlier ~than:now) alerts && is_noon now) + +let issue_data_to_json (issue: Datatypes.forgejo_issue_data) = + let open Yojson.Basic in + let due_date = Option.value ~default:"" issue.due_date in + let room_id = issue.matrix_target in + let content = [%string {|[%{issue.title}](%{issue.url}) - %{due_date}\n%{issue.body}|} ] in + + let d = `Assoc [ + ("content", `String content); + ("source_message_id", `Null); + ("room_id", `String room_id); + ("as_reply", `Bool false); + ("as_markdown", `Bool true) + ] in + to_string d