Monday, March 1, 2010

Comparing Emacs Version-parsing Libraries

Recently, I've become interested in advancing the (currently sad) state of Emacs package management. It is a well-documented problem (see here for more detail), so I won't discuss it generally.

One important aspect of a package-management system is the ability to deal with the different versions of a package. Because of the current state of packaging, there is no real standard format for version numbers. Any potential package manager must be able to figure out the version of a given package without being told it explicitly by a user (otherwise maintaining the packages would be way too hard). Currently, package.el has a simple-ish way of dealing with version numbers: it uses the lisp-mnt library (which comes with Emacs) to pull out the Version or Package-Version headers, strips RCS info (some version numbers are like $Id: linkd.el,v 1.63 2007/05/19 00:16:17 dto Exp dto $, and we really only want 1.63), and then split the result by periods and convert the pieces to integers. This means that it only works for versions like 1.2.3 and not 1.2.3alpha.

It would be nice if we could get everyone to use only dotted-numeric version numbers, but that's not happening any time soon. Instead, a package manager must be able to make sense of more complex version numbers, such as 6.34a (which is what org-mode uses), 1.0pre7 or (cedet). I'm going to look at the following three version parsing solutions:

  • version-to-list, included in Gnu Emacs.
  • inversion, from CEDET, now included in Gnu Emacs (not sure which version).
  • vcomp Written by Jonas Bernoulli, creator of the Emacs Mirror

I'll take a bunch of examples and show the output that each one produces. If you have any suggestions for additional version formats, please let me know :)

  • "1.0pre7"
    version-to-list
    (1 0 -1 7)
    inversion-decode-version
    (prerelease 1 0 7)
    vcomp--intern
    nil

  • "1.0.7pre"
    version-to-list
    (1 0 7 -1)
    inversion-decode-version
    nil
    vcomp--intern
    nil

  • "6.34a"
    version-to-list
    (6 34 -3)
    inversion-decode-version
    nil
    vcomp--intern
    ((6 34) (104 0 96 0))

  • "1.3.7"
    version-to-list
    (1 3 7)
    inversion-decode-version
    (point 1 3 7)
    vcomp--intern
    ((1 3 7) (104 0 96 0))

  • "1.0alpha"
    version-to-list
    (1 0 -3)
    inversion-decode-version
    (alpha 1 0 1)
    vcomp--intern
    nil

  • "1.0PRE2"
    version-to-list
    (1 0 -1 2)
    inversion-decode-version
    (prerelease 1 0 2)
    vcomp--intern
    nil

  • "0.9alpha"
    version-to-list
    (0 9 -3)
    inversion-decode-version
    (alpha 0 9 1)
    vcomp--intern
    nil

  • "2009.04.01"
    version-to-list
    (2009 4 1)
    inversion-decode-version
    (point 2009 4 1)
    vcomp--intern
    ((2009 4 1) (104 0 96 0))

  • "2009.10.5"
    version-to-list
    (2009 10 5)
    inversion-decode-version
    (point 2009 10 5)
    vcomp--intern
    ((2009 10 5) (104 0 96 0))

  • "20091005"
    version-to-list
    (20091005)
    inversion-decode-version
    nil
    vcomp--intern
    ((20091005) (104 0 96 0))

  • "20091005pre"
    version-to-list
    (20091005 -1)
    inversion-decode-version
    nil
    vcomp--intern
    nil

  • "20091005alpha"
    version-to-list
    (20091005 -3)
    inversion-decode-version
    nil
    vcomp--intern
    nil

  • "20091005alpha2"
    version-to-list
    (20091005 -3 2)
    inversion-decode-version
    nil
    vcomp--intern
    nil


Looking at this, it seems like version-to-list is the way to go, as it handles the different possibilities better than any of the other functions.