Merge pull request #406 from c-cube/wip-format-stag

use `stag` for color handling in CCFormat
This commit is contained in:
Simon Cruanes 2022-03-19 18:53:43 -04:00 committed by GitHub
commit 6fa4c1c7d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 133 additions and 49 deletions

View file

@ -19,6 +19,7 @@ let do_not_test file =
assert (not (is_suffix ~sub:"make.ml" file)); assert (not (is_suffix ~sub:"make.ml" file));
str_sub ~sub:"Labels.ml" file || str_sub ~sub:"Labels.ml" file ||
is_suffix ~sub:".pp.ml" file || is_suffix ~sub:".pp.ml" file ||
(if Sys.ocaml_version < "4.08" then Filename.basename file = "CCFormat.ml" else false) ||
is_suffix ~sub:".pp.mli" file || is_suffix ~sub:".pp.mli" file ||
is_suffix ~sub:"containers.ml" file || is_suffix ~sub:"containers.ml" file ||
is_suffix ~sub:"_top.ml" file || is_suffix ~sub:"_top.ml" file ||

View file

@ -312,8 +312,7 @@ module ANSI_codes = struct
exception No_such_style exception No_such_style
(* parse a string tag. (* parse a string tag. *)
TODO: use [stag], for OCaml >= 4.08 *)
let style_of_tag_ s = match String.trim s with let style_of_tag_ s = match String.trim s with
| "reset" -> [`Reset] | "reset" -> [`Reset]
| "black" -> [`FG `Black] | "black" -> [`FG `Black]
@ -338,69 +337,122 @@ end
let color_enabled = ref false let color_enabled = ref false
(* either prints the tag of [s] or delegate to [or_else] *) let mark_open_style st style =
let mark_open_tag st ~or_else s = Stack.push style st;
let open ANSI_codes in if !color_enabled then ANSI_codes.string_of_style_list style else ""
try
let style = style_of_tag_ s in
Stack.push style st;
if !color_enabled then string_of_style_list style else ""
with No_such_style -> or_else s
let mark_close_tag st ~or_else s = let mark_close_style st : string =
let open ANSI_codes in let style =
(* check if it's indeed about color *) try
match style_of_tag_ s with ignore (Stack.pop st); (* pop current style (if well-scoped …) *)
| _ -> Stack.top st (* look at previous style *)
let style = with Stack.Empty ->
try [`Reset]
ignore (Stack.pop st); (* pop current style (if well-scoped …) *) in
Stack.top st (* look at previous style *) if !color_enabled then ANSI_codes.string_of_style_list style else ""
with Stack.Empty ->
[`Reset]
in
if !color_enabled then string_of_style_list style else ""
| exception No_such_style -> or_else s
[@@@ifge 4.8] [@@@ifge 4.8]
type stag +=
| Style of ANSI_codes.style list
let pp_open_tag out s = pp_open_stag out (String_tag s) let pp_open_tag out s = pp_open_stag out (String_tag s)
let pp_close_tag out () = pp_close_stag out () let pp_close_tag out () = pp_close_stag out ()
[@@@ocaml.warning "-3"] (* either prints the tag of [s] or delegate to [or_else] *)
let pp_get_formatter_tag_functions = pp_get_formatter_tag_functions let mark_open_stag st ~or_else (tag:stag) : string =
let pp_set_formatter_tag_functions = pp_set_formatter_tag_functions match tag with
| Style style ->
mark_open_style st style;
| String_tag s ->
let open ANSI_codes in
begin
try
let style = style_of_tag_ s in
mark_open_style st style
with No_such_style -> or_else tag
end
| _ -> or_else tag
let update_tag_funs_ funs f1 f2 = let mark_close_stag st ~or_else (tag:stag) : string =
{ funs with match tag with
mark_open_tag = f1 ~or_else:funs.mark_open_tag; | Style _ -> mark_close_style st
mark_close_tag = f2 ~or_else:funs.mark_close_tag; | String_tag s ->
} let open ANSI_codes in
(* check if it's indeed about color *)
begin match style_of_tag_ s with
| _ -> mark_close_style st
| exception No_such_style -> or_else tag
end
| _ -> or_else tag
[@@@ocaml.warning "+3"] let with_styling stl out f =
pp_open_stag out (Style stl);
try let x = f() in pp_close_stag out (); x
with e -> pp_close_stag out (); raise e
[@@@else_] let styling stl pp out x =
with_styling stl out @@ fun () -> pp out x
let update_tag_funs_ funs f1 f2 =
{ funs with
mark_open_tag = f1 funs.mark_open_tag;
mark_close_tag = f2 funs.mark_close_tag;
}
[@@@endif]
(* add color handling to formatter [ppf] *) (* add color handling to formatter [ppf] *)
let set_color_tag_handling ppf = let set_color_tag_handling ppf =
let functions = pp_get_formatter_tag_functions ppf () in
let st = Stack.create () in (* stack of styles *) let st = Stack.create () in (* stack of styles *)
let functions' =
update_tag_funs_ functions
(mark_open_tag st)
(mark_close_tag st)
in
pp_set_mark_tags ppf true; (* enable tags *) pp_set_mark_tags ppf true; (* enable tags *)
pp_set_formatter_tag_functions ppf functions' let funs = pp_get_formatter_stag_functions ppf () in
let funs' = {
funs with
mark_open_stag = mark_open_stag st ~or_else:funs.mark_open_stag;
mark_close_stag = mark_close_stag st ~or_else:funs.mark_close_stag;
}
in
pp_set_formatter_stag_functions ppf funs'
(*$R
set_color_default true;
let s = sprintf
"what is your %a? %a! No, %a! Ahhhhhhh@."
(styling [`FG `White; `Bold] string) "favorite color"
(styling [`FG `Blue] string) "blue"
(styling [`FG `Red] string) "red"
in
assert_equal ~printer:CCFun.id
"what is your \027[37;1mfavorite color\027[0m? \027[34mblue\027[0m! No, \027[31mred\027[0m! Ahhhhhhh\n"
s
*)
[@@@else_]
(* either prints the tag of [s] or delegate to [or_else] *)
let mark_open_tag st ~or_else (s:string) : string =
let open ANSI_codes in
begin
try
let style = style_of_tag_ s in
mark_open_style st style
with No_such_style -> or_else s
end
let mark_close_tag st ~or_else (s:string) : string =
let open ANSI_codes in
(* check if it's indeed about color *)
begin match style_of_tag_ s with
| _ -> mark_close_style st
| exception No_such_style -> or_else s
end
(* add color handling to formatter [ppf] *)
let set_color_tag_handling ppf =
let st = Stack.create () in (* stack of styles *)
pp_set_mark_tags ppf true; (* enable tags *)
let funs = pp_get_formatter_tag_functions ppf () in
let functions = {
funs with
mark_open_tag = mark_open_tag st ~or_else:funs.mark_open_tag;
mark_close_tag = mark_close_tag st ~or_else:funs.mark_close_tag;
} in
pp_set_formatter_tag_functions ppf functions
[@@@endif]
let set_color_default = let set_color_default =
let first = ref true in let first = ref true in

View file

@ -324,6 +324,37 @@ module ANSI_codes : sig
is a very shiny style. *) is a very shiny style. *)
end end
[@@@ifge 4.8]
val styling : ANSI_codes.style list -> 'a printer -> 'a printer
(** [styling st p] is the same printer as [p], except it locally sets
the style [st].
Example:
{[
open CCFormat;
set_color_default true;
sprintf
"what is your %a? %a! No, %a! Ahhhhhhh@."
(styling [`FG `White; `Bold] string) "favorite color"
(styling [`FG `Blue] string) "blue"
(styling [`FG `Red] string) "red"
]}
Available only on OCaml >= 4.08.
@since NEXT_RELEASE *)
val with_styling : ANSI_codes.style list -> t -> (unit -> 'a) -> 'a
(** [with_styling style fmt f] sets the given style on [fmt],
calls [f()], then restores the previous style.
It is useful in imperative-style printers (a sequence of "print a; print b; …").
Available only on OCaml >= 4.08.
@since NEXT_RELEASE *)
[@@@endif]
(** {2 IO} *) (** {2 IO} *)
val output : t -> 'a printer -> 'a -> unit val output : t -> 'a printer -> 'a -> unit