(** Create TODO list with "done" items from commit log of a Subversion repository. *)



(**

Building a TODO list from Subversion commits.

*)


(*c==v=[Misc.my_int_of_string]=1.0====*)
let rec my_int_of_string s =
  let len = String.length s in
  if len <= 0 then invalid_arg "my_int_of_string";
  match s.[0] with
    '+' -> my_int_of_string (String.sub s 1 (len - 1))
  | _ -> int_of_string s
(*/c==v=[Misc.my_int_of_string]=1.0====*)

(** Parsing a svn log to retrieve a list of log entries. *)

let parse_svn_log wc_dir =
  let re_line = "r\\([0-9]+\\) | \\([^ ]+\\) | \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([+-][0-9][0-9]\\)[^|]*| \\([0-9]+\\) " in
  let re_line = Str.regexp re_line in
  let read_entry ic =
    let line = input_line ic in
    if Str.string_match re_line line 0 then
      begin
        let g n = Str.matched_group n line in
        let revid = my_int_of_string (g 1) in
        let login = g 2 in
        let year = my_int_of_string (g 3) in
        let month = my_int_of_string (g 4) in
        let day = my_int_of_string (g 5) in
        let hour = my_int_of_string (g 6) in
        let min = my_int_of_string (g 7) in
        let sec = my_int_of_string (g 8) in
        let tz = my_int_of_string (g 9) in
        let lines = my_int_of_string (g 10) in
        let b = Buffer.create 256 in
        ignore(input_line ic);
        for i = 1 to lines do
          Printf.bprintf b "%s\n" (input_line ic)
        done;
        Some (revid, login, (year, month, day, hour, min, sec, tz), Buffer.contents b)
      end
    else
      None
  in
  let command = Printf.sprintf "(cd %s && svn log)" (Filename.quote wc_dir) in
  try
    let ic = Unix.open_process_in command in
    let l = ref [] in
    begin
      try
        while true do
          match read_entry ic with
            Some e -> l := e :: !l
          | None -> ()
        done
      with
      End_of_file -> ignore(Unix.close_process_in ic)
    end;
    !l
  with
    Unix.Unix_error (e,s1,s2) ->
      let msg = Printf.sprintf "%s %s: %s"
        (Unix.error_message e) s2 s1
      in
      failwith msg
;;

(*c==v=[String.chop_n_char]=1.0====*)
let chop_n_char n s =
  let len = String.length s in
  if len <= n +1 or n < 0 then
    s
  else
    Printf.sprintf "%s..." (String.sub s 0 (n+1))
(*/c==v=[String.chop_n_char]=1.0====*)

let tdl_item_of_entry (revid, login, d, comment) =
  let (y,m,d,h,mi,s,tz) = d in
  let date = {
      Tdl.year = y;
      month = m ;
      day = d ;
      hour = h;
      minute = m;
      second = s;
      zone = tz * 60;
      week_day = - 1;
    }
  in
  let title = Printf.sprintf "[r%d] %s"
    revid
    (chop_n_char 40 (Str.global_replace (Str.regexp "\n\008*""; " comment))
  in
  Tdl.item ~title ~enddate: date ~date ~desc: comment ~state: Tdl.Done ()
;;

let tdl_of_svn_log ~login ~title ~dir =
  let re_login = Str.regexp login in
  let entries = parse_svn_log dir in
  prerr_endline (Printf.sprintf "%d entries" (List.length entries));
  let entries = List.filter
    (fun (_,login,_,_) -> Str.string_match re_login login 0)
    entries
  in
  prerr_endline (Printf.sprintf "%d entries after filtering" (List.length entries));
  let items = List.map tdl_item_of_entry entries in
  let g = Tdl.group ~title ~items () in
  Tdl.group ~title: dir ~groups: [g] ()
;;

(**

Options and main

*)


let login = ref ".*";;
let group_title = ref "Commits";;

let options = [
  "-l"Arg.Set_string login,
  "<regexp>\tconsider only log entries with a login matching the given "^
  "regular expression" ;

  "-g"Arg.Set_string group_title,
  "<title>\tuse the given title for the group in the created TODO list";
];;

let dir = ref None;;

let main () =
  Arg.parse options
    (fun s -> match !dir with
         None -> dir := Some s
       | _ -> failwith "Please give only working copy directory"
    )
    (Printf.sprintf
     "Usage: %s [options] [<directory of a subversion working copy>]\nwhere options are:"
       Sys.argv.(0)
    );
  let dir = match !dir with None -> Filename.current_dir_name | Some d -> d in
  let tdl = tdl_of_svn_log ~login: !login ~title: !group_title ~dir in
  Tdl.print_group Format.std_formatter tdl;
  Format.print_flush ()
;;

(*c==v=[Misc.safe_main]=1.0====*)
let safe_main main =
  try main ()
  with
    Failure s
  | Sys_error s ->
      prerr_endline s;
      exit 1
(*/c==v=[Misc.safe_main]=1.0====*)

let () = safe_main main