My agentic slop goes here. Not intended for anyone else!
0
fork

Configure Feed

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

more

+180 -199
+5
stack/requests/lib/requests.ml
··· 16 16 module Retry = Retry 17 17 module Cache = Cache 18 18 19 + (* Note: RNG initialization should be done by the application using 20 + Mirage_crypto_rng_unix.initialize before calling Eio_main.run. 21 + We don't call use_default() here as it spawns background threads 22 + that are incompatible with Eio's structured concurrency. *) 23 + 19 24 (* Main API - Session functionality with concurrent fiber spawning *) 20 25 21 26 type ('clock, 'net) t = {
+2 -12
stack/requests/lib/response.ml
··· 18 18 Eio.Switch.on_release sw (fun () -> 19 19 if not response.closed then begin 20 20 Log.debug (fun m -> m "Auto-closing response for %s via switch" url); 21 - try 22 - (* Read and discard remaining data *) 23 - let rec drain () = 24 - let buf = Cstruct.create 8192 in 25 - match Eio.Flow.single_read body buf with 26 - | 0 -> () (* EOF *) 27 - | _ -> drain () 28 - in 29 - drain (); 30 - response.closed <- true 31 - with _ -> 32 - response.closed <- true 21 + response.closed <- true; 22 + (* TODO Body cleanup is handled by the underlying HTTP library but test this *) 33 23 end 34 24 ); 35 25
+1 -1
stack/river/bin/dune
··· 1 1 (executable 2 2 (public_name river-cli) 3 3 (name river_cli) 4 - (libraries river cmdliner yojson logs logs.fmt logs.cli fmt fmt.tty fmt.cli eio_main unix ptime syndic)) 4 + (libraries river cmdliner yojson logs logs.fmt logs.cli fmt fmt.tty fmt.cli eio_main unix ptime syndic xdge))
+88 -103
stack/river/bin/river_cli.ml
··· 12 12 } 13 13 14 14 type state = { 15 - state_dir : Eio.Fs.dir_ty Eio.Path.t; 15 + xdg : Xdge.t; 16 16 } 17 17 18 18 (* State directory management *) 19 19 module State = struct 20 - let users_dir state = Eio.Path.(state.state_dir / "users") 21 - let feeds_dir state = Eio.Path.(state.state_dir / "feeds") 20 + let users_dir state = Eio.Path.(Xdge.state_dir state.xdg / "users") 21 + let feeds_dir state = Eio.Path.(Xdge.state_dir state.xdg / "feeds") 22 22 let user_feeds_dir state = Eio.Path.(feeds_dir state / "user") 23 23 24 24 let user_file state username = ··· 259 259 Ptime.compare b.updated a.updated 260 260 ) combined 261 261 262 - let sync_user env state ~username = 262 + let sync_user ~sw env state ~username = 263 263 match State.load_user state username with 264 264 | None -> 265 265 Log.err (fun m -> m "User %s not found" username); ··· 270 270 | Some user -> 271 271 Log.info (fun m -> m "Syncing feeds for user %s..." username); 272 272 273 - (* Fetch all feeds *) 273 + (* Create a single Requests session for all feeds *) 274 + let requests = Requests.create ~sw env 275 + ~follow_redirects:true 276 + ~max_redirects:5 in 277 + 278 + (* Fetch all feeds using the shared session and switch *) 274 279 let fetched_feeds = 275 280 List.filter_map (fun source -> 276 281 try 277 282 Log.info (fun m -> m " Fetching %s (%s)..." source.River.name source.River.url); 278 - Some (River.fetch env source) 283 + Some (River.fetch ~sw ~requests env source) 279 284 with e -> 280 285 Log.err (fun m -> m " Failed to fetch %s: %s" 281 286 source.River.name (Printexc.to_string e)); ··· 320 325 0 321 326 end 322 327 323 - let sync_all env state = 328 + let sync_all ~sw env state = 324 329 let users = State.list_users state in 325 330 if users = [] then begin 326 331 Log.info (fun m -> m "No users to sync"); ··· 329 334 Log.info (fun m -> m "Syncing %d users..." (List.length users)); 330 335 let results = 331 336 List.map (fun username -> 332 - let result = sync_user env state ~username in 337 + let result = sync_user ~sw env state ~username in 333 338 Log.debug (fun m -> m "Completed sync for user"); 334 339 result 335 340 ) users ··· 348 353 (* Cmdliner interface *) 349 354 open Cmdliner 350 355 351 - let state_dir = 352 - let doc = "State directory for storing user and feed data" in 353 - Arg.(value & opt string "~/.river" & info ["state-dir"; "d"] ~doc) 354 - 355 356 let username_arg = 356 357 let doc = "Username" in 357 358 Arg.(required & pos 0 (some string) None & info [] ~docv:"USERNAME" ~doc) ··· 380 381 let log_level = Logs_cli.level () 381 382 let log_style_renderer = Fmt_cli.style_renderer () 382 383 383 - (* Commands *) 384 - let user_add_cmd = 384 + (* Commands - these are created within Eio context *) 385 + let user_add_cmd fs = 385 386 let doc = "Add a new user" in 386 - let term = Term.(const (fun state_dir log_level style_renderer username fullname email -> 387 + let xdg_term = Xdge.Cmd.term "river" fs ~config:false ~data:false ~cache:false ~runtime:false () in 388 + let run log_level style_renderer (xdg, _cfg) username fullname email = 387 389 setup_logs style_renderer log_level; 388 - Eio_main.run @@ fun env -> 389 - let state_dir = 390 - let path = if String.starts_with ~prefix:"~" state_dir then 391 - Filename.concat (Sys.getenv "HOME") (String.sub state_dir 2 (String.length state_dir - 2)) 392 - else state_dir in 393 - Eio.Path.(Eio.Stdenv.fs env / path) 394 - in 395 - let state = { state_dir } in 390 + let state = { xdg } in 396 391 State.ensure_directories state; 397 392 User.add state ~username ~fullname ~email 398 - ) $ state_dir $ log_level $ log_style_renderer $ username_arg $ fullname_arg $ email_arg) in 393 + in 394 + let term = Term.(const run $ log_level $ log_style_renderer $ xdg_term $ username_arg $ fullname_arg $ email_arg) in 399 395 Cmd.v (Cmd.info "add" ~doc) term 400 396 401 - let user_remove_cmd = 397 + let user_remove_cmd fs = 402 398 let doc = "Remove a user" in 403 - let term = Term.(const (fun state_dir log_level style_renderer username -> 399 + let xdg_term = Xdge.Cmd.term "river" fs ~config:false ~data:false ~cache:false ~runtime:false () in 400 + let run log_level style_renderer (xdg, _cfg) username = 404 401 setup_logs style_renderer log_level; 405 - Eio_main.run @@ fun env -> 406 - let state_dir = 407 - let path = if String.starts_with ~prefix:"~" state_dir then 408 - Filename.concat (Sys.getenv "HOME") (String.sub state_dir 2 (String.length state_dir - 2)) 409 - else state_dir in 410 - Eio.Path.(Eio.Stdenv.fs env / path) 411 - in 412 - let state = { state_dir } in 402 + let state = { xdg } in 413 403 User.remove state ~username 414 - ) $ state_dir $ log_level $ log_style_renderer $ username_arg) in 404 + in 405 + let term = Term.(const run $ log_level $ log_style_renderer $ xdg_term $ username_arg) in 415 406 Cmd.v (Cmd.info "remove" ~doc) term 416 407 417 - let user_list_cmd = 408 + let user_list_cmd fs = 418 409 let doc = "List all users" in 419 - let term = Term.(const (fun state_dir log_level style_renderer -> 410 + let xdg_term = Xdge.Cmd.term "river" fs ~config:false ~data:false ~cache:false ~runtime:false () in 411 + let run log_level style_renderer (xdg, _cfg) = 420 412 setup_logs style_renderer log_level; 421 - Eio_main.run @@ fun env -> 422 - let state_dir = 423 - let path = if String.starts_with ~prefix:"~" state_dir then 424 - Filename.concat (Sys.getenv "HOME") (String.sub state_dir 2 (String.length state_dir - 2)) 425 - else state_dir in 426 - Eio.Path.(Eio.Stdenv.fs env / path) 427 - in 428 - let state = { state_dir } in 413 + let state = { xdg } in 429 414 User.list state 430 - ) $ state_dir $ log_level $ log_style_renderer) in 415 + in 416 + let term = Term.(const run $ log_level $ log_style_renderer $ xdg_term) in 431 417 Cmd.v (Cmd.info "list" ~doc) term 432 418 433 - let user_show_cmd = 419 + let user_show_cmd fs = 434 420 let doc = "Show user details" in 435 - let term = Term.(const (fun state_dir log_level style_renderer username -> 421 + let xdg_term = Xdge.Cmd.term "river" fs ~config:false ~data:false ~cache:false ~runtime:false () in 422 + let run log_level style_renderer (xdg, _cfg) username = 436 423 setup_logs style_renderer log_level; 437 - Eio_main.run @@ fun env -> 438 - let state_dir = 439 - let path = if String.starts_with ~prefix:"~" state_dir then 440 - Filename.concat (Sys.getenv "HOME") (String.sub state_dir 2 (String.length state_dir - 2)) 441 - else state_dir in 442 - Eio.Path.(Eio.Stdenv.fs env / path) 443 - in 444 - let state = { state_dir } in 424 + let state = { xdg } in 445 425 User.show state ~username 446 - ) $ state_dir $ log_level $ log_style_renderer $ username_arg) in 426 + in 427 + let term = Term.(const run $ log_level $ log_style_renderer $ xdg_term $ username_arg) in 447 428 Cmd.v (Cmd.info "show" ~doc) term 448 429 449 - let user_add_feed_cmd = 430 + let user_add_feed_cmd fs = 450 431 let doc = "Add a feed to a user" in 451 - let term = Term.(const (fun state_dir log_level style_renderer username name url -> 432 + let xdg_term = Xdge.Cmd.term "river" fs ~config:false ~data:false ~cache:false ~runtime:false () in 433 + let run log_level style_renderer (xdg, _cfg) username name url = 452 434 setup_logs style_renderer log_level; 453 - Eio_main.run @@ fun env -> 454 - let state_dir = 455 - let path = if String.starts_with ~prefix:"~" state_dir then 456 - Filename.concat (Sys.getenv "HOME") (String.sub state_dir 2 (String.length state_dir - 2)) 457 - else state_dir in 458 - Eio.Path.(Eio.Stdenv.fs env / path) 459 - in 460 - let state = { state_dir } in 435 + let state = { xdg } in 461 436 User.add_feed state ~username ~name ~url 462 - ) $ state_dir $ log_level $ log_style_renderer $ username_arg $ feed_name_arg $ feed_url_arg) in 437 + in 438 + let term = Term.(const run $ log_level $ log_style_renderer $ xdg_term $ username_arg $ feed_name_arg $ feed_url_arg) in 463 439 Cmd.v (Cmd.info "add-feed" ~doc) term 464 440 465 - let user_remove_feed_cmd = 441 + let user_remove_feed_cmd fs = 466 442 let doc = "Remove a feed from a user" in 467 - let term = Term.(const (fun state_dir log_level style_renderer username url -> 443 + let xdg_term = Xdge.Cmd.term "river" fs ~config:false ~data:false ~cache:false ~runtime:false () in 444 + let run log_level style_renderer (xdg, _cfg) username url = 468 445 setup_logs style_renderer log_level; 469 - Eio_main.run @@ fun env -> 470 - let state_dir = 471 - let path = if String.starts_with ~prefix:"~" state_dir then 472 - Filename.concat (Sys.getenv "HOME") (String.sub state_dir 2 (String.length state_dir - 2)) 473 - else state_dir in 474 - Eio.Path.(Eio.Stdenv.fs env / path) 475 - in 476 - let state = { state_dir } in 446 + let state = { xdg } in 477 447 User.remove_feed state ~username ~url 478 - ) $ state_dir $ log_level $ log_style_renderer $ username_arg $ feed_url_arg) in 448 + in 449 + let term = Term.(const run $ log_level $ log_style_renderer $ xdg_term $ username_arg $ feed_url_arg) in 479 450 Cmd.v (Cmd.info "remove-feed" ~doc) term 480 451 481 - let user_cmd = 452 + let user_cmd fs = 482 453 let doc = "Manage users" in 483 454 let info = Cmd.info "user" ~doc in 484 455 Cmd.group info [ 485 - user_add_cmd; 486 - user_remove_cmd; 487 - user_list_cmd; 488 - user_show_cmd; 489 - user_add_feed_cmd; 490 - user_remove_feed_cmd; 456 + user_add_cmd fs; 457 + user_remove_cmd fs; 458 + user_list_cmd fs; 459 + user_show_cmd fs; 460 + user_add_feed_cmd fs; 461 + user_remove_feed_cmd fs; 491 462 ] 492 463 493 - let sync_cmd = 464 + let sync_cmd fs env = 494 465 let doc = "Sync feeds for users" in 466 + let xdg_term = Xdge.Cmd.term "river" fs ~config:false ~data:false ~cache:false ~runtime:false () in 495 467 let username_opt = 496 468 let doc = "Sync specific user (omit to sync all)" in 497 469 Arg.(value & pos 0 (some string) None & info [] ~docv:"USERNAME" ~doc) 498 470 in 499 - let term = Term.(const (fun state_dir log_level style_renderer username_opt -> 471 + let run log_level style_renderer (xdg, _cfg) username_opt = 500 472 setup_logs style_renderer log_level; 501 - Eio_main.run @@ fun env -> 502 - let state_dir = 503 - let path = if String.starts_with ~prefix:"~" state_dir then 504 - Filename.concat (Sys.getenv "HOME") (String.sub state_dir 2 (String.length state_dir - 2)) 505 - else state_dir in 506 - Eio.Path.(Eio.Stdenv.fs env / path) 473 + let state = { xdg } in 474 + State.ensure_directories state; 475 + (* Create a switch for all sync operations *) 476 + Logs.info (fun m -> m "Creating switch for sync operations"); 477 + let result = Eio.Switch.run @@ fun sw -> 478 + Logs.info (fun m -> m "Switch created, running sync"); 479 + let res = match username_opt with 480 + | Some username -> Sync.sync_user ~sw env state ~username 481 + | None -> Sync.sync_all ~sw env state 482 + in 483 + Logs.info (fun m -> m "Sync completed, about to exit switch"); 484 + res 507 485 in 508 - let state = { state_dir } in 509 - State.ensure_directories state; 510 - match username_opt with 511 - | Some username -> Sync.sync_user env state ~username 512 - | None -> Sync.sync_all env state 513 - ) $ state_dir $ log_level $ log_style_renderer $ username_opt) in 486 + Logs.info (fun m -> m "Switch exited, returning result %d" result); 487 + result 488 + in 489 + let term = Term.(const run $ log_level $ log_style_renderer $ xdg_term $ username_opt) in 514 490 Cmd.v (Cmd.info "sync" ~doc) term 515 491 516 - let main_cmd = 492 + let main_cmd fs env = 517 493 let doc = "River feed management CLI" in 518 494 let info = Cmd.info "river-cli" ~version:"1.0" ~doc in 519 - Cmd.group info [user_cmd; sync_cmd] 495 + Cmd.group info [user_cmd fs; sync_cmd fs env] 520 496 521 497 let () = 522 - exit (Cmd.eval' main_cmd) 498 + (* Initialize the Mirage_crypto RNG for TLS. 499 + Note: This spawns a background thread for entropy collection. *) 500 + Mirage_crypto_rng_unix.use_default (); 501 + 502 + let exit_code = ref 0 in 503 + Eio_main.run @@ fun env -> 504 + exit_code := Cmd.eval' (main_cmd env#fs env); 505 + Logs.info (fun m -> m "About to exit Eio_main.run"); 506 + Logs.info (fun m -> m "Exited Eio_main.run, calling exit %d" !exit_code); 507 + exit !exit_code
+1
stack/river/dune-project
··· 37 37 cmdliner 38 38 yojson 39 39 fmt 40 + xdge 40 41 (odoc :with-doc)))
+2 -1
stack/river/example/aggregate_feeds.ml
··· 9 9 ] 10 10 11 11 let main env = 12 - let feeds = List.map (River.fetch env) sources in 12 + Eio.Switch.run @@ fun sw -> 13 + let feeds = List.map (River.fetch ~sw env) sources in 13 14 let posts = River.posts feeds in 14 15 let entries = River.create_atom_entries posts in 15 16 let feed =
+2 -2
stack/river/lib/feed.ml
··· 44 44 msg (fst pos) (snd pos)); 45 45 failwith "Neither Atom nor RSS2 feed") 46 46 47 - let fetch env (source : source) = 47 + let fetch ~sw ?requests env (source : source) = 48 48 Log.info (fun m -> m "Fetching feed '%s' from %s" source.name source.url); 49 49 50 50 let xmlbase = Uri.of_string @@ source.url in 51 51 let response = 52 - try Http.get env source.url 52 + try Http.get ~sw ?requests env source.url 53 53 with e -> 54 54 Log.err (fun m -> m "Failed to fetch feed '%s': %s" source.name (Printexc.to_string e)); 55 55 raise e
+10 -7
stack/river/lib/http.ml
··· 23 23 exception Status_unhandled of string 24 24 exception Timeout 25 25 26 - let get env url = 26 + let get ~sw ?requests env url = 27 27 Log.info (fun m -> m "Fetching URL: %s" url); 28 28 29 - Eio.Switch.run @@ fun sw -> 30 29 try 31 - (* Create a Requests session with automatic redirect following *) 32 - let req = Requests.create ~sw env 33 - ~follow_redirects:true 34 - ~max_redirects:5 in 30 + (* Create or use existing Requests session with automatic redirect following *) 31 + let req = match requests with 32 + | Some r -> r 33 + | None -> 34 + Requests.create ~sw env 35 + ~follow_redirects:true 36 + ~max_redirects:5 37 + in 35 38 36 - Log.debug (fun m -> m "Created Requests session with max_redirects=5"); 39 + Log.debug (fun m -> m "Using Requests session with max_redirects=5"); 37 40 38 41 (* Make the GET request with timeout *) 39 42 let response =
-25
stack/river/lib/http.mli
··· 1 - (* 2 - * Copyright (c) 2014, OCaml.org project 3 - * Copyright (c) 2015 KC Sivaramakrishnan <sk826@cl.cam.ac.uk> 4 - * 5 - * Permission to use, copy, modify, and distribute this software for any 6 - * purpose with or without fee is hereby granted, provided that the above 7 - * copyright notice and this permission notice appear in all copies. 8 - * 9 - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 - *) 17 - 18 - exception Status_unhandled of string 19 - exception Timeout 20 - 21 - val get : Eio_unix.Stdenv.base -> string -> string 22 - (** [get env uri] returns the body of the response of the HTTP GET request on [uri]. 23 - 24 - If the answer is a redirection, it will follow the redirections up to 5 25 - redirects. Uses the provided Eio environment. *)
+2 -2
stack/river/lib/river.ml
··· 22 22 type feed = Feed.t 23 23 type post = Post.t 24 24 25 - let fetch env source = 25 + let fetch ~sw ?requests env source = 26 26 Log.info (fun m -> m "Fetching feed: %s" source.name); 27 - Feed.fetch env source 27 + Feed.fetch ~sw ?requests env source 28 28 29 29 let name feed = feed.Feed.name 30 30 let url feed = feed.Feed.url
+15 -4
stack/river/lib/river.mli
··· 21 21 type feed 22 22 type post 23 23 24 - val fetch : Eio_unix.Stdenv.base -> source -> feed 25 - (** [fetch env source] returns an Atom or RSS feed from a source 26 - using the provided Eio environment. *) 24 + val fetch : 25 + sw:Eio.Switch.t -> 26 + ?requests:(([> float Eio.Time.clock_ty ] as 'a) Eio.Resource.t, ([> [> `Generic ] Eio.Net.ty ] as 'b) Eio.Resource.t) Requests.t -> 27 + < clock : 'a Eio.Resource.t; 28 + fs : Eio.Fs.dir_ty Eio.Path.t; 29 + net : 'b Eio.Resource.t; .. > -> 30 + source -> 31 + feed 32 + (** [fetch ~sw ?requests env source] returns an Atom or RSS feed from a source. 33 + 34 + @param sw The switch to use for resource cleanup 35 + @param requests Optional Requests session to reuse. If not provided, a new session will be created 36 + @param env The Eio environment 37 + @param source The feed source to fetch *) 27 38 28 39 val name : feed -> string 29 40 (** [name feed] is the name of the feed source passed to [fetch]. *) ··· 71 82 72 83 val create_atom_entries : post list -> Syndic.Atom.entry list 73 84 (** [create_atom_feed posts] creates a list of atom entries, which can then be 74 - used to create an atom feed that is an aggregate of the posts. *) 85 + used to create an atom feed that is an aggregate of the posts. *)
+1
stack/river/river.opam
··· 22 22 "cmdliner" 23 23 "yojson" 24 24 "fmt" 25 + "xdge" 25 26 "odoc" {with-doc} 26 27 ] 27 28 build: [
+2 -1
stack/river/test/test_eio_river.ml
··· 9 9 let main env = 10 10 Printf.printf "Testing River library with Eio and Requests...\n"; 11 11 12 + Eio.Switch.run @@ fun sw -> 12 13 (* Test fetching feeds *) 13 14 let feeds = 14 15 try 15 - List.map (River.fetch env) test_sources 16 + List.map (River.fetch ~sw env) test_sources 16 17 with 17 18 | River__Http.Status_unhandled msg -> 18 19 Printf.printf "HTTP error: %s\n" msg;
+2 -1
stack/river/test/test_logging.ml
··· 27 27 (* Test with logging *) 28 28 Printf.printf "Testing River library with logging...\n\n"; 29 29 30 + Eio.Switch.run @@ fun sw -> 30 31 (* Demonstrate fetching with logging *) 31 32 let feeds = 32 33 try 33 - List.map (River.fetch env) test_sources 34 + List.map (River.fetch ~sw env) test_sources 34 35 with 35 36 | River__Http.Status_unhandled msg -> 36 37 Printf.printf "Expected error (for demo): %s\n" msg;
+15 -13
stack/zulip/examples/bot_example.ml
··· 1 1 (* Simple Bot Example using core Zulip library *) 2 2 3 - let () = 3 + let () = Eio_main.run @@ fun env -> 4 + Eio.Switch.run @@ fun sw -> 5 + 4 6 Printf.printf "OCaml Zulip Bot Example\n"; 5 7 Printf.printf "=======================\n\n"; 6 - 8 + 7 9 (* Create test authentication *) 8 - let auth = Zulip.Auth.create 10 + let auth = Zulip.Auth.create 9 11 ~server_url:"https://example.zulipchat.com" 10 - ~email:"bot@example.com" 12 + ~email:"bot@example.com" 11 13 ~api_key:"example-api-key" in 12 - 14 + 13 15 Printf.printf "✅ Created authentication for: %s\n" (Zulip.Auth.email auth); 14 16 Printf.printf "✅ Server URL: %s\n" (Zulip.Auth.server_url auth); 15 - 17 + 16 18 (* Create client *) 17 - let client = Zulip.Client.create () auth in 19 + let client = Zulip.Client.create ~sw env auth in 18 20 let client_str = Format.asprintf "%a" Zulip.Client.pp client in 19 21 Printf.printf "✅ Created client: %s\n" client_str; 20 - 22 + 21 23 (* Test message creation *) 22 24 let message = Zulip.Message.create 23 25 ~type_:`Channel ··· 25 27 ~content:"Hello from OCaml bot!" 26 28 ~topic:"Bot Testing" 27 29 () in 28 - 30 + 29 31 Printf.printf "✅ Created message to: %s\n" (String.concat ", " (Zulip.Message.to_ message)); 30 32 Printf.printf "✅ Message content: %s\n" (Zulip.Message.content message); 31 33 Printf.printf "✅ Message topic: %s\n" (match Zulip.Message.topic message with Some t -> t | None -> "none"); 32 - 34 + 33 35 (* Test API call (mock) *) 34 36 (match Zulip.Client.request client ~method_:`GET ~path:"/users/me" () with 35 37 | Ok response -> 36 - Printf.printf "✅ API request successful: %s\n" 37 - (match response with 38 + Printf.printf "✅ API request successful: %s\n" 39 + (match response with 38 40 | `O fields -> String.concat ", " (List.map fst fields) 39 41 | _ -> "unknown format") 40 42 | Error err -> 41 43 Printf.printf "❌ API request failed: %s\n" (Zulip.error_message err)); 42 - 44 + 43 45 Printf.printf "\n🎉 Bot example completed successfully!\n"; 44 46 Printf.printf "Note: This uses mock responses since we're not connected to a real Zulip server.\n"
+22 -20
stack/zulip/examples/example.ml
··· 1 1 open Zulip 2 2 3 - let () = 3 + let () = Eio_main.run @@ fun env -> 4 + Eio.Switch.run @@ fun sw -> 5 + 4 6 Printf.printf "OCaml Zulip Library Example\n"; 5 7 Printf.printf "===========================\n\n"; 6 - 8 + 7 9 (* Create authentication *) 8 - let auth = Auth.create 10 + let auth = Auth.create 9 11 ~server_url:"https://example.zulipchat.com" 10 - ~email:"bot@example.com" 12 + ~email:"bot@example.com" 11 13 ~api_key:"your-api-key" in 12 - 14 + 13 15 Printf.printf "Created auth for: %s\n" (Auth.email auth); 14 16 Printf.printf "Server URL: %s\n" (Auth.server_url auth); 15 - 17 + 16 18 (* Create a message *) 17 - let message = Message.create 19 + let message = Message.create 18 20 ~type_:`Channel 19 21 ~to_:["general"] 20 22 ~content:"Hello from OCaml Zulip library!" 21 23 ~topic:"Test" 22 24 () in 23 - 25 + 24 26 Printf.printf "\nCreated message:\n"; 25 27 Printf.printf "- Type: %s\n" (Message_type.to_string (Message.type_ message)); 26 28 Printf.printf "- To: %s\n" (String.concat ", " (Message.to_ message)); 27 29 Printf.printf "- Content: %s\n" (Message.content message); 28 - Printf.printf "- Topic: %s\n" 30 + Printf.printf "- Topic: %s\n" 29 31 (match Message.topic message with Some t -> t | None -> "None"); 30 - 32 + 31 33 (* Test JSON serialization *) 32 34 let json = Message.to_json message in 33 - Printf.printf "\nMessage JSON: %s\n" 34 - (match json with 35 - | `O _ -> "JSON object (serialized correctly)" 35 + Printf.printf "\nMessage JSON: %s\n" 36 + (match json with 37 + | `O _ -> "JSON object (serialized correctly)" 36 38 | _ -> "Invalid JSON"); 37 - 38 - (* Create client (mock) *) 39 - let client = Client.create () auth in 40 - Printf.printf "\nCreated mock client\n"; 41 - 39 + 40 + (* Create client *) 41 + let client = Client.create ~sw env auth in 42 + Printf.printf "\nCreated client\n"; 43 + 42 44 (* Test basic client request *) 43 45 (match Client.request client ~method_:`GET ~path:"/test" () with 44 46 | Ok _ -> Printf.printf "Mock request succeeded\n" 45 - | Error err -> Printf.printf "Mock request failed: %s\n" (Error.message err)); 46 - 47 + | Error err -> Printf.printf "Mock request failed: %s\n" (Zulip.error_message err)); 48 + 47 49 Printf.printf "\nLibrary is working correctly!\n"
+9 -7
stack/zulip/examples/toml_example.ml
··· 33 33 Printf.printf " Auth Header: %s\n" (Auth.to_basic_auth_header auth); 34 34 35 35 (* Test creating client *) 36 - let client = Client.create () auth in 36 + Eio_main.run @@ fun env -> 37 + Eio.Switch.run @@ fun sw -> 38 + let client = Client.create ~sw env auth in 37 39 Printf.printf "✅ Created client successfully\n\n"; 38 - 40 + 39 41 (* Test basic functionality *) 40 42 (match Client.request client ~method_:`GET ~path:"/users/me" () with 41 43 | Ok _response -> Printf.printf "✅ Mock API request succeeded\n" 42 - | Error err -> Printf.printf "❌ API request failed: %s\n" (Error.message err)) 44 + | Error err -> Printf.printf "❌ API request failed: %s\n" (Zulip.error_message err)) 43 45 | Error err -> 44 - Printf.printf "❌ Failed to load auth from TOML: %s\n" (Error.message err)); 46 + Printf.printf "❌ Failed to load auth from TOML: %s\n" (Zulip.error_message err)); 45 47 46 48 (* Example 2: Root-level TOML configuration *) 47 49 let root_toml_content = {| ··· 62 64 Printf.printf " Email: %s\n" (Auth.email auth); 63 65 Printf.printf " Server: %s\n" (Auth.server_url auth) 64 66 | Error err -> 65 - Printf.printf "❌ Failed to parse root-level TOML: %s\n" (Error.message err)); 67 + Printf.printf "❌ Failed to parse root-level TOML: %s\n" (Zulip.error_message err)); 66 68 67 69 (* Example 3: Test error handling with invalid TOML *) 68 70 let invalid_toml = {| ··· 78 80 Printf.printf "\nTesting error handling with invalid TOML:\n"; 79 81 (match Auth.from_zuliprc ~path:invalid_file () with 80 82 | Ok _ -> Printf.printf "❌ Should have failed with invalid TOML\n" 81 - | Error err -> Printf.printf "✅ Correctly handled invalid TOML: %s\n" (Error.message err)); 83 + | Error err -> Printf.printf "✅ Correctly handled invalid TOML: %s\n" (Zulip.error_message err)); 82 84 83 85 (* Example 4: Test missing file handling *) 84 86 Printf.printf "\nTesting missing file handling:\n"; 85 87 (match Auth.from_zuliprc ~path:"nonexistent.toml" () with 86 88 | Ok _ -> Printf.printf "❌ Should have failed with missing file\n" 87 - | Error err -> Printf.printf "✅ Correctly handled missing file: %s\n" (Error.message err)); 89 + | Error err -> Printf.printf "✅ Correctly handled missing file: %s\n" (Zulip.error_message err)); 88 90 89 91 (* Clean up *) 90 92 List.iter (fun file ->
+1
stack/zulip/lib/zulip_bot/lib/dune
··· 1 1 (library 2 2 (public_name zulip_bot) 3 3 (name zulip_bot) 4 + (wrapped true) 4 5 (libraries zulip unix eio logs mirage-crypto-rng fmt) 5 6 (flags (:standard -warn-error -3)))