about summary refs log tree commit diff stats
path: root/content/post/emacs-package-archive-statistics.md
blob: 9a8e8bfc3d7b34f771c38d0f1f7498f1621aeaaf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
+++
description = "Working out which package archives I'm using"
title = "Emacs Package Archive Statistics"
date = 2014-07-19T13:19:54Z
[taxonomies]
tags = ["emacs"]
+++

I use [cask][] for managing the dependencies of my Emacs
configuration.  Whenever I opened my `Cask` file, I wondered if I
really was using all the sources I had defined:

```elisp
(source gnu)
(source marmalade)
(source melpa)
(source melpa-stable)
(source org)
```

It seemed quite strange that we have so many package repositories in
the Emacs world and I'm not even using all of them.  I find this state
less than ideal, much as
[Jorgen Schäfer details][state of emacs package archives].  My ideal
package repository would be once that works with VCS releases, mostly
because it's a much simpler process to work with than having to sign
up to yet another website just to upload a package, then ensure it's
kept up-to-date on every release.

As such, I prefer the concepts behing [MELPA][] and [MELPA Stable][] to
those of [Marmalade][].  [GNU ELPA][] doesn't appear to allow any
submissions and [org][org archive] is specific to [org-mode].  I've
also noticed that many packages I find and use are on github and so
work with the [MELPA][] system.  However, I don't like [MELPA's][MELPA]
versioning: it just gets the latest code and puts the build date in
the version, meaning that packages could break at any time.

So, ideally I would use [MELPA Stable][] as much as possible and reduce my
usage of [Marmalade][] and [MELPA][].  [GNU ELPA][] doesn't appear to have
many packages, but I wasn't sure if I was using any.
I couldn't see the information listed in the `*Packages*` buffer, so I
decided to try to figure out how to generate some usage statistics.

I found [how to get a list of installed packages][], but that just gives
a list:

```elisp
(ace-jump-mode ag auto-compile auto-indent-mode autopair ...)
```

I needed to get more information about those packages.  I looked at
where `list-packages` gets that information from.  It seems that
`package-archive-contents` is a list of cons cells:

```elisp
(org-plus-contrib .
				  [(20140714)
				  nil "Outline-based notes management and organizer" tar "org"])
```

Then created a function to loop over the contents of
`package-activated-list`, retrieving the corresponding contents of
`package-archive-contents`:

```elisp
(defun package-list-installed ()
  (loop for pkg in package-activated-list
        collect (assq pkg package-archive-contents)))
```

This generates a list of arrays from `package-archive-contents`.
There are some helper functions in package.el such as
`package-desc-kind`.  `package-desc-archive` was exactly what I
needed.  I happened to be using a pretest version of Emacs at the time
and didn't know that it's not in 24.3, so I just made sure it was defined:

```elisp
(if (not (fboundp #'package-desc-archive))
    (defsubst package-desc-archive (desc)
      (aref desc (1- (length desc)))))
```

Weirdly, some of the arrays (seemingly the ones from the
[org archive][]) had a different length, but the repository/archive was
always the last element, which is why I used `(1- (length ))` and not
a constant, like the other `package-desc-*` functions.

To generate a list of statistics, I just needed to loop over the
installed packages from `package-list-installed` and update a count
for each archive:

```elisp
(defun package-archive-stats ()
  (let ((archives (makehash))
        (assoc '()))
    (dolist (arc package-archives)
      (puthash (car arc) 0 archives))
    (maphash (lambda (k v)
               (setq assoc (cons (cons k v) assoc)))
             (dolist (pkg (-filter #'identity (package-list-installed)) archives)
               (let ((pkg-arc (package-desc-archive (cdr pkg))))
                 (incf (gethash pkg-arc archives)))))
    assoc))
```

Running this gives a list of cons cells:

```elisp
(("gnu" . 0)
 ("org" . 1)
 ("melpa-stable" . 2)
 ("melpa" . 106)
 ("marmalade" . 1))
```

I wrapped it in an interactive function so that I could check the
numbers quickly:

```elisp
(defun package-show-archive-stats ()
  (interactive)
  (message "%s" (package-archive-stats)))
```

With that, I removed `(source gnu)` from my `Cask` file.  Now I had
another question.  What package was installed from [marmalade][]?  In
the lisp fashion, I created yet another function:

```elisp
(defun package-show-installed-from-archive (archive)
  (interactive (list (helm-comp-read "Archive: " (mapcar #'car package-archives)
                                      :must-match t)))
  (let ((from-arc (mapcar #'car
                          (--filter (equalp (package-desc-archive (cdr it)) archive)
                                    (package-list-installed)))))
    (if (called-interactively-p)
        (message "%s" from-arc)
      from-arc)))
```
(Non-helm users can replace `helm-comp-read` with
`ido-completing-read` or similar)

Running this with the argument `"marmalade"` gives:

```elisp
(php-extras)
```

I checked on [MELPA Stable][] and [MELPA][], but it's not available
there.  Given that I use [php-extras][] quite a bit at work, I can't remove
[marmalade][] just yet.  However, as it's a git repository, it should be
easy for me to create a recipe for MELPA.  Then I can remove marmalade
from my [cask][] configuration.  Hooray for simplification!

Hopefully, packaging in Emacs will become simpler in the future.
There are some interesting things in 24.4 like pinning packages to a
repository, which would allow [MELPA Stable][] to be used even when
[MELPA][] defines the same package with a higher "version".

[cask]: https://github.com/cask/cask/
[state of emacs package archives]: http://blog.jorgenschaefer.de/2014/06/the-sorry-state-of-emacs-lisp-package.html
[marmalade]: http://marmalade-repo.org/
[GNU ELPA]: http://elpa.gnu.org/packages/
[MELPA]: http://hiddencameras.milkbox.net/
[MELPA Stable]: http://melpa-stable.milkbox.net/
[org archive]: http://orgmode.org/elpa.html
[how to get a list of installed packages]: http://stackoverflow.com/questions/13866848/how-to-save-a-list-of-all-the-installed-packages-in-emacs-24
[php-extras]: https://github.com/arnested/php-extras
[org-mode]: http://orgmode.org/