A fork of mtelver's day10 project
at main 140 lines 5.0 kB view raw
1(** Read run data from day10's log directory *) 2 3let list_runs ~log_dir = 4 let runs_dir = Filename.concat log_dir "runs" in 5 if Sys.file_exists runs_dir && Sys.is_directory runs_dir then 6 Sys.readdir runs_dir 7 |> Array.to_list 8 |> List.filter (fun name -> 9 let path = Filename.concat runs_dir name in 10 Sys.is_directory path) 11 |> List.sort (fun a b -> String.compare b a) (* Descending *) 12 else 13 [] 14 15let get_latest_run_id ~log_dir = 16 let latest = Filename.concat log_dir "latest" in 17 if Sys.file_exists latest then 18 try 19 let target = Unix.readlink latest in 20 (* Target is like "runs/2026-02-04-120000" *) 21 Some (Filename.basename target) 22 with Unix.Unix_error _ -> None 23 else 24 None 25 26(** Get the most recent run, including runs in progress. 27 This scans the runs/ directory directly rather than relying on the 28 'latest' symlink which is only created when a run completes. *) 29let get_most_recent_run_id ~log_dir = 30 match list_runs ~log_dir with 31 | [] -> None 32 | most_recent :: _ -> Some most_recent 33 34let read_summary ~log_dir ~run_id = 35 let path = Filename.concat log_dir 36 (Filename.concat "runs" (Filename.concat run_id "summary.json")) in 37 if Sys.file_exists path then 38 try 39 let content = In_channel.with_open_text path In_channel.input_all in 40 let json = Yojson.Safe.from_string content in 41 let open Yojson.Safe.Util in 42 let failures = 43 json |> member "failures" |> to_list 44 |> List.map (fun f -> 45 (f |> member "package" |> to_string, 46 f |> member "error" |> to_string)) 47 in 48 Some { 49 Day10_lib.Run_log.run_id = json |> member "run_id" |> to_string; 50 start_time = json |> member "start_time" |> to_string; 51 end_time = json |> member "end_time" |> to_string; 52 duration_seconds = json |> member "duration_seconds" |> to_float; 53 targets_requested = json |> member "targets_requested" |> to_int; 54 solutions_found = json |> member "solutions_found" |> to_int; 55 build_success = json |> member "build_success" |> to_int; 56 build_failed = json |> member "build_failed" |> to_int; 57 doc_success = json |> member "doc_success" |> to_int; 58 doc_failed = json |> member "doc_failed" |> to_int; 59 doc_skipped = json |> member "doc_skipped" |> to_int; 60 failures; 61 } 62 with _ -> None 63 else 64 None 65 66let read_log_file path = 67 if Sys.file_exists path then 68 try Some (In_channel.with_open_text path In_channel.input_all) 69 with _ -> None 70 else 71 None 72 73let read_build_log ~log_dir ~run_id ~package = 74 let path = Filename.concat log_dir 75 (Filename.concat "runs" 76 (Filename.concat run_id 77 (Filename.concat "build" (package ^ ".log")))) in 78 read_log_file path 79 80let read_doc_log ~log_dir ~run_id ~package = 81 let path = Filename.concat log_dir 82 (Filename.concat "runs" 83 (Filename.concat run_id 84 (Filename.concat "docs" (package ^ ".log")))) in 85 read_log_file path 86 87let list_logs_in_dir dir = 88 if Sys.file_exists dir && Sys.is_directory dir then 89 Sys.readdir dir 90 |> Array.to_list 91 |> List.filter (fun name -> Filename.check_suffix name ".log") 92 |> List.map (fun name -> Filename.chop_suffix name ".log") 93 |> List.sort String.compare 94 else 95 [] 96 97let list_build_logs ~log_dir ~run_id = 98 let dir = Filename.concat log_dir 99 (Filename.concat "runs" (Filename.concat run_id "build")) in 100 list_logs_in_dir dir 101 102let list_doc_logs ~log_dir ~run_id = 103 let dir = Filename.concat log_dir 104 (Filename.concat "runs" (Filename.concat run_id "docs")) in 105 list_logs_in_dir dir 106 107let has_build_log ~log_dir ~run_id ~package = 108 let path = Filename.concat log_dir 109 (Filename.concat "runs" 110 (Filename.concat run_id 111 (Filename.concat "build" (package ^ ".log")))) in 112 Sys.file_exists path 113 114let has_doc_log ~log_dir ~run_id ~package = 115 let path = Filename.concat log_dir 116 (Filename.concat "runs" 117 (Filename.concat run_id 118 (Filename.concat "docs" (package ^ ".log")))) in 119 Sys.file_exists path 120 121let is_run_in_progress ~log_dir ~run_id = 122 let summary_path = Filename.concat log_dir 123 (Filename.concat "runs" (Filename.concat run_id "summary.json")) in 124 (* If no summary.json exists, the run is likely still in progress *) 125 not (Sys.file_exists summary_path) 126 127let get_package_status_from_summary ~log_dir ~run_id ~package = 128 match read_summary ~log_dir ~run_id with 129 | None -> None 130 | Some summary -> 131 (* Check if package is in the failures list *) 132 match List.find_opt (fun (pkg, _) -> pkg = package) summary.failures with 133 | Some (_, error) -> Some (`Failed error) 134 | None -> 135 (* Not in failures - check if logs exist to confirm it was processed *) 136 if has_build_log ~log_dir ~run_id ~package || 137 has_doc_log ~log_dir ~run_id ~package then 138 Some `Success 139 else 140 Some `Not_in_run