summaryrefslogtreecommitdiff
path: root/lib/util.ml
blob: 30b8fef1e86540f2689f1055bb1489be727917b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
(*─────────────────────────────────────────────────────────────────────────────┐
│ SPDX-FileCopyrightText: 2025 toastal <https://toast.al/contact/>             │
│ SPDX-License-Identifier: LGPL-2.1-or-later WITH OCaml-LGPL-linking-exception │
└─────────────────────────────────────────────────────────────────────────────*)
module Formatter = struct
	let to_flow pp flow =
		let buffer = Buffer.create 4096 in
		let fmt = Format.formatter_of_buffer buffer in
		pp fmt;
		Format.pp_print_flush fmt ();
		Eio.Flow.copy_string (Buffer.contents buffer) flow
end

module Jsont = struct
	include Jsont

	let encode_tag tag_code encoder v = [|
		(Json.encode uint8) tag_code |> Result.get_ok;
		Json.encode encoder v |> Result.get_ok;
	|]

	let pp_piset fmt json =
		let rec pp_value fmt = function
			| Jsont.Null _ -> Fmt.pf fmt "%a" Jsont.pp_null ()
			| Jsont.Bool (b, _) -> Fmt.pf fmt "%a" Jsont.pp_bool b
			| Jsont.Number (f, _) -> Fmt.pf fmt "%a" Jsont.pp_number f
			| Jsont.String (s, _) -> Fmt.pf fmt "%a" Jsont.pp_string s
			| Jsont.Array (arr, _) ->
				Fmt.pf fmt "@[<hov>[%a]@]" (Fmt.list ~sep: (Fmt.any ",@ ") pp_value) arr
			| Jsont.Object (obj, _) ->
				let pp_mem fmt ((k, _), v) =
					Fmt.pf fmt "@[<hv>%a: %a@]" Jsont.pp_string k pp_value v
				in
				Fmt.pf fmt "@[<hv>{@;<0 1>%a@;<0 0>}@]" (Fmt.list ~sep: (Fmt.any ",@ ") pp_mem) obj
		in
		pp_value fmt json

	let of_flow codec flow =
		try
			let buf = Eio.Buf_read.of_flow flow ~max_size: max_int in
			let str = Eio.Buf_read.take_all buf in
			Jsont_bytesrw.decode_string codec str
		with
			| exn -> Error (Printexc.to_string exn)

	let to_flow codec value flow =
		match Jsont_bytesrw.encode_string codec value with
		| Ok str ->
			Eio.Flow.copy_string str flow;
			Ok ()
		| Error err -> Error err

	let to_flow_piset codec value flow =
		match Jsont.Json.encode codec value with
		| Ok json ->
			let buffer = Buffer.create 4096 in
			let fmt = Format.formatter_of_buffer buffer in
			let base_fmt = Format.pp_get_formatter_out_functions fmt () in
			let tabbed_fmt = {base_fmt with
				out_indent = (fun n -> base_fmt.out_string (String.make n '\t') 0 n)
			}
			in
			Format.pp_set_formatter_out_functions fmt tabbed_fmt;
			pp_piset fmt json;
			Format.pp_print_flush fmt ();
			Eio.Flow.copy_string (Buffer.contents buffer) flow;
			Ok ()
		| Error err -> Error err
end

module KDL = struct
	let of_flow flow =
		try
			Eio.Buf_read.parse_exn
				(fun buf -> Eio.Buf_read.take_all buf |> Kdl.of_string)
				~max_size: max_int
				flow
		with
			| exn -> failwith (Printexc.to_string exn)

	let to_flow flow doc =
		Eio.Buf_write.with_flow flow @@ fun buf ->
		let out_string s off len =
			String.sub s off len |> Eio.Buf_write.string buf
		in
		let flush () = () in
		let ppf = Format.make_formatter out_string flush in

		(* replace spaces with tabs for indentation *)
		let base_fmt = Format.pp_get_formatter_out_functions ppf () in
		let tabbed_fmt = {base_fmt with
			out_indent = (fun n -> base_fmt.out_string (String.make n '\t') 0 n)
		}
		in
		Format.pp_set_formatter_out_functions ppf tabbed_fmt;

		(* enable utf-8 and pretty-print *)
		Fmt.set_utf_8 ppf true;
		Kdl.pp ppf doc;
		Format.pp_print_flush ppf ()

	module L = Kdl_lens_result

	module Valid = struct
		type err = [
			| L.lerr
			| `ParseError of Kdl.error
			| `OneRequired of string list
			| `OnlyOneOf of string list
			| `InvalidLatestCmd of string
			| `InvalidHashAlgorithm of string
		]
		[@@deriving show]
		type 'a t = ('a, err list) result
		let pp ~(ok : 'a Fmt.t) : 'a t Fmt.t =
			Fmt.result ~ok ~error: (Fmt.list pp_err)
		let map = Result.map
		let map1 (f : 'a -> 'b) (vx : ('a, err) result) : 'b t =
			match vx with
			| Ok x -> Ok (f x)
			| Error e -> Error [e]
		let map_error = Result.map_error
		let pure x = Ok x
		let and_map (vx : 'a t) (vf : ('a -> 'b) t) =
			match vx, vf with
			| Ok x, Ok f -> Ok (f x)
			| Error e, Ok _ -> Error e
			| Ok _, Error e -> Error e
			| Error e1, Error e2 -> Error (e2 @ e1)
		let product (vx : 'a t) (vy : 'b t) =
			match vx, vy with
			| Ok x, Ok y -> Ok (x, y)
			| Error e, Ok _ -> Error e
			| Ok _, Error e -> Error e
			| Error e1, Error e2 -> Error (e2 @ e1)
		let and_map1 (vx : ('a, err) result) (vf : ('a -> 'b) t) =
			match vx, vf with
			| Ok x, Ok f -> Ok (f x)
			| Error e, Ok _ -> Error [e]
			| Ok _, Error e -> Error e
			| Error e1, Error e2 -> Error (e2 @ [e1])
		let bind = Result.bind
		let lift_lens (r : ('a, L.lerr) result) : ('a, err list) result =
			Result.map_error (fun e -> ([e] :> err list)) r
		let ll = lift_lens
		let (let+) vx f = map f vx
		let (and+) = product
		let (let*) = bind
	end

	type 'a codec = {
		to_kdl: 'a -> Kdl.t;
		of_kdl: Kdl.t -> 'a Valid.t
	}

	type 'a node_codec = {
		to_node: 'a -> Kdl.node;
		of_node: Kdl.node -> 'a Valid.t
	}
end

module These = struct
	type ('a, 'b) t =
		| This of 'a
		| That of 'b
		| These of 'a * 'b
	[@@deriving show]

	let map_both f g = function
		| This a -> This (f a)
		| That b -> That (g b)
		| These (a, b) -> These (f a, g b)

	let map_this f =
		map_both f Fun.id

	let map_that g =
		map_both Fun.id g

	let these f g h = function
		| This a -> f a
		| That b -> g b
		| These (a, b) -> h a b

	let merge h =
		these Fun.id Fun.id h

	let merge_with h f g =
		these f g (fun a b -> h (f a) (g b))
end

module URI = struct
	let jsont : Uri.t Jsont.t =
		Jsont.string
		|> Jsont.map ~kind: "URI" ~dec: Uri.of_string ~enc: Uri.to_string
end