A fork of mtelver's day10 project
0
fork

Configure Feed

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

feat(web): add package data layer with tests

Add package_data module for reading package information from the html
directory. Includes functions for listing packages, versions, and
checking if docs exist for specific packages.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+174 -1
+4
tests/unit/dune
··· 13 13 (executable 14 14 (name test_run_data) 15 15 (libraries day10_web_data unix yojson)) 16 + 17 + (executable 18 + (name test_package_data) 19 + (libraries day10_web_data unix))
+94
tests/unit/test_package_data.ml
··· 1 + (** Unit tests for package data reading *) 2 + 3 + let test_dir = ref "" 4 + 5 + let setup () = 6 + let dir = Filename.temp_dir "test-pkg-data-" "" in 7 + test_dir := dir; 8 + dir 9 + 10 + let teardown () = 11 + if !test_dir <> "" then begin 12 + ignore (Sys.command (Printf.sprintf "rm -rf %s" !test_dir)); 13 + test_dir := "" 14 + end 15 + 16 + let mkdir_p path = 17 + let rec create dir = 18 + if not (Sys.file_exists dir) then begin 19 + create (Filename.dirname dir); 20 + try Unix.mkdir dir 0o755 with Unix.Unix_error (Unix.EEXIST, _, _) -> () 21 + end 22 + in 23 + create path 24 + 25 + (** Test: list_packages returns packages from html/p directory *) 26 + let test_list_packages () = 27 + let base_dir = setup () in 28 + let html_dir = Filename.concat base_dir "html" in 29 + mkdir_p (Filename.concat html_dir "p/base/0.16.0"); 30 + mkdir_p (Filename.concat html_dir "p/base/0.15.0"); 31 + mkdir_p (Filename.concat html_dir "p/core/0.16.0"); 32 + 33 + let packages = Day10_web_data.Package_data.list_packages ~html_dir in 34 + assert (List.length packages = 3); 35 + assert (List.mem ("base", "0.16.0") packages); 36 + assert (List.mem ("base", "0.15.0") packages); 37 + assert (List.mem ("core", "0.16.0") packages); 38 + 39 + teardown (); 40 + Printf.printf "PASS: test_list_packages\n%!" 41 + 42 + (** Test: list_package_versions returns versions for a package *) 43 + let test_list_package_versions () = 44 + let base_dir = setup () in 45 + let html_dir = Filename.concat base_dir "html" in 46 + mkdir_p (Filename.concat html_dir "p/base/0.16.0"); 47 + mkdir_p (Filename.concat html_dir "p/base/0.15.0"); 48 + mkdir_p (Filename.concat html_dir "p/base/0.14.0"); 49 + 50 + let versions = Day10_web_data.Package_data.list_package_versions ~html_dir ~name:"base" in 51 + assert (List.length versions = 3); 52 + (* Should be sorted descending *) 53 + assert (List.hd versions = "0.16.0"); 54 + 55 + teardown (); 56 + Printf.printf "PASS: test_list_package_versions\n%!" 57 + 58 + (** Test: package_has_docs checks if docs exist *) 59 + let test_package_has_docs () = 60 + let base_dir = setup () in 61 + let html_dir = Filename.concat base_dir "html" in 62 + mkdir_p (Filename.concat html_dir "p/base/0.16.0"); 63 + 64 + assert (Day10_web_data.Package_data.package_has_docs ~html_dir ~name:"base" ~version:"0.16.0"); 65 + assert (not (Day10_web_data.Package_data.package_has_docs ~html_dir ~name:"base" ~version:"0.15.0")); 66 + 67 + teardown (); 68 + Printf.printf "PASS: test_package_has_docs\n%!" 69 + 70 + (** Test: list_package_names returns unique package names *) 71 + let test_list_package_names () = 72 + let base_dir = setup () in 73 + let html_dir = Filename.concat base_dir "html" in 74 + mkdir_p (Filename.concat html_dir "p/base/0.16.0"); 75 + mkdir_p (Filename.concat html_dir "p/base/0.15.0"); 76 + mkdir_p (Filename.concat html_dir "p/core/0.16.0"); 77 + mkdir_p (Filename.concat html_dir "p/async/0.16.0"); 78 + 79 + let names = Day10_web_data.Package_data.list_package_names ~html_dir in 80 + assert (List.length names = 3); 81 + assert (List.mem "base" names); 82 + assert (List.mem "core" names); 83 + assert (List.mem "async" names); 84 + 85 + teardown (); 86 + Printf.printf "PASS: test_list_package_names\n%!" 87 + 88 + let () = 89 + Printf.printf "Running Package_data tests...\n%!"; 90 + test_list_packages (); 91 + test_list_package_versions (); 92 + test_package_has_docs (); 93 + test_list_package_names (); 94 + Printf.printf "\nAll Package_data tests passed!\n%!"
+2 -1
web/data/dune
··· 1 1 (library 2 2 (name day10_web_data) 3 - (libraries unix yojson day10_lib)) 3 + (libraries unix yojson day10_lib) 4 + (modules run_data package_data))
+58
web/data/package_data.ml
··· 1 + (** Read package data from day10's html directory *) 2 + 3 + let list_package_names ~html_dir = 4 + let p_dir = Filename.concat html_dir "p" in 5 + if Sys.file_exists p_dir && Sys.is_directory p_dir then 6 + Sys.readdir p_dir 7 + |> Array.to_list 8 + |> List.filter (fun name -> 9 + let path = Filename.concat p_dir name in 10 + Sys.is_directory path) 11 + |> List.sort String.compare 12 + else 13 + [] 14 + 15 + let compare_versions v1 v2 = 16 + (* Simple version comparison - compare segments numerically where possible *) 17 + let parse v = 18 + String.split_on_char '.' v 19 + |> List.map (fun s -> try `Int (int_of_string s) with _ -> `Str s) 20 + in 21 + let rec cmp l1 l2 = match l1, l2 with 22 + | [], [] -> 0 23 + | [], _ -> -1 24 + | _, [] -> 1 25 + | `Int a :: t1, `Int b :: t2 -> 26 + let c = Int.compare a b in if c <> 0 then c else cmp t1 t2 27 + | `Str a :: t1, `Str b :: t2 -> 28 + let c = String.compare a b in if c <> 0 then c else cmp t1 t2 29 + | `Int _ :: _, `Str _ :: _ -> -1 30 + | `Str _ :: _, `Int _ :: _ -> 1 31 + in 32 + cmp (parse v2) (parse v1) (* Descending order *) 33 + 34 + let list_package_versions ~html_dir ~name = 35 + let pkg_dir = Filename.concat (Filename.concat html_dir "p") name in 36 + if Sys.file_exists pkg_dir && Sys.is_directory pkg_dir then 37 + Sys.readdir pkg_dir 38 + |> Array.to_list 39 + |> List.filter (fun version -> 40 + let path = Filename.concat pkg_dir version in 41 + Sys.is_directory path) 42 + |> List.sort compare_versions 43 + else 44 + [] 45 + 46 + let list_packages ~html_dir = 47 + list_package_names ~html_dir 48 + |> List.concat_map (fun name -> 49 + list_package_versions ~html_dir ~name 50 + |> List.map (fun version -> (name, version))) 51 + 52 + let package_has_docs ~html_dir ~name ~version = 53 + let path = Filename.concat html_dir 54 + (Filename.concat "p" (Filename.concat name version)) in 55 + Sys.file_exists path && Sys.is_directory path 56 + 57 + let docs_path ~name ~version = 58 + Printf.sprintf "/docs/p/%s/%s/" name version
+16
web/data/package_data.mli
··· 1 + (** Read package data from day10's html directory *) 2 + 3 + (** List all (name, version) pairs with docs *) 4 + val list_packages : html_dir:string -> (string * string) list 5 + 6 + (** List unique package names *) 7 + val list_package_names : html_dir:string -> string list 8 + 9 + (** List all versions for a package name, sorted descending *) 10 + val list_package_versions : html_dir:string -> name:string -> string list 11 + 12 + (** Check if docs exist for a package version *) 13 + val package_has_docs : html_dir:string -> name:string -> version:string -> bool 14 + 15 + (** Get the docs URL path for a package *) 16 + val docs_path : name:string -> version:string -> string