Kitty Graphics Protocol in OCaml
terminal
graphics
ocaml
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(* Kitty Graphics Protocol Response - Implementation *)
7
8type t = {
9 message : string;
10 image_id : int option;
11 image_number : int option;
12 placement_id : int option;
13}
14
15let is_ok t = t.message = "OK"
16let message t = t.message
17
18let error_code t =
19 if is_ok t then None
20 else
21 String.index_opt t.message ':'
22 |> Option.fold ~none:(Some t.message) ~some:(fun i ->
23 Some (String.sub t.message 0 i))
24
25let image_id t = t.image_id
26let image_number t = t.image_number
27let placement_id t = t.placement_id
28
29let parse s =
30 let ( let* ) = Option.bind in
31 let esc = '\027' in
32 let len = String.length s in
33 let* () =
34 if len >= 5 && s.[0] = esc && s.[1] = '_' && s.[2] = 'G' then Some ()
35 else None
36 in
37 let* semi_pos = String.index_from_opt s 3 ';' in
38 let rec find_end pos =
39 if pos + 1 < len && s.[pos] = esc && s.[pos + 1] = '\\' then Some pos
40 else if pos + 1 < len then find_end (pos + 1)
41 else None
42 in
43 let* end_pos = find_end (semi_pos + 1) in
44 let keys_str = String.sub s 3 (semi_pos - 3) in
45 let message = String.sub s (semi_pos + 1) (end_pos - semi_pos - 1) in
46 let parse_kv part =
47 if String.length part >= 3 && part.[1] = '=' then
48 Some (part.[0], String.sub part 2 (String.length part - 2))
49 else None
50 in
51 let keys = String.split_on_char ',' keys_str |> List.filter_map parse_kv in
52 let find_int key =
53 List.assoc_opt key keys |> Fun.flip Option.bind int_of_string_opt
54 in
55 Some
56 {
57 message;
58 image_id = find_int 'i';
59 image_number = find_int 'I';
60 placement_id = find_int 'p';
61 }