Command-line and Emacs Calendar Client
1open Cmdliner 2open Caledonia_lib 3open Query_args 4 5let run ?from_str ?to_str ~calendar ?count ?query_text ~summary ~description 6 ~location ~id ~format ~today ~tomorrow ~week ~month ~recurring 7 ~non_recurring ?timezone ~sort ~fs calendar_dir = 8 let ( let* ) = Result.bind in 9 let filters = ref [] in 10 let tz = Query_args.parse_timezone ~timezone in 11 let* from, to_ = 12 match 13 Date.convert_relative_date_formats ~tz ~today ~tomorrow ~week ~month () 14 with 15 | Some (from, to_) -> 16 let* _ = 17 match (from_str, to_str) with 18 | None, None -> Ok () 19 | _ -> 20 Error 21 (`Msg 22 "Can't specify --from / --to when using --today, --week, \ 23 --month") 24 in 25 Ok (Some from, to_) 26 | None -> ( 27 let* from = 28 match from_str with 29 | None -> Ok None 30 | Some s -> 31 let* d = Date.parse_date s `From in 32 Ok (Some d) 33 in 34 let* to_ = 35 match to_str with 36 | None -> Ok None 37 | Some s -> 38 let* d = Date.parse_date s `To in 39 Ok (Some d) 40 in 41 let max_date = Date.add_years (!Date.get_today ()) 75 in 42 match (from, to_) with 43 | Some f, Some t -> Ok (Some f, Date.to_end_of_day t) 44 | Some f, None -> Ok (Some f, Date.to_end_of_day max_date) 45 | None, Some t -> Ok (None, Date.to_end_of_day t) 46 | None, None -> Ok (None, Date.to_end_of_day max_date)) 47 in 48 (match calendar with 49 | [] -> () 50 | calendars -> filters := Query.in_calendars calendars :: !filters); 51 (match query_text with 52 | Some text -> 53 if summary then filters := Query.summary_contains text :: !filters; 54 if description then filters := Query.description_contains text :: !filters; 55 if location then filters := Query.location_contains text :: !filters; 56 if not (summary || description || location) then 57 filters := 58 Query.or_filter 59 [ 60 Query.summary_contains text; 61 Query.description_contains text; 62 Query.location_contains text; 63 ] 64 :: !filters 65 | None -> ()); 66 if recurring then filters := Query.recurring_only () :: !filters; 67 if non_recurring then filters := Query.non_recurring_only () :: !filters; 68 (match id with 69 | Some id -> filters := Query.with_id id :: !filters 70 | None -> ()); 71 let filter = Query.and_filter !filters in 72 let comparator = Query_args.create_event_comparator sort in 73 let* results = 74 Query.query ~fs calendar_dir ~filter ~from ~to_ ~comparator ?limit:count () 75 in 76 if results = [] then print_endline "No events found." 77 else print_endline (Event.format_events ~tz ~format results); 78 Ok () 79 80let query_text_arg = 81 let doc = 82 "Text to search for in the fields summary, description, and/or location." 83 in 84 Arg.(value & pos 0 (some string) None & info [] ~docv:"TEXT" ~doc) 85 86let summary_arg = 87 let doc = "Search in event summaries only" in 88 Arg.(value & flag & info [ "summary"; "s" ] ~doc) 89 90let description_arg = 91 let doc = "Search in event descriptions only" in 92 Arg.(value & flag & info [ "description"; "D" ] ~doc) 93 94let location_arg = 95 let doc = "Search in event locations only" in 96 Arg.(value & flag & info [ "location"; "l" ] ~doc) 97 98let recurring_arg = 99 let doc = "Search for recurring events only" in 100 Arg.(value & flag & info [ "recurring"; "r" ] ~doc) 101 102let non_recurring_arg = 103 let doc = "Search for non-recurring events only" in 104 Arg.(value & flag & info [ "non-recurring"; "R" ] ~doc) 105 106let id_arg = 107 let doc = "Search for an event with a specific ID" in 108 Arg.(value & opt (some string) None & info [ "id"; "i" ] ~docv:"ID" ~doc) 109 110let cmd ~fs calendar_dir = 111 let run query_text from_str to_str calendars count format summary description 112 location id today tomorrow week month recurring non_recurring timezone 113 sort = 114 match 115 run ?from_str ?to_str ~calendar:calendars ?count ?query_text ~summary 116 ~description ~location ~id ~format ~today ~tomorrow ~week ~month 117 ~recurring ~non_recurring ?timezone ~sort ~fs calendar_dir 118 with 119 | Error (`Msg msg) -> 120 Printf.eprintf "Error: %s\n%!" msg; 121 1 122 | Ok () -> 0 123 in 124 let term = 125 Term.( 126 const run $ query_text_arg $ from_arg $ to_arg $ calendar_arg $ count_arg 127 $ format_arg $ summary_arg $ description_arg $ location_arg $ id_arg 128 $ today_arg $ tomorrow_arg $ week_arg $ month_arg $ recurring_arg 129 $ non_recurring_arg $ timezone_arg $ sort_arg) 130 in 131 let doc = "Search calendar events for specific text" in 132 let man = 133 [ 134 `S Manpage.s_description; 135 `P 136 "Search calendar events for text in summary, description, or location \ 137 fields. By default, the search looks across all text fields in all \ 138 events regardless of date. You can specify specific fields to search \ 139 in using the --summary, --description, or --location flags. You can \ 140 use date flags to show events for a specific time period, and filter \ 141 events with the --sort option.\n\ 142 \ The search text is optional, so you search events according \ 143 to other query criteria."; 144 `S Manpage.s_examples; 145 `I ("Search for 'meeting' in all events:", "caled search meeting"); 146 `I 147 ( "Search for 'interview' in event summaries only:", 148 "caled search --summary interview" ); 149 `I 150 ( "Search for 'conference' in a specific calendar:", 151 "caled search --calendar work conference" ); 152 `I 153 ( "Search for 'workshop' in event descriptions for today only:", 154 "caled search --description --today workshop" ); 155 `I 156 ( "Search for 'project' in events this month:", 157 "caled search --month project" ); 158 `I 159 ( "Search for 'workshop' in event descriptions within a date range:", 160 "caled search --description --from 2025-03-27 --to 2025-04-01 \ 161 workshop" ); 162 `I 163 ("Search for recurring events only:", "caled search --recurring meeting"); 164 `I 165 ( "Search for non-recurring events only:", 166 "caled search --non-recurring appointment" ); 167 `I ("Find all recurring events:", "caled search --recurring"); 168 `I 169 ( "Find all events in a specific calendar:", 170 "caled search --calendar work" ); 171 `I 172 ( "Sort results by location and then summary:", 173 "caled search --sort location --sort summary" ); 174 `I 175 ( "Sort results by end time in descending order:", 176 "caled search --sort end:desc" ); 177 `S Manpage.s_options; 178 ] 179 @ date_format_manpage_entries 180 @ [ `S Manpage.s_see_also ] 181 in 182 let exit_info = 183 [ Cmd.Exit.info ~doc:"on success." 0; Cmd.Exit.info ~doc:"on error." 1 ] 184 in 185 let info = Cmd.info "search" ~doc ~man ~exits:exit_info in 186 Cmd.v info term