Saturday, 18 January 2014

Starting with Elisp and external processes

1 Getting started with Elisp

This post will describe my first steps with Emacs Lisp and how to use it to communicate with an external process.

The little 'script' I implemented will:

  • use the inotifywait tools on Linux to monitor filesystem changes1.
  • call a function to react to them.

As for all things Emacs, the Emacs documentation is incredibly detailed and very, very good and all informations in this post can be found (with a lot more information) in the documentation.

This post is simply a little write up of the most basic steps to get started.

It will cover the following steps:

  1. How to start a external program from Emacs.
  2. How to react to output generated from the started process.

2 Starting a process in Emacs

When deciding to start an external program there are two possibilities: either start a synchronous process or an asynchronous one.

As I want inotifywait to keep running in the background, while reacting to the output I have to start an external process using the start-process function provided by emacs:

(start-process "some-name" ;; A name for the process
        "*buffer-to-write-output-into*" ;; A buffer the process can write to
        "inotifywait" ;; The command to run
        ;; Following are any arguments that are required by the command:
        "-m"
        "-e"
        "create"
        "<file_to_watch>")
      

It is important that the command-line arguments are passed as variadic arguments, so if you want to apply this command to a list of arguments you already have stored in a list you can simply use the apply-function 2.

(process (apply 'start-process "some-name"
        "*buffer-to-write-output-into*"
        "inotifywait" argument-list))
      

That is all there is to starting an asynchronous process in Emacs.

3 Reacting to output from the process

Reacting to output generated by the process is done using classic callbacks.

To register the callback-function with a process, you have to store the return value of the start-process call and register a callback using the set-process-filter function:

(defun callback-func (process msg)
        "Callback function need to accept two arguments:
        - the process that created the output.
        - the message containing the currently produced output."
        (message msg))

        (let* ((arguments (list "-m" "-e" "create" "<filepath>"))
        (process (apply 'start-process "NOTIFYWAIT" "*notifywait*" "inotifywait" arguments)))
        (set-process-filter process 'callback-func))
      

Now all output generated by the inotifywait-command will be send to the callback and can be processed in whatever fashion required.

I wrote a little callback that will run all nosetests for a Python-project for example, as soon as a file under the source-directory changes. You can find the source-code on Github.

Footnotes:

1

If you are using Emacs ≥ 24.4 then cross-platform inotify-functionality is included in Emacs. As both Debian and OpenSUSE both still provide Emacs 24.3 however, I worked around this with this small script.

2

If you know Python then you can think of apply as being the same as calling a function with func(*a_list).

Wednesday, 8 January 2014

Automatically install packages in new virtual environments

1 Automatically install packages into a virtual environment

When using multiple virtual environments in Python I happen to always require the same basic packages in the environments I use:

# Default python tools
pyflakes
pep8
nose
# Emacs requirements
jedi
epc
sexpdata
# Ipython and its requirements
ipython
Jinja2
tornado
pyzmq

When creating new environments often to try out a few things it becomes quite cumbersome to always install the packages by hand.

So instead I was looking for a quick and easy solution to the problem and found it via virtualenvwrapper.

Installing and configuring virtualenvwrapper

As virtualenvwrapper is just a wrapper around virtualenv it is required to have virtualenv installed first1.

After that you can just use pip to install virtualenvwrapper.

sudo pip install virtualenvwrapper

Now add the following line to your shell-initialization file (probably ~/.bashrc or ~/.zshrc) and re-source the file:

export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
# Only add the previous export if you want to use virtualenvwrapper
# with Python3
export WORKON_HOME=$HOME/venv
source /usr/local/bin/virtualenvwrapper.sh

Now everything is ready to define the required hooks for automatically installing the default packages: by default the hooks are defined in the WORKON_HOME directory.

The hook we want to modify is the postmkvirtualenv-hook that is called after the successful creation of the environment.

I just created a file holding all requirements inside the WORKON_HOME directory that I can then install via this simple hook:

pip install -r "$WORKON_HOME/default_requirements.txt"

Footnotes:

1

Except if you are using a Python ≥ 3.3 which includes the pyvenv command. You can set virtualenvwrapper to use the venv module by also adding the following export to your shell-initialization:

export VIRTUALENVWRAPPER_VIRTUALENV=pyvenv-3.3

This command will not install pip inside the new environment however.