My agentic slop goes here. Not intended for anyone else!
1(** Sortal - Username to metadata mapping with XDG storage 2 3 This library provides a system for mapping usernames to various metadata 4 including URLs, emails, ORCID identifiers, and social media handles. 5 It uses XDG Base Directory Specification for storage locations and 6 jsont for JSON encoding/decoding. 7 8 {b Storage:} 9 10 Contact metadata is stored as JSON files in the XDG data directory, 11 with one file per contact using the handle as the filename. 12 13 {b Typical Usage:} 14 15 {[ 16 let store = Sortal.create env#fs "myapp" in 17 let contact = Sortal.Contact.make 18 ~handle:"avsm" 19 ~names:["Anil Madhavapeddy"] 20 ~email:"anil@recoil.org" 21 ~github:"avsm" 22 ~orcid:"0000-0002-7890-1234" 23 () in 24 Sortal.save store contact; 25 26 match Sortal.lookup store "avsm" with 27 | Some c -> Printf.printf "Found: %s\n" (Sortal.Contact.name c) 28 | None -> Printf.printf "Not found\n" 29 ]} 30*) 31 32(** {1 Feed Metadata} *) 33 34module Feed : sig 35 (** Feed subscription with type and URL. 36 37 A feed represents a subscription to a content source (Atom, RSS, or JSONFeed). *) 38 type t 39 40 (** Feed type identifier. *) 41 type feed_type = 42 | Atom (** Atom feed format *) 43 | Rss (** RSS feed format *) 44 | Json (** JSON Feed format *) 45 46 (** [make ~feed_type ~url ?name ()] creates a new feed. 47 48 @param feed_type The type of feed (Atom, RSS, or JSON) 49 @param url The feed URL 50 @param name Optional human-readable name/label for the feed 51 *) 52 val make : feed_type:feed_type -> url:string -> ?name:string -> unit -> t 53 54 (** [feed_type t] returns the feed type. *) 55 val feed_type : t -> feed_type 56 57 (** [url t] returns the feed URL. *) 58 val url : t -> string 59 60 (** [name t] returns the feed name if set. *) 61 val name : t -> string option 62 63 (** [set_name t name] returns a new feed with the name updated. *) 64 val set_name : t -> string -> t 65 66 (** [feed_type_to_string ft] converts a feed type to a string. *) 67 val feed_type_to_string : feed_type -> string 68 69 (** [feed_type_of_string s] parses a feed type from a string. 70 Returns [None] if the string is not recognized. *) 71 val feed_type_of_string : string -> feed_type option 72 73 (** [json_t] is the jsont encoder/decoder for feeds. *) 74 val json_t : t Jsont.t 75 76 (** [pp ppf t] pretty prints a feed. *) 77 val pp : Format.formatter -> t -> unit 78end 79 80(** {1 Contact Metadata} *) 81 82module Contact : sig 83 (** Individual contact metadata. 84 85 A contact represents metadata about a person, including their name(s), 86 social media handles, professional identifiers, and other contact information. *) 87 type t 88 89 (** [make ~handle ~names ?email ?icon ?thumbnail ?github ?twitter ?bluesky ?mastodon 90 ?orcid ?url ?feeds ()] creates a new contact. 91 92 @param handle A unique identifier/username for this contact (required) 93 @param names A list of names for this contact, with the first being primary (required) 94 @param email Email address 95 @param icon URL to an avatar/icon image 96 @param thumbnail Path to a local thumbnail image file 97 @param github GitHub username (without the [\@] prefix) 98 @param twitter Twitter/X username (without the [\@] prefix) 99 @param bluesky Bluesky handle 100 @param mastodon Mastodon handle (including instance) 101 @param orcid ORCID identifier 102 @param url Personal or professional website URL 103 @param feeds List of feed subscriptions (Atom/RSS/JSON) associated with this contact 104 *) 105 val make : 106 handle:string -> 107 names:string list -> 108 ?email:string -> 109 ?icon:string -> 110 ?thumbnail:string -> 111 ?github:string -> 112 ?twitter:string -> 113 ?bluesky:string -> 114 ?mastodon:string -> 115 ?orcid:string -> 116 ?url:string -> 117 ?feeds:Feed.t list -> 118 unit -> 119 t 120 121 (** {2 Accessors} *) 122 123 (** [handle t] returns the unique handle/username. *) 124 val handle : t -> string 125 126 (** [names t] returns all names associated with this contact. *) 127 val names : t -> string list 128 129 (** [name t] returns the primary (first) name. *) 130 val name : t -> string 131 132 (** [primary_name t] returns the primary (first) name. 133 This is an alias for {!name} for clarity. *) 134 val primary_name : t -> string 135 136 (** [email t] returns the email address if available. *) 137 val email : t -> string option 138 139 (** [icon t] returns the icon/avatar URL if available. *) 140 val icon : t -> string option 141 142 (** [thumbnail t] returns the path to the local thumbnail image if available. 143 This is a relative path from the Sortal data directory. *) 144 val thumbnail : t -> string option 145 146 (** [github t] returns the GitHub username if available. *) 147 val github : t -> string option 148 149 (** [twitter t] returns the Twitter/X username if available. *) 150 val twitter : t -> string option 151 152 (** [bluesky t] returns the Bluesky handle if available. *) 153 val bluesky : t -> string option 154 155 (** [mastodon t] returns the Mastodon handle if available. *) 156 val mastodon : t -> string option 157 158 (** [orcid t] returns the ORCID identifier if available. *) 159 val orcid : t -> string option 160 161 (** [url t] returns the personal/professional website URL if available. *) 162 val url : t -> string option 163 164 (** [feeds t] returns the list of feed subscriptions if available. *) 165 val feeds : t -> Feed.t list option 166 167 (** [add_feed t feed] returns a new contact with the feed added. *) 168 val add_feed : t -> Feed.t -> t 169 170 (** [remove_feed t url] returns a new contact with the feed matching the URL removed. *) 171 val remove_feed : t -> string -> t 172 173 (** {2 Derived Information} *) 174 175 (** [best_url t] returns the best available URL for this contact. 176 177 Priority order: 178 1. Personal URL (if set) 179 2. GitHub profile URL (if GitHub username is set) 180 3. Email as mailto: link (if email is set) 181 4. None if no URL-like information is available 182 *) 183 val best_url : t -> string option 184 185 (** {2 JSON Encoding} *) 186 187 (** [json_t] is the jsont encoder/decoder for contacts. 188 189 The JSON schema includes all contact fields with optional values 190 omitted when not present: 191 {[ 192 { 193 "handle": "avsm", 194 "names": ["Anil Madhavapeddy"], 195 "email": "anil@recoil.org", 196 "github": "avsm", 197 "orcid": "0000-0002-7890-1234" 198 } 199 ]} 200 *) 201 val json_t : t Jsont.t 202 203 (** {2 Utilities} *) 204 205 (** [compare a b] compares two contacts by their handles. *) 206 val compare : t -> t -> int 207 208 (** [pp ppf t] pretty prints a contact with formatting. *) 209 val pp : Format.formatter -> t -> unit 210end 211 212(** {1 Contact Store} *) 213 214(** The contact store manages reading and writing contact metadata 215 using XDG-compliant storage locations. *) 216type t 217 218(** [create fs app_name] creates a new contact store. 219 220 The store will use XDG data directories for persistent storage 221 of contact metadata. Each contact is stored as a separate JSON 222 file named after its handle. 223 224 @param fs Eio filesystem for file operations 225 @param app_name Application name for XDG directory structure 226 *) 227val create : Eio.Fs.dir_ty Eio.Path.t -> string -> t 228 229(** {2 Storage Operations} *) 230 231(** [save t contact] saves a contact to the store. 232 233 The contact is serialized to JSON and written to a file 234 named "handle.json" in the XDG data directory. 235 236 If a contact with the same handle already exists, it is overwritten. 237 *) 238val save : t -> Contact.t -> unit 239 240(** [lookup t handle] retrieves a contact by handle. 241 242 Searches for a file named "handle.json" in the XDG data directory 243 and deserializes it if found. 244 245 @return [Some contact] if found, [None] if not found or deserialization fails 246 *) 247val lookup : t -> string -> Contact.t option 248 249(** [delete t handle] removes a contact from the store. 250 251 Deletes the file "handle.json" from the XDG data directory. 252 Does nothing if the contact does not exist. 253 *) 254val delete : t -> string -> unit 255 256(** [list t] returns all contacts in the store. 257 258 Scans the XDG data directory for all .json files and attempts 259 to deserialize them as contacts. Files that fail to parse are 260 silently skipped. 261 262 @return A list of all successfully loaded contacts 263 *) 264val list : t -> Contact.t list 265 266(** [thumbnail_path t contact] returns the absolute filesystem path to the contact's thumbnail. 267 268 Returns [None] if the contact has no thumbnail set, or [Some path] with 269 the full path to the thumbnail file in Sortal's data directory. 270 271 @param t The Sortal store 272 @param contact The contact whose thumbnail path to retrieve *) 273val thumbnail_path : t -> Contact.t -> Eio.Fs.dir_ty Eio.Path.t option 274 275(** {2 Searching} *) 276 277(** [find_by_name t name] searches for contacts by name. 278 279 Performs a case-insensitive search through all contacts, 280 checking if any of their names match the provided name. 281 282 @param name The name to search for (case-insensitive) 283 @return The matching contact if exactly one match is found 284 @raise Not_found if no contacts match the name 285 @raise Invalid_argument if multiple contacts match the name 286 *) 287val find_by_name : t -> string -> Contact.t 288 289(** [find_by_name_opt t name] searches for contacts by name, returning an option. 290 291 Like {!find_by_name} but returns [None] instead of raising exceptions 292 when no match or multiple matches are found. 293 294 @param name The name to search for (case-insensitive) 295 @return [Some contact] if exactly one match is found, [None] otherwise 296 *) 297val find_by_name_opt : t -> string -> Contact.t option 298 299(** {2 Utilities} *) 300 301(** [handle_of_name name] generates a handle from a full name. 302 303 Creates a handle by concatenating the initials of all words 304 in the name with the full last name, all in lowercase. 305 306 Examples: 307 - "Anil Madhavapeddy" -> "ammadhavapeddy" 308 - "John Smith" -> "jssmith" 309 310 @param name The full name to convert 311 @return A suggested handle 312 *) 313val handle_of_name : string -> string 314 315(** {2 Convenience Functions} *) 316 317(** [create_from_xdg xdg] creates a contact store from an XDG context. 318 319 This is a convenience function for creating a store when you already 320 have an XDG context (e.g., from eiocmd or your own XDG initialization). 321 The store will use the XDG data directory for the application. 322 323 @param xdg An existing XDG context 324 @return A contact store using the XDG data directory 325 *) 326val create_from_xdg : Xdge.t -> t 327 328(** [search_all t query] searches for contacts matching a query string. 329 330 Performs a flexible search through all contact names, looking for: 331 - Exact matches (case-insensitive) 332 - Names that start with the query 333 - Multi-word names where any word starts with the query 334 335 This is useful for autocomplete or fuzzy search functionality. 336 337 @param t The contact store 338 @param query The search query (case-insensitive) 339 @return A list of matching contacts, sorted by handle 340 *) 341val search_all : t -> string -> Contact.t list 342 343(** {2 Pretty Printing} *) 344 345(** [pp ppf t] pretty prints the contact store showing statistics. *) 346val pp : Format.formatter -> t -> unit 347 348(** {1 Cmdliner Integration} *) 349 350module Cmd : sig 351 (** Cmdliner terms and commands for contact management. 352 353 This module provides ready-to-use Cmdliner terms for building 354 CLI applications that work with contact metadata. *) 355 356 (** [list_cmd] is a Cmdliner command that lists all contacts. 357 358 Usage: Integrate into your CLI with [Cmd.group] or use standalone. 359 Requires eiocmd setup (env, xdg, profile parameters). *) 360 val list_cmd : unit -> (Eio_unix.Stdenv.base -> Xdge.t -> 'a -> int) 361 362 (** [show_cmd handle] creates a command to show detailed contact information. 363 364 @param handle The contact handle to display *) 365 val show_cmd : string -> (Eio_unix.Stdenv.base -> Xdge.t -> 'a -> int) 366 367 (** [search_cmd query] creates a command to search contacts by name. 368 369 @param query The search query string *) 370 val search_cmd : string -> (Eio_unix.Stdenv.base -> Xdge.t -> 'a -> int) 371 372 (** [stats_cmd] is a command that shows database statistics. *) 373 val stats_cmd : unit -> (Eio_unix.Stdenv.base -> Xdge.t -> 'a -> int) 374 375 (** {2 Cmdliner Info Objects} *) 376 377 (** [list_info] is the command info for the list command. *) 378 val list_info : Cmdliner.Cmd.info 379 380 (** [show_info] is the command info for the show command. *) 381 val show_info : Cmdliner.Cmd.info 382 383 (** [search_info] is the command info for the search command. *) 384 val search_info : Cmdliner.Cmd.info 385 386 (** [stats_info] is the command info for the stats command. *) 387 val stats_info : Cmdliner.Cmd.info 388 389 (** {2 Cmdliner Argument Definitions} *) 390 391 (** [handle_arg] is the positional argument for a contact handle. *) 392 val handle_arg : string Cmdliner.Term.t 393 394 (** [query_arg] is the positional argument for a search query. *) 395 val query_arg : string Cmdliner.Term.t 396end