Below is the file 'viz_style.ml' from this revision. You can also download the file.

open Viz_misc
open Viz_types

(* "Generic" preferences *)

type item = [
    `BOOL of bool
  | `FLOAT of float
  | `INT of int
  | `STRING of string ]

type generic_prefs = (string * item) list

let print_item () = function
  | `BOOL b   -> string_of_bool b
  | `FLOAT f  -> string_of_float f
  | `INT i    -> string_of_int i
  | `STRING s -> Printf.sprintf "%S" s

let bool_of_item = function
  | `BOOL b -> b
  | `INT i -> i <> 0
  | `STRING "yes" | `STRING "true" -> true
  | `STRING "no" | `STRING "false" -> false
  | _ -> failwith "bool_of_item"

let int_of_item = function
  | `INT i -> i
  | `FLOAT f -> int_of_float f
  | _ -> failwith "int_of_item"

let string_of_item = function
  | `STRING s -> s
  | _ -> failwith "string_of_item"
let string_list_of_item = function
  | `STRING s -> Str.split (Str.regexp ", *") s
  | _ -> failwith "string_list_of_item"

let autocolor_of_item = function
  | `STRING "none" | `BOOL false -> NONE
  | `STRING "author" -> BY_AUTHOR_HASH
  | `STRING "branch" -> BY_BRANCH_HASH
  | _ -> failwith "autocolor_of_item"
let string_of_autocolor = function
  | NONE -> "none"
  | BY_AUTHOR_HASH -> "author"
  | BY_BRANCH_HASH -> "branch"
let item_of_autocolor ac =
  `STRING (string_of_autocolor ac)

type 'a key = string * (item -> 'a)
let bool_key s = (s, bool_of_item)
let string_key s = (s, string_of_item)
let string_list_key s = (s, string_list_of_item)
let autocolor_key s = (s, autocolor_of_item)

let add_pref l (k, v) =
  (k, v) :: (if List.mem_assoc k l
             then List.remove_assoc k l
             else l)

let lookup p (k, conv) =
  try Some (conv (List.assoc k p))
  with Failure _ | Not_found -> None




(* Styles *)

type cert_style = string * Str.regexp * string * generic_prefs
type style = cert_style list

type prefs = {
    font          : string ;
    autocolor     : autocolor ;
    lr_layout     : bool ;
    monotone_path : string ;
    dot_path      : string ;
    ignored_certs : string list ;
    style         : style ;
  }




type shape_props = [
    `FILL_COLOR of string
  | `OUTLINE_COLOR of string
  | `WIDTH_PIXELS of int]
type text_props = [
    `FILL_COLOR of string
  | `FONT of string
  | `WEIGHT of int]

let match_style { autocolor = autocolor_pref ; style = style } g mtn =
  let autocolor = Autocolor.autocolor autocolor_pref in

  fun id (default_rect_props, default_txt_props) ->
    let get_cert = Monotone.cert_value mtn id in

    let matching_attrs =
      List.fold_left
	(fun acc (cert_name, value_re, _, attr) ->
	  List.fold_left
	    (fun acc c_value ->
	      if Str.string_match value_re c_value 0
	      then List.fold_left add_pref acc attr
	      else acc)
	    acc (get_cert cert_name))
	[] style in
    let build_props dflt keys =
      List.fold_left
	(fun acc key ->
	  Viz_misc.may_cons (lookup matching_attrs key) acc)
	dflt keys in
    let rect_props : shape_props list =
      build_props default_rect_props
	[ ("color",         fun i -> `FILL_COLOR (string_of_item i)) ;
	  ("outline_color", fun i -> `OUTLINE_COLOR (string_of_item i)) ;
	  ("width_pixels",  fun i -> `WIDTH_PIXELS (int_of_item i)) ; ] in
    let text_props : text_props list =
      build_props default_txt_props
	[ ("text_color", fun i -> `FILL_COLOR (string_of_item i)) ;
	  ("font",       fun i -> `FONT (string_of_item i)) ;
	  ("weight",     fun i -> `WEIGHT (int_of_item i)) ; ] in
    let cleanup_rect_props =
      fst
	(List.fold_left
	   (fun ((props, seen_props) as acc) -> function
	     | `FILL_COLOR _ when List.mem `FILL_COLOR seen_props -> acc
	     | `FILL_COLOR _ as i -> (i :: props, `FILL_COLOR :: seen_props)
	     | `OUTLINE_COLOR _ when List.mem `OUTLINE_COLOR seen_props -> acc
	     | `OUTLINE_COLOR _ as i -> (i :: props, `OUTLINE_COLOR :: seen_props)
	     | `WIDTH_PIXELS _ when List.mem `WIDTH_PIXELS seen_props -> acc
	     | `WIDTH_PIXELS _ as i -> (i :: props, `WIDTH_PIXELS :: seen_props))
	   ([], []) rect_props) in
    let cleanup_text_props =
      fst
	(List.fold_left
	   (fun ((props, seen_props) as acc) -> function
	     | `FILL_COLOR _ when List.mem `FILL_COLOR seen_props -> acc
	     | `FILL_COLOR _ as i -> (i :: props, `FILL_COLOR :: seen_props)
	     | `FONT _ when List.mem `FONT seen_props -> acc
	     | `FONT _ as i -> (i :: props, `FONT :: seen_props)
	     | `WEIGHT _ when List.mem `WEIGHT seen_props -> acc
	     | `WEIGHT _ as i -> (i :: props, `WEIGHT :: seen_props))
	   ([], []) text_props) in
    let rect_props =
      if List.exists (function `FILL_COLOR _ -> true | _ -> false) cleanup_rect_props
      then cleanup_rect_props
      else begin
	let autocolor_key =
	  match autocolor_pref with
	  | BY_AUTHOR_HASH -> get_cert "author"
	  | BY_BRANCH_HASH -> get_cert "branch"
	  | NONE -> [] in
	`FILL_COLOR_RGBA (autocolor autocolor_key) :: cleanup_rect_props
      end in

    (rect_props, cleanup_text_props)




(* Parsing *)

type token = Genlex.token =
  | Kwd of string
  | Ident of string
  | Int of int
  | Float of float
  | String of string
  | Char of char

let lex = Genlex.make_lexer [ "[" ; "="; ";"; "]" ]

let rec parse_list ?(q=Q.empty) p = parser
  | [< e = p ; nxt >] -> parse_list ~q:(Q.push q e) p nxt
  | [<>] -> Q.to_list q
let rec parse_list_sep sep ?(q=Q.empty) p = parser
  | [< e = p ; nxt >] -> opt_sep sep (Q.push q e) p nxt
  | [<>] -> Q.to_list q
and opt_sep sep q p = parser
  | [< ' Kwd s when s = sep ; nxt >] -> parse_list_sep sep ~q p nxt
  | [<>] -> Q.to_list q

type entry =
  | Cert_style of cert_style
  | Pref of (string * item)

let rec parse strm = parse_list style_or_pref strm
and style_or_pref = parser
  | [< ' Ident "cert" ; ' Ident cert_name ; ' String cert_value_re ; attr = attr_list >] ->
      Cert_style (cert_name, Str.regexp cert_value_re, cert_value_re, attr)
  | [< ' Ident key ; v = value >] ->
      Pref (key, v)
and attr_list = parser
  | [< ' Kwd "["; l = parse_list_sep ";" attr ; ' Kwd "]" >] -> l
and attr = parser
  | [< ' Ident key; ' Kwd "="; v = value >] -> (key, v)
and value = parser
  | [< ' Ident "true" >] -> `BOOL true
  | [< ' Ident "yes" >] -> `BOOL true
  | [< ' Ident "false" >] -> `BOOL false
  | [< ' Ident "no" >] -> `BOOL false
  | [< ' Ident i >] -> `STRING i
  | [< ' Int i >] -> `INT i
  | [< ' Float f >] -> `FLOAT f
  | [< ' String s >] -> `STRING s

let parse_file filename =
  if not (Sys.file_exists filename ) then [] else
  Viz_misc.with_file_in
    (fun ic -> parse (lex (Stream.of_channel ic)))
    filename

let parse_merge_items (prefs, style) filename =
  let parsed_data =
    try parse_file filename
    with
    | Sys_error msg ->
	Viz_types.errorf
	  "Error while accessing the style file '%s':\n%s" filename msg
    | Stream.Failure | Stream.Error _ ->
	Viz_types.errorf
	  "Syntax error in the style file '%s'" filename in
  let (prefs, style_entries) =
    List.fold_left
      (fun (prefs, style) -> function
	| Cert_style c -> (prefs, Q.push style c)
	| Pref k -> (add_pref prefs k, style))
      (prefs, Q.empty)
      parsed_data in
  (prefs, style @ (Q.to_list style_entries))




(* Conversion *)

let defaults =
  {
   font          = "Monospace 8" ;
   autocolor     = BY_AUTHOR_HASH ;
   lr_layout     = false ;
   monotone_path = "mtn" ;
   dot_path      = "dot" ;
   ignored_certs = [] ;
   style         = [] ;
 }

let prefs_of_items (i, style) =
  let get mk n = some (lookup i (mk n)) in
  { font          = get string_key    "font" ;
    autocolor     = get autocolor_key "autocolor" ;
    lr_layout     = get bool_key      "lr_layout" ;
    monotone_path = get string_key    "monotone" ;
    dot_path      = get string_key    "dot" ;
    ignored_certs = get string_list_key "ignored_certs" ;
    style         = style ;
  }

let items_of_prefs p =
  [ "font",          `STRING p.font ;
    "autocolor",     item_of_autocolor p.autocolor ;
    "lr_layout",     `BOOL p.lr_layout ;
    "monotone",      `STRING p.monotone_path ;
    "dot",           `STRING p.dot_path ;
    "ignored_certs",  `STRING (String.concat "," p.ignored_certs) ;
  ], p.style

let style_file_name =
  Filename.concat
    (get_home_dir ())
    ".monotone-viz.style"

let parse_merge p filename =
  prefs_of_items (parse_merge_items (items_of_prefs p) filename)

let load () = parse_merge defaults style_file_name

let separator = "-*- monotone-viz -*-"

let save_prefs b prefs =
  Printf.bprintf b "autocolor %s\n" (string_of_autocolor prefs.autocolor) ;
  Printf.bprintf b "lr_layout %B\n" prefs.lr_layout ;
  Printf.bprintf b "monotone  %S\n" prefs.monotone_path ;
  Printf.bprintf b "dot  %S\n" prefs.dot_path

let save prefs =
  let lines =
    if Sys.file_exists style_file_name
    then with_file_in input_lines style_file_name
    else [] in
  let keep_lines =
    let re = Str.regexp (" *(\\* *" ^ (Str.quote separator) ^ " *\\*)") in
    let rec keep = function
      | l :: tl when Str.string_match re l 0 -> []
      | l :: tl -> l :: keep tl
      | [] -> [] in
    keep lines in
  let keep_styles =
    List.fold_right
      (fun i acc -> match i with
      | Cert_style s -> s :: acc
      | Pref _ -> acc)
      (parse (lex (Stream.of_string (String.concat "\n" keep_lines))))
      [] in
  let b = Buffer.create 4096 in
  save_prefs b prefs ;
  List.iter
    (function
      | s when List.mem s keep_styles -> ()
      | (cert_name, _, re, attrs) ->
      Printf.bprintf b "cert %s %S [%s]\n"
	cert_name re
	(String.concat "; "
	   (List.map
	      (fun (k, v) -> Printf.sprintf "%s = %a" k print_item v)
	      attrs)))
    prefs.style ;
  with_file_out
    (fun oc ->
      let ol l = output_string oc l ; output_char oc '\n' in
      List.iter ol keep_lines ;
      Printf.fprintf oc "(* %s *)\n\n" separator ;
      Buffer.output_buffer oc b)
    style_file_name