I don’t know about you, but I like to monitor my Emacs configuration to see if it is growing out of control due to my penchant for experimenting with ELisp and new packages. This led me to a simple question: why not have a command that can produce a summary of the installed packages? Specifically, I want to know the total number of packages installed and I’d like to list the packages for every archive set up in package-archives.

(defun mu-package-report ()
  "Report total package counts grouped by archive."
  (interactive)
  (package-refresh-contents)
  (mu--display-package-report
   (let* ((arch-pkgs (mu--archive-packages))
          (counts (seq-sort-by #'cdr #'> (mu--archive-counts arch-pkgs)))
          (by-arch (seq-group-by #'car arch-pkgs)))
     (concat
      (format "Total packages: %s\n\n" (apply #'+ (mapcar #'cdr counts)))
      (mapconcat
       (lambda (archive)
         (concat "• "
                 (format "%s (%s)" (car archive) (cdr archive))
                 ": "
                 (mapconcat (lambda (ap-pair) (cdr ap-pair))
                            (alist-get (car archive) by-arch)
                            ", ")))
       counts
       "\n\n")))))

Now, let’s unpack it.

I want my report buffer to be displayed in a new window, so this is what mu--display-package-report does:

(defun mu--display-package-report (output)
  "Display OUTPUT in a popup buffer."
  (let ((buffer-name "*package-report*"))
    (with-help-window buffer-name
      (with-current-buffer buffer-name
        (visual-line-mode 1)
        (erase-buffer)
        (insert output)
        (goto-char (point-min))))))

Next, I want to know from which archive (e.g., GNU ELPA) a package is coming to my system:

(defun mu--archive-packages ()
  "Return a list of (archive . package) cons cells."
  (seq-reduce
   (lambda (res package)
     (let ((archive (package-desc-archive
                     (cadr (assq package package-archive-contents))))
           (pkg (symbol-name package)))
       (push (cons archive pkg) res)))
   (mapcar #'car package-alist)
   nil))

This is nice because now I can also have the number of packages for every archive.

(defun mu--archive-counts (arch-pkgs)
  "Return a list of cons cells from alist ARCH-PKGS.
The cars are package archives, the cdrs are the number of
packages installed from each archive."
  (seq-reduce
   (lambda (counts key)
     (cons (cons key (+ 1 (or (cdr (assoc key counts)) 0)))
           (assoc-delete-all key counts)))
   (mapcar #'car arch-pkgs)
   nil))

The rest of the code in mu-package-report deals with formatting the output. For example, I am using seq-sort-by to have the archive with the highest number of packages on top. As for the mapconcat producing the actual bullet points, the Emacs community came to the rescue on Emacs Stack Exchange.

Finally, this is how M-x mu-package-report RET looks like: