]> granicus.if.org Git - neomutt/commitdiff
devel: graphviz dump devel/account 1739/head
authorRichard Russon <rich@flatcap.org>
Thu, 27 Jun 2019 23:49:35 +0000 (00:49 +0100)
committerRichard Russon <rich@flatcap.org>
Mon, 21 Oct 2019 21:42:37 +0000 (22:42 +0100)
Walk though the Account and Mailbox objects and draw a Graphviz diagram
to represent them.

The `.gv` file is given a timestamp filename.

Makefile.autosetup
auto.def
graphviz.c [new file with mode: 0644]
index.c
mutt_signal.c
protos.h
version.c

index 87f9f3b9f8ac81a1b53e6dcebfcae2384484b503..fb97a9dd30a5ce99237b0b5d028fffb2d73acea9 100644 (file)
@@ -71,7 +71,7 @@ NEOMUTTOBJS=  addrbook.o alias.o bcache.o browser.o color.o commands.o \
                muttlib.o mx.o myvar.o pager.o pattern.o postpone.o progress.o query.o \
                recvattach.o recvcmd.o resize.o rfc3676.o score.o send.o sendlib.o \
                sidebar.o smtp.o sort.o state.o status.o system.o terminal.o version.o \
-               tracker.o
+               tracker.o graphviz.o
 @if HAVE_LIBUNWIND
 NEOMUTTOBJS+=  backtrace.o
 @endif
index 1b92e27db237643ba74f36ff0e0daccc9dffe67c..ffbec1ffed221b286f343248306f4cd338f68f6e 100644 (file)
--- a/auto.def
+++ b/auto.def
@@ -102,6 +102,8 @@ options {
 # Testing
   testing=0                 => "Enable Unit Testing"
   coverage=0                => "Enable Coverage Testing"
+# Development
+  devel-graphviz            => "Enable Graphviz dump (DEVEL)"
 # Configure with pkg-config
   pkgconf=0                 => "Use pkg-config during configure"
 # Enable all options
@@ -116,7 +118,7 @@ options {
 if {1} {
   # Keep sorted, please.
   foreach opt {
-    autocrypt backtrace bdb coverage doc everything fmemopen full-doc gdbm
+    autocrypt backtrace bdb coverage devel-graphviz doc everything fmemopen full-doc gdbm
     gnutls gpgme gss homespool idn idn2 inotify kyotocabinet lmdb locales-fix
     lua mixmaster nls notmuch pgp pkgconf qdbm sasl smime sqlite ssl testing
     tokyocabinet
@@ -384,6 +386,10 @@ switch [opt-val with-lock fcntl] {
 # Locales fix
 if {[get-define want-locales-fix]} {define LOCALES_HACK}
 
+###############################################################################
+# (DEVEL) Graphviz dump
+if {[get-define want-devel-graphviz]} {define USE_DEVEL_GRAPHVIZ}
+
 ###############################################################################
 # Documentation
 if {[get-define want-doc]} {
diff --git a/graphviz.c b/graphviz.c
new file mode 100644 (file)
index 0000000..fe658a6
--- /dev/null
@@ -0,0 +1,811 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include "imap/imap_private.h"
+#include "maildir/maildir_private.h"
+#include "notmuch/notmuch_private.h"
+#include "pop/pop_private.h"
+#include "email/lib.h"
+#include "conn/conn.h"
+#include "compress.h"
+#include "context.h"
+#include "core/lib.h"
+#include "globals.h"
+#include "mbox/mbox.h"
+#include "nntp/nntp.h"
+#include "notmuch/mutt_notmuch.h"
+
+// #define GV_HIDE_CONTEXT
+#define GV_HIDE_CONTEXT_CONTENTS
+// #define GV_HIDE_MBOX
+// #define GV_HIDE_NEOMUTT
+// #define GV_HIDE_CONFIG
+
+void dot_type_bool(FILE *fp, const char *name, bool val)
+{
+  static const char *values[] = { "false", "true" };
+  fprintf(fp, "\t\t<tr>\n");
+  fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", name);
+  fprintf(fp, "\t\t\t<td border=\"0\">=</td>\n");
+  fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", values[val]);
+  fprintf(fp, "\t\t</tr>\n");
+}
+
+void dot_type_date(char *buf, size_t buflen, time_t timestamp)
+{
+  mutt_date_localtime_format(buf, buflen, "%Y-%m-%d %H:%M:%S", timestamp);
+}
+
+void dot_type_file(FILE *fp, const char *name, FILE *struct_fp)
+{
+  fprintf(fp, "\t\t<tr>\n");
+  fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", name);
+  fprintf(fp, "\t\t\t<td border=\"0\">=</td>\n");
+  if (struct_fp)
+  {
+    fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%p (%d)</td>\n",
+            (void *) struct_fp, fileno(struct_fp));
+  }
+  else
+  {
+    fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">NULL</td>\n");
+  }
+  fprintf(fp, "\t\t</tr>\n");
+}
+
+void dot_type_number(FILE *fp, const char *name, int num)
+{
+  fprintf(fp, "\t\t<tr>\n");
+  fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", name);
+  fprintf(fp, "\t\t\t<td border=\"0\">=</td>\n");
+  fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%d</td>\n", num);
+  fprintf(fp, "\t\t</tr>\n");
+}
+
+void dot_type_string_escape(char *buf, size_t buflen)
+{
+  for (; buf[0]; buf++)
+  {
+    if (buf[0] == '<')
+      mutt_str_inline_replace(buf, buflen, 1, "&lt;");
+    else if (buf[0] == '>')
+      mutt_str_inline_replace(buf, buflen, 1, "&gt;");
+    else if (buf[0] == '&')
+      mutt_str_inline_replace(buf, buflen, 1, "&amp;");
+  }
+}
+
+void dot_type_string(FILE *fp, const char *name, const char *str)
+{
+  char buf[1024] = "[NULL]";
+
+  if (str)
+  {
+    mutt_str_strfcpy(buf, str, sizeof(buf));
+    dot_type_string_escape(buf, sizeof(buf));
+  }
+
+  bool quoted = ((buf[0] != '[') && (buf[0] != '*'));
+
+  fprintf(fp, "\t\t<tr>\n");
+  fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", name);
+  fprintf(fp, "\t\t\t<td border=\"0\">=</td>\n");
+  if (quoted)
+    fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">\"%s\"</td>\n", buf);
+  else
+    fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", buf);
+  fprintf(fp, "\t\t</tr>\n");
+}
+
+void dot_type_umask(char *buf, size_t buflen, int umask)
+{
+  snprintf(buf, buflen, "0%03o", umask);
+}
+
+void dot_ptr_name(char *buf, size_t buflen, void *ptr)
+{
+  snprintf(buf, buflen, "obj_%p", ptr);
+}
+
+void dot_ptr(FILE *fp, const char *name, void *ptr, const char *colour)
+{
+  fprintf(fp, "\t\t<tr>\n");
+  fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", name);
+  fprintf(fp, "\t\t\t<td border=\"0\">=</td>\n");
+  if (colour && ptr)
+  {
+    fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\" bgcolor=\"%s\">%p</td>\n",
+            colour, ptr);
+  }
+  else
+  {
+    fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%p</td>\n", ptr);
+  }
+  fprintf(fp, "\t\t</tr>\n");
+}
+
+void dot_add_link(struct ListHead *links, void *src, void *dst, const char *label, bool back)
+{
+  if (!src || !dst)
+    return;
+
+  char obj1[16] = { 0 };
+  char obj2[16] = { 0 };
+  char text[256] = { 0 };
+  char lstr[128] = { 0 };
+
+  dot_ptr_name(obj1, sizeof(obj1), src);
+  dot_ptr_name(obj2, sizeof(obj2), dst);
+
+  if (label)
+    snprintf(lstr, sizeof(lstr), "edgetooltip=\"%s\"", label);
+
+  snprintf(text, sizeof(text), "%s -> %s [ %s %s ]", obj1, obj2,
+           back ? "dir=back" : "", lstr);
+  mutt_list_insert_tail(links, mutt_str_strdup(text));
+}
+
+void dot_graph_header(FILE *fp)
+{
+  fprintf(fp, "digraph neomutt\n");
+  fprintf(fp, "{\n\n");
+
+  fprintf(fp, "\tgraph [\n");
+  fprintf(fp, "\t\trankdir=\"TB\"\n");
+  fprintf(fp, "\t\tnodesep=\"0.5\"\n");
+  fprintf(fp, "\t\tranksep=\"0.5\"\n");
+  fprintf(fp, "\t];\n");
+  fprintf(fp, "\n");
+  fprintf(fp, "\tnode [\n");
+  fprintf(fp, "\t\tshape=\"plain\"\n");
+  fprintf(fp, "\t];\n");
+  fprintf(fp, "\n");
+  fprintf(fp, "\tedge [\n");
+  fprintf(fp, "\t\tpenwidth=\"4.5\"\n");
+  fprintf(fp, "\t\tarrowsize=\"1.0\"\n");
+  fprintf(fp, "\t\tcolor=\"#c0c0c0\"\n");
+  fprintf(fp, "\t];\n");
+  fprintf(fp, "\n");
+}
+
+void dot_graph_footer(FILE *fp, struct ListHead *links)
+{
+  fprintf(fp, "\n");
+  struct ListNode *np = NULL;
+  STAILQ_FOREACH(np, links, entries)
+  {
+    fprintf(fp, "\t%s;\n", np->data);
+  }
+  fprintf(fp, "\n}\n");
+}
+
+void dot_object_header(FILE *fp, void *ptr, const char *name, const char *colour)
+{
+  char obj[16] = { 0 };
+  dot_ptr_name(obj, sizeof(obj), ptr);
+
+  if (!colour)
+    colour = "#ffff80";
+
+  fprintf(fp, "\t%s [\n", obj);
+  fprintf(fp, "\t\tlabel=<<table cellspacing=\"0\" border=\"1\" rows=\"*\" "
+              "color=\"#d0d0d0\">\n");
+  fprintf(fp, "\t\t<tr>\n");
+  fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\" bgcolor=\"%s\" port=\"top\" colspan=\"3\"><font color=\"#000000\" point-size=\"20\"><b>%s</b></font> <font point-size=\"12\">(%p)</font></td>\n",
+          colour, name, ptr);
+  fprintf(fp, "\t\t</tr>\n");
+}
+
+void dot_object_footer(FILE *fp)
+{
+  fprintf(fp, "\t\t</table>>\n");
+  fprintf(fp, "\t];\n");
+  fprintf(fp, "\n");
+}
+
+void dot_node(FILE *fp, void *ptr, const char *name, const char *colour)
+{
+  char obj[16] = { 0 };
+  dot_ptr_name(obj, sizeof(obj), ptr);
+
+  fprintf(fp, "\t%s [\n", obj);
+  fprintf(fp, "\t\tlabel=<<table cellspacing=\"0\" border=\"1\" rows=\"*\" "
+              "color=\"#d0d0d0\">\n");
+  fprintf(fp, "\t\t<tr>\n");
+  fprintf(fp, "\t\t\t<td border=\"0\" bgcolor=\"%s\" port=\"top\"><font color=\"#000000\" point-size=\"20\"><b>%s</b></font></td>\n",
+          colour, name);
+  fprintf(fp, "\t\t</tr>\n");
+  dot_object_footer(fp);
+}
+
+void dot_node_link(FILE *fp, void *ptr, const char *name, void *link, const char *colour)
+{
+  char obj[16] = { 0 };
+  dot_ptr_name(obj, sizeof(obj), ptr);
+
+  fprintf(fp, "\t%s [\n", obj);
+  fprintf(fp, "\t\tlabel=<<table cellspacing=\"0\" border=\"1\" rows=\"*\" "
+              "color=\"#d0d0d0\">\n");
+  fprintf(fp, "\t\t<tr>\n");
+  fprintf(fp, "\t\t\t<td border=\"0\" bgcolor=\"%s\" port=\"top\"><font color=\"#000000\" point-size=\"20\"><b>%s</b></font></td>\n",
+          colour, name);
+  fprintf(fp, "\t\t</tr>\n");
+
+  fprintf(fp, "\t\t<tr>\n");
+  fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\" bgcolor=\"%s\">%p</td>\n", colour, link);
+  fprintf(fp, "\t\t</tr>\n");
+
+  dot_object_footer(fp);
+}
+
+void dot_path_fs(char *buf, size_t buflen, const char *path)
+{
+  const char *slash = strrchr(path, '/');
+  if (slash)
+    slash++;
+  else
+    slash = path;
+
+  mutt_str_strfcpy(buf, slash, buflen);
+}
+
+void dot_path_imap(char *buf, size_t buflen, const char *path)
+{
+  char tmp[1024] = { 0 };
+  mutt_str_strfcpy(tmp, path, sizeof(tmp));
+
+  struct Url *u = url_parse(tmp);
+
+  if (u->path && (u->path[0] != '\0'))
+    mutt_str_strfcpy(buf, u->path, buflen);
+  else
+    snprintf(buf, buflen, "%s:%s", u->host, u->user);
+
+  url_free(&u);
+}
+
+void dot_config(FILE *fp, const char *name, int type, struct ConfigSubset *sub,
+                struct ListHead *links)
+{
+  if (!sub)
+    return;
+
+  struct Buffer value = mutt_buffer_make(256);
+  dot_object_header(fp, (void *) name, "Config", "#ffff80");
+  dot_type_string(fp, "scope", sub->name);
+
+  char scope[256];
+  snprintf(scope, sizeof(scope), "%s:", sub->name);
+
+  struct HashElem **list = get_elem_list(sub->cs);
+  for (size_t i = 0; list[i]; i++)
+  {
+    struct HashElem *item = list[i];
+    if ((item->type & type) == 0)
+      continue;
+
+    const char *iname = item->key.strkey;
+    size_t slen = strlen(scope);
+    if (mutt_str_startswith(iname, scope, CASE_MATCH) != 0)
+    {
+      if (strchr(iname + slen, ':'))
+        continue;
+      mutt_buffer_reset(&value);
+      cs_subset_string_get(sub, item, &value);
+      dot_type_string(fp, iname + slen, value.data);
+    }
+  }
+
+  dot_object_footer(fp);
+  mutt_buffer_dealloc(&value);
+}
+
+void dot_comp(FILE *fp, struct CompressInfo *ci, struct ListHead *links)
+{
+  dot_object_header(fp, ci, "CompressInfo", "#c0c060");
+  dot_type_string(fp, "append", ci->cmd_append);
+  dot_type_string(fp, "close", ci->cmd_close);
+  dot_type_string(fp, "open", ci->cmd_open);
+  dot_object_footer(fp);
+}
+
+void dot_mailbox_type(FILE *fp, const char *name, enum MailboxType type)
+{
+  const char *typestr = NULL;
+
+  switch (type)
+  {
+    case MUTT_MBOX:
+      typestr = "MBOX";
+      break;
+    case MUTT_MMDF:
+      typestr = "MMDF";
+      break;
+    case MUTT_MH:
+      typestr = "MH";
+      break;
+    case MUTT_MAILDIR:
+      typestr = "MAILDIR";
+      break;
+    case MUTT_NNTP:
+      typestr = "NNTP";
+      break;
+    case MUTT_IMAP:
+      typestr = "IMAP";
+      break;
+    case MUTT_NOTMUCH:
+      typestr = "NOTMUCH";
+      break;
+    case MUTT_POP:
+      typestr = "POP";
+      break;
+    case MUTT_COMPRESSED:
+      typestr = "COMPRESSED";
+      break;
+    default:
+      typestr = "UNKNOWN";
+  }
+
+  fprintf(fp, "\t\t<tr>\n");
+  fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", name);
+  fprintf(fp, "\t\t\t<td border=\"0\">=</td>\n");
+  fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", typestr);
+  fprintf(fp, "\t\t</tr>\n");
+}
+
+void dot_mailbox_imap(FILE *fp, struct ImapMboxData *mdata, struct ListHead *links)
+{
+  dot_object_header(fp, mdata, "ImapMboxData", "#60c060");
+  dot_type_string(fp, "name", mdata->name);
+  dot_type_string(fp, "munge_name", mdata->munge_name);
+  dot_type_string(fp, "real_name", mdata->real_name);
+  dot_object_footer(fp);
+}
+
+void dot_mailbox_maildir(FILE *fp, struct MaildirMboxData *mdata, struct ListHead *links)
+{
+  char buf[64] = { 0 };
+
+  dot_object_header(fp, mdata, "MaildirMboxData", "#60c060");
+
+  dot_type_date(buf, sizeof(buf), mdata->mtime_cur.tv_sec);
+  dot_type_string(fp, "mtime_cur", buf);
+
+  dot_type_umask(buf, sizeof(buf), mdata->mh_umask);
+  dot_type_string(fp, "mh_umask", buf);
+  dot_object_footer(fp);
+}
+
+void dot_mailbox_mbox(FILE *fp, struct MboxAccountData *mdata, struct ListHead *links)
+{
+  char buf[64] = { 0 };
+
+  dot_object_header(fp, mdata, "MboxAccountData", "#60c060");
+  dot_ptr(fp, "fp", mdata->fp, NULL);
+
+  dot_type_date(buf, sizeof(buf), mdata->atime.tv_sec);
+  dot_type_string(fp, "atime", buf);
+
+  dot_object_footer(fp);
+}
+
+void dot_mailbox_nntp(FILE *fp, struct NntpMboxData *mdata, struct ListHead *links)
+{
+  dot_object_header(fp, mdata, "NntpMboxData", "#60c060");
+  dot_type_string(fp, "group", mdata->group);
+  dot_type_string(fp, "desc", mdata->desc);
+
+  dot_type_number(fp, "first_message", mdata->first_message);
+  dot_type_number(fp, "last_message", mdata->last_message);
+  dot_type_number(fp, "last_loaded", mdata->last_loaded);
+  dot_type_number(fp, "last_cached", mdata->last_cached);
+  dot_type_number(fp, "unread", mdata->unread);
+
+  dot_type_bool(fp, "subscribed", mdata->subscribed);
+  dot_type_bool(fp, "has_new_mail", mdata->has_new_mail);
+  dot_type_bool(fp, "allowed", mdata->allowed);
+  dot_type_bool(fp, "deleted", mdata->deleted);
+
+  dot_object_footer(fp);
+}
+
+void dot_mailbox_notmuch(FILE *fp, struct NmMboxData *mdata, struct ListHead *links)
+{
+  dot_object_header(fp, mdata, "NmMboxData", "#60c060");
+  dot_type_number(fp, "db_limit", mdata->db_limit);
+  dot_object_footer(fp);
+}
+
+void dot_mailbox_pop(FILE *fp, struct PopAccountData *mdata, struct ListHead *links)
+{
+  dot_object_header(fp, mdata, "PopAccountData", "#60c060");
+  dot_ptr(fp, "conn", mdata->conn, "#ff8080");
+  dot_object_footer(fp);
+}
+
+void dot_mailbox(FILE *fp, struct Mailbox *m, struct ListHead *links)
+{
+  char buf[64] = { 0 };
+
+  dot_object_header(fp, m, "Mailbox", "#80ff80");
+  dot_mailbox_type(fp, "type", m->magic);
+  if (m->name)
+    dot_type_string(fp, "name", m->name);
+
+  if ((m->magic == MUTT_IMAP) || (m->magic == MUTT_POP))
+  {
+    dot_path_imap(buf, sizeof(buf), mutt_b2s(&m->pathbuf));
+    dot_type_string(fp, "pathbuf", buf);
+    dot_path_imap(buf, sizeof(buf), m->realpath);
+    dot_type_string(fp, "realpath", buf);
+  }
+  else
+  {
+    dot_path_fs(buf, sizeof(buf), mutt_b2s(&m->pathbuf));
+    dot_type_string(fp, "pathbuf", buf);
+    dot_path_fs(buf, sizeof(buf), m->realpath);
+    dot_type_string(fp, "realpath", buf);
+  }
+
+  // dot_ptr(fp, "mdata", m->mdata, "#e0e060");
+  dot_ptr(fp, "account", m->account, "#80ffff");
+
+  dot_object_footer(fp);
+
+  // dot_add_link(links, m, m->mdata, false);
+
+  if (m->mdata)
+  {
+    if (m->magic == MUTT_MAILDIR)
+      dot_mailbox_maildir(fp, m->mdata, links);
+    else if (m->magic == MUTT_IMAP)
+      dot_mailbox_imap(fp, m->mdata, links);
+    else if (m->magic == MUTT_POP)
+      dot_mailbox_pop(fp, m->mdata, links);
+    else if (m->magic == MUTT_MBOX)
+      dot_mailbox_mbox(fp, m->mdata, links);
+    else if (m->magic == MUTT_NNTP)
+      dot_mailbox_nntp(fp, m->mdata, links);
+    else if (m->magic == MUTT_NOTMUCH)
+      dot_mailbox_notmuch(fp, m->mdata, links);
+
+    dot_add_link(links, m, m->mdata, "Mailbox->mdata", false);
+  }
+
+  if (m->compress_info)
+  {
+    dot_comp(fp, m->compress_info, links);
+    dot_add_link(links, m, m->compress_info, "Mailbox->compress_info", false);
+  }
+
+#ifndef GV_HIDE_CONFIG
+  if (m->name)
+  {
+    dot_config(fp, m->name, DT_INHERIT_MBOX, m->sub, links);
+    dot_add_link(links, m, m->name, "Mailbox Config", false);
+  }
+#endif
+}
+
+void dot_mailbox_node(FILE *fp, struct MailboxNode *mn, struct ListHead *links)
+{
+  dot_node(fp, mn, "MN", "#80ff80");
+
+  dot_mailbox(fp, mn->mailbox, links);
+
+  dot_add_link(links, mn, mn->mailbox, "MailboxNode->mailbox", false);
+
+  struct Buffer buf;
+  mutt_buffer_init(&buf);
+
+  char name[256] = { 0 };
+  mutt_buffer_addstr(&buf, "{ rank=same ");
+
+  dot_ptr_name(name, sizeof(name), mn);
+  mutt_buffer_add_printf(&buf, "%s ", name);
+
+  dot_ptr_name(name, sizeof(name), mn->mailbox);
+  mutt_buffer_add_printf(&buf, "%s ", name);
+
+  if (mn->mailbox->mdata)
+  {
+    dot_ptr_name(name, sizeof(name), mn->mailbox->mdata);
+    mutt_buffer_add_printf(&buf, "%s ", name);
+  }
+
+#ifndef GV_HIDE_CONFIG
+  if (mn->mailbox->name)
+  {
+    dot_ptr_name(name, sizeof(name), mn->mailbox->name);
+    mutt_buffer_add_printf(&buf, "%s ", name);
+  }
+#endif
+
+  mutt_buffer_addstr(&buf, "}");
+
+  mutt_list_insert_tail(links, buf.data);
+  buf.data = NULL;
+}
+
+void dot_mailbox_list(FILE *fp, struct MailboxList *ml, struct ListHead *links, bool abbr)
+{
+  struct MailboxNode *prev = NULL;
+  struct MailboxNode *np = NULL;
+  STAILQ_FOREACH(np, ml, entries)
+  {
+    if (abbr)
+      dot_node_link(fp, np, "MN", np->mailbox, "#80ff80");
+    else
+      dot_mailbox_node(fp, np, links);
+    if (prev)
+      dot_add_link(links, prev, np, "MailboxNode->next", false);
+    prev = np;
+  }
+}
+
+void dot_connection(FILE *fp, struct Connection *c, struct ListHead *links)
+{
+  dot_object_header(fp, c, "Connection", "#ff8080");
+  dot_type_string(fp, "user", c->account.user);
+  dot_type_string(fp, "host", c->account.host);
+  dot_type_number(fp, "port", c->account.port);
+  // dot_ptr(fp, "data", c->data, "#60c0c0");
+  dot_type_number(fp, "fd", c->fd);
+  dot_object_footer(fp);
+}
+
+void dot_account_imap(FILE *fp, struct ImapAccountData *adata, struct ListHead *links)
+{
+  dot_object_header(fp, adata, "ImapAccountData", "#60c0c0");
+  // dot_type_string(fp, "mbox_name", adata->mbox_name);
+  // dot_type_string(fp, "login", adata->conn_account.login);
+  dot_type_string(fp, "user", adata->conn_account.user);
+  dot_type_string(fp, "pass", adata->conn_account.pass[0] ? "***" : "");
+  dot_type_number(fp, "port", adata->conn_account.port);
+  // dot_ptr(fp, "conn", adata->conn, "#ff8080");
+  dot_ptr(fp, "mailbox", adata->mailbox, "#80ff80");
+  dot_object_footer(fp);
+
+  if (adata->conn)
+  {
+    dot_connection(fp, adata->conn, links);
+    dot_add_link(links, adata, adata->conn, "ImapAccountData->conn", false);
+  }
+}
+
+void dot_account_mbox(FILE *fp, struct MboxAccountData *adata, struct ListHead *links)
+{
+  char buf[64] = { 0 };
+
+  dot_object_header(fp, adata, "MboxAccountData", "#60c0c0");
+  dot_ptr(fp, "fp", adata->fp, NULL);
+
+  dot_type_date(buf, sizeof(buf), adata->atime.tv_sec);
+  dot_type_string(fp, "atime", buf);
+  dot_type_bool(fp, "locked", adata->locked);
+  dot_type_bool(fp, "append", adata->append);
+
+  dot_object_footer(fp);
+}
+
+void dot_account_nntp(FILE *fp, struct NntpAccountData *adata, struct ListHead *links)
+{
+  dot_object_header(fp, adata, "NntpAccountData", "#60c0c0");
+  dot_type_number(fp, "groups_num", adata->groups_num);
+
+  dot_type_bool(fp, "hasCAPABILITIES", adata->hasCAPABILITIES);
+  dot_type_bool(fp, "hasSTARTTLS", adata->hasSTARTTLS);
+  dot_type_bool(fp, "hasDATE", adata->hasDATE);
+  dot_type_bool(fp, "hasLIST_NEWSGROUPS", adata->hasLIST_NEWSGROUPS);
+  dot_type_bool(fp, "hasXGTITLE", adata->hasXGTITLE);
+  dot_type_bool(fp, "hasLISTGROUP", adata->hasLISTGROUP);
+  dot_type_bool(fp, "hasLISTGROUPrange", adata->hasLISTGROUPrange);
+  dot_type_bool(fp, "hasOVER", adata->hasOVER);
+  dot_type_bool(fp, "hasXOVER", adata->hasXOVER);
+  dot_type_bool(fp, "cacheable", adata->cacheable);
+  dot_type_bool(fp, "newsrc_modified", adata->newsrc_modified);
+
+  dot_type_string(fp, "authenticators", adata->authenticators);
+  dot_type_string(fp, "overview_fmt", adata->overview_fmt);
+  dot_type_string(fp, "newsrc_file", adata->newsrc_file);
+  dot_type_file(fp, "newsrc_fp", adata->fp_newsrc);
+
+  dot_type_number(fp, "groups_num", adata->groups_num);
+  dot_type_number(fp, "groups_max", adata->groups_max);
+
+  char buf[128];
+  dot_type_date(buf, sizeof(buf), adata->mtime);
+  dot_type_string(fp, "mtime", buf);
+  dot_type_date(buf, sizeof(buf), adata->newgroups_time);
+  dot_type_string(fp, "newgroups_time", buf);
+  dot_type_date(buf, sizeof(buf), adata->check_time);
+  dot_type_string(fp, "check_time", buf);
+
+  dot_object_footer(fp);
+
+  if (adata->conn)
+  {
+    dot_connection(fp, adata->conn, links);
+    dot_add_link(links, adata, adata->conn, "NntpAccountData->conn", false);
+  }
+}
+
+void dot_account_notmuch(FILE *fp, struct NmAccountData *adata, struct ListHead *links)
+{
+  dot_object_header(fp, adata, "NmAccountData", "#60c0c0");
+  dot_ptr(fp, "db", adata->db, NULL);
+  dot_object_footer(fp);
+}
+
+void dot_account_pop(FILE *fp, struct PopAccountData *adata, struct ListHead *links)
+{
+  char buf[64] = { 0 };
+
+  dot_object_header(fp, adata, "PopAccountData", "#60c0c0");
+
+  dot_type_date(buf, sizeof(buf), adata->check_time);
+  dot_type_string(fp, "check_time", buf);
+
+  dot_type_string(fp, "login", adata->conn_account.login);
+  dot_type_string(fp, "user", adata->conn_account.user);
+  dot_type_string(fp, "pass", adata->conn_account.pass[0] ? "***" : "");
+  dot_type_number(fp, "port", adata->conn_account.port);
+  // dot_ptr(fp, "conn", adata->conn, "#ff8080");
+  dot_object_footer(fp);
+
+  if (adata->conn)
+  {
+    dot_connection(fp, adata->conn, links);
+    dot_add_link(links, adata, adata->conn, "PopAccountData->conn", false);
+  }
+}
+
+void dot_account(FILE *fp, struct Account *a, struct ListHead *links)
+{
+  dot_object_header(fp, a, "Account", "#80ffff");
+  dot_mailbox_type(fp, "magic", a->magic);
+  dot_type_string(fp, "name", a->name);
+  // dot_ptr(fp, "adata", a->adata, "#60c0c0");
+  dot_object_footer(fp);
+
+  if (a->adata)
+  {
+    if (a->magic == MUTT_IMAP)
+      dot_account_imap(fp, a->adata, links);
+    else if (a->magic == MUTT_POP)
+      dot_account_pop(fp, a->adata, links);
+    else if (a->magic == MUTT_MBOX)
+      dot_account_mbox(fp, a->adata, links);
+    else if (a->magic == MUTT_NNTP)
+      dot_account_nntp(fp, a->adata, links);
+    else if (a->magic == MUTT_NOTMUCH)
+      dot_account_notmuch(fp, a->adata, links);
+
+    dot_add_link(links, a, a->adata, "Account->adata", false);
+  }
+
+#ifndef GV_HIDE_CONFIG
+  if (a->name)
+  {
+    dot_config(fp, a->name, DT_INHERIT_ACC, a->sub, links);
+    dot_add_link(links, a, a->name, "Config", false);
+
+    char name[256] = { 0 };
+    struct Buffer buf;
+    mutt_buffer_init(&buf);
+
+    mutt_buffer_addstr(&buf, "{ rank=same ");
+
+    dot_ptr_name(name, sizeof(name), a);
+    mutt_buffer_add_printf(&buf, "%s ", name);
+
+    dot_ptr_name(name, sizeof(name), a->name);
+    mutt_buffer_add_printf(&buf, "%s ", name);
+
+    mutt_buffer_addstr(&buf, "}");
+    mutt_list_insert_tail(links, buf.data);
+    buf.data = NULL;
+  }
+#endif
+
+  struct MailboxNode *first = STAILQ_FIRST(&a->mailboxes);
+  dot_add_link(links, a, first, "Account->mailboxes", false);
+  dot_mailbox_list(fp, &a->mailboxes, links, false);
+}
+
+void dot_account_list(FILE *fp, struct AccountList *al, struct ListHead *links)
+{
+  struct Account *prev = NULL;
+  struct Account *np = NULL;
+  TAILQ_FOREACH(np, al, entries)
+  {
+#ifdef GV_HIDE_MBOX
+    if (np->magic == MUTT_MBOX)
+      continue;
+#endif
+    dot_account(fp, np, links);
+    if (prev)
+      dot_add_link(links, prev, np, "Account->next", false);
+
+    prev = np;
+  }
+}
+
+void dot_context(FILE *fp, struct Context *ctx, struct ListHead *links)
+{
+  dot_object_header(fp, ctx, "Context", "#ff80ff");
+  dot_ptr(fp, "mailbox", ctx->mailbox, "#80ff80");
+#ifdef GV_HIDE_CONTEXT_CONTENTS
+  dot_type_number(fp, "vsize", ctx->vsize);
+  dot_type_string(fp, "pattern", ctx->pattern);
+  dot_type_bool(fp, "collapsed", ctx->collapsed);
+#endif
+  dot_object_footer(fp);
+}
+
+void dump_graphviz(const char *title)
+{
+  char name[256] = { 0 };
+  struct ListHead links = STAILQ_HEAD_INITIALIZER(links);
+
+  time_t now = time(NULL);
+  if (title)
+  {
+    char date[128];
+    mutt_date_localtime_format(date, sizeof(date), "%R", now);
+    snprintf(name, sizeof(name), "%s-%s.gv", date, title);
+  }
+  else
+  {
+    mutt_date_localtime_format(name, sizeof(name), "%R.gv", now);
+  }
+
+  umask(022);
+  FILE *fp = fopen(name, "w");
+  if (!fp)
+    return;
+
+  dot_graph_header(fp);
+
+#ifndef GV_HIDE_NEOMUTT
+  dot_node(fp, NeoMutt, "NeoMutt", "#ffa500");
+  dot_add_link(&links, NeoMutt, TAILQ_FIRST(&NeoMutt->accounts), "NeoMutt->accounts", false);
+#endif
+
+  dot_account_list(fp, &NeoMutt->accounts, &links);
+
+#ifndef GV_HIDE_CONTEXT
+  if (Context)
+    dot_context(fp, Context, &links);
+
+  /* Globals */
+  fprintf(fp, "\t{ rank=same ");
+  if (Context)
+  {
+    dot_ptr_name(name, sizeof(name), Context);
+    fprintf(fp, "%s ", name);
+  }
+  dot_ptr_name(name, sizeof(name), NeoMutt);
+  fprintf(fp, "%s ", name);
+  fprintf(fp, "}\n");
+#endif
+
+  fprintf(fp, "\t{ rank=same ");
+  struct Account *np = NULL;
+  TAILQ_FOREACH(np, &NeoMutt->accounts, entries)
+  {
+#ifdef GV_HIDE_MBOX
+    if (np->magic == MUTT_MBOX)
+      continue;
+#endif
+    dot_ptr_name(name, sizeof(name), np);
+    fprintf(fp, "%s ", name);
+  }
+  fprintf(fp, "}\n");
+
+  dot_graph_footer(fp, &links);
+  fclose(fp);
+  mutt_list_free(&links);
+}
diff --git a/index.c b/index.c
index e517c70ff238768152dc18bdfa33c07b2b542040..a8fc87034c50bade633bdb012c60310c4ef7722c 100644 (file)
--- a/index.c
+++ b/index.c
@@ -1646,6 +1646,9 @@ int mutt_index_menu(void)
         }
 
         log_queue_save(fp);
+#ifdef USE_DEVEL_GRAPHVIZ
+        dump_graphviz("index");
+#endif
         mutt_file_fclose(&fp);
 
         mutt_do_pager("messages", tempfile, MUTT_PAGER_LOGS, NULL);
index c1b27b736f9470ce8b2c8f21c66642459ac287e7..cf25fbe73e33c55a8bb4bba03c9c45500b48ab97 100644 (file)
@@ -34,6 +34,7 @@
 #include "globals.h"
 #include "mutt_attach.h"
 #include "mutt_curses.h"
+#include "protos.h"
 #ifdef HAVE_LIBUNWIND
 #include "mutt.h"
 #endif
@@ -103,6 +104,9 @@ static void curses_segv_handler(int sig)
 #ifdef HAVE_LIBUNWIND
   show_backtrace();
 #endif
+#ifdef USE_DEVEL_GRAPHVIZ
+  dump_graphviz("segfault");
+#endif
 
   struct sigaction act;
   sigemptyset(&act.sa_mask);
index 9ad5b65bbeb787b6e16e2c20238695ac2511bcd9..e718d66e23a7656e0d29266600fdbca5ae315230 100644 (file)
--- a/protos.h
+++ b/protos.h
@@ -89,4 +89,8 @@ int wcscasecmp(const wchar_t *a, const wchar_t *b);
 
 int mutt_reply_observer(struct NotifyCallback *nc);
 
+#ifdef USE_DEVEL_GRAPHVIZ
+void dump_graphviz(const char *title);
+#endif
+
 #endif /* MUTT_PROTOS_H */
index 62964c16f42597910ae4f052e09d03c87dc357f3..6f181f959327e2998c115dd93fda0e426ad42287 100644 (file)
--- a/version.c
+++ b/version.c
@@ -162,6 +162,9 @@ static struct CompileOptions comp_opts[] = {
 #else
   { "curs_set", 0 },
 #endif
+#ifdef USE_DEVEL_GRAPHVIZ
+  { "graphviz", 2 },
+#endif
 #ifdef USE_FCNTL
   { "fcntl", 1 },
 #else