The Emacs Package Developer’s Handbook
After developing some little Emacs packages for a year or so, I began to notice how I’d forget little things that I learned, and then I’d have to go hunting for that information again. I also noticed how there are some issues for which there doesn’t seem to be a “best practice” or “Standard Operating Procedure” to refer to.
So this is intended to be a place to collect and organize information related to Emacs package development. Built with Emacs, by Emacs package developers, for Emacs package developers.
You can read this Org file directly on the repository rendered by GitHub (which lacks support for some features of the document, such as links between sections), or you can read the HTML version.
Note: The primary sections are listed at the top of the page in the horizontal bar.
Contents
- Emacs Lisp
- Animations / Screencasts
- Asynchronicity
- Binding
- Buffers
- Checkers / linters
- Collections (lists, vectors, hash-tables, etc.)
- Data structure
- Date / Time
- Debugging
- Destructuring
- Editing
- Functions
- General
- Highlighting / font-locking
- Multiprocessing (generators, threads)
- Packaging
- Pattern matching
- Profiling / Optimization
- Refactoring
- Regular expressions
- Strings
- Testing
- Version control
- XML / HTML
- Blogs
- People
- Contributions
- Tasks
- Code
Emacs Lisp
Animations / Screencasts
Tools
emacs-gif-screencast
Most Emacs screencasts are done by first recording a video, then converting it to a GIF. It seems like a waste of resources considering the little animation Emacs features.
Most of the time, one user action equals one visual change. By exploiting this fact, this package tailors GIF-screencasting for Emacs and captures one frame per user action. It’s much more efficient than capturing frames even at a frequency as low as 10 per second. You could roughly expect 3 to 10 times smaller files compared to videos of the same quality. (Benchmarks are welcome.)
Another neat perk of action-based captures is that it produces editable GIF files: programs such as Gifsicle or The GIMP can be used to edit the delays or rearrange frames. This is particularly useful to fix mistakes in “post-processing”, without having to record the demo all over again.
Asynchronicity
See Multiprocessing (generators, threads).
Binding
Information related to variable scope and binding in elisp code (e.g. lexical vs. dynamic scope).
Articles
Make Flet Great Again « null program
:archive.is: http://archive.is/T8dHMChris Wellons explains how the old cl macro flet changes in its new cl-lib version, cl-flet, and how to use cl-letf to achieve the old functionality. It’s a way to override functions in both lexical and dynamic scope, which is especially useful for unit testing.
Libraries
dash.el
thunk
Thunk provides functions and macros to delay the evaluation of forms.
Use
thunk-delayto delay the evaluation of a form (requires lexical-binding), andthunk-forceto evaluate it. The result of the evaluation is cached, and only happens once.Here is an example of a form which evaluation is delayed:
(setq delayed (thunk-delay (message "this message is delayed")))
delayedis not evaluated untilthunk-forceis called, like the following:
(thunk-force delayed)This file also defines macros
thunk-letandthunk-let*that are analogous toletandlet*but provide lazy evaluation of bindings by using thunks implicitly (i.e. in the expansion).
Tools
Lexical binding
Buffers
Best practices
Inserting strings
Inserting strings into buffers with insert is generally fast, but it can slow down in buffers with lots of markers or overlays. In general, it can be faster to insert one large string (which may include newlines). For example:
(let ((strings (cl-loop for i from 1 to 1000
collect (number-to-string i))))
(garbage-collect)
(--sort (< (caddr it) (caddr other))
(cl-loop for times in '(1 10 100)
append (a-list "(loop do (insert ..."
(cons times
(benchmark-run-compiled times
(with-temp-buffer
(cl-loop for string in strings
do (insert string)))))
"(apply #'insert ..."
(cons times
(benchmark-run-compiled times
(with-temp-buffer
(apply #'insert strings))))
"(insert (apply #'concat ..."
(cons times
(benchmark-run-compiled times
(with-temp-buffer
(insert (apply #'concat strings)))))))))#+RESULTS[aa866ca87dbf71476c735ed51fca7373934bbf4f]:
| (insert (apply #’concat … | 100 | 0.000142085 | 0 | 0.0 |
| (insert (apply #’concat … | 10 | 0.000161172 | 0 | 0.0 |
| (insert (apply #’concat … | 1 | 0.00018764 | 0 | 0.0 |
| (apply #’insert … | 10 | 0.000665472 | 0 | 0.0 |
| (apply #’insert … | 100 | 0.000678471 | 0 | 0.0 |
| (apply #’insert … | 1 | 0.000755329 | 0 | 0.0 |
| (loop do (insert … | 10 | 0.000817031 | 0 | 0.0 |
| (loop do (insert … | 100 | 0.000869779 | 0 | 0.0 |
| (loop do (insert … | 1 | 0.001490397 | 0 | 0.0 |
The fastest method here is to call insert once with the result of calling concat once, using apply to pass all of the strings. With 100 iterations, it’s about 6x faster than the next-fastest method, and even with 1 iteration, it’s over 2x faster.
Libraries
m-buffer-el: List Oriented Buffer Operations
bui.el: Buffer interface library
BUI (Buffer User Interface) is an Emacs library that can be used to make user interfaces to display some kind of entries (like packages, buffers, functions, etc.). The intention of BUI is to be a high-level library which is convenient to be used both by:
package makers, as there is no need to bother about implementing routine details and usual features (like buffer history, filtering displayed entries, etc.);
users, as it provides familiar and intuitive interfaces with usual keys (for moving by lines, marking, sorting, switching between buttons); and what is also important, the defined interfaces are highly configurable through various generated variables.
Checkers / linters
Flycheck-package
Collections (lists, vectors, hash-tables, etc.)
Best practices
Filtering a list
Using -select from dash.el seems to be the fastest way:
(let ((list (cl-loop for i from 1 to 1000
collect i)))
(bench-multi :times 100
:ensure-equal t
:forms (("(-non-nil (--map (when ..." (-non-nil
(--map (when (cl-evenp it) it) list)))
("(delq nil (--map (when ..." (delq nil
(--map (when (cl-evenp it) it) list)))
("cl-loop" (cl-loop for i in list
when (cl-evenp i)
collect i))
("-select" (-select #'cl-evenp list))
("cl-remove-if-not" (cl-remove-if-not #'cl-evenp list))
("seq-filter" (seq-filter #'cl-evenp list)))))#+RESULTS[6b2e97c1ebead84a53fd771684cc3e155e7f6b1e]:
| Form | x faster than next | Total runtime | # of GCs | Total GC runtime |
|---|---|---|---|---|
| -select | 1.17 | 0.01540391 | 0 | 0.0 |
| cl-loop | 1.05 | 0.01808226 | 0 | 0.0 |
| seq-filter | 1.13 | 0.01891708 | 0 | 0.0 |
| (delq nil (–map (when … | 1.15 | 0.02134727 | 0 | 0.0 |
| cl-remove-if-not | 1.18 | 0.02459478 | 0 | 0.0 |
| (-non-nil (–map (when … | slowest | 0.02903999 | 0 | 0.0 |
Examples
Alists
Creation
;;;; Built-in methods
(list (cons 'one 1) (cons 'two 2)) ;; => ((one . 1) (two . 2))
'((one . 1) (two . 2)) ;; => ((one . 1) (two . 2))
(let ((numbers (list)))
(map-put numbers 'one 1)
(map-put numbers 'two 2)) ;; => ((two . 2) (one . 1))
;;;; Packages
;; `a-list' from a.el is the best way to make a new alist.
(a-list 'one 1
'two 2) ;; => ((one . 1) (two . 2))Adding to
Single elements
;;;; Built-in methods
;; `map-put' is the best built-in way. Requires Emacs 25.1+.
(let ((numbers (list (cons 'one 1))))
(map-put numbers 'two 2)
numbers) ; => ((two . 2) (one . 1))
;; More primitive methods
;; Not recommended, but not too complicated:
(let ((numbers (list (cons 'one 1)))
(more-numbers (a-list 'two 2
'three 3)))
(append numbers more-numbers)) ;; => ((one . 1) (two . 2) (three . 3))
;; Don't do it this way, but it does demonstrate list/cons-cell
;; structure:
(let ((numbers (list (cons 'one 1))))
(cons (cons 'three 3)
(cons (cons 'two 2)
numbers))) ;; => ((three . 3) (two . 2) (one . 1))Multiple elements
;;;; Built-in methods
;; `map-merge': if you're restricted to built-in packages, this works
;; well (requires Emacs 25.1+):
(let ((numbers (list (cons 'one 1)))
(more-numbers (a-list 'two 2
'three 3)))
(map-merge 'list numbers more-numbers)) ;; => ((three . 3) (two . 2) (one . 1))
;; Without map.el, you could use `append':
(let ((numbers (list (cons 'one 1)))
(more-numbers (a-list 'two 2
'three 3)))
(append numbers more-numbers)) ;; => ((one . 1) (two . 2) (three . 3))
;;;; Packages
;; `a-merge' from a.el is probably the best way:
(let ((numbers (list (cons 'one 1)))
(more-numbers (a-list 'two 2
'three 3)))
(a-merge numbers more-numbers)) ;; => ((three . 3) (two . 2) (one . 1))Libraries
a.el: functions for dealing with association lists and hash tables. Inspired by Clojure.
asoc.el: alist library
emacs-kv: key/value collection-type functions, for alists, hash tables and plists
ht.el: The missing hash table library
This library provides a consistent and comprehensive set of functions for working with hash tables: they’re named consistently, take a natural and consistent argument order, and cover operations that the standard Emacs functions don’t.
list-utils: List-manipulation utility functions
Similar to dash.el, but with slightly different behavior that may be useful, and some unique features. These functions are provided:
make-tconc | list-utils-depth |
tconc-p | list-utils-flat-length |
tconc-list | list-utils-flatten |
tconc | list-utils-alist-or-flat-length |
list-utils-cons-cell-p | list-utils-alist-flatten |
list-utils-cyclic-length | list-utils-insert-before |
list-utils-improper-p | list-utils-insert-after |
list-utils-make-proper-copy | list-utils-insert-before-pos |
list-utils-make-proper-inplace | list-utils-insert-after-pos |
list-utils-make-improper-copy | list-utils-and |
list-utils-make-improper-inplace | list-utils-not |
list-utils-linear-p | list-utils-xor |
list-utils-linear-subseq | list-utils-uniq |
list-utils-cyclic-p | list-utils-dupes |
list-utils-cyclic-subseq | list-utils-singlets |
list-utils-make-linear-copy | list-utils-partition-dupes |
list-utils-make-linear-inplace | list-utils-plist-reverse |
list-utils-safe-length | list-utils-plist-del |
list-utils-safe-equal |
Tools
let-alist
=with-dict=, =with-plist-vals=
Data structure
Articles
Options for Structured Data in Emacs Lisp « null program
:archive.is: http://archive.is/YxwP5Date / Time
Libraries
emacs-datetime
The primary function provided is: (datetime-format SYM-OR-FMT &optional TIME &rest OPTION)
(datetime-format "%Y-%m-%d") ;=> "2018-08-22"
(datetime-format 'atom) ;=> "2018-08-22T18:23:47-05:00"
(datetime-format 'atom "2112-09-03 00:00:00" :timezone "UTC") ;=> "2112-09-03T00:00:00+00:00"There are several other symbols provided besides atom, such as rfc-3339, which formats dates according to that RFC.
Debugging
Tools
Edebug
Edebug is a built-in stepping debugger in Emacs. It’s thoroughly documented in the elisp manual.
Declaring debug forms for keyword arguments
Declaring debug forms for functions and macros that take keyword arguments can be confusing. Here’s a contrived example:
(cl-defmacro make-fn (name docstring &key args bindings body)
(declare (indent defun)
(debug (&define symbolp stringp
&rest [&or [":body" def-form] [keywordp listp]])))
`(defun ,name ,args
,docstring
(let* ,bindings
,body)))
(make-fn my-fn
"This is my function."
:bindings ((one 1)
(two 2))
:body (list one two))Destructuring
See Pattern matching.
Editing
Tools
aggressive-indent-mode: minor mode that keeps your code always indented
beginend.el
This package, by Damien Cassou and Matus Goljer, helps navigation by redefining the M-< and M-> keys do, depending on the major-mode.
expand-region.el: Increase selected region by semantic units
helm-navi: Navigate file sections and language keywords using Helm
iedit: Edit multiple regions simultaneously in a buffer or a region
iedit makes it easy to rename symbols within a function or in a whole buffer. Simply activate iedit-mode with point on a symbol, and it will be highlighted in the chosen scope, and any changes you make to the symbol are made in each highlighted occurrence. It’s like a smart, purposeful version of multiple-cursors.
The editor of this handbook uses iedit with these customizations:
ap/iedit-or-flyspell- Globally bound to C-;. In a
prog-mode-derived buffer, either corrects the last misspelled word withflyspellwhen point is in a comment or string, or activatesiedit-mode. In non-prog-mode-derived buffers, corrects withflyspell.
(defun ap/iedit-or-flyspell ()
"Call `iedit-mode' or correct misspelling with flyspell, depending..."
(interactive)
(if (or iedit-mode
(and (derived-mode-p 'prog-mode)
(not (or (nth 4 (syntax-ppss))
(nth 3 (syntax-ppss))))))
;; prog-mode is active and point is in a comment, string, or
;; already in iedit-mode
(call-interactively #'ap/iedit-mode)
;; Not prog-mode or not in comment or string
(if (not (equal flyspell-previous-command this-command))
;; FIXME: This mostly works, but if there are two words on the
;; same line that are misspelled, it doesn't work quite right
;; when correcting the earlier word after correcting the later
;; one
;; First correction; autocorrect
(call-interactively 'flyspell-auto-correct-previous-word)
;; First correction was not wanted; use popup to choose
(progn
(save-excursion
(undo)) ; This doesn't move point, which I think may be the problem.
(flyspell-region (line-beginning-position) (line-end-position))
(call-interactively 'flyspell-correct-previous-word-generic)))))ap/iedit-mode- Calls
iedit-modewith function-local scope by default, or global scope when called with a universal prefix.
(defun ap/iedit-mode (orig-fn)
"Call `iedit-mode' with function-local scope by default, or global scope if called with a universal prefix."
(interactive)
(pcase current-prefix-arg
('nil (funcall orig-fn '(0)))
('(4) (funcall orig-fn))
(_ (user-error "`ap/iedit-mode' called with prefix: %s" prefix))))
;; Override default `iedit-mode' function with advice.
(advice-add #'iedit-mode :around #'ap/iedit-mode)- Helpful minibuffer message
- Confirms when an
ieditsession has started.
(advice-add 'iedit-mode :after (lambda (&optional ignore)
(when iedit-mode
(minibuffer-message "iedit session started. Press C-; to end."))))lispy: short and sweet LISP editing
multi-line: multi-line everything from function invocations and definitions to array and map literals in a wide variety of languages
multiple-cursors.el: Multiple cursors
smartparens: Minor mode that deals with parens pairs and tries to be smart about it
Functions
Including anonymous functions (lambdas).
Articles
What’s in an Emacs Lambda « null program
:archive.is: http://archive.is/ppIuJGeneral
Libraries
Common Lisp Extensions (cl-lib)
This is the built-in cl-lib package which implements Common Lisp functions and control structures for Emacs Lisp.
dash.el
Dash is a powerful general-purpose library that provides many useful functions and macros.
subr-x
Less commonly used functions that complement basic APIs, often implemented in C code (like hash-tables and strings), and are not eligible for inclusion in subr.el.
This is a built-in package that provides several useful functions and macros, such as thread-first / last, if-let / when-let, hash-table functions, and string functions. It’s easy to forget about this, since:
Do not document these functions in the lispref. http://lists.gnu.org/archive/html/emacs-devel/2014-01/msg01006.html
Tools
chemacs: Emacs profile switcher
This package may be especially helpful for developing in one’s own environment and testing in another, like default Emacs, Spacemacs, etc.
Chemacs is an Emacs profile switcher, it makes it easy to run multiple Emacs configurations side by side. Think of it as a bootloader for Emacs.
Emacs configuration is either kept in a
~/.emacsfile or, more commonly, in a~/.emacs.ddirectory. These paths are hard-coded. If you want to try out someone else’s configuration, or run different distributions like Prelude or Spacemacs, then you either need to swap out~/.emacs.d, or run Emacs with a different$HOMEdirectory set. This last approach is quite common, but has some real drawbacks, since now packages will no longer know where your actual home directory is.All of these makes trying out different Emacs configurations and distributions needlessly cumbersome. Various approaches to solving this have been floated over the years. There’s an Emacs patch around that adds an extra command line option, and various examples of how to add a command line option in userspace from Emacs Lisp.
Chemacs tries to implement this idea in a user-friendly way, taking care of the various edge cases and use cases that come up.
el2markdown: Convert Emacs Lisp comments to MarkDown
multicolumn: Multiple side-by-side windows support
lentic: Create views of the same content in two buffers
suggest.el: discover elisp functions that do what you want
Highlighting / font-locking
Packages
Packages that do highlighting/font-locking.
lisp-extra-font-lock: Highlight bound variables and quoted expressions in lisp
Tools
Tools for developing highlighting/font-locking packages.
face-explorer: Library and tools for faces and text properties
faceup: Regression test system for font-lock keywords
font-lock-profiler: Coverage and timing tool for font-lock keywords
font-lock-regression-suite: Regression test suite for font-lock keywords of Emacs standard modes
font-lock-studio: Debugger for Font Lock keywords
highlight-refontification: Visualize how font-lock refontifies a buffer
Multiprocessing (generators, threads)
Articles
Emacs 26 Brings Generators and Threads « null program
:archive.is: http://archive.is/IraneChris Wellons explains the new generators and threads that Emacs 26 provides. He also shows an example of writing a cl-case form that uses the new switch jump table opcode in Emacs 26.
Turning Asynchronous into Synchronous in Elisp « null program
:archive.is: http://archive.is/AfL0yAsynchronous Requests from Emacs Dynamic Modules « null program
:archive.is: http://archive.is/ZS6pUManual
GNU Emacs Lisp Reference Manual: Generators
GNU Emacs Lisp Reference Manual: Threads
Packaging
Best practices
Autoloads
Autoloading macro-generated functions
This may actually be a bug, or at least an unanswered question.
How to use autoload cookies for custom defun-like macros? : emacs:
Say I have a macro
deffoothat expands to some custom kind ofdefun, and I want to use an autoload cookie to autoload the result. According to the manual,;;;###autoload (deffoo bar ...)copies the entire form to
autoloads.el, and something like;;;###autoload (autoload 'bar "this-file") (deffoo bar ...)should be used instead. What confuses me is this StackOverflow comment by who appears to be Stefan Monnier, saying that Emacs should expand the macro before generating the autoload, and that it’s probably a bug when this does not happen.
Can anyone clear up what the intended behaviour is?
[2018-01-15 Mon 03:37] The correct way to do this is documented in this bug report.
Articles
Autoloads in Emacs Lisp | Sebastian Wiesner
:archive.is: http://archive.is/UZHhSIntegration with other packages
Optional support
Sometimes you want your package to integrate with other packages, but you don’t want to require users to install those other packages. For example, you might want your package to work with Helm, Ivy, or the built-in Emacs completing-read, but you don’t want to declare a dependency on and require Helm or Ivy, which would force users to install them to use your package.
The best way to handle this is with the =with-eval-after-load= macro. The Emacs manual has a page on it, and this StackOverflow question has some more info. You can also see an example, which also uses =declare-function= to prevent byte-compiler errors.
Lexical binding
You should always use lexical binding by setting the header in the first line of the file:
;;; filename.el --- File description -*- lexical-binding: t; -*-
Articles
Emacs Lisp lexical binding gotchas and related best practices | Yoo Box
:archive.is: http://archive.is/0nfB4elisp - Why is `let’ faster with lexical scope? - Emacs Stack Exchange
:archive.is: http://archive.is/LUtfZSebastian Wiesner provides a detailed explanation.
EmacsWiki: Dynamic Binding Vs Lexical Binding
:archive.is: http://archive.is/2VtOUA lot of good examples and discussion.
Template
When you make a new package, the auto-insert command will insert a set of standard package headers for you. However, here is a more comprehensive template you can use:
;;; package-name.el --- Package description (don't include the word "Emacs") -*- lexical-binding: t; -*-
;; Copyright (C) 2017 First Last
;; Author: First Last <name@example.com>
;; URL: http://example.com/package-name.el
;; Version: 0.1-pre
;; Package-Requires: ((emacs "25.2"))
;; Keywords: something
;; This file is not part of GNU Emacs.
;;; Commentary:
;; This is my package. It is nice. You should try it.
;;;; Installation
;;;;; MELPA
;; If you installed from MELPA, you're done.
;;;;; Manual
;; Install these required packages:
;; + foo
;; + bar
;; Then put this file in your load-path, and put this in your init
;; file:
;; (require 'package-name)
;;;; Usage
;; Run one of these commands:
;; `package-name-command': Frobnicate the flange.
;;;; Tips
;; + You can customize settings in the `package-name' group.
;;;; Credits
;; This package would not have been possible without the following
;; packages: foo[1], which showed me how to bifurcate, and bar[2],
;; which takes care of flanges.
;;
;; [1] https://example.com/foo.el
;; [2] https://example.com/bar.el
;;; License:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
;;;; Requirements
(require 'foo)
(require 'bar)
;;;; Customization
(defgroup package-name nil
"Settings for `package-name'."
:link '(url-link "http://example.com/package-name.el"))
(defcustom package-name-something nil
"This setting does something."
:type 'something)
;;;; Variables
(defvar package-name-var nil
"A variable.")
;;;;; Keymaps
;; This technique makes it easier and less verbose to define keymaps.
(defvar package-name-map
;; This makes it easy and much less verbose to define keys
(let ((map (make-sparse-keymap "package-name map"))
(maps (list
;; Mappings go here, e.g.:
"RET" #'package-name-RET-command
[remap search-forward] #'package-name-search-forward
)))
(cl-loop for (key fn) on maps by #'cddr
do (progn
(when (stringp key)
(setq key (kbd key)))
(define-key map key fn)))
map))
;;;; Functions
;;;;; Commands
;;;###autoload
(defun package-name-command (args)
"Frobnicate the flange."
(interactive)
(package-name--something)
(bar))
;;;;; Support
(defun package-name--something (args)
"This function helps frobnicate the flange."
(foo))
;;;; Footer
(provide 'package-name)
;;; package-name.el ends hereReadme
You should always include a readme with your project. Typically it will be include most of the commentary section. Here’s a template that goes with the package template above:
#+PROPERTY: LOGGING nil
# Note: This readme works with the org-make-toc <https://github.com/alphapapa/org-make-toc> package, which automatically updates the table of contents.
* package-name
:PROPERTIES:
:TOC: ignore
:END:
[[https://melpa.org/#/package-name][file:https://melpa.org/packages/package-name-badge.svg]] [[https://stable.melpa.org/#/package-name][file:https://stable.melpa.org/packages/package-name-badge.svg]]
This is my package. It is nice. You should try it.
** Screenshots
This screenshot shows how to frobnicate the fripulator:
[[screenshot1.png]]
* Contents :noexport:
:PROPERTIES:
:TOC: this
:END:
- [[#installation][Installation]]
- [[#usage][Usage]]
- [[#changelog][Changelog]]
- [[#credits][Credits]]
- [[#development][Development]]
- [[#license][License]]
* Installation
:PROPERTIES:
:TOC: 0
:END:
** MELPA
If you installed from MELPA, you're done. Just run one of the commands below.
** Manual
Install these required packages:
+ =foo=
+ =bar=
Then put this file in your load-path, and put this in your init file:
#+BEGIN_SRC elisp
(require 'package-name)
#+END_SRC
* Usage
:PROPERTIES:
:TOC: 0
:END:
Run one of these commands:
+ =package-name-command=: Frobnicate the flange.
** Tips
+ You can customize settings in the =package-name= group.
* Changelog
:PROPERTIES:
:TOC: 0
:END:
** 1.1.0
*Additions*
+ Add command =package-name-debarnacle= to de-barnacle the hull.
*Changes*
+ Command =package-name-anchor= now takes an argument, =weigh= or =let-go=.
*Internal*
+ Rewrote input parsing.
+ Factored out anchor-weighing.
** 1.0.1
*Fixes*
+ Ensure anchor is secure before returning from =package-name-anchor=.
** 1.0.0
Initial release.
* Credits
This package would not have been possible without the following packages: [[https://example.com/foo.el][foo]] which showed me how to bifurcate, and [[https://example.com/bar.el][bar]], which takes care of flanges.
* Development
Bug reports, feature requests, suggestions — /oh my/!
* License
GPLv3
# Local Variables:
# eval: (require 'org-make-toc)
# before-save-hook: org-make-toc
# org-export-with-properties: ()
# org-export-with-title: t
# End:
Version numbers
Version numbers which are valid in Emacs are those accepted by the function version-to-list, which uses the variables version-separator and version-regexp-alist. See their documentation for specific, up-to-date information. version-to-list’s documentation (as of Emacs 26.1) is reproduced here for convenience:
The version syntax is given by the following EBNF: VERSION ::= NUMBER ( SEPARATOR NUMBER )*. NUMBER ::= (0|1|2|3|4|5|6|7|8|9)+. SEPARATOR ::= ‘version-separator’ (which see) | ‘version-regexp-alist’ (which see). The NUMBER part is optional if SEPARATOR is a match for an element in ‘version-regexp-alist’. Examples of valid version syntax: 1.0pre2 1.0.7.5 22.8beta3 0.9alpha1 6.9.30Beta 2.4.snapshot .5 Examples of invalid version syntax: 1.0prepre2 1.0..7.5 22.8X3 alpha3.2 Examples of version conversion: Version String Version as a List of Integers ".5" (0 5) "0.9 alpha" (0 9 -3) "0.9AlphA1" (0 9 -3 1) "0.9snapshot" (0 9 -4) "1.0-git" (1 0 -4) "1.0.7.5" (1 0 7 5) "1.0.cvs" (1 0 -4) "1.0PRE2" (1 0 -1 2) "1.0pre2" (1 0 -1 2) "22.8 Beta3" (22 8 -2 3) "22.8beta3" (22 8 -2 3)
Reference
Package headers and structure
The Emacs manual gives this example (I’ve added the lexical-binding part). Also see template.
;;; superfrobnicator.el --- Frobnicate and bifurcate flanges -*- lexical-binding: t; -*- ;; Copyright (C) 2011 Free Software Foundation, Inc. ;; Author: J. R. Hacker <jrh@example.com> ;; Version: 1.3 ;; Package-Requires: ((flange "1.0")) ;; Keywords: multimedia, frobnicate ;; URL: http://example.com/jrhacker/superfrobnicate ... ;;; Commentary: ;; This package provides a minor mode to frobnicate and/or ;; bifurcate any flanges you desire. To activate it, just type ... ;;;###autoload (define-minor-mode superfrobnicator-mode ...
Tools
Package installation/management
paradox: modernizing Emacs’ Package Menu. With package ratings, usage statistics, customizability, and more.
el-get: Manage the external elisp bits and pieces upon which you depend!
straight.el: Next-generation, purely functional package manager for the Emacs hacker
- State “TODO” from [2018-07-29 Sun 13:11]
use-package: A use-package declaration for simplifying your .emacs
- State “TODO” from [2018-07-29 Sun 13:11]
Developed by the current maintainer of Emacs, himself, John Wiegley.
Pattern matching
Articles
Pattern Matching in Emacs Lisp – Wilfred Hughes::Blog
:archive.is: http://archive.is/J4DqYPattern matching is invaluable in elisp. Lists are ubiquitous, and a small amount of pattern matching can often replace a ton of verbose list fiddling.
Since this is Lisp, we have lots of choices! In this post, we’ll compare cl.el, pcase.el, dash.el, and shadchen, so you can choose the best fit for your project. We’ll look at the most common use cases, and end with some recommendations.
For the sake of this post, we’ll consider both pattern matching and destructuring, as they’re closely related concepts.
A callable plist data structure for Emacs
:archive.is: http://archive.is/vmITXJohn Kitchin demonstrates some macros that make it easy to access plist values.
Libraries
dash.el
Dash is a powerful library, and one of its features is powerful destructuring with its -let macro, and several others that work the same way.
pcase
pcase is built-in to Emacs. Its syntax can be confusing, but it is very powerful.
Articles
EmacsWiki: Pattern Matching
There are lots of examples here.
Emacs: Pattern Matching with pcase - Lost in Technopolis
:archive.is: http://archive.is/FAzd8Nic Ferrier, Using Polymorphism as a Lisp refactoring tool
:archive.is: http://archive.is/0Y3MdExamples
dash
[2018-07-27 Fri 23:29] Dash has new abilities, including -setq, and destructuring plists with implied variable names (i.e. just the keys can be specified, reducing repetition).
pcase-let
This example shows the use of pcase-let* to destructure and bind a nested alist:
(let ((alphabets (a-list 'English (a-list 'first "a"
'second "b")
'Greek (a-list 'first "α"
'second "β"))))
(pcase-let* (((map English) alphabets)
((map ('first letter) second) English))
(list letter second))) ;; => ("a" "b")shadchen-el
A powerful, Racket-style pattern-matching library.
Tools
let-alist
let-alist is the best thing to happen to associative lists since the invention of the cons cell. This little macro lets you easily access the contents of an alist, concisely and efficiently, without having to specify them preemptively. It comes built-in with 25.1, and is also available on GNU Elpa for older Emacsen.
(defun sx-question-list--print-info (question-data)
"DOC"
(let-alist question-data
(list
question-data
(vector
(int-to-string .score)
(int-to-string .answer_count)
.title " "
.owner.display_name
.last_activity_date sx-question-list-ago-string
" " .tags))))Articles
New on Elpa and in Emacs 25.1: let-alist · Endless Parentheses
:archive.is: http://archive.is/2wNFmHere Artur introduces the macro and gives examples.
with-dict, with-plist-vals
Courtesy of John Kitchin:[fn:1:Copyright by John Kitchin, licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.]
(defmacro with-dict (key-vals &rest body)
"A context-manager for a plist where each key is a callable
function that returns the value."
(declare (indent 1))
(let* ((g (if (symbolp key-vals)
(symbol-value key-vals)
key-vals))
(keys (-slice g 0 nil 2)))
`(labels ,(loop for key in keys
collect
(list key '() `(plist-get ',g ,key)))
,@body)))
;; Used as:
(with-dict (:a 1 :b 'some-symbol :c 3)
(:b))
(let ((d '(:key1 1 :key2 some-other-symbol :key3 3)))
(with-dict d
(format "We got %s" (:key2))))And:
(defmacro with-plist-vals (plist &rest body)
"Bind the values of a plist to variables with the name of the keys."
(declare (indent 1))
`(let ,(loop for key in (-slice plist 0 nil 2)
for val in (-slice plist 1 nil 2)
collect (list (intern
(substring (symbol-name key) 1))
val))
,@body))
;; Used like:
(with-plist-vals (:a 4 :b 6)
(* 2 a))Profiling / Optimization
Articles
How to Write Fast(er) Emacs Lisp « null program
Chris Wellons explains five ways to write faster Emacs Lisp code.
What’s in an Emacs Lambda « null program
Macros
bench
From Phil Lord’s m-buffer-el:
(cl-defmacro bench (&optional (times 100000) &rest body)
"Call `benchmark-run-compiled' on BODY with TIMES iterations, returning list suitable for Org source block evaluation.
Garbage is collected before calling `benchmark-run-compiled' to
avoid counting existing garbage which needs collection."
(declare (indent defun))
`(progn
(garbage-collect)
(list '("Total runtime" "# of GCs" "Total GC runtime")
'hline
(benchmark-run-compiled ,times
(progn
,@body)))))Used like this:
(bench 1000000
(cons 'time (current-time)))When called from an Org source block, it gives output like this:
| Total runtime | # of GCs | Total GC runtime |
|---|---|---|
| 1.657838266 | 3 | 1.4723854609999876 |
bench-multi
This macro makes comparing multiple forms easy:
(cl-defmacro bench-multi (&key (times 1) forms ensure-equal)
"Run FORMS with `benchmark-run-compiled' for TIMES iterations, returning list suitable for Org source block evaluation.
When ENSURE-EQUAL is non-nil, compare the results of FORMS and
ensure they are `equal'. If they aren't, raise an error, and if
the results are sequences, show the difference between them using
`seq-difference'.
If the first element of a form is a string, the string is used as
the form's description in the results; otherwise, forms are
numbered from 0.
Before `benchmark-run-compiled' is called for each form,
`garbage-collect' is called."
(declare (indent defun))
(let ((results (gensym))
(result-times (gensym))
(header '(("Form" "x faster than next" "Total runtime" "# of GCs" "Total GC runtime")
hline))
(descriptions (cl-loop for form in forms
for i from 0
collect (if (stringp (car form))
(prog1 (car form)
(setf (nth i forms) (cadr (nth i forms))))
i))))
`(let* ((,results (make-hash-table))
(,result-times (sort (list ,@(cl-loop for form in forms
for i from 0
for description = (nth i descriptions)
collect `(progn
(garbage-collect)
(cons ,description
(benchmark-run-compiled ,times
,(if ensure-equal
`(puthash ,description ,form ,results)
form))))))
(lambda (a b)
(< (second a) (second b))))))
,(when ensure-equal
`(cl-loop with keys = (hash-table-keys ,results)
for i from 0 to (- (length keys) 2)
unless (equal (gethash (nth i keys) ,results)
(gethash (nth (1+ i) keys) ,results))
do (if (sequencep (gethash (car (hash-table-keys ,results)) ,results))
(let* ((k1) (k2)
;; If the difference in one order is nil, try in other order.
(difference (or (setq k1 (nth i keys)
k2 (nth (1+ i) keys)
difference (seq-difference (gethash k1 ,results)
(gethash k2 ,results)))
(setq k1 (nth (1+ i) keys)
k2 (nth i keys)
difference (seq-difference (gethash k1 ,results)
(gethash k2 ,results))))))
(user-error "Forms' results not equal: difference (%s - %s): %S"
k1 k2 difference))
;; Not a sequence
(user-error "Forms' results not equal: %s:%S %s:%S"
(nth i keys) (nth (1+ i) keys)
(gethash (nth i keys) ,results)
(gethash (nth (1+ i) keys) ,results)))))
;; Add factors to times and return table
(append ',header
(cl-loop with length = (length ,result-times)
for i from 0 to (1- length)
for time = (second (nth i ,result-times))
for description = (car (nth i ,result-times))
for factor = (if (< i (1- length))
(format "%.2f" (/ (second (nth (1+ i) ,result-times))
(second (nth i ,result-times))))
"slowest")
collect (append (list description factor)
(cdr (nth i ,result-times))))))))Used like:
(bench-multi
:forms (("org-map-entries" (sort (org-map-entries (lambda ()
(nth 4 (org-heading-components)))
"/+MAYBE" 'agenda)
#'string<))
("regexp" (sort (-flatten
(-non-nil
(mapcar (lambda (file)
(let ((case-fold-search t))
(with-current-buffer (find-buffer-visiting file)
(org-with-wide-buffer
(goto-char (point-min))
(cl-loop with regexp = (format org-heading-keyword-regexp-format "MAYBE")
while (re-search-forward regexp nil t)
collect (nth 4 (org-heading-components)))))))
(org-agenda-files))))
#'string<))))#+RESULTS[3316dc4375a3b162e32790bb7e72d715d7f756fb]:
| Form | x faster than next | Total runtime | # of GCs | Total GC runtime |
|---|---|---|---|---|
| regexp | 62.50 | 0.043138672 | 0 | 0.0 |
| org-map-entries | slowest | 2.69609941 | 0 | 0.0 |
It can also help catch bugs by ensuring that each form returns the same results. For example, the benchmark above contains a subtle bug: because case-fold-search in the regexp form is non-nil, the regexp is compared case-insensitively, so it matches Org headings which start with Maybe rather than only ones which start with MAYBE. Using the :ensure-equal t argument to bench-multi compares the results and raises an error showing the difference between the two sequences the forms evaluate to:
(bench-multi
:ensure-equal t
:forms (("org-map-entries" (sort (org-map-entries (lambda ()
(nth 4 (org-heading-components)))
"/+MAYBE" 'agenda)
#'string<))
("regexp" (sort (-flatten
(-non-nil
(mapcar (lambda (file)
(let ((case-fold-search t))
(with-current-buffer (find-buffer-visiting file)
(org-with-wide-buffer
(goto-char (point-min))
(cl-loop with regexp = (format org-heading-keyword-regexp-format "MAYBE")
while (re-search-forward regexp nil t)
collect (nth 4 (org-heading-components)))))))
(org-agenda-files))))
#'string<))))user-error: Forms’ results not equal: difference (regexp - org-map-entries): ("Maybe this is not the case?")
Fixing the error, by setting case-fold-search to nil, not only makes the forms give the same result but, in this case, doubles the performance of the faster form:
(bench-multi
:ensure-equal t
:forms (("org-map-entries" (sort (org-map-entries (lambda ()
(nth 4 (org-heading-components)))
"/+MAYBE" 'agenda)
#'string<))
("regexp" (sort (-flatten
(-non-nil
(mapcar (lambda (file)
(let ((case-fold-search nil))
(with-current-buffer (find-buffer-visiting file)
(org-with-wide-buffer
(goto-char (point-min))
(cl-loop with regexp = (format org-heading-keyword-regexp-format "MAYBE")
while (re-search-forward regexp nil t)
collect (nth 4 (org-heading-components)))))))
(org-agenda-files))))
#'string<))))#+RESULTS[13bda5a63b7b851ff3aac91c6487a9eb22ad0fb7]:
| Form | x faster than next | Total runtime | # of GCs | Total GC runtime |
|---|---|---|---|---|
| regexp | 125.70 | 0.021080791 | 0 | 0.0 |
| org-map-entries | slowest | 2.649818971 | 0 | 0.0 |
So this macro showed which code is faster and helped catch a subtle bug.
elp-profile
Call this macro from an Org source block and you’ll get a results block showing which 20 functions were called the most times, how long they took to run, etc. prefixes should be a list of symbols matching the prefixes of the functions you want to instrument.
(defmacro elp-profile (times prefixes &rest body)
(declare (indent defun))
`(let (output)
(dolist (prefix ,prefixes)
(elp-instrument-package (symbol-name prefix)))
(dotimes (x ,times)
,@body)
(elp-results)
(elp-restore-all)
(point-min)
(forward-line 20)
(delete-region (point) (point-max))
(setq output (buffer-substring-no-properties (point-min) (point-max)))
(kill-buffer)
(delete-window)
(let ((rows (s-lines output)))
(append (list (list "Function" "Times called" "Total time" "Average time")
'hline)
(cl-loop for row in rows
collect (s-split (rx (1+ space)) row 'omit-nulls))))))
;; Use like this:
(elp-profile 10 '(map search goto-char car append)
(goto-char (point-min))
(search-forward "something"))This gives a table like:
| Function | Times called | Total time | Average time |
|---|---|---|---|
| mapcar | 30 | 0.0036004130 | 0.0001200137 |
| search-forward | 10 | 2.089…e-05 | 2.089…e-06 |
| goto-char | 10 | 6.926e-06 | 6.926e-07 |
| car | 13 | 3.956…e-06 | 3.043…e-07 |
| append | 1 | 5.96e-07 | 5.96e-07 |
| mapatoms | 1 | 0 | 0.0 |
Refactoring
Tools
emacs-refactor: language-specific refactoring
Regular expressions
Articles
Exploring Emacs rx Macro
:archive.is: http://archive.is/xPWJPLibraries
pcre2el: Convert between PCRE, Emacs and rx regexp syntax
Tools
ample-regexps.el: Compose and reuse regular expressions with ease
ample-regexps complements the built-in rx macro by flexibly defining regular expressions with reusable parts. In the following example, the define-arx macro defines three things:
- A macro
url-rx, which expands to a regular expression string at compile time - A function
url-rx-to-string, which can be used at runtime - A variable
url-rx-constituents, containing form definitions to use
(define-arx url-rx
'((http (seq bos (group "http") "://") )
(https (seq bos (group "https") "://") )
(https? (seq bos (group "http" (optional "s")) "://") )
(protocol (seq bos (group (1+ (not (any ":")))) "://"))
(host (group (1+ (not (any "/")))))
(path (group "/" (1+ (not (any "?")))))
(query (seq "?" (group (1+ (not (any "#"))))))
(fragment (seq "#" (group (1+ anything))))))The url-rx macro can then be used to test and select parts of URLs:
;; Accept HTTP or HTTPS
(let ((url "http://server/path?query#fragment"))
(when (string-match (url-rx https? host path (optional query) (optional fragment)) url)
(list (match-string 0 url)
(match-string 1 url)
(match-string 2 url)
(match-string 3 url)
(match-string 4 url)
(match-string 5 url)))) ;=> ("http://server/path?query#fragment" "http" "server" "/path" "query" "fragment")
;; Only accept HTTPS, not plain HTTP
(let ((url "http://server/path?query#fragment"))
(when (string-match (url-rx https host path (optional query) (optional fragment)) url)
(list (match-string 0 url)))) ;=> nil
;; Accept any protocol, not just HTTP
(let ((url "ftp://server/path"))
(when (string-match (url-rx protocol host path (optional query) (optional fragment)) url)
(list (match-string 0 url)
(match-string 1 url)
(match-string 2 url)
(match-string 3 url)
(match-string 4 url)
(match-string 5 url)))) ;=> ("ftp://server/path" "ftp" "server" "/path" nil nil)This example shows the use of a function to expand a list of strings into a sequence:
(define-arx cond-assignment-rx
'((alpha_ (regexp "[[:alpha:]_]"))
(alnum_ (regexp "[[:alnum:]_]"))
(ws (* blank))
(sym (:func (lambda (_form &rest args)
`(seq symbol-start (or ,@args) symbol-end))))
(cond-keyword (sym "if" "elif" "while"))
(id (sym (+ alpha_) (* alnum_))))) ;; -> cond-assignment-rx
(cond-assignment-rx cond-keyword ws id ":" id ws "=" ws id) ;; -> "\\_<\\(?:elif\\|if\\|while\\)\\_>[[:blank:]]*\\_<\\(?:[[:alpha:]_]+\\|[[:alnum:]_]*\\)\\_>:\\_<\\(?:[[:alpha:]_]+\\|[[:alnum:]_]*\\)\\_>[[:blank:]]*=[[:blank:]]*\\_<\\(?:[[:alpha:]_]+\\|[[:alnum:]_]*\\)\\_>"Strings
Libraries
s.el: The long lost Emacs string manipulation library
Tools
format$ macro
The format$ macro (currently hosted here) allows for easy string interpolation, including optional % sequences as used by format. For example, this:
(format$ "Amount: ${amount% .02f} $name %s" date)Expands to:
(format "Amount: % .02f %s %s" amount name date)Since this happens at macro expansion time rather than at runtime, there is no performance penalty, in contrast to using s-lex-format.
Testing
Libraries
assess: Test support functions
Tools
buttercup: Behavior-Driven Emacs Lisp Testing
ecukes: Cucumber for Emacs
Emacs Lisp Regression Testing (ERT)
This is the standard, built-in Emacs testing library, used by core code and third-party packages alike.
emake.el: Test Elisp without the hoops
Test Elisp with services like Travis CI without the fuss of Cask – just you, your project, and (Emacs-)Make.
Things EMake does:
- parses, installs, and runs tests for your package
- provides all the power of Elisp to extend its capabilities on-demand
Things EMake will never do (or ‘reasons you may still need Cask’):
- manage your development environment or provide tools to do so
- provide ’bundler-like’ exec abilities (this includes Cask’s emacs and eval commands)
Version control
Packages
Magit
One of the “killer apps” for Emacs–and for git!
XML / HTML
Libraries
esxml: An elisp library for working with xml, esxml and sxml
Probably the most featureful, usable library at the moment.
This library provides to formats for xml code generation. The primary form is esxml. esxml is the form that is returned by such functions as libxml-parse-xml-region and is used internally by emacs in many xml related libraries.
It also provides esxml-query:
;; Traditionally people pick one of the following options when faced
;; with the task of extracting data from XML in Emacs Lisp:
;;
;; - Using regular expressions on the unparsed document
;; - Manual tree traversal with `assoc', `car' and `cdr'
;;
;; Browsers faced a similar problem until jQuery happened, shortly
;; afterwards they started providing the `node.querySelector' and
;; `node.querySelectorAll' API for retrieving one or all nodes
;; matching a given CSS selector. This code implements the same API
;; with the `esxml-query' and `esxml-query-all' functions. The
;; following table summarizes the currently supported modifiers and
;; combinators:
;;
;; | Name | Supported? | Syntax |
;; |------------------------------------+------------+-------------|
;; | Namespaces | No | foo|bar |
;; | Commas | Yes | foo, bar |
;; | Descendant combinator | Yes | foo bar |
;; | Child combinator | Yes | foo>bar |
;; | Adjacent sibling combinator | No | foo+bar |
;; | General sibling combinator | No | foo~bar |
;; | Universal selector | Yes | * |
;; | Type selector | Yes | tag |
;; | ID selector | Yes | #foo |
;; | Class selector | Yes | .foo |
;; | Attribute selector | Yes | [foo] |
;; | Exact match attribute selector | Yes | [foo=bar] |
;; | Prefix match attribute selector | Yes | [foo^=bar] |
;; | Suffix match attribute selector | Yes | [foo$=bar] |
;; | Substring match attribute selector | Yes | [foo*=bar] |
;; | Include match attribute selector | Yes | [foo~=bar] |
;; | Dash match attribute selector | Yes | [foo|=bar] |
;; | Attribute selector modifiers | No | [foo=bar i] |
;; | Pseudo elements | No | ::foo |
;; | Pseudo classes | No | :foo | Example:
(defun org-books--amazon (url)
"Return plist of data for book at Amazon URL."
(cl-flet ((field (target-field list)
(cl-loop for li in list
for (field value) = (ignore-errors
(-let (((_ _ (_ _ field) value) li))
(list field value)))
when (equal field target-field)
return (s-trim value))))
(let* ((html (org-web-tools--get-url url))
(tree (with-temp-buffer
(insert html)
(libxml-parse-html-region (point-min) (point-max))))
(author (esxml-query "span.author a.contributorNameID *" tree))
(title (esxml-query "div#booksTitle h1#title > span *" tree))
(details (esxml-query-all "table#productDetailsTable ul li" tree))
(date (if-let ((printed (third (esxml-query-all "div#booksTitle h1#title span *" tree))))
;; Printed book
(s-replace "– " "" printed)
;; Kindle book
(field "Publication Date:" details)))
(asin (field "ASIN:" details))
(publisher (-some->> (field "Publisher:" details)
(replace-regexp-in-string (rx " (" (1+ anything) ")") "")))
(isbn-10 (field "ISBN-10:" details))
(isbn-13 (field "ISBN-13:" details)))
(list :author author :title title :publisher publisher :date date
:asin asin :isbn-10 isbn-10 :isbn-13 isbn-13))))elquery: Read and manipulate HTML
It’s like jQuery, but way less useful.
Example:
<html style="height: 100vh">
<head class="kek"><title class="kek" data-bar="foo">Complex HTML Page</title></head>
<body class="kek bur" style="height: 100%">
<h1 id="bar" class="kek wow">Wow this is an example</h1>
<input id="quux" class="kek foo"/>
<iframe id="baz" sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
width="100%" height="100%" src="example.org">
</iframe>
</body>
</html>(let ((html (elq-read-file "~/kek.html")))
(elq-el (car (elq-$ ".kek#quux" html))) ; => "input"
(mapcar 'elq-el (elq-$ ".kek" html)) ; => ("input" "h1" "body" "title" "head")
(mapcar (lambda (el) (elq-el (elq-parent el)))
(elq-$ ".kek" html)) ; => ("body" "body" "html" "head" "html")
(mapcar (lambda (el) (mapcar 'elq-el (elq-siblings el)))
(elq-$ ".kek" html)) ; => (("h1" "input" "iframe") ("h1" "input" "iframe") ("head" "body") ("title") ("head" "body"))
(elq-$ ".kek" html) ; => Hope you didn't like your messages buffer
(elq-write html nil)) ; => "<html style=\"height: 100vh\"> ... </html>"elfeed/xml-query.el
Provides lisp-based (rather than string-based) selectors. This library is primarily aimed at internal elfeed use rather than general use, however it may be useful to others. The author is considering publishing it separately.
;; Grab the top-level paragraph content from XHTML.
(xml-query-all '(html body p *) xhtml)
;; Extract all the links from an Atom feed.
(xml-query-all '(feed entry link [rel "alternate"] :href) xml)enlive: query html document with css selectors
This provides a limited set of lisp-based selectors (rather than string-based selectors).
Example:
(require 'enlive)
(enlive-text
(enlive-query (enlive-fetch "http://gnu.org/") [title])) ; => "The GNU Operating System and the Free Software Movement"xml-plus: XML/HTML utilities
Mostly undocumented, providing three main functions:
;; Utility functions for xml parse trees.
;; - `xml+-query-all' and `xml+-query-first' are query functions that search
;; descendants in node lists. They don't work with namespace-aware parsing yet
;;
;; - `xml+-node-text' gets node textBlogs
Planet Emacsen
This is the main community aggregator. You can find just about everyone’s Emacs-related blog posts here.
Sacha Chua’s /Emacs News/
This is Sacha’s weekly Emacs news digest. Don’t miss it!
Artur Malabarba’s /Endless Parentheses/
Irreal
One of the top Emacs blogs, frequently updated, and often highlights other interesting blog entries in the community.
Oleh Krehel’s /(or emacs/
Sacha Chua’s /Living an Awesome Life/
People
The Emacs community is so full of brilliant, generous people that I can’t keep track of them all! I will surely overlook many, and I will add them in no particular order, but merely as I come across them again and again.
Anders Lindgren
Anders, aka Lindydancer, has written numerous packages to help with developing highlighting and font-lock packages, as well as some other useful tools.
Packages
el2markdown: Convert Emacs Lisp comments to MarkDown
face-explorer: Library and tools for faces and text properties
faceup: Regression test system for font-lock keywords
font-lock-profiler: Coverage and timing tool for font-lock keywords
font-lock-regression-suite: Regression test suite for font-lock keywords of Emacs standard modes
font-lock-studio: Debugger for Font Lock keywords
highlight-refontification: Visualize how font-lock refontifies a buffer
lisp-extra-font-lock: Highlight bound variables and quoted expressions in lisp
multicolumn: Multiple side-by-side windows support
Artur Malabarba
Another prolific Emacs contributor, package developer, and blogger.
Packages
aggressive-indent-mode
paradox
Damien Cassou
Packages
beginend.el
John Wiegley
John is the current Emacs maintainer.
Packages
use-package
Jonas Bernoulli
Jonas is a prolific Emacs package developer and maintainer. You could spend hours on his GitHub repo.
Packages
Magit
Jorgen Schäfer
Packages
buttercup: Behavior-Driven Emacs Lisp Testing
Circe, a Client for IRC in Emacs
elpy: Emacs Python Development Environment
pyvenv: Python virtual environment interface
Magnar Sveen
Packages
dash.el
expand-region.el
multiple-cursors.el
s.el
Matus Goljer
Packages
dash.el
smartparens
Oleh Krehel
Oleh is a prolific package author, having contributed many very high-quality packages. He also writes at his blog.
Packages
ace-window: Quickly switch windows
avy: Jump to things tree-style
hydra: make bindings that stick around
lispy: short and sweet LISP editing
swiper: Ivy - a generic completion frontend, Swiper - isearch with an overview, and more. Oh, man!
Phil Lord
Packages
lentic: Create views of the same content in two buffers
m-buffer-el
Roland Walker
Roland has published a wide variety of useful Emacs packages.
Packages
list-utils: List-manipulation utility functions
Sacha Chua
Sacha could easily be nominated the official Emacs ambassador, were there to be one. Her contributions to the Emacs and Org-mode communities are innumerable. One of her greatest recent contributions is her weekly Emacs news posts that serve as a digest of everything that happened in the Emacs world over the past week.
Wilfred Hughes
Wilfred has published several useful packages, and he’s also leading the Rust Emacs port.
Packages
emacs-refactor
ht.el
suggest.el
Contributions
Yes, please! Send pull requests and file issues on the GitHub repo. This is intended to be a community project.
Guidelines
- Catalog and tag appropriately
- New entries in the outline should have the appropriate tags and should follow the existing hierarchy. For example, articles should be tagged
articles, and generally filed under anArticlesheading using tag inheritance to apply the tag. - “Loosely” or “usefully” opinionated
- Rather than being a place to dump links for users to sort out, we should do that ourselves. Links should have summaries and examples. Where there are multiple links to similar projects, we should compare them and guide users to what we think is generally the best solution, or the best solution for each use case.
- Archive reference material
- Much of the shared wisdom in the Emacs community is written in a variety of blogs by users and developers, as well as posts on Reddit, StackOverflow, etc. These tend to hang around for a long time, but being the Internet, this is never guaranteed. When linking to an article or other reference material, we should store a link to an archived version using this code.
Requirements
- org-make-toc
- This package updates the table of contents. It’s automatically used by this document through file-local variables, which you should be prompted to allow when opening the file.
Tasks
These resources should be added to the appropriate sections above. Since it takes some work to catalog and organize them, they are dumped here for future reference. Pull requests for these are welcome!
Articles to add [0/13]
Read and write files in Emacs Lisp (5 min read)
A Future For Concurrency In Emacs Lisp (6 min read)
A Blast From The Past: The Tale Of Concurrency In Emacs (7 min read)
I wished GNU Emacs had… (2 min read)
Reproduce bugs in emacs -Q (4 min read)
Why package.el? (1 min read)
My Emacs Configuration with use-package (8 min read)
Emacs script pitfalls (13 min read)
Autoloads in Emacs Lisp (5 min read)
Advanced syntactic fontification (11 min read)
Calling Python from Haskell (12 min read)
Search-based fontification with keywords (18 min read)
Syntactic fontification in Emacs (10 min read)
Add tips for new developers
e.g.:
- Commonly used minor modes
highlight-funcallshighlight-quotedoutline-minor-mode
Add https://github.com/joddie/pcre2el
Add GitHub - bbatsov/emacs-lisp-style-guide: A community-driven Emacs Lisp style guide
Add Nic Ferrier
GitHub - nicferrier/emacs-noflet: noflet - nic’s overriding flet, for fleting functions for the purpose of decorating them
GitHub - nicferrier/emacs-db: very simple database for emacslisp, can also wrap other databases.
Add Sean Allred
Add Chris Wellons
- http://github.com/skeeto
- Elfeed
- Other Emacs packages
Add Vasilij Schneidermann
Vincent Toups’ projects
He has a lot of interesting libraries on his repo, and some of them are extensively documented. An aspiring Emacs Lisp developer could learn a lot from his code.
Add more of Roland Walker’s packages
Add MELPA
Mention @milkypostman, @purcell, @syohex, etc. Mention sandbox.
Add GitHub - vermiculus/apiwrap.el: Generate wrappers for your API endpoints!
Add Modern Emacs site
Add GitHub - sigma/pcache: persistent caching for Emacs
Dynamic modules section
GitHub - jkitchin/emacs-modules: Dynamic modules for emacs
Add resources from its readme
For my own notes here are all the resources on dynamic modules I know of:
Here are the official Emacs header and example: emacs-module.h: http://git.savannah.gnu.org/cgit/emacs.git/tree/src/emacs-module.h?id=e18ee60b02d08b2f075903005798d3d6064dc013 mod_test.c: http://git.savannah.gnu.org/cgit/emacs.git/tree/modules/mod-test/mod-test.c?id=e18ee60b02d08b2f075903005798d3d6064dc013
This simple example in C http://diobla.info/blog-archive/modules-tut.html
- joymacs
- http://nullprogram.com/blog/2016/11/05/
- mruby
- https://github.com/syohex/emacs-mruby-test
- https://github.com/tromey/emacs-ffi
- an actual ffi for emacs
- elfuse
- https://github.com/vkazanov/elfuse a file system in Emacs
- asynchronous events
- http://nullprogram.com/blog/2017/02/14/ related to elfuse
- emacs-sqlite3
- sqlite3 binding of Emacs Lisp
- emacs-parson
- JSON parser with dynamic module feature with parson
- libyaml
- libyaml
- emacs-perl
- Embed Perl into Emacs
- https://github.com/syohex/emacs-eject
- eject a cd
- emacs-capstone
- elisp bindings for the capstone disassembler
- emacs-csound
- EmacsLisp link to Csound’s API via Emacs Modules
- emacs-cmigemo
- Emacs dynamic module for cmigemo
- emacs-cipher
- OpenSSL cipher binding of Emacs Lisp
- emacs-lua
- Lua engine from Emacs Lisp
- emacs-ztd
- libzstd binding of Emacs Lisp
- mem-cached
- libmemcached
- https://coldnew.github.io/2d16cc25/
- in Chinese, but with code
A collection of module resources: https://github.com/emacs-pe/emacs-modules
- Nim https://github.com/yuutayamada/nim-emacs-module
- OCaml https://github.com/janestreet/ecaml
- Rust https://github.com/lunaryorn/emacs-module.rs https://github.com/jjpe/emacs_module_bindings
- golang
- https://github.com/sigma/go-emacs writing modules in go
This may not be a dynamic module but claims an ffi haskell https://github.com/knupfer/haskell-emacs
Testing
Everything at EmacsWiki: Unit Testing
GitHub - rejeep/el-mock.el: Mocking library for Emacs
GitHub - sigma/mocker.el: a simple mocking framework for Emacs
EmacsWiki: Emacs Lisp Expectations
Add databases section
GitHub - skeeto/emacsql: A high-level Emacs Lisp RDBMS front-end
GitHub - pekingduck/emacs-sqlite3-api: Native SQLite3 API for GNU Emacs
GitHub - syohex/emacs-sqlite3: sqlite3 binding of Emacs Lisp
GitHub - kiwanami/emacs-edbi: Database Interface for Emacs Lisp
Add GitHub - ijp/mbe.el: macros by example in elisp
Tree-traversal
GitHub - volrath/treepy.el: Generic tree traversing tools for Emacs Lisp
- State “TODO” from [2017-09-06 Wed 00:21]
Test in MELPA sandbox
- State “TODO” from [2017-12-16 Sat 20:16]
[2017-07-29 Sat 00:33] Not only should you test installing and using your package in the sandbox, but you should also test then exiting the sandbox Emacs, running it again with the package already installed, and loading it. This is because, when the sandbox installs the package, the byte-compilation seems to load some things that won’t be loaded the same way when only loading the byte-compiled file (especially if you have any eval-when-compile lines, or unusual macros or things that modify the environment when loaded).
Code
This section contains code used to add to and update this document.
UNDERWAY Automate adding new links and summaries
- State “UNDERWAY” from “TODO” [2017-08-03 Thu 15:12]
Get summary of page
Get archive.is link for page
- State “DONE” from “TODO” [2017-08-03 Thu 15:11]
This bookmarklet should provide a way to get the URL:
javascript:void(open('https://archive.today/?run=1&url='+encodeURIComponent(document.location)))Seems to only work if run in a browser, with JavaScript. But there’s a Python package that has a shell command. So:
(require 's)
(defun emacs-package-dev-handbook--archiveis-capture (url)
"Return archive.is archived URL for URL."
(with-temp-buffer
(when (zerop (call-process "archiveis" nil t nil url))
(s-trim (buffer-string)))))
(cl-defun emacs-package-dev-handbook-insert-archiveis-property (url)
"Set the \"archive.is\" property for entry at point to the archived version of URL.
Interactively, assumes heading on/before point is an Org link to
a web page."
(interactive (list (save-excursion
(unless (org-at-heading-p)
(org-back-to-heading))
(beginning-of-line)
(when (re-search-forward org-bracket-link-regexp (line-end-position) 'noerror)
(org-link-unescape (match-string-no-properties 1))))))
(when-let ((archive-url (emacs-package-dev-handbook--archiveis-capture url)))
(org-set-property "archive.is" archive-url)))Insert new entry at point
Maybe use capture templates and refile?
Table of Contents
The ToC is generated using org-make-toc.
Config
I love Emacs and Org mode. This makes it so easy to make the document…alive! And automated! Beautiful.