feat: support multiple input files
- Accept multiple positional arguments instead of a single required file - Extract `read_file` helper to separate file reading from parsing - Collect all valid reminders across files before printing - Handle per-file errors gracefully without aborting the whole run
This commit is contained in:
@@ -1,17 +1,16 @@
|
|||||||
open Cmdliner
|
open Cmdliner
|
||||||
open Cmdliner.Term.Syntax
|
open Cmdliner.Term.Syntax
|
||||||
|
|
||||||
let ical_file =
|
let files =
|
||||||
let doc = "TODO" in
|
let doc = "Files to process" in
|
||||||
let docv = "ICAL" in
|
Arg.(non_empty & pos_all string [] & info [] ~docv:"FILE" ~doc)
|
||||||
Arg.(required & pos ~rev:true 0 (some string) None & info [] ~docv ~doc)
|
|
||||||
|
|
||||||
let main_command f =
|
let main_command f =
|
||||||
let doc = "Convert iCalendar files to remind format" in
|
let doc = "Convert iCalendar files to remind format" in
|
||||||
let man = [] in
|
let man = [] in
|
||||||
Cmd.make (Cmd.info "ical2rem" ~version:"%%VERSION%%" ~doc ~man)
|
Cmd.make (Cmd.info "ical2rem" ~version:"%%VERSION%%" ~doc ~man)
|
||||||
@@
|
@@
|
||||||
let+ ical_file = ical_file in
|
let+ files = files in
|
||||||
f ical_file
|
f files
|
||||||
|
|
||||||
let main f = Cmd.eval @@ main_command f
|
let main f = Cmd.eval @@ main_command f
|
||||||
|
|||||||
47
bin/main.ml
47
bin/main.ml
@@ -7,18 +7,24 @@ module Map = MoreLabels.Map.Make (String)
|
|||||||
(e.g., due to updates or recurring events or cancellations).
|
(e.g., due to updates or recurring events or cancellations).
|
||||||
*)
|
*)
|
||||||
|
|
||||||
let ical2rem ical_file =
|
let read_file filename =
|
||||||
let ic = open_in ical_file in
|
let ic = open_in filename in
|
||||||
let n = in_channel_length ic in
|
let n = in_channel_length ic in
|
||||||
let s = Bytes.create n in
|
let s = Bytes.create n in
|
||||||
really_input ic s 0 n;
|
really_input ic s 0 n;
|
||||||
close_in ic;
|
close_in ic;
|
||||||
let cal_or_error = Icalendar.parse (Bytes.unsafe_to_string s) in
|
Bytes.unsafe_to_string s
|
||||||
match cal_or_error with
|
|
||||||
|
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 ->
|
| Error e ->
|
||||||
if e = ": not enough input" then
|
if e <> ": not enough input" then prerr_endline ("Error parsing iCalendar file: " ^ e);
|
||||||
exit 0 (* This is a common error when the file is empty, so we treat it as a non-error case *)
|
good_rems_acc
|
||||||
else prerr_endline ("Error parsing iCalendar file: " ^ e)
|
|
||||||
| Ok (_, components) -> begin
|
| Ok (_, components) -> begin
|
||||||
let events_map : Icalendar.event list Map.t =
|
let events_map : Icalendar.event list Map.t =
|
||||||
ListLabels.fold_left ~init:Map.empty components ~f:(fun acc comp ->
|
ListLabels.fold_left ~init:Map.empty components ~f:(fun acc comp ->
|
||||||
@@ -29,16 +35,29 @@ let ical2rem ical_file =
|
|||||||
Map.add ~key:uid ~data:(ev :: event_list) acc
|
Map.add ~key:uid ~data:(ev :: event_list) acc
|
||||||
| _ -> acc (* Ignore non-event components *))
|
| _ -> acc (* Ignore non-event components *))
|
||||||
in
|
in
|
||||||
(* Now revert all the lists *)
|
|
||||||
let events_map = Map.map ~f:List.rev events_map in
|
|
||||||
(* Printf.printf "Events: %d\n\n" (Map.cardinal events_map); *)
|
|
||||||
|
|
||||||
Map.iter events_map ~f:(fun ~key:uid ~data:events ->
|
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
|
let rem_or_error = EventPredicates.remind_of_event events in
|
||||||
match rem_or_error with
|
match rem_or_error with
|
||||||
| Ok rem -> begin Printf.printf "%s\n" (Remind.string_of_rem rem) end
|
| Ok rem -> rem :: good_rems
|
||||||
| Error (EventPredicates.Invalid_date s) -> Printf.eprintf "UID: %s Invalid date: %s\n" uid s
|
| Error (EventPredicates.Invalid_date s) ->
|
||||||
| Error Skip -> Printf.eprintf "UID: %s Skipped\n" uid)
|
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
|
end
|
||||||
|
with e ->
|
||||||
|
prerr_endline ("Error reading file " ^ filename ^ ": " ^ Printexc.to_string e);
|
||||||
|
good_rems_acc)
|
||||||
|
in
|
||||||
|
|
||||||
|
ListLabels.iter good_rems ~f:(fun rem -> Printf.printf "%s\n" (Remind.string_of_rem rem))
|
||||||
|
|
||||||
let () = if !Sys.interactive then () else exit (CommandLine.main ical2rem)
|
let () = if !Sys.interactive then () else exit (CommandLine.main ical2rem)
|
||||||
|
|||||||
Reference in New Issue
Block a user