Skip to content

Commit d50cb18

Browse files
authored
Clarify opam package version status and dates (#3713)
* Clarify opam package version status and dates * Link OCaml versions to the manual * Clarify opam and official OCaml releases
1 parent 12503ca commit d50cb18

11 files changed

Lines changed: 398 additions & 77 deletions

File tree

src/ocamlorg_frontend/components/package_breadcrumbs.eml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ let render_package_and_version
4141
| Overview _ -> Url.Package.overview package.name ?version
4242
| Documentation _ -> Url.Package.documentation package.name ?version ?hash ?page
4343
in
44-
let version_options (v: Package.version_with_publication_date) =
44+
let version_options (v: Package.version_summary) =
4545
<% if v.version = package.latest_version then ( %>
4646
<option value="<%s url None %>" <%s if package.version = Latest then "selected" else "" %>>
4747
<%s "latest (" ^ package.latest_version ^ ")" %>

src/ocamlorg_frontend/package.ml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
type version = Latest | Specific of string
22
type documentation_status = Success | Failure | Unknown
3-
type version_with_publication_date = { version : string; publication : float }
3+
type version_status = Avoided | Deprecated
4+
5+
type version_summary = {
6+
version : string;
7+
opam_repository_date : float;
8+
statuses : version_status list;
9+
}
410

511
type package = {
612
name : string;
713
synopsis : string;
814
description : string;
915
license : string;
1016
version : version;
11-
versions : version_with_publication_date list;
17+
versions : version_summary list;
1218
latest_version : string;
1319
tags : string list;
1420
rev_deps : string list;
1521
authors : Data.Opam_user.t list;
1622
maintainers : Data.Opam_user.t list;
17-
publication : float;
23+
opam_repository_date : float;
1824
homepages : string list;
1925
source : (string * string list) option;
2026
(* TODO: these should be part of package.json coming from voodoo, but they

src/ocamlorg_frontend/pages/package_overview.eml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,8 @@ let render
220220
<% ); %>
221221
<% ); %>
222222
<div class="flex-grow"></div>
223-
<h2 class="font-semibold text-base text-content dark:text-dark-content">Published:
224-
<span class="font-normal"><%s Utils.human_date_of_timestamp package.publication %></span>
223+
<h2 class="font-semibold text-base text-content dark:text-dark-content">Added to opam-repository:
224+
<time datetime="<%s Utils.iso_date_of_timestamp package.opam_repository_date %>" class="font-normal"><%s Utils.human_date_of_timestamp package.opam_repository_date %></time>
225225
</h2>
226226
</div>
227227

Lines changed: 169 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,177 @@
1+
let has_status status (item : Package.version_summary) =
2+
List.mem status item.statuses
3+
4+
let is_discouraged item =
5+
has_status Package.Avoided item || has_status Package.Deprecated item
6+
7+
let status_badge class_ label =
8+
<span class="inline-flex items-center whitespace-nowrap rounded border px-2 py-1 text-xs font-medium <%s class_ %>"><%s label %></span>
9+
10+
let ocaml_manual_version version =
11+
match String.split_on_char '.' version with
12+
| major :: minor :: _ ->
13+
let minor = List.hd (String.split_on_char '~' minor) in
14+
let minor = List.hd (String.split_on_char '+' minor) in
15+
Printf.sprintf "%s.%s" major minor
16+
| _ -> version
17+
18+
let documentation_href (package : Package.package) version =
19+
if package.name = "ocaml" then
20+
Printf.sprintf "/manual/%s/index.html" (ocaml_manual_version version)
21+
else Printf.sprintf "/p/%s/%s/doc/index.html" package.name version
22+
23+
let is_ocaml_compiler_package name =
24+
List.mem name
25+
[
26+
"ocaml";
27+
"ocaml-compiler";
28+
"ocaml-base-compiler";
29+
"ocaml-system";
30+
"ocaml-variants";
31+
]
32+
33+
let render_statuses ~is_latest ~is_selected item =
34+
let previous = not is_latest && not (is_discouraged item) in
35+
let badges =
36+
[
37+
(if is_latest then
38+
Some
39+
(status_badge
40+
"border-primary bg-primary_25 text-title dark:border-dark-primary dark:bg-dark-primary_10 dark:text-dark-title"
41+
"Latest URL target")
42+
else None);
43+
(if previous then
44+
Some
45+
(status_badge
46+
"border-gray-300 bg-gray-100 text-title dark:border-gray-500 dark:bg-gray-700 dark:text-dark-title"
47+
"Previous")
48+
else None);
49+
(if has_status Package.Avoided item then
50+
Some
51+
(status_badge
52+
"border-yellow-600 bg-yellow-100 text-yellow-900 dark:border-yellow-500 dark:bg-yellow-900 dark:text-yellow-100"
53+
"Avoided by opam")
54+
else None);
55+
(if has_status Package.Deprecated item then
56+
Some
57+
(status_badge
58+
"border-red-600 bg-red-100 text-red-900 dark:border-red-500 dark:bg-red-900 dark:text-red-100"
59+
"Deprecated by opam")
60+
else None);
61+
(if is_selected then
62+
Some
63+
(status_badge
64+
"border-title bg-white text-title dark:border-dark-title dark:bg-dark-card dark:text-dark-title"
65+
"Selected")
66+
else None);
67+
]
68+
|> List.filter_map Fun.id
69+
in
70+
<span class="flex flex-wrap gap-2"><%s! String.concat "" badges %></span>
71+
172
let render
273
(package : Package.package)
374
=
75+
let selected_version = Package.specific_version package in
76+
let latest_versions, remaining_versions =
77+
List.partition
78+
(fun (item : Package.version_summary) ->
79+
item.version = package.latest_version)
80+
package.versions
81+
in
82+
let discouraged_versions, previous_versions =
83+
List.partition is_discouraged remaining_versions
84+
in
85+
let render_row (item : Package.version_summary) =
86+
let is_latest = item.version = package.latest_version in
87+
let is_selected = selected_version = item.version in
88+
let cell_background =
89+
if is_selected then
90+
"bg-primary_25 dark:bg-dark-primary_10"
91+
else "bg-white dark:bg-dark-card"
92+
in
93+
<tr>
94+
<th scope="row" class="rounded-l text-left text-title dark:text-dark-title <%s cell_background %>">
95+
<a href="/p/<%s package.name %>/<%s item.version %>" aria-current="<%s if is_selected then "page" else "false" %>"
96+
class="inline-flex min-h-[44px] items-center p-4 text-base hover:underline focus-visible:rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-4px] focus-visible:outline-primary dark:focus-visible:outline-dark-primary">
97+
<%s item.version %>
98+
</a>
99+
</th>
100+
<td class="p-4 text-left text-title dark:text-dark-title <%s cell_background %>">
101+
<%s! render_statuses ~is_latest ~is_selected item %>
102+
</td>
103+
<td class="p-4 text-left text-sm text-title dark:text-dark-title <%s cell_background %>">
104+
<time datetime="<%s Utils.iso_date_of_timestamp item.opam_repository_date %>">
105+
<%s Utils.human_date_of_timestamp item.opam_repository_date %>
106+
</time>
107+
</td>
108+
<td class="rounded-r text-left text-sm <%s cell_background %>">
109+
<a href="<%s documentation_href package item.version %>"
110+
class="inline-flex min-h-[44px] items-center gap-2 p-4 font-medium text-primary hover:underline focus-visible:rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-4px] focus-visible:outline-primary dark:text-dark-primary dark:focus-visible:outline-dark-primary">
111+
<span aria-hidden="true"><%s! Icons.document "h-5 w-5"; %></span>
112+
Documentation
113+
</a>
114+
</td>
115+
</tr>
116+
in
117+
let render_group title versions =
118+
match versions with
119+
| [] -> ""
120+
| versions ->
121+
<tbody>
122+
<tr>
123+
<th colspan="4" scope="rowgroup" class="pb-1 pt-6 text-left text-lg font-semibold text-title dark:text-dark-title"><%s title %></th>
124+
</tr>
125+
<%s! versions |> List.map render_row |> String.concat "" %>
126+
</tbody>
127+
in
4128
Layout.render
5-
~title:(Printf.sprintf "%s Versions" package.name)
6-
~description:"Package Versions" @@
7-
let version = Package.specific_version package in
8-
<div class="bg-default dark:bg-dark-default py-8">
129+
~title:(Printf.sprintf "%s opam package versions" package.name)
130+
~description:(Printf.sprintf "Versions and opam status for the %s package" package.name) @@
131+
<div class="py-8">
9132
<div class="container-fluid flex items-center gap-2">
10-
<a href="/p/<%s package.name %>" class="h-11 w-11 m-5 border-primary border-2 rounded-full flex items-center justify-center"><%s! Icons.arrow_left "h-6 w-6 text-primary"; %></a>
11-
<h1 class="text-2xl text-title dark:text-dark-title font-medium"><%s package.name %> Versions (<%i List.length package.versions %>) </h1>
133+
<a href="/p/<%s package.name %>" aria-label="Back to <%s package.name %> package"
134+
class="m-5 flex h-11 w-11 shrink-0 items-center justify-center rounded-full border-2 border-primary focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary dark:border-dark-primary dark:focus-visible:outline-dark-primary">
135+
<span aria-hidden="true"><%s! Icons.arrow_left "h-6 w-6 text-primary dark:text-dark-primary"; %></span>
136+
</a>
137+
<h1 class="text-2xl font-medium text-title dark:text-dark-title"><%s package.name %> opam package versions (<%i List.length package.versions %>)</h1>
12138
</div>
13-
<div class="container-fluid flex justify-center">
14-
<table class="table-auto border-separate border-spacing-y-6">
15-
<thead class="container-fluid text-md text-lighter text-left">
16-
<tr class="py-4">
17-
<th class="hidden sm:table-cell text-left text-title dark:text-dark-title font-normal border-b-2 border-gray-200 dark:border-gray-400 py-4 pl-3 md:pr-14"><%s! Icons.bars_3 "h-5 w-5"; %></th>
18-
<th class="text-left text-title dark:text-dark-title font-normal border-b-2 border-gray-200 dark:border-gray-400 px-5 md:pr-12">Version</th>
19-
<th class="text-left text-title dark:text-dark-title font-normal border-b-2 border-gray-200 dark:border-gray-400 md:pr-14">Release Date</th>
20-
<th class="text-left text-title dark:text-dark-title font-normal border-b-2 border-gray-200 dark:border-gray-400 px-5 md:pr-14">Links</th>
21-
</tr>
22-
</thead>
23-
<tbody>
24-
<% package.versions |> List.iter (fun (item: Package.version_with_publication_date) -> %>
25-
<tr class="tr-selected">
26-
<td class="hidden sm:table-cell text-left font-normal py-5 px-4 <%s if version = item.version then "bg-primary-200 rounded-l" else "" %>">
27-
<% if version = item.version then ( %><div class="bg-primary dark:bg-dark-primary h-3 w-3 rounded-full"></div><% ) else ( %><div class="bg-gray-200 dark:bg-gray-400 h-2 w-2 rounded-full ml-0.5"></div><% ); %>
28-
<div class="absolute h-[4.5rem] pl-1">
29-
<% if List.nth package.versions (List.length package.versions -1) <> item then ( %>
30-
<div class="-ml-px w-1 border-solid border-r-2 border-gray-200 dark:border-gray-400 h-full"></div>
31-
<% ); %>
32-
</div>
33-
</td>
34-
<td class="text-left text-title dark:text-dark-title bg-primary-200">
35-
<a href="/p/<%s package.name %>/<%s item.version %>" class="p-5 hover:no-underline hover:text-primary focus:text-primary focus:no-underline <%s if version = item.version then "font-bold focus:font-bold" else "" %> text-base md:mr-10">
36-
<%s item.version %>
37-
</a>
38-
</td>
39-
<td class="pr-4 text-left text-title dark:text-dark-title text-sm bg-primary-200">
40-
<%s Utils.human_date_of_timestamp item.publication %>
41-
</td>
42-
<td class="text-left text-sm bg-primary-200 rounded-r">
43-
<a href="/p/<%s package.name %>/<%s item.version %>/doc/index.html" class="text-primary dark:text-dark-primary flex items-center p-2 font-medium sm:p-4"><%s! Icons.document "h-5 w-5"; %> Documentation</a>
44-
</td>
139+
<div class="container-fluid">
140+
<% if is_ocaml_compiler_package package.name then ( %>
141+
<aside aria-labelledby="official-ocaml-releases-title"
142+
class="mb-6 max-w-3xl rounded border-l-4 border-primary bg-primary_25 p-5 text-content dark:border-dark-primary dark:bg-dark-primary_10 dark:text-dark-content">
143+
<h2 id="official-ocaml-releases-title" class="text-lg font-semibold text-title dark:text-dark-title">Official OCaml releases</h2>
144+
<p class="mt-2">
145+
This page is an opam package listing, not the list of official OCaml releases.
146+
It may include compiler versions that have not been officially released.
147+
</p>
148+
<a href="<%s Url.releases %>"
149+
class="mt-2 inline-flex min-h-[44px] items-center font-medium text-primary underline underline-offset-4 focus-visible:rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary dark:text-dark-primary dark:focus-visible:outline-dark-primary">
150+
View official OCaml releases
151+
</a>
152+
</aside>
153+
<% ); %>
154+
<p class="max-w-3xl text-content dark:text-dark-content">
155+
This page lists package versions found in opam-repository.
156+
On ocaml.org, the <code>latest</code> URL currently points to version <strong><%s package.latest_version %></strong>;
157+
this does not indicate an upstream release status.
158+
Versions marked Avoided or Deprecated are not normally selected by opam.
159+
</p>
160+
<div class="mt-4 overflow-x-auto">
161+
<table class="w-full min-w-[48rem] table-auto border-separate border-spacing-y-3">
162+
<caption class="sr-only">Versions and opam status for the <%s package.name %> package</caption>
163+
<thead class="text-left">
164+
<tr>
165+
<th scope="col" class="border-b-2 border-gray-200 px-4 py-4 font-normal text-title dark:border-gray-400 dark:text-dark-title">Version</th>
166+
<th scope="col" class="border-b-2 border-gray-200 px-4 py-4 font-normal text-title dark:border-gray-400 dark:text-dark-title">Status</th>
167+
<th scope="col" class="border-b-2 border-gray-200 px-4 py-4 font-normal text-title dark:border-gray-400 dark:text-dark-title">Added to opam-repository</th>
168+
<th scope="col" class="border-b-2 border-gray-200 px-4 py-4 font-normal text-title dark:border-gray-400 dark:text-dark-title">Links</th>
45169
</tr>
46-
<% ); %>
47-
</tbody>
48-
</table>
170+
</thead>
171+
<%s! render_group "Version selected by the latest URL" latest_versions %>
172+
<%s! render_group "Previous versions" previous_versions %>
173+
<%s! render_group "Versions not normally selected by opam" discouraged_versions %>
174+
</table>
175+
</div>
49176
</div>
50-
</div>
177+
</div>

src/ocamlorg_frontend/pages/packages_search.eml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ let render ~total ~search ~(pagination_info : Pagination.t) (packages : Package.
114114
<span>Used by <%d List.length package.rev_deps %> other packages</span>
115115
</div>
116116
<div class="flex items-center gap-1">
117-
<%s! Icons.calendar "h-5 w-5"; %>
118-
<span><%s Utils.human_date_of_timestamp package.publication %></span>
117+
<span aria-hidden="true"><%s! Icons.calendar "h-5 w-5"; %></span>
118+
<span>Added to opam-repository <time datetime="<%s Utils.iso_date_of_timestamp package.opam_repository_date %>"><%s Utils.human_date_of_timestamp package.opam_repository_date %></time></span>
119119
</div>
120120
</div>
121121
</li>

src/ocamlorg_frontend/utils.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ let human_date_of_timestamp t =
2929
let ts = Timestamp.of_float_s t in
3030
Format.asprintf "%a" (Timestamp.pp ~format:"{day:0X} {mon:Xxx} {year}" ()) ts
3131

32+
let iso_date_of_timestamp t =
33+
let date = Unix.gmtime t in
34+
Printf.sprintf "%04d-%02d-%02d" (date.tm_year + 1900) (date.tm_mon + 1)
35+
date.tm_mday
36+
3237
let host_of_uri uri =
3338
let uri = Uri.of_string uri in
3439
Uri.host_with_default uri

src/ocamlorg_package/lib/ocamlorg_package.ml

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -113,19 +113,22 @@ let save_state t =
113113
(fun () -> Marshal.to_channel channel t [])
114114
~finally:(fun () -> close_out channel)
115115

116+
let has_flag flag (info : Info.t) = List.exists (( = ) flag) info.flags
117+
118+
let is_discouraged info =
119+
has_flag OpamTypes.Pkgflag_AvoidVersion info
120+
|| has_flag OpamTypes.Pkgflag_Deprecated info
121+
122+
let select_latest versions =
123+
let _, preferred = Version.Map.partition (fun _ -> is_discouraged) versions in
124+
match Version.Map.max_binding_opt preferred with
125+
| Some package -> Some package
126+
| None -> Version.Map.max_binding_opt versions
127+
116128
let get_latest' packages name =
117-
Name.Map.find_opt name packages
118-
|> Option.map (fun versions ->
119-
let avoid_version _ (info : Info.t) =
120-
List.exists (( = ) OpamTypes.Pkgflag_AvoidVersion) info.flags
121-
in
122-
let avoided, packages = Version.Map.partition avoid_version versions in
123-
let version, info =
124-
match Version.Map.max_binding_opt packages with
125-
| None -> Version.Map.max_binding avoided
126-
| Some (version, info) -> (version, info)
127-
in
128-
{ version; info; name })
129+
Option.bind (Name.Map.find_opt name packages) (fun versions ->
130+
select_latest versions
131+
|> Option.map (fun (version, info) -> { version; info; name }))
129132

130133
let time_it_lwt name f =
131134
let open Lwt.Syntax in
@@ -188,10 +191,10 @@ let init ?(disable_polling = false) () =
188191
state
189192

190193
let all_latest t =
191-
t.packages
192-
|> Name.Map.map Version.Map.max_binding
193-
|> Name.Map.bindings
194-
|> List.map (fun (name, (version, info)) -> { name; version; info })
194+
t.packages |> Name.Map.bindings
195+
|> List.filter_map (fun (name, versions) ->
196+
select_latest versions
197+
|> Option.map (fun (version, info) -> { name; version; info }))
195198

196199
let stats t = t.stats
197200

@@ -200,17 +203,32 @@ let get_by_name t name =
200203
|> Option.map Version.Map.bindings
201204
|> Option.map (List.map (fun (version, info) -> { name; version; info }))
202205

203-
type version_with_publication_date = {
206+
type version_status = Avoided | Deprecated
207+
208+
type version_summary = {
204209
version : Version.t;
205-
publication : float;
210+
opam_repository_date : float;
211+
statuses : version_status list;
206212
}
207213

214+
let version_statuses info =
215+
let status flag value statuses =
216+
if has_flag flag info then value :: statuses else statuses
217+
in
218+
[]
219+
|> status OpamTypes.Pkgflag_Deprecated Deprecated
220+
|> status OpamTypes.Pkgflag_AvoidVersion Avoided
221+
208222
let get_versions t name =
209223
t.packages |> Name.Map.find_opt name
210224
|> Option.map (fun p ->
211225
p |> Version.Map.bindings
212226
|> List.map (fun (version, info) ->
213-
{ version; publication = info.Info.publication }))
227+
{
228+
version;
229+
opam_repository_date = info.Info.publication;
230+
statuses = version_statuses info;
231+
}))
214232
|> Option.value ~default:[]
215233
|> List.sort (fun v1 v2 -> Version.compare v2.version v1.version)
216234

src/ocamlorg_package/lib/ocamlorg_package.mli

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,15 @@ val stats : state -> Statistics.t option
184184
val get_by_name : state -> Name.t -> t list option
185185
(** Get the list of packages with the given name. *)
186186

187-
type version_with_publication_date = {
187+
type version_status = Avoided | Deprecated
188+
189+
type version_summary = {
188190
version : Version.t;
189-
publication : float;
191+
opam_repository_date : float;
192+
statuses : version_status list;
190193
}
191194

192-
val get_versions : state -> Name.t -> version_with_publication_date list
195+
val get_versions : state -> Name.t -> version_summary list
193196
(** Get the list of versions for a package name, newest coming first. *)
194197

195198
val get_latest : state -> Name.t -> t option

0 commit comments

Comments
 (0)