From c541273d2233a0dfd4fa29f4559d70c75249d877 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 10 Jan 2024 11:31:30 +0300 Subject: feat(tazjin/elisp-deps): visualise structure of elisp module Creates a simple dot graph that shows the internal dependencies of an elisp module. Useful for certain kinds of refactoring ... Change-Id: Id7a7756b866751d0e7288e0d22b72ec8056a9eef Reviewed-on: https://cl.tvl.fyi/c/depot/+/10591 Reviewed-by: tazjin Autosubmit: tazjin Tested-by: BuildkiteCI --- users/tazjin/elisp-deps/deps.el | 83 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 users/tazjin/elisp-deps/deps.el (limited to 'users/tazjin') diff --git a/users/tazjin/elisp-deps/deps.el b/users/tazjin/elisp-deps/deps.el new file mode 100644 index 0000000000..954d71cfba --- /dev/null +++ b/users/tazjin/elisp-deps/deps.el @@ -0,0 +1,83 @@ +;; Visualise the internal structure of an Emacs Lisp file using +;; Graphviz. +;; +;; Entry point is the function `edeps-analyse-file'. + +(require 'map) + +(defun edeps-read-defs (file-name) + "Stupidly read all definitions from an Emacs Lisp file. This only +considers top-level forms, where the first element of the form is +a symbol whose name contains the string `def', and where the +second element is a symbol. + +Returns a hashmap of all these symbols with the remaining forms +in their bodies." + + (with-temp-buffer + (insert-file-contents file-name) + (goto-char (point-min)) + + (let ((symbols (make-hash-table))) + (condition-case _err + (while t + (let ((form (read (current-buffer)))) + (when (and (listp form) + (symbolp (car form)) + (string-match "def" (symbol-name (car form))) + (symbolp (cadr form))) + (when (and (map-contains-key symbols (cadr form)) + ;; generic methods have multiple definitions + (not (eq (car form) 'cl-defmethod))) + (error "Duplicate symbol: %s" (symbol-name (cadr form)))) + + (map-put! symbols (cadr form) + (cons (car form) (cddr form)))))) + (end-of-file symbols))))) + +(defun edeps-analyse-structure (symbols) + "Analyse the internal structure of the symbols found by +edeps-read-defs, and return a hashmap with the results of the +analysis. The hashmap uses the symbols as keys, " + (let ((deps (make-hash-table))) + (map-do + (lambda (sym val) + (dolist (expr (flatten-list (cdr val))) + (when (map-contains-key symbols expr) + (map-put! deps expr (cons sym (ht-get deps expr)))))) + symbols) + deps)) + +(defun edeps-graph-deps (symbols deps) + (with-temp-buffer + (insert "digraph edeps {\n") + + ;; List all symbols first + (insert " subgraph {\n") + (map-do + (lambda (sym val) + (insert " " (format "\"%s\" [label=\"%s\\n(%s)\"];\n" sym sym (car val)))) + symbols) + (insert " }\n\n") + + ;; Then drop all the edges in there .. + (insert " subgraph {\n") + (map-do + (lambda (sym deps) + (dolist (dep deps) + (insert " " (format "\"%s\" -> \"%s\";\n" dep sym)))) + deps) + (insert " }\n") + + (insert "}\n") + (buffer-string))) + +(defun edeps-analyse-file (infile outfile) + "Produces a dot-graph in OUTFILE from an internal structural +analysis of INFILE. This can be graphed using the graphviz +package." + (let* ((symbols (edeps-read-defs infile)) + (deps (edeps-analyse-structure symbols))) + (with-temp-buffer + (insert (edeps-graph-deps symbols deps)) + (write-file outfile)))) -- cgit 1.4.1