diff --git a/Makefile b/Makefile index 157f5f9..fd96d2b 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ all: build build: cd src && $(MAKE) + cp src/search.js ext/js/ install: cd src && $(MAKE) install diff --git a/ext/js/search.js b/ext/js/search.js deleted file mode 120000 index 110dbb6..0000000 --- a/ext/js/search.js +++ /dev/null @@ -1 +0,0 @@ -../../src/search.js \ No newline at end of file diff --git a/src/o2wMisc.ml b/src/o2wMisc.ml index c880b08..2400a47 100644 --- a/src/o2wMisc.ml +++ b/src/o2wMisc.ml @@ -64,3 +64,10 @@ let string_of_timestamp ?(short = false) time = Printf.sprintf "%s %d" month_str tm.tm_mday else Printf.sprintf "%s %d, %d" month_str tm.tm_mday year + +let ident_of_timestamp time = + let tm = Unix.gmtime time in + let open Unix in + let month_str = string_of_month tm.tm_mon in + let year = 1900 + tm.tm_year in + Printf.sprintf "%d%s%d" year month_str tm.tm_mday diff --git a/src/o2wMisc.mli b/src/o2wMisc.mli index 35b969a..20495fb 100644 --- a/src/o2wMisc.mli +++ b/src/o2wMisc.mli @@ -27,3 +27,6 @@ val string_of_month: int -> string (** Return the string representation of a timestamp *) val string_of_timestamp: ?short:bool -> float -> string + +(** Return a fragment identifier for a timestamp *) +val ident_of_timestamp: float -> string diff --git a/src/o2wPackage.ml b/src/o2wPackage.ml index 06aab28..a6e266c 100644 --- a/src/o2wPackage.ml +++ b/src/o2wPackage.ml @@ -116,7 +116,11 @@ let to_html ~statistics universe pkg_info = let pkg_homepage = links "Homepage" (OpamFile.OPAM.homepage pkg_opam) in let pkg_issues = links "Issue Tracker" (OpamFile.OPAM.bug_reports pkg_opam) in let pkg_tags = list "Tag" (OpamFile.OPAM.tags pkg_opam) in - let pkg_published = O2wMisc.string_of_timestamp pkg_info.published in + let pkg_published = match pkg_info.published with + | None -> None + | Some timestamp -> + Some ("Published",<:html<$str:O2wMisc.string_of_timestamp timestamp$>>) + in let html_conj = <:html<&>> in let html_disj = <:html<|>> in let vset_of_name name = @@ -354,7 +358,7 @@ let to_html ~statistics universe pkg_info = >>)) in <:html< -

$str: pkg_info.name$

+

$str: pkg_info.name$ $str: pkg_info.version$

@@ -370,18 +374,13 @@ let to_html ~statistics universe pkg_info = $mk_tr pkg_license$ $mk_tr pkg_homepage$ $mk_tr pkg_issues$ - $mk_tr pkg_tags$ $mk_tr pkg_maintainer$ + $mk_tr pkg_tags$ $mk_tr pkg_depends$ $mk_tr pkg_depopts$ $mk_tr pkg_compiler$ $mk_tr pkg_os$ - - Published - - $str: pkg_published$ - - + $mk_tr pkg_published$ $pkg_url$ $pkg_stats$ $pkg_edit$ diff --git a/src/o2wProject.ml b/src/o2wProject.ml new file mode 100644 index 0000000..7bdca6a --- /dev/null +++ b/src/o2wProject.ml @@ -0,0 +1,336 @@ +(* + * Copyright (c) 2014 David Sheets + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + *) + +let pkg_href = OpamfUniverse.Pkg.href ~href_base:Uri.(of_string "../") + +module Field = struct + type t = Author | License | Homepage | Maintainer + + let to_string = function + | Author -> "authorship" + | License -> "license" + | Homepage -> "homepage" + | Maintainer -> "maintainership" +end + +module Change = struct + type change = + | Add of (Field.t * string) + | Remove of (Field.t * string) + | Change of (Field.t * string * string) + type t = { + change : change; + } + + let to_html = Field.(function + | { change = Add ((Author | Maintainer) as p,a) } -> + <:html<$str:a$ gained $str:to_string p$.>> + | { change = Remove ((Author | Maintainer) as p,a) } -> + <:html<$str:a$ lost $str:to_string p$.>> + | { change = Change ((Author | Maintainer) as p,a,a') } -> + <:html<$str:a$ assumed $str:to_string p$ + from $str:a'$.>> + | { change = Add (License,a) } -> + <:htmllicensed under $str:a$.>> + | { change = Remove (License,a) } -> + <:htmllicensed under $str:a$.>> + | { change = Change (License,a,a') } -> + <:html<License changed to $str:a$ + from $str:a'$.>> + | { change = Add (Homepage,a) } -> + <:html<$str:a$ added as + homepage.>> + | { change = Remove (Homepage,a) } -> + <:html<$str:a$ removed as + homepage.>> + | { change = Change (Homepage,a,a') } -> + <:html<Homepage changed to + $str:a$ + from $str:a'$.>> + ) +end + +module Event = struct + type event = + | Published of (OpamPackage.Name.t * OpamPackage.Version.t * Change.t list) + type t = { + timestamp : float; + event : event; + } + + let to_html name = function + | { timestamp; event = Published (name, version, changes) } -> + let href = pkg_href name version in + let v = OpamPackage.Version.to_string version in + let changes_html = match changes with + | [] -> <:html<&>> + | _ -> + let changes_list = List.map (fun c -> + <:html<
  • $Change.to_html c$
  • &>> + ) changes in + <:html<
      $list:changes_list$
    &>> + in + <:html< +

    + $str:O2wMisc.string_of_timestamp timestamp$ +

    +

    Published + version $str: v$ + $changes_html$ +

    + >> +end + +let to_html ~statistics universe name vset = + let open OpamfUniverse in + let pname = OpamPackage.Name.of_string name in + + let versions = OpamPackage.Version.(List.sort compare (Set.elements vset)) in + let packages = List.rev_map (OpamPackage.create pname) versions in + + let opams = List.rev_map (fun pkg -> + OpamPackage.version pkg, OpamPackage.Map.find pkg universe.pkgs_opams + ) packages in + + let infos = List.rev_map (fun pkg -> + OpamPackage.version pkg, OpamPackage.Map.find pkg universe.pkgs_infos + ) packages in + + let versions_from_newest = List.rev versions in + let previous_version v = + let rec prev = function + | version::pv::_ when version = v -> Some pv + | _::vs -> prev vs + | [_] | [] -> None + in + prev versions_from_newest + in + + let latest_v = OpamPackage.Name.Map.find pname universe.max_versions in + let latest_p = OpamPackage.create pname latest_v in + let latest = OpamPackage.Map.find latest_p universe.pkgs_infos in + + let version_links = + List.map + (fun version -> + let href = pkg_href pname version in + let version = OpamPackage.Version.to_string version in + if latest.version = version then + <:html< +
  • latest $str: version$
  • + >> + else + <:html<
  • $str: version$
  • &>>) + versions + in + + let mk_tr = function + | None -> <:html<&>> + | Some (title, contents) -> + <:html< + + $str: title$ + $contents$ + + >> in + + let rec pretty_html_list ?(last="and") = function + | [] -> <:html<&>> + | [a] -> a + | [a;b] -> <:html<$a$ $str: last$ $b$>> + | h::t -> <:html<$h$, $pretty_html_list ~last t$>> + in + + let v_after_link v = match previous_version v with + | None -> <:html<&>> + | Some v -> + let v_href = pkg_href pname v in + let v_str = OpamPackage.Version.to_string v in + <:html< (after $str: v_str$)>> + in + + let span_list name l : (string * Cow.Xml.t) option = match l with + | [] -> None + | [e, v] -> Some (name, <:html<$e$$v_after_link v$>>) + | l -> + let l = List.map (fun (e,v) -> <:html<$e$$v_after_link v$>>) l in + Some (name ^ "s", pretty_html_list l) + in + + let span_strings name lo = match lo with + | None -> None + | Some l -> span_list name (List.map (fun (s,v) -> (<:html<$str:s$>>, v)) l) + in + + let span_links name lo = match lo with + | None -> None + | Some l -> span_list name (List.map (fun (s,v) -> + (<:html< $str: s$ >>,v)) + l) + in + + let tenure valuevs = + let rec prev (value, v) = function + | [] -> Some (value, v) + | (None, version)::vs -> prev (value, version) vs + | (v', version)::vs when v' = value -> prev (value, version) vs + | (Some _, _)::_ -> Some (value, v) + in + match valuevs with [] -> None | v::vs -> prev v vs + in + + let tenures lists = List.fold_left (fun m (v, items) -> match m with + | None -> Some (List.map (fun i -> (i, v)) items) + | Some ts -> Some (List.map (fun (i,late_v) -> + (i, if items = [] || List.mem i items then v else late_v) + ) ts) + ) None lists in + + let opam_field span_fn name field_fn = + let items = List.rev_map (fun (v, o) -> (v, field_fn o)) opams in + span_fn name (tenures items) + in + + let proj_author = opam_field span_strings "Author" OpamFile.OPAM.author in + + let proj_license = opam_field span_strings "License" OpamFile.OPAM.license in + + let proj_homepage = opam_field span_links "Homepage" OpamFile.OPAM.homepage in + + let proj_issues = + opam_field span_links "Issue Tracker" OpamFile.OPAM.bug_reports + in + + let proj_maintainer = + opam_field span_strings "Maintainer" OpamFile.OPAM.maintainer + in + + let proj_tags = opam_field span_strings "Tag" OpamFile.OPAM.tags in + + let opam_compiler name field_fn = + let valuevs = List.rev_map (fun (v, o) -> (field_fn o, v)) opams in + match tenure valuevs with + | None | Some (None, _) -> None + | Some (Some v, version) -> + let formula_str = OpamFormula.( + string_of_formula (fun (relop,v) -> + (string_of_relop relop)^" "^(OpamCompiler.Version.to_string v) + ) v + ) in + Some (name, <:html<$str:formula_str$$v_after_link version$>>) + in + + let opam_os name field_fn = + let valuevs = List.rev_map (fun (v, o) -> (Some (field_fn o), v)) opams in + OpamFormula.(match tenure valuevs with + | None | Some (None, _) | Some (Some Empty, _) -> None + | Some (Some f, version) -> + let formula_str = string_of_formula (fun (b,s) -> + if b then s else "!"^s + ) f + in + Some (name, <:html<$str:formula_str$$v_after_link version$>>) + ) + in + + let proj_compiler = opam_compiler "OCaml" OpamFile.OPAM.ocaml_version in + + let proj_os = opam_os "OS" OpamFile.OPAM.os in + + let module StringSet = Set.Make(String) in + + let set_of_list = + List.fold_left (fun set v -> StringSet.add v set) StringSet.empty + in + + let list_diff field_t field field' = match field, field' with + | [], [] -> [] + | _, _ -> + let a = set_of_list field in + let b = set_of_list field' in + let removes = StringSet.(elements (diff b a)) in + let adds = StringSet.(elements (diff a b)) in + match adds, removes with + | [a], [r] -> [Change.({ change = Change (field_t, a, r)})] + | _, _ -> + (List.map + (fun v -> Change.({ change = Add (field_t, v) })) + adds) + @(List.map + (fun v -> Change.({ change = Remove (field_t, v) })) + removes) + in + + let opam_list_diff pkg pkg' field_t field_fn = + let opam = OpamPackage.Map.find pkg universe.pkgs_opams in + let opam' = OpamPackage.Map.find pkg' universe.pkgs_opams in + list_diff field_t (field_fn opam) (field_fn opam') + in + + let events = List.fold_left OpamfUniverse.(fun l -> function + | _, { published = None } -> l + | v, { published = Some timestamp } -> + let p = OpamPackage.create pname v in + let changes = match previous_version v with + | None -> [] + | Some prev -> + let prev = OpamPackage.create pname prev in + (opam_list_diff p prev Field.Author OpamFile.OPAM.author) + @(opam_list_diff p prev Field.License OpamFile.OPAM.license) + @(opam_list_diff p prev Field.Maintainer OpamFile.OPAM.maintainer) + @(opam_list_diff p prev Field.Homepage OpamFile.OPAM.homepage) + in + Event.({ timestamp; event=Published (pname, v, changes) }::l) + ) [] infos in + + let proj_events = List.map (fun ev -> + <:html<
    $Event.to_html name ev$
    &>> + ) events in + + <:html< +

    $str: name$

    + +
    +
    +
    + +
    + +
    $latest.descr$
    + + + + $mk_tr proj_author$ + $mk_tr proj_license$ + $mk_tr proj_homepage$ + $mk_tr proj_issues$ + $mk_tr proj_maintainer$ + $mk_tr proj_tags$ + + $mk_tr proj_compiler$ + $mk_tr proj_os$ + +
    + +

    Events

    + + $list:proj_events$ +
    +
    + >> diff --git a/src/o2wUniverse.ml b/src/o2wUniverse.ml index 8e09088..548af44 100644 --- a/src/o2wUniverse.ml +++ b/src/o2wUniverse.ml @@ -19,32 +19,39 @@ open OpamfUniverse open Cow.Html open O2wTypes -let to_page ~statistics universe pkg pkg_info acc = - match pkg_info with - | None -> - Printf.printf "Skipping %s\n%!" (OpamPackage.to_string pkg); +let pkg_to_page ~statistics universe pkg pkg_info acc = + try + let page = { + page_source = pkg_info.OpamfUniverse.name; + page_link = { Cow.Html.text=pkg_info.title; + href=Uri.to_string pkg_info.OpamfUniverse.href }; + page_depth = 3; + page_contents = Template.serialize + (O2wPackage.to_html ~statistics universe pkg_info) + } in + page :: acc + with e -> + Printf.printf "Skipping %s (%s)\n%!" (OpamPackage.to_string pkg) + (Printexc.to_string e); + Printexc.print_backtrace stdout; acc - | Some pkg_info -> - try - let page = { - page_source = pkg_info.OpamfUniverse.name; - page_link = { Cow.Html.text=pkg_info.title; - href=Uri.to_string pkg_info.OpamfUniverse.href }; - page_depth = 3; - page_contents = Template.serialize - (O2wPackage.to_html ~statistics universe pkg_info) - } in - page :: acc - with e -> - Printf.printf "Skipping %s (%s)\n%!" (OpamPackage.to_string pkg) - (Printexc.to_string e); - Printexc.print_backtrace stdout; - acc -(* Create a list of package pages to generate for a universe *) -let to_pages ~statistics universe = +(* Create a list of project and package pages to generate for a universe *) +let to_pages ~statistics ~prefix universe = + let projects = OpamPackage.Name.Map.fold (fun name vset acc -> + let name = OpamPackage.Name.to_string name in + let href = Filename.(concat prefix (concat name "")) in + let page = { + page_source = name; + page_link = { Cow.Html.text=name; href; }; + page_depth = 2; + page_contents = Template.serialize + (O2wProject.to_html ~statistics universe name vset) + } in + page :: acc + ) universe.versions [] in OpamPackage.Map.fold - (to_page ~statistics universe) universe.pkgs_infos [] + (pkg_to_page ~statistics universe) universe.pkgs_infos projects let sortby_links ~links ~default ~active = let mk_item title = @@ -89,7 +96,7 @@ let to_html ~content_dir ~sortby_links ~popularity ~active let packages_html = List.fold_left (fun acc pkg -> let info = - try OpamPackage.Map.find pkg universe.pkgs_infos + try Some (OpamPackage.Map.find pkg universe.pkgs_infos) with Not_found -> None in match info with | None -> acc @@ -98,22 +105,31 @@ let to_html ~content_dir ~sortby_links ~popularity ~active try let d = OpamPackage.Name.Map.find (OpamPackage.name pkg) popularity in - Printf.sprintf "Downloads: %Ld | Published: %s" - d (O2wMisc.string_of_timestamp pkg_info.published) - with Not_found -> + [Printf.sprintf "Downloads: %Ld" d] + with Not_found -> [] + in + let pkg_published = match pkg_info.published with + | Some timestamp -> [ Printf.sprintf "Published: %s" - (O2wMisc.string_of_timestamp pkg_info.published) + (O2wMisc.string_of_timestamp timestamp) + ] + | None -> [] in + let pkg_tooltip = String.concat " | " (pkg_download @ pkg_published) in let pkg_href = Uri.(resolve "http" (of_string "../") pkg_info.OpamfUniverse.href) in <:html< - - + + $str: pkg_info.name$ - $str: pkg_info.version$ + + + $str: pkg_info.version$ + + $str: pkg_info.synopsis$ >> :: acc) diff --git a/src/o2wUniverse.mli b/src/o2wUniverse.mli index 6c4ce2f..4cdf04b 100644 --- a/src/o2wUniverse.mli +++ b/src/o2wUniverse.mli @@ -20,7 +20,7 @@ open OpamTypes open O2wTypes (** Create a list of package pages to generate for a repository *) -val to_pages: statistics:statistics_set option -> +val to_pages: statistics:statistics_set option -> prefix:string -> Cow.Html.t OpamfUniverse.t -> page list (** Generate the list of HTML links for a list of page names *) diff --git a/src/opam2web.ml b/src/opam2web.ml index d65e12d..7b2f687 100644 --- a/src/opam2web.ml +++ b/src/opam2web.ml @@ -51,7 +51,9 @@ let make_website user_options universe = let statistics = O2wStatistics.statistics_set user_options.logfiles in let content_dir = user_options.content_dir in Printf.printf "++ Building the package pages.\n%!"; - let pages = O2wUniverse.to_pages ~statistics universe in + let pages = O2wUniverse.to_pages + ~statistics ~prefix:packages_prefix universe + in Printf.printf "++ Building the documentation pages.\n%!"; let menu_of_doc () = O2wDocumentation.to_menu ~content_dir in Printf.printf "++ Building the blog.\n%!"; diff --git a/src/opam2web.mllib b/src/opam2web.mllib index e5fcc55..38efd46 100644 --- a/src/opam2web.mllib +++ b/src/opam2web.mllib @@ -8,3 +8,4 @@ Compressed Lexcombinedlog O2wGlobals O2wPackage +O2wProject diff --git a/src/template.ml b/src/template.ml index 925c03d..900572d 100644 --- a/src/template.ml +++ b/src/template.ml @@ -1,18 +1,19 @@ -(**************************************************************************) -(* *) -(* Copyright 2012-2013 OCamlPro *) -(* Copyright 2012 INRIA *) -(* *) -(* All rights reserved.This file is distributed under the terms of the *) -(* GNU Lesser General Public License version 3.0 with linking *) -(* exception. *) -(* *) -(* OPAM is distributed in the hope that it will be useful, but WITHOUT *) -(* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *) -(* or FITNESS FOR A PARTICULAR PURPOSE.See the GNU General Public *) -(* License for more details. *) -(* *) -(**************************************************************************) +(* + * Copyright (c) 2013-2014 David Sheets + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + *) type param_prop = Default of Cow.Xml.signal list | Mandatory type field_prop = Optional | Required diff --git a/src/template.mli b/src/template.mli index 2d44bc6..26863b1 100644 --- a/src/template.mli +++ b/src/template.mli @@ -1,18 +1,19 @@ -(**************************************************************************) -(* *) -(* Copyright 2012-2013 OCamlPro *) -(* Copyright 2012 INRIA *) -(* *) -(* All rights reserved.This file is distributed under the terms of the *) -(* GNU Lesser General Public License version 3.0 with linking *) -(* exception. *) -(* *) -(* OPAM is distributed in the hope that it will be useful, but WITHOUT *) -(* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *) -(* or FITNESS FOR A PARTICULAR PURPOSE.See the GNU General Public *) -(* License for more details. *) -(* *) -(**************************************************************************) +(* + * Copyright (c) 2013 David Sheets + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + *) type param_prop = Default of Cow.Xml.signal list | Mandatory type field_prop = Optional | Required