Monorepo management for opam overlays
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 174 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 ~now = 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 Fmt.str "%04d-%02d-%02d" y m d 16 in 17 (* Get current date for range end *) 18 let now_date = 19 let (y, m, d), _ = Ptime.to_date_time now in 20 Fmt.str "%04d-%02d-%02d" y m d 21 in 22 match 23 Changes_aggregated.load_range ~fs ~changes_dir ~from_date:since_date 24 ~to_date:now_date 25 with 26 | Error e -> Error e 27 | Ok aggregated_files -> 28 (* Filter to files generated after 'since' and collect entries *) 29 let entries = 30 List.concat_map 31 (fun (agg : Changes_aggregated.t) -> 32 if Ptime.compare agg.generated_at since > 0 then agg.entries else []) 33 aggregated_files 34 in 35 Ok entries 36 37let has_new_changes ~fs ~changes_dir ~since ~now = 38 match changes_since ~fs ~changes_dir ~since ~now with 39 | Ok entries -> entries <> [] 40 | Error _ -> false 41 42let format_repo_link repo url_opt = 43 match url_opt with 44 | Some url -> Fmt.str "[%s](%s)" repo url 45 | None -> repo (* No URL available, just use repo name *) 46 47let format_entry_zulip buf (entry : Changes_aggregated.entry) = 48 let repo_link = format_repo_link entry.repository entry.repo_url in 49 Buffer.add_string buf (Fmt.str "**%s**: %s\n" repo_link entry.summary); 50 List.iter 51 (fun change -> Buffer.add_string buf (Fmt.str "- %s\n" change)) 52 entry.changes; 53 if entry.contributors <> [] then 54 Buffer.add_string buf 55 (Fmt.str "*Contributors: %s*\n" (String.concat ", " entry.contributors)); 56 Buffer.add_string buf "\n" 57 58let format_type_section buf title entries = 59 if entries <> [] then begin 60 Buffer.add_string buf (Fmt.str "### %s\n\n" title); 61 List.iter (format_entry_zulip buf) entries 62 end 63 64let format_for_zulip ~entries ~include_date ~date = 65 if entries = [] then "No changes to report." 66 else begin 67 let buf = Buffer.create 1024 in 68 if include_date then 69 Buffer.add_string buf 70 (match date with 71 | Some d -> Fmt.str "Updates for %s:\n\n" d 72 | None -> "Recent updates:\n\n"); 73 (* Group by change type *) 74 let by_type = 75 [ 76 (Changes_aggregated.New_library, "New Libraries"); 77 (Changes_aggregated.Feature, "Features"); 78 (Changes_aggregated.Bugfix, "Bug Fixes"); 79 (Changes_aggregated.Documentation, "Documentation"); 80 (Changes_aggregated.Refactor, "Improvements"); 81 (Changes_aggregated.Unknown, "Other Changes"); 82 ] 83 in 84 List.iter 85 (fun (ct, title) -> 86 let matching = 87 List.filter 88 (fun (e : Changes_aggregated.entry) -> e.change_type = ct) 89 entries 90 in 91 format_type_section buf title matching) 92 by_type; 93 Buffer.contents buf 94 end 95 96let format_summary ~entries = 97 if entries = [] then "No new changes." 98 else 99 let count = List.length entries in 100 let repos = 101 List.sort_uniq String.compare 102 (List.map (fun (e : Changes_aggregated.entry) -> e.repository) entries) 103 in 104 Fmt.str "%d change%s across %d repositor%s: %s" count 105 (if count = 1 then "" else "s") 106 (List.length repos) 107 (if List.length repos = 1 then "y" else "ies") 108 (String.concat ", " repos) 109 110(** {1 Daily Changes (Real-time)} *) 111 112let daily_changes_since ~fs ~changes_dir ~since = 113 Changes_daily.entries_since ~fs ~changes_dir ~since 114 115let has_new_daily_changes ~fs ~changes_dir ~since = 116 daily_changes_since ~fs ~changes_dir ~since <> [] 117 118let format_daily_entry buf (entry : Changes_daily.entry) = 119 Buffer.add_string buf (Fmt.str "**%s**\n" entry.summary); 120 List.iter 121 (fun change -> Buffer.add_string buf (Fmt.str "- %s\n" change)) 122 entry.changes; 123 if entry.contributors <> [] then 124 Buffer.add_string buf 125 (Fmt.str "*Contributors: %s*\n" (String.concat ", " entry.contributors)); 126 Buffer.add_string buf "\n" 127 128let format_repo_section buf repo repo_entries = 129 if repo_entries <> [] then begin 130 let first_entry = List.hd repo_entries in 131 let repo_link = format_repo_link repo first_entry.Changes_daily.repo_url in 132 Buffer.add_string buf (Fmt.str "### %s\n\n" repo_link); 133 List.iter (format_daily_entry buf) repo_entries 134 end 135 136let format_daily_for_zulip ~entries ~include_date ~date = 137 if entries = [] then "No changes to report." 138 else begin 139 let buf = Buffer.create 1024 in 140 if include_date then 141 Buffer.add_string buf 142 (match date with 143 | Some d -> Fmt.str "## Changes for %s\n\n" d 144 | None -> "## Recent Changes\n\n"); 145 (* Group by repository *) 146 let repos = 147 List.sort_uniq String.compare 148 (List.map (fun (e : Changes_daily.entry) -> e.repository) entries) 149 in 150 List.iter 151 (fun repo -> 152 let repo_entries = 153 List.filter 154 (fun (e : Changes_daily.entry) -> e.repository = repo) 155 entries 156 in 157 format_repo_section buf repo repo_entries) 158 repos; 159 Buffer.contents buf 160 end 161 162let format_daily_summary ~entries = 163 if entries = [] then "No new changes." 164 else 165 let count = List.length entries in 166 let repos = 167 List.sort_uniq String.compare 168 (List.map (fun (e : Changes_daily.entry) -> e.repository) entries) 169 in 170 Fmt.str "%d change%s across %d repositor%s: %s" count 171 (if count = 1 then "" else "s") 172 (List.length repos) 173 (if List.length repos = 1 then "y" else "ies") 174 (String.concat ", " repos)