OCaml CLI and library to the Karakeep bookmarking app

Add title and bookmark_title functions

Added two helper functions to extract meaningful titles:
- title: Extracts a title from bookmark content based on its type
- bookmark_title: Gets the best available title for a bookmark
combining custom title and content-derived title

Updated all test files to use these functions for better display.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

+55 -4
+38
lib/karakeep.ml
··· 364 364 | Some "asset" -> Asset (parse_asset_content json) 365 365 | _ -> Unknown 366 366 367 + (** Extract a meaningful title from bookmark content *) 368 + let title = function 369 + | Link lc -> 370 + begin match lc.title with 371 + | Some t when String.trim t <> "" -> t 372 + | _ -> lc.url 373 + end 374 + | Text tc -> 375 + let text_excerpt = 376 + let s = tc.text in 377 + let max_len = 40 in 378 + if String.length s <= max_len then s 379 + else String.sub s 0 max_len ^ "..." 380 + in 381 + begin match tc.source_url with 382 + | Some url -> "Text from " ^ url 383 + | None -> "Text: " ^ text_excerpt 384 + end 385 + | Asset ac -> 386 + begin match ac.file_name with 387 + | Some fn -> fn 388 + | None -> 389 + begin match ac.source_url with 390 + | Some url -> "Asset from " ^ url 391 + | None -> 392 + match ac.asset_type with 393 + | `Image -> "Image asset" 394 + | `PDF -> "PDF asset" 395 + end 396 + end 397 + | Unknown -> "Unnamed bookmark" 398 + 367 399 (** Parse asset from JSON *) 368 400 let parse_asset json = 369 401 { ··· 418 450 (try parse_content (J.find json [ "content" ]) with _ -> Unknown); 419 451 assets; 420 452 } 453 + 454 + (** Get the best available title for a bookmark *) 455 + let bookmark_title bookmark = 456 + match bookmark.title with 457 + | Some t when String.trim t <> "" -> t 458 + | _ -> title bookmark.content 421 459 422 460 (** Parse paginated bookmarks from JSON *) 423 461 let parse_paginated_bookmarks json =
+12
lib/karakeep.mli
··· 149 149 | Asset of asset_content (** Asset-type content *) 150 150 | Unknown (** Unknown content type *) 151 151 152 + val title : content -> string 153 + (** [title content] extracts a meaningful title from the bookmark content. 154 + For Link content, it returns the title if available, otherwise the URL. 155 + For Text content, it returns a short excerpt from the text. 156 + For Asset content, it returns the filename if available, otherwise a generic title. 157 + For Unknown content, it returns a generic title. *) 158 + 152 159 type asset = { 153 160 id : asset_id; (** ID of the asset *) 154 161 asset_type : asset_type; (** Type of the asset *) ··· 177 184 assets : asset list; (** Assets attached to the bookmark *) 178 185 } 179 186 (** A bookmark from the Karakeep service *) 187 + 188 + val bookmark_title : bookmark -> string 189 + (** [bookmark_title bookmark] returns the best available title for a bookmark. 190 + It prioritizes the bookmark's title field if available, and falls back to 191 + extracting a title from the bookmark's content. *) 180 192 181 193 type paginated_bookmarks = { 182 194 bookmarks : bookmark list; (** List of bookmarks in the current page *)
+3 -1
test/asset_test.ml
··· 34 34 Lwt.return_unit 35 35 | Some bookmark -> ( 36 36 (* Print assets info *) 37 + let bookmark_title_str = bookmark_title bookmark in 37 38 let url = 38 39 match bookmark.content with 39 40 | Link lc -> lc.url ··· 41 42 | Asset ac -> Option.value ac.source_url ~default:"(asset content)" 42 43 | Unknown -> "(unknown content)" 43 44 in 44 - Printf.printf "Found bookmark with %d assets: %s\n" 45 + Printf.printf "Found bookmark \"%s\" with %d assets: %s\n" 46 + bookmark_title_str 45 47 (List.length bookmark.assets) 46 48 url; 47 49
+1 -2
test/create_test.ml
··· 28 28 create_bookmark ~api_key ~url ~title ~tags base_url >>= fun bookmark -> 29 29 Printf.printf "Successfully created bookmark:\n"; 30 30 Printf.printf "- ID: %s\n" bookmark.id; 31 - Printf.printf "- Title: %s\n" 32 - (match bookmark.title with Some t -> t | None -> "(No title)"); 31 + Printf.printf "- Title: %s\n" (bookmark_title bookmark); 33 32 34 33 let url = 35 34 match bookmark.content with
+1 -1
test/test.ml
··· 2 2 open Karakeep 3 3 4 4 let print_bookmark bookmark = 5 - let title = match bookmark.title with Some t -> t | None -> "(No title)" in 5 + let title = bookmark_title bookmark in 6 6 let url = 7 7 match bookmark.content with 8 8 | Link lc -> lc.url