Command-line and Emacs Calendar Client
1type filter = 2 | SummaryContains of string 3 | DescriptionContains of string 4 | LocationContains of string 5 | InCollections of Collection.t list 6 | RecurringOnly 7 | NonRecurringOnly 8 | WithId of Event.event_id 9 | And of filter list 10 | Or of filter list 11 | Not of filter 12 13type sort_order = [ `Ascending | `Descending ] 14type sort_by = [ `Start | `End | `Summary | `Location | `Calendar ] 15 16let summary_contains text = SummaryContains text 17let description_contains text = DescriptionContains text 18let location_contains text = LocationContains text 19let in_collections ids = InCollections ids 20let recurring_only () = RecurringOnly 21let non_recurring_only () = NonRecurringOnly 22let with_id id = WithId id 23let and_filter filters = And filters 24let or_filter filters = Or filters 25let not_filter filter = Not filter 26 27let rec matches_filter event = function 28 | SummaryContains text -> ( 29 match Event.get_summary event with 30 | Some summary -> 31 let re = Re.Pcre.regexp ~flags:[ `CASELESS ] (Re.Pcre.quote text) in 32 Re.Pcre.pmatch ~rex:re summary 33 | None -> false) 34 | DescriptionContains text -> ( 35 match Event.get_description event with 36 | Some desc -> 37 let re = Re.Pcre.regexp ~flags:[ `CASELESS ] (Re.Pcre.quote text) in 38 Re.Pcre.pmatch ~rex:re desc 39 | None -> false) 40 | LocationContains text -> ( 41 match Event.get_location event with 42 | Some loc -> 43 let re = Re.Pcre.regexp ~flags:[ `CASELESS ] (Re.Pcre.quote text) in 44 Re.Pcre.pmatch ~rex:re loc 45 | None -> false) 46 | InCollections ids -> 47 let id = Event.get_collection event in 48 List.exists (fun col -> col = id) ids 49 | RecurringOnly -> Event.get_recurrence event <> None 50 | NonRecurringOnly -> Event.get_recurrence event = None 51 | WithId id -> Event.get_id event = id 52 | And filters -> List.for_all (matches_filter event) filters 53 | Or filters -> List.exists (matches_filter event) filters 54 | Not filter -> not (matches_filter event filter) 55 56let compare_events sort_by order e1 e2 = 57 let compare = 58 match sort_by with 59 | `Start -> 60 let t1 = Event.get_start e1 in 61 let t2 = Event.get_start e2 in 62 Ptime.compare t1 t2 63 | `End -> ( 64 match (Event.get_end e1, Event.get_end e2) with 65 | Some t1, Some t2 -> Ptime.compare t1 t2 66 | Some _, None -> 1 67 | None, Some _ -> -1 68 | None, None -> 0) 69 | `Summary -> ( 70 match (Event.get_location e1, Event.get_location e2) with 71 | Some l1, Some l2 -> String.compare l1 l2 72 | Some _, None -> 1 73 | None, Some _ -> -1 74 | None, None -> 0) 75 | `Location -> ( 76 match (Event.get_location e1, Event.get_location e2) with 77 | Some l1, Some l2 -> String.compare l1 l2 78 | Some _, None -> 1 79 | None, Some _ -> -1 80 | None, None -> 0) 81 | `Calendar -> ( 82 match (Event.get_collection e1, Event.get_collection e2) with 83 | Collection.Col c1, Collection.Col c2 -> String.compare c1 c2) 84 in 85 match order with `Ascending -> compare | `Descending -> -compare 86 87let query_events ~fs calendar_dir ?filter ?sort_by ?order ?limit () = 88 let ( let* ) = Result.bind in 89 let* collections = Calendar_dir.get_collections ~fs calendar_dir in 90 let events = 91 List.flatten (List.map (fun (_collection, events) -> events) collections) 92 in 93 let filtered_events = 94 match filter with 95 | Some f -> List.filter (fun event -> matches_filter event f) events 96 | None -> events 97 in 98 let sorted_events = 99 match (sort_by, order) with 100 | Some criteria, Some ord -> 101 List.sort (compare_events criteria ord) filtered_events 102 | Some criteria, None -> 103 List.sort (compare_events criteria `Ascending) filtered_events 104 | None, _ -> List.sort (compare_events `Start `Ascending) filtered_events 105 in 106 Ok 107 (match limit with 108 | Some n when n > 0 -> 109 let rec take n lst acc = 110 match (lst, n) with 111 | _, 0 -> List.rev acc 112 | [], _ -> List.rev acc 113 | x :: xs, n -> take (n - 1) xs (x :: acc) 114 in 115 take n sorted_events [] 116 | _ -> sorted_events) 117 118let query ~fs calendar_dir ?filter ~from ~to_ ?sort_by ?order ?limit () = 119 match query_events ~fs calendar_dir ?filter ?sort_by ?order () with 120 | Ok events -> 121 let instances = 122 List.concat_map 123 (fun event -> Recur.expand_event event ~from ~to_) 124 events 125 in 126 let compare_instances criteria ord i1 i2 = 127 match criteria with 128 | `Start -> 129 let c = Ptime.compare i1.Recur.start i2.Recur.start in 130 if ord = `Ascending then c else -c 131 | `End -> ( 132 match (i1.Recur.end_, i2.Recur.end_) with 133 | Some t1, Some t2 -> 134 let c = Ptime.compare t1 t2 in 135 if ord = `Ascending then c else -c 136 | Some _, None -> if ord = `Ascending then 1 else -1 137 | None, Some _ -> if ord = `Ascending then -1 else 1 138 | None, None -> 0) 139 | other -> 140 let c = compare_events other ord i1.Recur.event i2.Recur.event in 141 if ord = `Ascending then c else -c 142 in 143 let sorted_instances = 144 match (sort_by, order) with 145 | Some criteria, Some ord -> 146 List.sort (compare_instances criteria ord) instances 147 | Some criteria, None -> 148 List.sort (compare_instances criteria `Ascending) instances 149 | None, _ -> List.sort (compare_instances `Start `Ascending) instances 150 in 151 Ok 152 (match limit with 153 | Some n when n > 0 -> 154 let rec take n lst acc = 155 match (lst, n) with 156 | _, 0 -> List.rev acc 157 | [], _ -> List.rev acc 158 | x :: xs, n -> take (n - 1) xs (x :: acc) 159 in 160 take n sorted_instances [] 161 | _ -> sorted_instances) 162 | Error e -> Error e