feat(cli): add output control flags, sort order, and verbose mode

- 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
This commit is contained in:
2026-05-24 12:25:22 +02:00
parent 510f178630
commit 69384dcfc2
7 changed files with 150 additions and 55 deletions

View File

@@ -14,16 +14,32 @@ let read_file filename =
close_in ic;
Bytes.unsafe_to_string s
let ical2rem tz_opt ical_files =
Utils.init_target_tz tz_opt;
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 = Filename.remove_extension (Filename.basename 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 prerr_endline ("Error parsing iCalendar file: " ^ 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 =
@@ -44,21 +60,26 @@ let ical2rem tz_opt ical_files =
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;
Utils.warn "Warning: invalid date: %s (UID: %s)\n" s uid;
good_rems
| Error Skip ->
Printf.eprintf "UID: %s Skipped\n" uid;
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 ->
prerr_endline ("Error reading file " ^ filename ^ ": " ^ Printexc.to_string e);
Utils.warn "Error: could not read file %s: %s\n" filename (Printexc.to_string e);
good_rems_acc)
in
let good_rems = List.sort (fun a b -> Timedesc.Date.compare b.Remind.date a.Remind.date) good_rems 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);