- Add `--verbose`/`-v` flag; gate all diagnostic stderr output behind it - Add `--no-uuid`, `--no-source`, `--no-location`, `--no-description`, `--no-conference-url` flags to suppress individual INFO lines - Add `--sort` option (`asc`, `desc`, `original`) replacing hardcoded descending sort - Add `--source` option to override calendar name (single-file only) - Introduce `Config` module with global `ref` flags set at startup from CLI args - Add `Utils.warn` helper that writes to stderr only when `Config.verbose` is set - Normalise all diagnostic messages to a consistent format (`Warning: ... (UID: ...)`) - Remove `debug_print_of_recurrence_and_skip`; inline skip at each call site - Fix `add_common_part` to always emit a trailing `\\\n ` continuation line
89 lines
3.7 KiB
OCaml
89 lines
3.7 KiB
OCaml
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 (args : CommandLine.cli_args) ical_files =
|
|
(* Validate --source with multiple files *)
|
|
(match args.source with
|
|
| Some _ when List.length ical_files > 1 ->
|
|
Printf.eprintf "Error: --source can only be used with a single input file.\n";
|
|
exit 1
|
|
| _ -> ());
|
|
Config.verbose := args.verbose;
|
|
Config.no_uuid := args.no_uuid;
|
|
Config.no_source := args.no_source;
|
|
Config.no_location := args.no_location;
|
|
Config.no_description := args.no_description;
|
|
Config.no_conference_url := args.no_conference_url;
|
|
Utils.init_target_tz args.tz;
|
|
let good_rems =
|
|
ListLabels.fold_left ~init:[] ical_files ~f:(fun good_rems_acc filename ->
|
|
try
|
|
let file_content = read_file filename in
|
|
let basename =
|
|
match args.source with
|
|
| Some name -> name
|
|
| None -> Filename.remove_extension (Filename.basename filename)
|
|
in
|
|
match Icalendar.parse file_content with
|
|
| Error e ->
|
|
if e <> ": not enough input" then Utils.warn "Error: could not parse %s: %s\n" filename 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 basename events in
|
|
match rem_or_error with
|
|
| Ok rem -> rem :: good_rems
|
|
| Error (EventPredicates.Invalid_date s) ->
|
|
Utils.warn "Warning: invalid date: %s (UID: %s)\n" s uid;
|
|
good_rems
|
|
| Error Skip ->
|
|
Utils.warn "Warning: event skipped (UID: %s)\n" uid;
|
|
good_rems)
|
|
in
|
|
let good_rems = List.rev good_rems in
|
|
good_rems @ good_rems_acc
|
|
end
|
|
with e ->
|
|
Utils.warn "Error: could not read file %s: %s\n" filename (Printexc.to_string e);
|
|
good_rems_acc)
|
|
in
|
|
|
|
let good_rems =
|
|
match args.sort with
|
|
| CommandLine.Desc -> List.sort (fun a b -> Timedesc.Date.compare b.Remind.date a.Remind.date) good_rems
|
|
| CommandLine.Asc -> List.sort (fun a b -> Timedesc.Date.compare a.Remind.date b.Remind.date) good_rems
|
|
| CommandLine.Original -> good_rems
|
|
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)
|