feat(graph): render to svg and open it in the browser

This commit is contained in:
sneeker committed 2022-08-03 13:10:57 +00:00
1 parent 31aa8d47a7
commit 034095ceac
1 file changed
+31 -29
+31 -29
View File
@@ -381,29 +381,27 @@ let cmd_targets () =
(Style.plural (List.length p.targets) "target" (Style.plural (List.length p.targets) "target"
:: (if p.scripts = [] then [] else [ Style.plural (List.length p.scripts) "run block" ])))) :: (if p.scripts = [] then [] else [ Style.plural (List.length p.scripts) "run block" ]))))
(* A picture wants a window of its own. Any real image viewer beats drawing over (* Graphviz draws vectors, and a browser is the one viewer everyone has that
the terminal that asked for it, so take whichever one is installed and fall renders them without turning the text into pixels. *)
back to a fullscreen kitty, which a kitty user already has. *) let browsers = [ "firefox"; "xdg-open" ]
let viewers =
[ ("imv", [ "-f" ]); ("swayimg", [ "--fullscreen" ]); ("nsxiv", [ "-f" ]); ("sxiv", [ "-f" ]);
("feh", [ "-F" ]); ("eog", [ "-f" ]); ("qimgv", [ "--fullscreen" ]) ]
let graphical () = let graphical () =
Sys.getenv_opt "DISPLAY" <> None || Sys.getenv_opt "WAYLAND_DISPLAY" <> None Sys.getenv_opt "DISPLAY" <> None || Sys.getenv_opt "WAYLAND_DISPLAY" <> None
let viewer png = let words v = List.filter (fun w -> w <> "") (String.split_on_char ' ' v)
match Sys.getenv_opt "MEOWC_VIEWER" with
| Some v when String.trim v <> "" -> (* A browser reads a relative path as something to search for, not a file *)
Some (Array.of_list (List.filter (fun w -> w <> "") (String.split_on_char ' ' v) @ [ png ])) let file_url p =
| _ -> ( "file://" ^ if Filename.is_relative p then Filename.concat (Sys.getcwd ()) p else p
match List.find_opt (fun (v, _) -> Exec.which v) viewers with
| Some (v, flags) -> Some (Array.of_list ((v :: flags) @ [ png ])) let viewer svg =
| None -> let named =
if Exec.which "kitty" then match (Sys.getenv_opt "MEOWC_VIEWER", Sys.getenv_opt "BROWSER") with
Some | Some v, _ when String.trim v <> "" -> Some (words v)
[| "kitty"; "--start-as=fullscreen"; "--title"; "meowc graph"; "--hold"; "kitten"; | _, Some v when String.trim v <> "" -> Some (words v)
"icat"; "--scale-up"; png |] | _ -> Option.map (fun b -> [ b ]) (List.find_opt Exec.which browsers)
else None) in
Option.map (fun argv -> Array.of_list (argv @ [ file_url svg ])) named
(* The viewer outlives meowc, and its startup chatter is not meowc's output *) (* The viewer outlives meowc, and its startup chatter is not meowc's output *)
let spawn_detached cmd = let spawn_detached cmd =
@@ -420,19 +418,22 @@ let spawn_detached cmd =
let inline () = let inline () =
Sys.getenv_opt "KITTY_WINDOW_ID" <> None && Unix.isatty Unix.stdout && Exec.which "kitten" Sys.getenv_opt "KITTY_WINDOW_ID" <> None && Unix.isatty Unix.stdout && Exec.which "kitten"
let draw ~png dot = let draw ~svg dot =
if not (Exec.which "dot") then if not (Exec.which "dot") then
Diag.error ~hint:"install graphviz" "dot is not on the path, so there is nothing to draw with"; Diag.error ~hint:"install graphviz" "dot is not on the path, so there is nothing to draw with";
let src = Filename.temp_file "meowc-graph" ".dot" in let src = Filename.temp_file "meowc-graph" ".dot" in
Fs.mkdir_p (Filename.dirname png); Fs.mkdir_p (Filename.dirname svg);
Fs.write src dot; Fs.write src dot;
let rendered = Exec.ok [| "dot"; "-Tpng"; "-o"; png; src |] in let rendered = Exec.ok [| "dot"; "-Tsvg"; "-o"; svg; src |] in
(try Sys.remove src with Sys_error _ -> ()); let finish () = try Sys.remove src with Sys_error _ -> () in
if not rendered then Diag.error "dot could not render the graph"; if not rendered then (finish (); Diag.error "dot could not render the graph");
match (if graphical () then viewer png else None) with (match (if graphical () then viewer svg else None) with
| Some cmd -> spawn_detached cmd | Some cmd -> spawn_detached cmd
| None -> | None ->
if inline () then begin if inline () then begin
(* a terminal cannot draw vectors, so this one branch wants pixels *)
let png = Filename.remove_extension svg ^ ".png" in
ignore (Exec.ok [| "dot"; "-Tpng"; "-o"; png; src |]);
flush stdout; flush stdout;
let cmd = [| "kitten"; "icat"; png |] in let cmd = [| "kitten"; "icat"; png |] in
match Unix.create_process cmd.(0) cmd Unix.stdin Unix.stdout Unix.stderr with match Unix.create_process cmd.(0) cmd Unix.stdin Unix.stdout Unix.stderr with
@@ -440,24 +441,25 @@ let draw ~png dot =
| exception Unix.Unix_error (e, _, _) -> | exception Unix.Unix_error (e, _, _) ->
Diag.error "cannot run kitten: %s" (Unix.error_message e) Diag.error "cannot run kitten: %s" (Unix.error_message e)
end end
else Printf.printf " %s %s\n" (Style.dim "wrote") png else Printf.printf " %s %s\n" (Style.dim "wrote") svg);
finish ()
(* Raw graphviz on a terminal is for nobody, so draw it there instead *) (* Raw graphviz on a terminal is for nobody, so draw it there instead *)
let drawing () = fl.show || (fl.dot && Unix.isatty Unix.stdout) let drawing () = fl.show || (fl.dot && Unix.isatty Unix.stdout)
let cmd_graph names = let cmd_graph names =
let p = configure () in let p = configure () in
let png = Filename.concat p.tc.builddir "graph.png" in let svg = Filename.concat p.tc.builddir "graph.svg" in
if fl.targets_only then if fl.targets_only then
let text = Dot.targets_only p in let text = Dot.targets_only p in
if drawing () then draw ~png text else print_string text if drawing () then draw ~svg text else print_string text
else else
let b = Build.of_project p in let b = Build.of_project p in
let selected = let selected =
match names with [] -> None | _ -> Some (Build.select b (select_targets p names)) match names with [] -> None | _ -> Some (Build.select b (select_targets p names))
in in
let keep i = match selected with None -> true | Some s -> Hashtbl.mem s i in let keep i = match selected with None -> true | Some s -> Hashtbl.mem s i in
if drawing () then draw ~png (Dot.render ?selected b) if drawing () then draw ~svg (Dot.render ?selected b)
else if fl.dot then print_string (Dot.render ?selected b) else if fl.dot then print_string (Dot.render ?selected b)
else begin else begin
banner p; banner p;