Developing an emacs package
2022-06-01
I wrote a package long time ago, maybe 6 years or so. Now, I switched my employer not long ago which uses clockodo to track times used on a task. Clockodo serves a clean webpage and some packages for major operating systems. But the lack of emacs support is a pain point for me since I use emacs most of the time and it’s the first program running and the last I close.
So, I decided to write a simple integration thanks to the API clockodo provides. This blog post tries to give an overview above all the steps taken. The last time I wrote a package I missed the overall documentation thus I start from zero again.
Prerequisites
Before starting the package development I searched a bit through the latest ressources I found on that topic.
- Elisp Snippets - Not related by a good source for quick snippets
- eldev - A emacs-based build tool like cask but not cask
- atomic objects blog post - A nice blog post related to package development with
commit-mode
- System Crafters blog - A short introduction into
minor modes
- Emacs package dev handbook - Extensive ressources about package development
- Elisp cheat sheet - A cheat sheet is always nice to have
I expect basic emacs-lisp and programming knowledge.
Starting the package
Create a new git repository with git init <name>
and open a new file/buffer from emacs in
this repository.
An emacs packages a just a single or bunch of elisp files which are loaded at runtime. For the clockodo package we only need a simple file which is easier to handle. Emacs uses a common scheme to correctly identify and handle packages:
|
|
The top line starts with three ;;;
and provides the name and a short description about the
package. Notice the usage of the more modern lexical binding with lexical-binding: t
.
The next section sets some common informations about the author and the package it-self which
are used by the package management systems. The Version:
is mandatory in this section.
Note the commentary section starts with three ;;;
again. Here is the place for the whole
documentation and usage of the package. The code marker starts with three ;;;
again and is
important to show the end of the commentary section. At the end the package is
closed by (provide 'clockodo)
and the commentary below is also needed. I toke the template
from here.
Package basics
The clockodo package is only a small one which should wrap parts of the api to start and stop the clock and get some informations from clockodo. We have several predefined options for a new mode in emacs:
- a
minor mode
which are mostly small packages for providing or enhancing emacs. - a
major mode
is mostly related to a programming language and provides options for for syntax highlighting and so one. - a
commit mode
is used for interacting with external commands like compilers which need fixed buffers.
For clockodo I choose a minor mode
. Additionally, authentication and dealing with HTTP requests
and responses are neede.
First we load the dependencies which are needed for our package. All external packages
(not provided by emacs itself) are mandatory within the ;; Package-Requires:
section to
work correctly when installed from a package management.
|
|
Then we introduce some customizations for the user and define package variables.
Use a common prefix for your functions and variables. I use clockodo
in my example.
|
|
This is just a small excerpt from all definitions used for the clockodo package.
The last two function are useful helpers. The clockodo--key
function provides an easy way
to bind keys to the package keymap. The clockodo--with-face
function provides an easy way
to color some part of output using the emacs faces.
Now, the skeleton definition for a minor mode with the define-minor-mode
macro.
|
|
The first function defines the minor mode clockodo-mode
which is:
:init-value
not enabled by default:lighter
as the nameclockodo
on the modeline:group
uses the custimization groupclockodo
:global
the mode is a global mode:keymap
the modes keymap
Additionally, we could provide some function body to do some setup or cleanup. This macro also creates a hook which is run if the mode is turned on or off.
Authentication
Now, the skeleton is ready. To interact with the clockodo api credentails are needed.
Emacs provides the auth-source
package to deal with authentication and credentials.
|
|
The function clockodo--get-credentials
creates ask the user for credentials and splits
them into the username, password and a save function to store the credentials over a single
emacs session. The clockodo--initialize
function pokes the clockodo api and stores some
basic informations about the user. The clockodo-get-credentials
ties them together. First
it asks for the credentials either from the user or the auth store, than tests if we get valid
informations from the clockodo api and if so stores the credential for longe time use.
The user can toggle this behaviour through the clockodo-store-api-credential
variable.
Dealing with http requests and responses
To interact with the clockodo api I used the request
package which is easy to use and
well documented. First set the header for every requests taken.
|
|
Afterwards, the basic requests are modeled as a function which gets the credentials and the partial URL for the requests. As an example, I modeled the get requests like this.
|
|
Now, every get request is nothing more then a couple of lines.
|
|
Maybe there is a better way to deal with the repeating user
and token
variables (Let me know).
Special buffers
Besides using the clock, I wanted to show reports generated from the clockodo api. For this purpose I use a buffer which is set into special mode.
|
|
This function creates a new buffer prefixed with clockodo
or takes an existing one.
It is set into read-only mode and the first part of the header
is set as header-line-format
.
The second part is a block of general informations about the api user. As last part a function is
executed which renders the report body.
request
returns the data as alist
thus the following function is handy.
|
|
This binds the alist
properties as variables of the form .variable.subvariable
.
Hints and Takeaways
This post should give me and others some hints when developing a new emacs package or dealing with some api. It is not a real template but more a set of hints since the documentation for some packages or emacs parts are not well-suited vor beginners.
Lastly, another short list of general hints:
- Use melpazoid as Github action
- Use package-lint to get the documentation correctly
- Use internal function, than
cl-lib
and if nothing helps external libs - Keep your functions small and composable
- Take your time reading documentation and code
- Reading code of others and other packages helps to understand certain patterns of elisp
- Use
when
andunless
instead of openif
forms - Provide sensible default keybindings
- Prefer functions with a toggle effect for keybindings
- Use
defvar
anddefcustom
to allow easy modifications and prevent void variable assignments - Use
(or nullable-var default)
to set defaults
Thanks for the attentation and leave me a comment.