(*********************************************************************************)

(*                Cameleon                                                       *)
(*                                                                               *)
(*    Copyright (C) 2005,2006 Institut National de Recherche en Informatique     *)
(*    et en Automatique. All rights reserved.                                    *)
(*                                                                               *)
(*    This program is free software; you can redistribute it and/or modify       *)
(*    it under the terms of the GNU Library General Public License as            *)
(*    published by the Free Software Foundation; either version 2 of the         *)
(*    License, or  any later version.                                            *)
(*                                                                               *)
(*    This program 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 Library General Public License for more details.                       *)
(*                                                                               *)
(*    You should have received a copy of the GNU Library General Public          *)
(*    License along with this program; if not, write to the Free Software        *)
(*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA                   *)
(*    02111-1307  USA                                                            *)
(*                                                                               *)
(*    Contact: Maxence.Guesdon@inria.fr                                          *)
(*                                                                               *)
(*********************************************************************************)


(** cvslog2rss utility. *)


open Unix

(**

Command line arguments

*)


let title = ref "CVS log"
let link = ref ""
let desc = ref ""
let exec = ref None
let max_items = ref None


let options = [
  "-t"Arg.Set_string title, "<title>  set <title> as title of the channel" ;
  "-l"Arg.Set_string link, "<url>  set <url> as link of the channel" ;
  "-d"Arg.Set_string desc, "<s>  set <s> as description of the channel" ;
  "-e"Arg.String (fun s -> exec := Some s),
  "<options>  execute \"cvs log <options>\" to get the log rather than read it from stdin" ;
  "-maxitems"Arg.Int (fun n -> max_items := Some n), "<n>  keep only <n> items maximum";
]

(**

Utils

*)


(*c==v=[String.string_of_in_channel]=1.0====*)
let string_of_in_channel ic =
  let len = 1024 in
  let s = String.create len in
  let buf = Buffer.create len in
  let rec iter () =
    try
      let n = input ic s 0 len in
      if n = 0 then
        ()
      else
        (
         Buffer.add_substring buf s 0 n;
         iter ()
        )
    with
      End_of_file -> ()
  in
  iter ();
  Buffer.contents buf
(*/c==v=[String.string_of_in_channel]=1.0====*)


let get_cvslog params =
  let inch = Unix.open_process_in (Printf.sprintf "cvs log %s" params) in
  let s = string_of_in_channel inch in
  ignore(Unix.close_process_in inch);
  s

let date_of_string s =
  let f ye mo da ho mi se =
    let t =
      {
        tm_year = ye - 1900 ;
        tm_mon = mo - 1;
        tm_mday = da ;
        tm_wday = 0 ;
        tm_hour = ho ;
        tm_min = mi ;
        tm_sec = 0 ;
        tm_yday = 0;
        tm_isdst = false ;
      }
    in
    fst (Unix.mktime t)
  in
  Scanf.sscanf s "%d-%d-%d %d:%d:%d" f

let get_field s pos field =
  let f = field^": " in
  let len = String.length f in
  let re = Str.regexp_string f in
  let p = Str.search_forward re s pos in
  let p2 = String.index_from s p ';' in
  let res = (p2, String.sub s (p+len) (p2 - p - len)) in
(*  prerr_endline (Printf.sprintf "get_field: %s=%s" field (snd res));*)
  res

let analyze_log s =
  let s_date = "\ndate" in
  let s_end = "----------------------------" in
  let re_end = Str.regexp_string s_end in
  let t = Hashtbl.create 37 in
  let rec next_date s pos =
    try
      let (p, field) = get_field s pos s_date in
        let date = date_of_string field in
        try
          let (p2, author) = get_field s p "author" in
          let (p2, state) = get_field s p2 "state" in
          let p3 = String.index_from s p2 '\n' in
          if state = "dead" then
            next_date s p3
          else
            let p4 = Str.search_forward re_end s p3 in
            let comment = String.sub s (p3 + 1) (p4 - p3 - 1) in
            let comment =
              try
                let (p,_) = get_field comment 0 "branches" in
                let p2 = String.index_from comment p '\n' in
                String.sub comment (p2+1) ((String.length comment) - p2 - 1)
              with _ -> comment
            in
            let authors_comments =
              try Hashtbl.find t date
              with Not_found -> []
            in
            let new_l =
              match List.partition (fun (s,_) -> s = author) authors_comments with
                ([], l) -> (author, [comment]) :: l
              | (((a,c)::_), l) ->
                  if List.mem comment c then
                    (a, c) :: l
                  else
                    (a, c @ [comment]) :: l
            in
            Hashtbl.replace t date new_l;
            next_date s p4
      with
        _ ->
          next_date s (p+1)
    with
      Not_found ->
        ()
  in
  let re_sep =
    "\n============================================================================="
  in
  let l = Str.split (Str.regexp_string re_sep) s in
  List.iter (fun s -> next_date s 0) l;
  let l = Hashtbl.fold (fun date l acc -> (date, l) :: acc) t [] in
  List.sort
    (fun(d1,_) (d2,_) -> compare d1 d2)
    l

let replace_cr s =
  let len = String.length s in
  let buf = Buffer.create len in
  for i = 0 to len - 1 do
    match s.[i] with
      '\n' -> Buffer.add_string buf "<br/>\n"
    | c -> Buffer.add_char buf c
  done;
  Buffer.contents buf


let rss_items_of_log s =
  let entries = analyze_log s in
  let f (date, l) =
    List.map
      (fun (author, comments) ->
        Rss.item
          ~pubdate: (Rss.float_to_date date)
          ~title: author
          ~comments: (String.concat "\n" comments)
          ()
      )
      l
  in
  List.flatten (List.map f entries)

let rec list_n_first n = function
    [] -> []
  | h :: q ->
      if n > 0 then
        h :: (list_n_first (n-1) q)
      else
        []

(**

Main

*)


let main () =
  Arg.parse options (fun _ -> ())
    (Printf.sprintf "Usage %s [options]\nwhere options are:" Sys.argv.(0));
  let log =
    match !exec with
      Some params -> get_cvslog params
    | None -> string_of_in_channel Pervasives.stdin
  in
  let items = rss_items_of_log log in
  let items =
    match !max_items with
      None -> items
    | Some n -> List.rev (list_n_first n (List.rev items))
  in
  let channel = Rss.channel
      ~title: !title
      ~link: !link
      ~desc: !desc
      ~pubdate: (Rss.float_to_date (Unix.time()))
      items
  in
  Rss.print_channel Format.std_formatter channel

let _ = main ()