My agentic slop goes here. Not intended for anyone else!
1(** Command-line interface for cache management *)
2
3open Cmdliner
4open Eio
5
6let list_cmd ~app_name fs _stdin =
7 let list cache_dir stale expired pinned temporary older_than =
8 let base_dir = Path.(fs / cache_dir) in
9 let cache = Cacheio.create ~base_dir in
10 let entries = Cacheio.scan cache in
11
12 (* Filter entries based on flags *)
13 let filtered = List.filter (fun e ->
14 let open Cacheio.Entry in
15 (not stale || is_stale e) &&
16 (not expired || is_expired e) &&
17 (not pinned || is_pinned e) &&
18 (not temporary || is_temporary e) &&
19 (match older_than with
20 | None -> true
21 | Some days ->
22 let age = (Unix.time () -. mtime e) /. 86400. in
23 age > float_of_int days)
24 ) entries in
25
26 (* Print entries *)
27 List.iter (fun e ->
28 let open Cacheio.Entry in
29 let flags_str = Cacheio.Flags.pp Format.str_formatter (flags e);
30 Format.flush_str_formatter () in
31 let ttl_str = match ttl e with
32 | None -> "never"
33 | Some t -> Printf.sprintf "%.0f" t
34 in
35 let size = size e in
36 let size_str =
37 if size < 1024L then Printf.sprintf "%Ld B" size
38 else if size < 1048576L then Printf.sprintf "%.1f KB" (Int64.to_float size /. 1024.)
39 else Printf.sprintf "%.1f MB" (Int64.to_float size /. 1048576.)
40 in
41 Printf.printf "%s %s %s %s %s\n"
42 (String.sub (Cacheio.Entry.key e) 0 16)
43 size_str
44 ttl_str
45 flags_str
46 (if Cacheio.Entry.is_expired e then "[EXPIRED]" else "")
47 ) filtered;
48
49 Printf.printf "\nTotal: %d entries\n" (List.length filtered)
50 in
51
52 let stale = Arg.(value & flag & info ["stale"; "s"] ~doc:"Show only stale entries") in
53 let expired = Arg.(value & flag & info ["expired"; "e"] ~doc:"Show only expired entries") in
54 let pinned = Arg.(value & flag & info ["pinned"; "p"] ~doc:"Show only pinned entries") in
55 let temporary = Arg.(value & flag & info ["temporary"; "t"] ~doc:"Show only temporary entries") in
56 let older_than =
57 let doc = "Show entries older than N days" in
58 Arg.(value & opt (some int) None & info ["older-than"] ~docv:"DAYS" ~doc)
59 in
60
61 let doc = "List cache entries" in
62 let info = Cmd.info "list" ~doc in
63 let cache_dir_term = Xdge.Cmd.cache_term app_name in
64 Cmd.v info Term.(const list $ cache_dir_term $ stale $ expired $ pinned $ temporary $ older_than)
65
66let expire_cmd ~app_name fs _stdin =
67 let expire cache_dir =
68 let base_dir = Path.(fs / cache_dir) in
69 let cache = Cacheio.create ~base_dir in
70 let count = Cacheio.expire cache in
71 Printf.printf "Expired %d entries\n" count
72 in
73
74 let doc = "Remove expired entries from cache" in
75 let info = Cmd.info "expire" ~doc in
76 let cache_dir_term = Xdge.Cmd.cache_term app_name in
77 Cmd.v info Term.(const expire $ cache_dir_term)
78
79let clear_cmd ~app_name fs _stdin =
80 let clear cache_dir all temporary =
81 let base_dir = Path.(fs / cache_dir) in
82 let cache = Cacheio.create ~base_dir in
83
84 if all then begin
85 Cacheio.clear cache;
86 Printf.printf "Cleared all entries (except pinned)\n"
87 end else if temporary then begin
88 Cacheio.clear_temporary cache;
89 Printf.printf "Cleared temporary entries\n"
90 end else begin
91 Printf.printf "Specify --all or --temporary to clear cache\n";
92 exit 1
93 end
94 in
95
96 let all = Arg.(value & flag & info ["all"; "a"] ~doc:"Clear all entries (except pinned)") in
97 let temporary = Arg.(value & flag & info ["temporary"; "t"] ~doc:"Clear only temporary entries") in
98
99 let doc = "Clear cache entries" in
100 let info = Cmd.info "clear" ~doc in
101 let cache_dir_term = Xdge.Cmd.cache_term app_name in
102 Cmd.v info Term.(const clear $ cache_dir_term $ all $ temporary)
103
104let flag_cmd ~app_name fs _stdin =
105 let flag cache_dir key add remove set_flags =
106 let base_dir = Path.(fs / cache_dir) in
107 let cache = Cacheio.create ~base_dir in
108
109 (* Parse flags *)
110 let parse_flag_str s =
111 match String.lowercase_ascii s with
112 | "pinned" | "p" -> Some `Pinned
113 | "stale" | "s" -> Some `Stale
114 | "temporary" | "t" -> Some `Temporary
115 | _ -> None
116 in
117
118 (* Handle operations *)
119 match key with
120 | None ->
121 Printf.printf "Error: key required\n";
122 exit 1
123 | Some k ->
124 (* Add flags *)
125 List.iter (fun flag_str ->
126 match parse_flag_str flag_str with
127 | Some flag ->
128 Cacheio.add_flag cache ~key:k flag;
129 Printf.printf "Added flag %s to %s\n" flag_str k
130 | None ->
131 Printf.printf "Unknown flag: %s\n" flag_str
132 ) add;
133
134 (* Remove flags *)
135 List.iter (fun flag_str ->
136 match parse_flag_str flag_str with
137 | Some flag ->
138 Cacheio.remove_flag cache ~key:k flag;
139 Printf.printf "Removed flag %s from %s\n" flag_str k
140 | None ->
141 Printf.printf "Unknown flag: %s\n" flag_str
142 ) remove;
143
144 (* Set flags (replace all) *)
145 (match set_flags with
146 | [] -> ()
147 | flags ->
148 let parsed = List.filter_map parse_flag_str flags in
149 Cacheio.set_flags cache ~key:k (Cacheio.Flags.of_list parsed);
150 Printf.printf "Set flags for %s\n" k)
151 in
152
153 let key = Arg.(value & pos 0 (some string) None & info [] ~docv:"KEY" ~doc:"Cache key to modify") in
154 let add =
155 let doc = "Add flag (pinned|stale|temporary)" in
156 Arg.(value & opt_all string [] & info ["add"; "a"] ~doc)
157 in
158 let remove =
159 let doc = "Remove flag (pinned|stale|temporary)" in
160 Arg.(value & opt_all string [] & info ["remove"; "r"] ~doc)
161 in
162 let set_flags =
163 let doc = "Set flags (replaces all existing)" in
164 Arg.(value & opt_all string [] & info ["set"] ~doc)
165 in
166
167 let doc = "Manage cache entry flags" in
168 let info = Cmd.info "flag" ~doc in
169 let cache_dir_term = Xdge.Cmd.cache_term app_name in
170 Cmd.v info Term.(const flag $ cache_dir_term $ key $ add $ remove $ set_flags)
171
172let stats_cmd ~app_name fs _stdin =
173 let stats cache_dir =
174 let base_dir = Path.(fs / cache_dir) in
175 let cache = Cacheio.create ~base_dir in
176 let stats = Cacheio.stats cache in
177
178 let size_str size =
179 if size < 1024L then Printf.sprintf "%Ld B" size
180 else if size < 1048576L then Printf.sprintf "%.1f KB" (Int64.to_float size /. 1024.)
181 else if size < 1073741824L then Printf.sprintf "%.1f MB" (Int64.to_float size /. 1048576.)
182 else Printf.sprintf "%.1f GB" (Int64.to_float size /. 1073741824.)
183 in
184
185 let open Cacheio.Stats in
186 Printf.printf "Cache Statistics:\n";
187 Printf.printf " Total entries: %d\n" (entry_count stats);
188 Printf.printf " Total size: %s\n" (size_str (total_size stats));
189 Printf.printf " Expired entries: %d\n" (expired_count stats);
190 Printf.printf " Pinned entries: %d\n" (pinned_count stats);
191 Printf.printf " Stale entries: %d\n" (stale_count stats);
192 Printf.printf " Temporary entries: %d\n" (temporary_count stats)
193 in
194
195 let doc = "Show cache statistics" in
196 let info = Cmd.info "stats" ~doc in
197 let cache_dir_term = Xdge.Cmd.cache_term app_name in
198 Cmd.v info Term.(const stats $ cache_dir_term)
199
200let put_cmd ~app_name fs stdin =
201 let put cache_dir key file ttl pinned stale temporary =
202 let base_dir = Path.(fs / cache_dir) in
203 let cache = Cacheio.create ~base_dir in
204
205 (* Build flags from options *)
206 let flags =
207 let open Cacheio.Flags in
208 let f = empty in
209 let f = if pinned then add `Pinned f else f in
210 let f = if stale then add `Stale f else f in
211 let f = if temporary then add `Temporary f else f in
212 f
213 in
214
215 (* Read from file or stdin *)
216 Switch.run @@ fun sw ->
217 let source = match file with
218 | Some path ->
219 let file_path = Path.(fs / path) in
220 (Path.open_in ~sw file_path :> Eio.Flow.source_ty Eio.Resource.t)
221 | None ->
222 (* Use stdin passed from the CLI *)
223 stdin
224 in
225
226 (* Put into cache *)
227 Cacheio.put cache ~key ~source ?ttl:(Some ttl) ~flags ();
228 Printf.printf "Cached entry with key: %s\n" key;
229
230 (* Show info about what was cached *)
231 match Cacheio.size cache ~key with
232 | Some size -> Printf.printf "Size: %Ld bytes\n" size
233 | None -> ()
234 in
235
236 let key = Arg.(required & pos 0 (some string) None & info [] ~docv:"KEY"
237 ~doc:"Cache key for the entry") in
238 let file = Arg.(value & opt (some string) None & info ["file"; "f"] ~docv:"FILE"
239 ~doc:"File to cache (default: read from stdin)") in
240 let ttl = Arg.(value & opt (some float) None & info ["ttl"] ~docv:"SECONDS"
241 ~doc:"Time-to-live in seconds") in
242 let pinned = Arg.(value & flag & info ["pinned"; "p"] ~doc:"Mark entry as pinned") in
243 let stale = Arg.(value & flag & info ["stale"; "s"] ~doc:"Mark entry as stale") in
244 let temporary = Arg.(value & flag & info ["temporary"; "t"] ~doc:"Mark entry as temporary") in
245
246 let doc = "Put data into cache" in
247 let man = [
248 `S "DESCRIPTION";
249 `P "Store data in the cache with the specified key. Data can be read from a file or stdin.";
250 `P "Optionally set TTL (time-to-live) and flags (pinned, stale, temporary).";
251 `S "EXAMPLES";
252 `P "Cache a file: $(b,cache put mykey -f data.txt)";
253 `P "Cache from stdin: $(b,echo 'Hello' | cache put mykey)";
254 `P "Cache with TTL: $(b,cache put mykey -f data.txt --ttl 3600)";
255 `P "Cache as pinned: $(b,cache put mykey -f data.txt --pinned)";
256 ] in
257 let info = Cmd.info "put" ~doc ~man in
258 let cache_dir_term = Xdge.Cmd.cache_term app_name in
259 Cmd.v info Term.(const put $ cache_dir_term $ key $ file $ ttl $ pinned $ stale $ temporary)
260
261let get_cmd ~app_name fs _stdin =
262 let get cache_dir key output =
263 let base_dir = Path.(fs / cache_dir) in
264 let cache = Cacheio.create ~base_dir in
265
266 (* Get from cache *)
267 Switch.run @@ fun sw ->
268 match Cacheio.get cache ~key ~sw with
269 | None ->
270 Printf.eprintf "Key not found: %s\n" key;
271 exit 1
272 | Some source ->
273 (* Write to file or stdout *)
274 match output with
275 | Some path ->
276 let file_path = Path.(fs / path) in
277 let sink = Path.open_out ~sw ~create:(`Or_truncate 0o644) file_path in
278 Eio.Flow.copy source sink;
279 Printf.eprintf "Wrote cached data to: %s\n" path
280 | None ->
281 (* For stdout, we need to read the content and print it *)
282 let buf = Buffer.create 4096 in
283 Eio.Flow.copy source (Eio.Flow.buffer_sink buf);
284 print_string (Buffer.contents buf)
285 in
286
287 let key = Arg.(required & pos 0 (some string) None & info [] ~docv:"KEY"
288 ~doc:"Cache key to retrieve") in
289 let output = Arg.(value & opt (some string) None & info ["output"; "o"] ~docv:"FILE"
290 ~doc:"Output file (default: write to stdout)") in
291
292 let doc = "Get data from cache" in
293 let man = [
294 `S "DESCRIPTION";
295 `P "Retrieve data from the cache using the specified key. Data can be written to a file or stdout.";
296 `S "EXAMPLES";
297 `P "Get to stdout: $(b,cache get mykey)";
298 `P "Get to file: $(b,cache get mykey -o output.txt)";
299 `P "Use in pipeline: $(b,cache get mykey | grep pattern)";
300 ] in
301 let info = Cmd.info "get" ~doc ~man in
302 let cache_dir_term = Xdge.Cmd.cache_term app_name in
303 Cmd.v info Term.(const get $ cache_dir_term $ key $ output)
304
305(** Delete command - remove a specific cache entry *)
306let delete_cmd ~app_name fs _stdin =
307 let delete cache_dir key =
308 let base_dir = Path.(fs / cache_dir) in
309 let cache = Cacheio.create ~base_dir in
310
311 if Cacheio.exists cache ~key then begin
312 Cacheio.delete cache ~key;
313 Printf.printf "Deleted cache entry: %s\n" key
314 end else begin
315 Printf.eprintf "Key not found: %s\n" key;
316 exit 1
317 end
318 in
319
320 let key = Arg.(required & pos 0 (some string) None & info [] ~docv:"KEY"
321 ~doc:"Cache key to delete") in
322
323 let doc = "Delete a cache entry" in
324 let man = [
325 `S "DESCRIPTION";
326 `P "Remove a specific cache entry by key.";
327 `S "EXAMPLES";
328 `P "Delete entry: $(b,cache delete mykey)";
329 ] in
330 let info = Cmd.info "delete" ~doc ~man in
331 let cache_dir_term = Xdge.Cmd.cache_term app_name in
332 Cmd.v info Term.(const delete $ cache_dir_term $ key)
333
334(** Exists command - check if a key exists *)
335let exists_cmd ~app_name fs _stdin =
336 let exists cache_dir key quiet =
337 let base_dir = Path.(fs / cache_dir) in
338 let cache = Cacheio.create ~base_dir in
339
340 if Cacheio.exists cache ~key then begin
341 if not quiet then Printf.printf "Key exists: %s\n" key;
342 exit 0
343 end else begin
344 if not quiet then Printf.printf "Key does not exist: %s\n" key;
345 exit 1
346 end
347 in
348
349 let key = Arg.(required & pos 0 (some string) None & info [] ~docv:"KEY"
350 ~doc:"Cache key to check") in
351 let quiet = Arg.(value & flag & info ["quiet"; "q"]
352 ~doc:"Suppress output, use exit code only") in
353
354 let doc = "Check if a cache entry exists" in
355 let man = [
356 `S "DESCRIPTION";
357 `P "Check if a cache entry exists. Returns exit code 0 if exists, 1 if not.";
358 `S "EXAMPLES";
359 `P "Check existence: $(b,cache exists mykey)";
360 `P "Use in scripts: $(b,if cache exists mykey -q; then echo 'Found'; fi)";
361 ] in
362 let info = Cmd.info "exists" ~doc ~man in
363 let cache_dir_term = Xdge.Cmd.cache_term app_name in
364 Cmd.v info Term.(const exists $ cache_dir_term $ key $ quiet)
365
366(** {1 Command Groups} *)
367
368let make_commands ~app_name fs stdin = [
369 get_cmd ~app_name fs stdin;
370 put_cmd ~app_name fs stdin;
371 delete_cmd ~app_name fs stdin;
372 exists_cmd ~app_name fs stdin;
373 list_cmd ~app_name fs stdin;
374 expire_cmd ~app_name fs stdin;
375 clear_cmd ~app_name fs stdin;
376 flag_cmd ~app_name fs stdin;
377 stats_cmd ~app_name fs stdin
378]
379
380let make_cmd ~app_name fs stdin =
381 let doc = "Cache management commands" in
382 let info = Cmd.info "cache" ~doc in
383 Cmd.group info (make_commands ~app_name fs stdin)