Branch data Line data Source code
1 : : #include <mutable/util/DotTool.hpp> 2 : : 3 : : #include <mutable/util/fn.hpp> 4 : : #include <mutable/util/macro.hpp> 5 : : #include <fstream> 6 : : #include <iostream> 7 : : #include <sstream> 8 : : 9 : : #if __linux 10 : : #include <dlfcn.h> 11 : : #include <unistd.h> 12 : : #elif __APPLE__ 13 : : #include <dlfcn.h> 14 : : #include <unistd.h> 15 : : #endif 16 : : 17 : : 18 : : using namespace m; 19 : : 20 : : // POD type forward declarations 21 : : struct GVC_t; 22 : : struct Agraph_t; 23 : : struct graph_t; 24 : : 25 : : // function declarations: add the functions that you need here and to the `SYMBOLS` X macro 26 : : static int(*agclose)(Agraph_t*); 27 : : static Agraph_t*(*agmemread)(const char*); 28 : : static GVC_t*(*gvContext)(); 29 : : static int(*gvFreeContext)(GVC_t*); 30 : : static int(*gvFreeLayout)(GVC_t*, graph_t*); 31 : : static int(*gvLayout)(GVC_t*, graph_t*, const char*); 32 : : static int(*gvRenderFilename)(GVC_t*, graph_t*, const char*, const char*); 33 : : 34 : : #define SYMBOLS(X) \ 35 : : X(agclose) \ 36 : : X(agmemread) \ 37 : : X(gvContext) \ 38 : : X(gvFreeContext) \ 39 : : X(gvFreeLayout) \ 40 : : X(gvLayout) \ 41 : : X(gvRenderFilename) 42 : : 43 : : #define LOADSYM(SYM) SYM = (decltype(SYM))(dlsym(libgraphviz, #SYM)); 44 : : 45 : : #if __linux 46 : : static constexpr const char * LIB_GRAPHVIZ = "libgvc.so"; 47 : : #elif __APPLE__ 48 : : static constexpr const char * LIB_GRAPHVIZ = "libgvc.dylib"; 49 : : #endif 50 : : 51 : : static void *libgraphviz; 52 : : static GVC_t *gvc; 53 : : 54 : 0 : DotTool::DotTool(Diagnostic &diag) 55 : 0 : : diag(diag) 56 : : { 57 : : /*----- Test whether the graphviz library is available. ----------------------------------------------------------*/ 58 : : #if __linux || __APPLE__ 59 : 0 : libgraphviz = dlopen(LIB_GRAPHVIZ, RTLD_LAZY|RTLD_NOLOAD); 60 [ # # ]: 0 : if (libgraphviz == nullptr) { // shared object not yet present; must load 61 : 0 : libgraphviz = dlopen(LIB_GRAPHVIZ, RTLD_LAZY|RTLD_NODELETE); // load shared object 62 : : 63 [ # # ]: 0 : if (libgraphviz) { 64 : : /* Load the required symbols from the shared object. */ 65 : 0 : SYMBOLS(LOADSYM); 66 [ # # ]: 0 : gvc = gvContext(); 67 : 0 : } 68 : 0 : } 69 : : #endif 70 : 0 : } 71 : : 72 : 0 : int DotTool::render_to_pdf(const char *path_to_pdf, const char *algo) 73 : : { 74 : 1 : /*----- Render the dot graph with graphviz. ----------------------------------------------------------------------*/ 75 : 0 : auto dotstr = stream_.str(); 76 [ # # # # ]: 0 : Agraph_t *G = M_notnull(agmemread(dotstr.c_str())); 77 [ # # ]: 0 : gvLayout(gvc, (graph_t*) G, algo); 78 [ # # ]: 0 : auto ret = gvRenderFilename(gvc, (graph_t*) G, "pdf", path_to_pdf); 79 [ # # ]: 0 : gvFreeLayout(gvc, (graph_t*) G); 80 [ # # ]: 0 : agclose(G); 81 : 0 : return ret; 82 : 0 : } 83 : : 84 : 0 : void DotTool::show(const char *name, bool interactive, const char *algo) 85 : : { 86 : : /* Construct filename. */ 87 : 0 : std::ostringstream oss; 88 [ # # # # ]: 0 : oss << name << '_'; 89 : : #if __linux || __APPLE__ 90 [ # # ]: 0 : oss << getpid(); 91 : : #endif 92 : : 93 : : /* Try to render a PDF document. */ 94 [ # # ]: 0 : if (libgraphviz) { 95 [ # # # # ]: 0 : const std::string filename_pdf = oss.str() + ".pdf"; 96 [ # # # # ]: 0 : if (render_to_pdf(filename_pdf.c_str(), algo)) 97 : 0 : goto show_dot; // fall back to DOT 98 : : 99 [ # # ]: 0 : if (interactive) { 100 : : #if __linux 101 [ # # ]: 0 : exec("/usr/bin/setsid", { "--fork", "xdg-open", filename_pdf.c_str() }); 102 : : #elif __APPLE__ 103 : : exec("/usr/bin/open", { "-a", "Preview", filename_pdf.c_str() }); 104 : : #endif 105 : 0 : } else { 106 [ # # # # : 0 : diag.out() << diag.NOTE << "Rendering to '" << filename_pdf << "'.\n" << diag.RESET; # # # # # # # # ] 107 : : } 108 : 0 : return; 109 [ # # ]: 0 : } 110 : : 111 : : show_dot: 112 : : /* Fallback: emit graph as a DOT file. */ 113 [ # # # # ]: 0 : const std::string filename_dot = oss.str() + ".dot"; 114 [ # # ]: 0 : std::ofstream out(filename_dot); 115 [ # # # # ]: 0 : if (not out) { 116 [ # # # # : 0 : diag.err() << "Failed to generate '" << filename_dot << "'.\n"; # # # # ] 117 : 0 : return; 118 : : } 119 [ # # # # ]: 0 : out << stream_.rdbuf(); 120 [ # # ]: 0 : out.flush(); 121 [ # # # # : 0 : diag.out() << diag.NOTE << "Rendering to '" << filename_dot << "'.\n" << diag.RESET; # # # # # # # # ] 122 [ # # ]: 0 : }