My aggregated monorepo of OCaml code, automaintained
at http2 145 lines 5.9 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2026 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** High-level query interface for changes. 7 8 This module provides convenient functions for querying changes since a 9 specific timestamp and formatting them for broadcast. *) 10 11let changes_since ~fs ~changes_dir ~since = 12 (* Get the date part of since for filtering *) 13 let since_date = 14 let (y, m, d), _ = Ptime.to_date_time since in 15 Printf.sprintf "%04d-%02d-%02d" y m d 16 in 17 (* Get current date for range end *) 18 let now = Ptime_clock.now () in 19 let now_date = 20 let (y, m, d), _ = Ptime.to_date_time now in 21 Printf.sprintf "%04d-%02d-%02d" y m d 22 in 23 match Changes_aggregated.load_range ~fs ~changes_dir ~from_date:since_date ~to_date:now_date with 24 | Error e -> Error e 25 | Ok aggregated_files -> 26 (* Filter to files generated after 'since' and collect entries *) 27 let entries = List.concat_map (fun (agg : Changes_aggregated.t) -> 28 if Ptime.compare agg.generated_at since > 0 then 29 agg.entries 30 else 31 []) aggregated_files 32 in 33 Ok entries 34 35let has_new_changes ~fs ~changes_dir ~since = 36 match changes_since ~fs ~changes_dir ~since with 37 | Ok entries -> entries <> [] 38 | Error _ -> false 39 40let format_repo_link repo url_opt = 41 match url_opt with 42 | Some url -> Printf.sprintf "[%s](%s)" repo url 43 | None -> Printf.sprintf "[%s](https://tangled.org/@anil.recoil.org/%s.git)" repo repo 44 45let format_for_zulip ~entries ~include_date ~date = 46 if entries = [] then 47 "No changes to report." 48 else begin 49 let buf = Buffer.create 1024 in 50 if include_date then begin 51 match date with 52 | Some d -> Buffer.add_string buf (Printf.sprintf "Updates for %s:\n\n" d) 53 | None -> Buffer.add_string buf "Recent updates:\n\n" 54 end; 55 (* Group by change type *) 56 let by_type = [ 57 (Changes_aggregated.New_library, "New Libraries", []); 58 (Changes_aggregated.Feature, "Features", []); 59 (Changes_aggregated.Bugfix, "Bug Fixes", []); 60 (Changes_aggregated.Documentation, "Documentation", []); 61 (Changes_aggregated.Refactor, "Improvements", []); 62 (Changes_aggregated.Unknown, "Other Changes", []); 63 ] in 64 let grouped = List.map (fun (ct, title, _) -> 65 let matching = List.filter (fun (e : Changes_aggregated.entry) -> e.change_type = ct) entries in 66 (ct, title, matching)) by_type 67 in 68 List.iter (fun (_ct, title, entries) -> 69 if entries <> [] then begin 70 Buffer.add_string buf (Printf.sprintf "### %s\n\n" title); 71 List.iter (fun (entry : Changes_aggregated.entry) -> 72 let repo_link = format_repo_link entry.repository entry.repo_url in 73 Buffer.add_string buf (Printf.sprintf "**%s**: %s\n" repo_link entry.summary); 74 List.iter (fun change -> 75 Buffer.add_string buf (Printf.sprintf "- %s\n" change)) entry.changes; 76 if entry.contributors <> [] then 77 Buffer.add_string buf (Printf.sprintf "*Contributors: %s*\n" 78 (String.concat ", " entry.contributors)); 79 Buffer.add_string buf "\n") entries 80 end) grouped; 81 Buffer.contents buf 82 end 83 84let format_summary ~entries = 85 if entries = [] then 86 "No new changes." 87 else 88 let count = List.length entries in 89 let repos = List.sort_uniq String.compare 90 (List.map (fun (e : Changes_aggregated.entry) -> e.repository) entries) in 91 Printf.sprintf "%d change%s across %d repositor%s: %s" 92 count (if count = 1 then "" else "s") 93 (List.length repos) (if List.length repos = 1 then "y" else "ies") 94 (String.concat ", " repos) 95 96(** {1 Daily Changes (Real-time)} *) 97 98let daily_changes_since ~fs ~changes_dir ~since = 99 Changes_daily.entries_since ~fs ~changes_dir ~since 100 101let has_new_daily_changes ~fs ~changes_dir ~since = 102 daily_changes_since ~fs ~changes_dir ~since <> [] 103 104let format_daily_for_zulip ~entries ~include_date ~date = 105 if entries = [] then 106 "No changes to report." 107 else begin 108 let buf = Buffer.create 1024 in 109 if include_date then begin 110 match date with 111 | Some d -> Buffer.add_string buf (Printf.sprintf "## Changes for %s\n\n" d) 112 | None -> Buffer.add_string buf "## Recent Changes\n\n" 113 end; 114 (* Group by repository *) 115 let repos = List.sort_uniq String.compare 116 (List.map (fun (e : Changes_daily.entry) -> e.repository) entries) in 117 List.iter (fun repo -> 118 let repo_entries = List.filter (fun (e : Changes_daily.entry) -> e.repository = repo) entries in 119 if repo_entries <> [] then begin 120 let first_entry = List.hd repo_entries in 121 let repo_link = format_repo_link repo first_entry.repo_url in 122 Buffer.add_string buf (Printf.sprintf "### %s\n\n" repo_link); 123 List.iter (fun (entry : Changes_daily.entry) -> 124 Buffer.add_string buf (Printf.sprintf "**%s**\n" entry.summary); 125 List.iter (fun change -> 126 Buffer.add_string buf (Printf.sprintf "- %s\n" change)) entry.changes; 127 if entry.contributors <> [] then 128 Buffer.add_string buf (Printf.sprintf "*Contributors: %s*\n" 129 (String.concat ", " entry.contributors)); 130 Buffer.add_string buf "\n") repo_entries 131 end) repos; 132 Buffer.contents buf 133 end 134 135let format_daily_summary ~entries = 136 if entries = [] then 137 "No new changes." 138 else 139 let count = List.length entries in 140 let repos = List.sort_uniq String.compare 141 (List.map (fun (e : Changes_daily.entry) -> e.repository) entries) in 142 Printf.sprintf "%d change%s across %d repositor%s: %s" 143 count (if count = 1 then "" else "s") 144 (List.length repos) (if List.length repos = 1 then "y" else "ies") 145 (String.concat ", " repos)