Files
ical2rem/bin/main.ml
Paolo Donadeo 21215a2248 feat: implement simple weekly recurrence rendering
- Add `simple_weekly` type and `weekly` field to `rem` record
- Add `exdate` field to `rem` for excluded dates
- Add `collect_exdates` collector to pipeline
- implement weekly `RRULE` handling with `BYDAY`, `INTERVAL`,
  `COUNT`/`UNTIL`
- Add `render_weekly` to emit one `REM` per weekday with `UNTIL`/`*N`
- Replace `timedesc_of_date_or_datetime` with
  `timedesc_of_utc_or_timestamp_local` in utils
- Refactor `get_exdates`/`get_rdates` to separate dates, datetimes and
  periods; add debug logging per UID
- Wrap reminder output in try/catch in main; drop trailing newline
  duplication
- Mark implemented predicates (P00–P05, P09, P12, P14) with ;
  remove P18–P20 (ignored/deferred)
2026-05-16 21:56:14 +02:00

67 lines
2.7 KiB
OCaml

open Remind_sync
module Map = MoreLabels.Map.Make (String)
(*
We use a list of events here because there can be multiple events with the same UID, and we want to preserve all of
them. This is important for handling cases where there are multiple events with the same UID but different properties
(e.g., due to updates or recurring events or cancellations).
*)
let read_file filename =
let ic = open_in filename in
let n = in_channel_length ic in
let s = Bytes.create n in
really_input ic s 0 n;
close_in ic;
Bytes.unsafe_to_string s
let ical2rem ical_files =
let good_rems =
ListLabels.fold_left ~init:[] ical_files ~f:(fun good_rems_acc filename ->
try
Printf.eprintf "\nProcessing file: %s\n" filename;
let file_content = read_file filename in
match Icalendar.parse file_content with
| Error e ->
if e <> ": not enough input" then prerr_endline ("Error parsing iCalendar file: " ^ e);
good_rems_acc
| Ok (_, components) -> begin
let events_map : Icalendar.event list Map.t =
ListLabels.fold_left ~init:Map.empty components ~f:(fun acc comp ->
match comp with
| `Event ev ->
let uid = Utils.get_uid ev in
let event_list = Map.find_opt uid acc |> Option.value ~default:[] in
Map.add ~key:uid ~data:(ev :: event_list) acc
| _ -> acc (* Ignore non-event components *))
in
let events_map = Map.map ~f:List.rev events_map in
let good_rems =
Map.fold ~init:[] events_map ~f:(fun ~key:uid ~data:events good_rems ->
let rem_or_error = EventPredicates.remind_of_event events in
match rem_or_error with
| Ok rem -> rem :: good_rems
| Error (EventPredicates.Invalid_date s) ->
Printf.eprintf "UID: %s Invalid date: %s\n" uid s;
good_rems
| Error Skip ->
Printf.eprintf "UID: %s Skipped\n" uid;
good_rems)
in
let good_rems = List.rev good_rems in
good_rems @ good_rems_acc
end
with e ->
prerr_endline ("Error reading file " ^ filename ^ ": " ^ Printexc.to_string e);
good_rems_acc)
in
try ListLabels.iter good_rems ~f:(fun rem -> Printf.printf "%s" (Remind.string_of_rem rem))
with e ->
Printf.eprintf "Error processing reminders: %s\n" (Printexc.to_string e);
exit 1
let () = if !Sys.interactive then () else exit (CommandLine.main ical2rem)