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 <tazjin@tvl.su>
Autosubmit: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
This commit is contained in:
Vincent Ambo 2024-01-10 11:31:30 +03:00 committed by clbot
parent 4d135bcfa2
commit c541273d22

View file

@ -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))))