Views: Using ocp-indent to indent everything consistently + fixed code aesthetics (80 char rule etc.)

This commit is contained in:
rand00 2022-02-02 23:27:25 +01:00
parent c17802d84f
commit ae5c5cb67d

View file

@ -29,8 +29,10 @@ type nav = [
| `Comparison of (string * Builder_db.Build.t) * (string * Builder_db.Build.t) | `Comparison of (string * Builder_db.Build.t) * (string * Builder_db.Build.t)
] ]
let pp_platform = Fmt.(option ~none:(any "") (append (any "on ") string)) let pp_platform =
let pp_platform_query = Fmt.(option ~none:(any "") (append (any "?platform=") string)) Fmt.(option ~none:(any "") (append (any "on ") string))
let pp_platform_query =
Fmt.(option ~none:(any "") (append (any "?platform=") string))
let static_css = Tyxml.Html.Unsafe.data {| let static_css = Tyxml.Html.Unsafe.data {|
body { body {
@ -89,14 +91,28 @@ let layout ?include_static_css ?nav:(nav_=`Default) ~title:title_ body_ =
| `Default -> | `Default ->
to_nav [txt "Home", "/"] to_nav [txt "Home", "/"]
| `Job (job_name, platform) -> | `Job (job_name, platform) ->
to_nav [txt "Home", "/"; txtf "Job %s" job_name, Fmt.str "/job/%s/" job_name ; txtf "%a" pp_platform platform, Fmt.str "/job/%s/%a" job_name pp_platform_query platform ] to_nav [
txt "Home", "/";
txtf "Job %s" job_name, Fmt.str "/job/%s/" job_name ;
(
txtf "%a" pp_platform platform,
Fmt.str "/job/%s/%a" job_name pp_platform_query platform
)
]
| `Build (job_name, build) -> | `Build (job_name, build) ->
to_nav [ to_nav [
txt "Home", "/"; txt "Home", "/";
txtf "Job %s" job_name, Fmt.str "/job/%s/" job_name; txtf "Job %s" job_name, Fmt.str "/job/%s/" job_name;
txtf "%a" pp_platform (Some build.Builder_db.Build.platform), Fmt.str "/job/%s/%a" job_name pp_platform_query (Some build.Builder_db.Build.platform); (
txtf "%a" pp_platform (Some build.Builder_db.Build.platform),
Fmt.str "/job/%s/%a"
job_name
pp_platform_query (Some build.Builder_db.Build.platform)
);
txtf "Build %a" pp_ptime build.Builder_db.Build.start, txtf "Build %a" pp_ptime build.Builder_db.Build.start,
Fmt.str "/job/%s/build/%a/" job_name Uuidm.pp build.Builder_db.Build.uuid; Fmt.str "/job/%s/build/%a/"
job_name
Uuidm.pp build.Builder_db.Build.uuid;
] ]
| `Comparison ((job_left, build_left), (job_right, build_right)) -> | `Comparison ((job_left, build_left), (job_right, build_right)) ->
to_nav [ to_nav [
@ -104,7 +120,9 @@ let layout ?include_static_css ?nav:(nav_=`Default) ~title:title_ body_ =
txtf "Comparison between %s@%a and %s@%a" txtf "Comparison between %s@%a and %s@%a"
job_left pp_ptime build_left.Builder_db.Build.start job_left pp_ptime build_left.Builder_db.Build.start
job_right pp_ptime build_right.Builder_db.Build.start, job_right pp_ptime build_right.Builder_db.Build.start,
Fmt.str "/compare/%a/%a/" Uuidm.pp build_left.uuid Uuidm.pp build_right.uuid; Fmt.str "/compare/%a/%a/"
Uuidm.pp build_left.uuid
Uuidm.pp build_right.uuid;
] ]
in in
(*> Note: Last declared CSS wins - so one can override here*) (*> Note: Last declared CSS wins - so one can override here*)
@ -112,7 +130,7 @@ let layout ?include_static_css ?nav:(nav_=`Default) ~title:title_ body_ =
in in
html html
(head (title (txt title_)) (head (title (txt title_))
[style ~a:[a_mime_type "text/css"] static_css]) [style ~a:[a_mime_type "text/css"] static_css])
(body [ (body [
breadcrumb; breadcrumb;
@ -130,10 +148,10 @@ let toggleable ?(hidden=true) id description content =
[txt description]; [txt description];
input input
~a:(checked @ [ ~a:(checked @ [
a_input_type `Checkbox; a_input_type `Checkbox;
a_id id; a_id id;
a_style "display: none;"; a_style "display: none;";
]) (); ]) ();
div div
~a:[ ~a:[
a_class ["toggleable"] a_class ["toggleable"]
@ -141,15 +159,24 @@ let toggleable ?(hidden=true) id description content =
content; content;
] ]
let artifact ?(basename=false) job_name build { Builder_db.filepath; localpath = _; sha256; size } = let artifact
?(basename=false)
job_name
build
{ Builder_db.filepath; localpath = _; sha256; size }
=
let artifact_link =
Fmt.str "/job/%s/build/%a/f/%a"
job_name
Uuidm.pp build.Builder_db.Build.uuid
Fpath.pp filepath
in
[ [
a ~a:[a_href (Fmt.str "/job/%s/build/%a/f/%a" a ~a:[a_href artifact_link]
job_name [
Uuidm.pp build.Builder_db.Build.uuid if basename then txt (Fpath.basename filepath)
Fpath.pp filepath)] else txtf "%a" Fpath.pp filepath
[if basename ];
then txt (Fpath.basename filepath)
else txtf "%a" Fpath.pp filepath];
txt " "; txt " ";
code [txtf "SHA256:%a" Hex.pp (Hex.of_cstruct sha256)]; code [txtf "SHA256:%a" Hex.pp (Hex.of_cstruct sha256)];
txtf " (%a)" Fmt.byte_size size; txtf " (%a)" Fmt.byte_size size;
@ -159,33 +186,40 @@ module Builds = struct
let make section_job_map = let make section_job_map =
layout ~title:"Reproducible OPAM builds" layout ~title:"Reproducible OPAM builds"
([ h1 [txt "Reproducible OPAM builds"]; ([
p [ txt "This website offers binary MirageOS unikernels and supplementary OS packages." ]; h1 [txt "Reproducible OPAM builds"];
p [ txt {|Following is a list of jobs that are built daily. A persistent link to the latest successful build is available as /job/*jobname*/build/latest/. All builds can be reproduced with |} ; p [ txt "This website offers binary MirageOS unikernels and \
a ~a:[a_href "https://github.com/roburio/orb/"] [txt "orb"]; supplementary OS packages." ];
txt ". The builds are scheduled and executed by "; p [ txt "Following is a list of jobs that are built daily. A \
a ~a:[a_href "https://github.com/roburio/builder/"] [txt "builder"]; persistent link to the latest successful build is available \
txt ". The web interface is "; as /job/*jobname*/build/latest/. All builds can be \
a ~a:[a_href "https://git.robur.io/robur/builder-web/"] [txt "builder-web"]; reproduced with ";
txt ". Contact team@robur.coop if you have any questions or suggestions."; a ~a:[a_href "https://github.com/roburio/orb/"] [txt "orb"];
]; txt ". The builds are scheduled and executed by ";
form ~a:[a_action "/hash"; a_method `Get] a ~a:[a_href "https://github.com/roburio/builder/"] [txt "builder"];
[ txt ". The web interface is ";
label [ a ~a:[a_href "https://git.robur.io/robur/builder-web/"]
txt "Search artifact by SHA256"; [txt "builder-web"];
br (); txt ". Contact team@robur.coop if you have any questions or \
input ~a:[ suggestions.";
a_input_type `Search; ];
a_id "sha256"; form ~a:[a_action "/hash"; a_method `Get]
a_name "sha256"; [
] (); label [
]; txt "Search artifact by SHA256";
br ();
input ~a:[ input ~a:[
a_input_type `Submit; a_input_type `Search;
a_value "Search"; a_id "sha256";
a_name "sha256";
] (); ] ();
]; ];
] @ input ~a:[
a_input_type `Submit;
a_value "Search";
] ();
];
] @
Utils.String_map.fold (fun section jobs acc -> Utils.String_map.fold (fun section jobs acc ->
acc @ [ acc @ [
h2 [ txt section ]; h2 [ txt section ];
@ -195,27 +229,32 @@ module Builds = struct
br (); br ();
txt (Option.value ~default:"" synopsis); txt (Option.value ~default:"" synopsis);
br () br ()
] @ ] @ List.concat_map (fun (platform, latest_build, latest_artifact) ->
List.flatten [
(List.map (fun (platform, latest_build, latest_artifact) -> check_icon latest_build.Builder_db.Build.result;
[ txt " ";
check_icon latest_build.Builder_db.Build.result; a ~a:[
txt " "; Fmt.kstr a_href "job/%s/%a"
a ~a:[Fmt.kstr a_href "job/%s/%a" job_name pp_platform_query (Some platform)][txt platform]; job_name
txt " "; pp_platform_query (Some platform)]
a ~a:[Fmt.kstr a_href "job/%s/build/%a/" job_name Uuidm.pp [txt platform];
latest_build.Builder_db.Build.uuid] txt " ";
[txtf "%a" pp_ptime latest_build.Builder_db.Build.start]; a ~a:[
txt " "; Fmt.kstr a_href "job/%s/build/%a/"
] @ (match latest_artifact with job_name
| Some main_binary -> Uuidm.pp latest_build.Builder_db.Build.uuid]
artifact ~basename:true job_name latest_build main_binary [txtf "%a" pp_ptime latest_build.Builder_db.Build.start];
| None -> txt " ";
[ txtf "Build failure: %a" Builder.pp_execution_result ] @ (match latest_artifact with
latest_build.Builder_db.Build.result ] | Some main_binary ->
) @ [ br () ]) artifact ~basename:true job_name latest_build main_binary
platform_builds) | None ->
)) [ txtf "Build failure: %a" Builder.pp_execution_result
latest_build.Builder_db.Build.result ]
) @ [ br () ]
)
platform_builds)
)
jobs) jobs)
]) ])
section_job_map section_job_map
@ -232,7 +271,9 @@ end
module Job = struct module Job = struct
let make ~failed name platform readme builds = let make ~failed name platform readme builds =
layout ~nav:(`Job (name, platform)) ~title:(Fmt.str "Job %s %a" name pp_platform platform) layout
~nav:(`Job (name, platform))
~title:(Fmt.str "Job %s %a" name pp_platform platform)
((h1 [txtf "Job %s %a" name pp_platform platform] :: ((h1 [txtf "Job %s %a" name pp_platform platform] ::
(match readme with (match readme with
| None -> [] | None -> []
@ -249,7 +290,10 @@ module Job = struct
li ([ li ([
check_icon build.Builder_db.Build.result; check_icon build.Builder_db.Build.result;
txtf " %s " build.platform; txtf " %s " build.platform;
a ~a:[Fmt.kstr a_href "/job/%s/build/%a/" name Uuidm.pp build.Builder_db.Build.uuid] a ~a:[
Fmt.kstr a_href "/job/%s/build/%a/"
name
Uuidm.pp build.Builder_db.Build.uuid ]
[ [
txtf "%a" pp_ptime build.Builder_db.Build.start; txtf "%a" pp_ptime build.Builder_db.Build.start;
]; ];
@ -262,9 +306,15 @@ module Job = struct
build.Builder_db.Build.result ])) build.Builder_db.Build.result ]))
builds); builds);
if failed then if failed then
p [ txt "Excluding failed builds " ; a ~a:[a_href "../"] [txt "here"] ; txt "." ] p [
txt "Excluding failed builds " ;
a ~a:[a_href "../"] [txt "here"] ;
txt "." ]
else else
p [ txt "Including failed builds " ; a ~a:[a_href "failed/"] [txt "here"] ; txt "." ] p [
txt "Including failed builds " ;
a ~a:[a_href "failed/"] [txt "here"] ;
txt "." ]
]) ])
end end
@ -315,7 +365,9 @@ module Job_build = struct
]; ];
]) ])
artifacts); artifacts);
h3 [ txtf "Reproduced by %d builds" (List.length (same_input_same_output @ different_input_same_output))] ; h3 [
txtf "Reproduced by %d builds"
(List.length (same_input_same_output @ different_input_same_output))] ;
ul ul
((List.map (fun { Builder_db.Build.start ; uuid ; platform ; _ } -> ((List.map (fun { Builder_db.Build.start ; uuid ; platform ; _ } ->
li [ li [
@ -327,8 +379,10 @@ module Job_build = struct
List.map (fun { Builder_db.Build.start ; uuid = other_uuid ; platform ; _ } -> List.map (fun { Builder_db.Build.start ; uuid = other_uuid ; platform ; _ } ->
li [ li [
txtf "on %s, different input, " platform; txtf "on %s, different input, " platform;
a ~a:[Fmt.kstr a_href "/compare/%a/%a/" a ~a:[
Uuidm.pp other_uuid Uuidm.pp build.uuid] Fmt.kstr a_href "/compare/%a/%a/"
Uuidm.pp other_uuid
Uuidm.pp build.uuid]
[txtf "%a" pp_ptime start] [txtf "%a" pp_ptime start]
]) ])
different_input_same_output) different_input_same_output)
@ -341,7 +395,10 @@ module Job_build = struct
List.map (fun { Builder_db.Build.start ; uuid = other_uuid ; platform ; _ } -> List.map (fun { Builder_db.Build.start ; uuid = other_uuid ; platform ; _ } ->
li [ li [
txtf "on %s, " platform ; txtf "on %s, " platform ;
a ~a:[Fmt.kstr a_href "/compare/%a/%a/" Uuidm.pp other_uuid Uuidm.pp build.uuid] a ~a:[
Fmt.kstr a_href "/compare/%a/%a/"
Uuidm.pp other_uuid
Uuidm.pp build.uuid]
[txtf "%a" pp_ptime start] [txtf "%a" pp_ptime start]
]) ])
same_input_different_output) same_input_different_output)
@ -437,7 +494,8 @@ module Job_build = struct
" "
in in
let style_col_container = a_style "" in let style_col_container = a_style "" in
let style_col_left = a_style "width: 45em; min-width: 43em; padding-left: 2%" in let style_col_left =
a_style "width: 45em; min-width: 43em; padding-left: 2%" in
let style_col_right = a_style "width: 50%" in let style_col_right = a_style "width: 50%" in
let body = [ let body = [
div ~a:[ style_grid_container ] [ div ~a:[ style_grid_container ] [
@ -468,45 +526,45 @@ let key_value_changes xs =
let packages packages = let packages packages =
OpamPackage.Set.elements packages OpamPackage.Set.elements packages
|> List.concat_map (fun p -> [ |> List.concat_map (fun p -> [
txtf "%a" Opamdiff.pp_opampackage p; txtf "%a" Opamdiff.pp_opampackage p;
br (); br ();
]) ])
let package_diffs diffs = let package_diffs diffs =
List.concat_map (fun pd -> [ List.concat_map (fun pd -> [
txtf "%a" Opamdiff.pp_version_diff pd; txtf "%a" Opamdiff.pp_version_diff pd;
br (); br ();
]) ])
diffs diffs
let opam_diffs diffs = let opam_diffs diffs =
List.concat_map (fun pd -> List.concat_map (fun pd ->
h4 [ txtf "%a" Opamdiff.pp_opam_diff pd ] :: h4 [ txtf "%a" Opamdiff.pp_opam_diff pd ] ::
(match pd.Opamdiff.build with None -> [] | Some a -> (match pd.Opamdiff.build with None -> [] | Some a ->
let l, r = Opamdiff.commands_to_strings a in let l, r = Opamdiff.commands_to_strings a in
[ [
h5 [ txt "build instruction (without common prefix) modifications, old:" ] ; h5 [ txt "build instruction (without common prefix) modifications, old:" ] ;
code (List.concat_map (fun s -> [ txt s ; br () ]) l) ; code (List.concat_map (fun s -> [ txt s ; br () ]) l) ;
h5 [ txt "new" ] ; h5 [ txt "new" ] ;
code (List.concat_map (fun s -> [ txt s ; br () ]) r) code (List.concat_map (fun s -> [ txt s ; br () ]) r)
]) @ ]) @
(match pd.Opamdiff.install with None -> [] | Some a -> (match pd.Opamdiff.install with None -> [] | Some a ->
let l, r = Opamdiff.commands_to_strings a in let l, r = Opamdiff.commands_to_strings a in
[ [
h5 [ txt "install instruction (without common prefix) modifications, old:" ] ; h5 [ txt "install instruction (without common prefix) modifications, old:" ] ;
code (List.concat_map (fun s -> [ txt s ; br () ]) l) ; code (List.concat_map (fun s -> [ txt s ; br () ]) l) ;
h5 [ txt "new" ] ; h5 [ txt "new" ] ;
code (List.concat_map (fun s -> [ txt s ; br () ]) r) code (List.concat_map (fun s -> [ txt s ; br () ]) r)
]) @ ]) @
(match pd.Opamdiff.url with None -> [] | Some a -> (match pd.Opamdiff.url with None -> [] | Some a ->
let l, r = Opamdiff.opt_url_to_string a in let l, r = Opamdiff.opt_url_to_string a in
[ [
h5 [ txt "URL" ] ; h5 [ txt "URL" ] ;
txtf "old: %s" l; txtf "old: %s" l;
br (); br ();
txtf "new: %s" r txtf "new: %s" r
]) @ ]) @
[ br () ]) [ br () ])
diffs diffs
let compare_builds job_left job_right let compare_builds job_left job_right
@ -517,68 +575,81 @@ let compare_builds job_left job_right
layout layout
~nav:(`Comparison ((job_left, build_left), (job_right, build_right))) ~nav:(`Comparison ((job_left, build_left), (job_right, build_right)))
~title:(Fmt.str "Comparing builds %a and %a" ~title:(Fmt.str "Comparing builds %a and %a"
Uuidm.pp build_left.uuid Uuidm.pp build_right.uuid) Uuidm.pp build_left.uuid Uuidm.pp build_right.uuid)
([ ([
h1 [txt "Comparing builds"]; h1 [txt "Comparing builds"];
h2 [ h2 [
txt "Builds "; txt "Builds ";
a ~a:[a_href a ~a:[a_href
(Fmt.str "/job/%s/build/%a/" (Fmt.str "/job/%s/build/%a/"
job_left job_left
Uuidm.pp build_left.uuid)] Uuidm.pp build_left.uuid)]
[txtf "%s@%a %a" job_left pp_ptime build_left.start pp_platform (Some build_left.platform)]; [ txtf "%s@%a %a"
job_left
pp_ptime build_left.start
pp_platform (Some build_left.platform)];
txt " and "; txt " and ";
a ~a:[a_href a ~a:[a_href
(Fmt.str "/job/%s/build/%a/" (Fmt.str "/job/%s/build/%a/"
job_right job_right
Uuidm.pp build_right.uuid)] Uuidm.pp build_right.uuid)]
[txtf "%s@%a %a" job_right pp_ptime build_right.start pp_platform (Some build_right.platform)]; [ txtf "%s@%a %a"
job_right
pp_ptime build_right.start
pp_platform (Some build_right.platform)];
]; ];
h3 [ a ~a:[Fmt.kstr a_href "/compare/%a/%a/" Uuidm.pp build_right.uuid Uuidm.pp build_left.uuid] h3 [ a ~a:[
[txt "Compare in reverse direction"]] ; Fmt.kstr a_href "/compare/%a/%a/"
Uuidm.pp build_right.uuid
Uuidm.pp build_left.uuid ]
[txt "Compare in reverse direction"]] ;
ul [ ul [
li [ li [
a ~a:[a_href "#opam-packages-removed"] a ~a:[a_href "#opam-packages-removed"]
[txtf "%d opam packages removed" (OpamPackage.Set.cardinal left)] [txtf "%d opam packages removed"
(OpamPackage.Set.cardinal left)]
]; ];
li [ li [
a ~a:[a_href "#opam-packages-installed"] a ~a:[a_href "#opam-packages-installed"]
[txtf "%d new opam packages installed" (OpamPackage.Set.cardinal right)] [txtf "%d new opam packages installed"
(OpamPackage.Set.cardinal right)]
]; ];
li [ li [
a ~a:[a_href "#opam-packages-version-diff"] a ~a:[a_href "#opam-packages-version-diff"]
[txtf "%d opam packages with version changes" (List.length version_diff)] [txtf "%d opam packages with version changes"
(List.length version_diff)]
]; ];
li [ li [
a ~a:[a_href "#opam-packages-opam-diff"] a ~a:[a_href "#opam-packages-opam-diff"]
[txtf "%d opam packages with changes in their opam file" (List.length opam_diff)] [txtf "%d opam packages with changes in their opam file"
(List.length opam_diff)]
]; ];
li [ li [
a ~a:[a_href "#opam-packages-unchanged"] a ~a:[a_href "#opam-packages-unchanged"]
[txtf "%d opam packages unchanged" (OpamPackage.Set.cardinal same)] [txtf "%d opam packages unchanged" (OpamPackage.Set.cardinal same)]
]; ];
li [ li [
a ~a:[a_href "#env-added"] a ~a:[a_href "#env-added"]
[ txtf "%d environment variables added" (List.length added_env)] [ txtf "%d environment variables added" (List.length added_env)]
]; ];
li [ li [
a ~a:[a_href "#env-removed"] a ~a:[a_href "#env-removed"]
[ txtf "%d environment variables removed" (List.length removed_env)] [ txtf "%d environment variables removed" (List.length removed_env)]
]; ];
li [ li [
a ~a:[a_href "#env-changed"] a ~a:[a_href "#env-changed"]
[ txtf "%d environment variables changed" (List.length changed_env)] [ txtf "%d environment variables changed" (List.length changed_env)]
]; ];
li [ li [
a ~a:[a_href "#pkgs-added"] a ~a:[a_href "#pkgs-added"]
[ txtf "%d system packages added" (List.length added_pkgs)] [ txtf "%d system packages added" (List.length added_pkgs)]
]; ];
li [ li [
a ~a:[a_href "#pkgs-removed"] a ~a:[a_href "#pkgs-removed"]
[ txtf "%d system packages removed" (List.length removed_pkgs)] [ txtf "%d system packages removed" (List.length removed_pkgs)]
]; ];
li [ li [
a ~a:[a_href "#pkgs-changed"] a ~a:[a_href "#pkgs-changed"]
[ txtf "%d system packages changed" (List.length changed_pkgs)] [ txtf "%d system packages changed" (List.length changed_pkgs)]
]; ];
]; ];
@ -593,23 +664,23 @@ let compare_builds job_left job_right
code (package_diffs version_diff); code (package_diffs version_diff);
h3 ~a:[a_id "opam-packages-opam-diff"] h3 ~a:[a_id "opam-packages-opam-diff"]
[txt "Opam packages with changes in their opam file"]] @ [txt "Opam packages with changes in their opam file"]] @
opam_diffs opam_diff @ [ opam_diffs opam_diff @ [
h3 ~a:[a_id "opam-packages-unchanged"] h3 ~a:[a_id "opam-packages-unchanged"]
[txt "Unchanged opam packages"]; [txt "Unchanged opam packages"];
code (packages same); code (packages same);
h3 ~a:[a_id "env-added"] [txt "Environment variables added"]; h3 ~a:[a_id "env-added"] [txt "Environment variables added"];
code (key_values added_env); code (key_values added_env);
h3 ~a:[a_id "env-removed"] [txt "Environment variables removed"]; h3 ~a:[a_id "env-removed"] [txt "Environment variables removed"];
code (key_values removed_env); code (key_values removed_env);
h3 ~a:[a_id "env-changed"] [txt "Environment variables changed"]; h3 ~a:[a_id "env-changed"] [txt "Environment variables changed"];
code (key_value_changes changed_env); code (key_value_changes changed_env);
h3 ~a:[a_id "pkgs-added"] [txt "System packages added"]; h3 ~a:[a_id "pkgs-added"] [txt "System packages added"];
code (key_values added_pkgs); code (key_values added_pkgs);
h3 ~a:[a_id "pkgs-removed"] [txt "System packages removed"]; h3 ~a:[a_id "pkgs-removed"] [txt "System packages removed"];
code (key_values removed_pkgs); code (key_values removed_pkgs);
h3 ~a:[a_id "pkgs-changed"] [txt "System packages changed"]; h3 ~a:[a_id "pkgs-changed"] [txt "System packages changed"];
code (key_value_changes changed_pkgs); code (key_value_changes changed_pkgs);
]) ])
let failed_builds ~start ~count builds = let failed_builds ~start ~count builds =
let build (job_name, build) = let build (job_name, build) =
@ -622,12 +693,15 @@ let failed_builds ~start ~count builds =
] ]
in in
layout ~title:"Failed builds" layout ~title:"Failed builds"
([ h1 [txt "Failed builds"]; ([
ul (List.map build builds); h1 [txt "Failed builds"];
p [ txtf "View the next %d failed builds " count; ul (List.map build builds);
a ~a:[Fmt.kstr a_href "/failed-builds/?count=%d&start=%d" count (start + count)] p [ txtf "View the next %d failed builds " count;
[ txt "here"]; a ~a:[
txt "."; Fmt.kstr a_href "/failed-builds/?count=%d&start=%d"
] count (start + count) ]
[ txt "here"];
txt ".";
]
]) ])