SGCG

…esto no es un subtítulo…

Ir a: contenido categorías calendario archivo suscripción

Volver arriba

Emacs Lisp en casos prácticos (7): ejecutar un programa externo periódicamente (1)

2015-07-23

GNU Emacs es un editor de textos potentísimo. Por debajo, es un intérprete de Emacs Lisp, un lenguaje de programación de la familia de Lisp. En esta serie de artículos vamos a mostrar algunas formas de adaptar Emacs a nuestras necesidades con algunos casos prácticos.

En el artículo de hoy, crearemos una función para ejecutar un programa externo periódicamente y mostrar su salida en un búfer: una réplica de las herramientas watch de GNU/Linux, cmdwatch de FreeBSD y gnuwatch de OpenBSD. Vamos a usar temporizadores, así que el gestor de temporizadores del último artículo puede ayudarnos durante el desarrollo.

El código de este artículo funciona en GNU Emacs 24.4.

El problema

Las herramientas watch, cmdwatch y gnuwatch sirven para ejecutar periódicamente una orden y mostrar su salida a pantalla completa en una consola. Queremos hacer algo equivalente en Emacs: ejecutar periódicamente un programa externo y mostrar su última salida en un búfer.

Plan a seguir

Vamos a construir una función interactiva que preguntará al usuario qué orden ejecutar y cada cuánto tiempo hacerlo. Después, la función creará un temporizador (con run-with-timer) que se ocupará de ejecutar nuestro programa externo de forma asíncrona con async-shell-command. La salida irá en un búfer con el nombre *Watch: PROGRAMA* (donde PROGRAMA es el programa externo que hemos mandado ejecutar); si eliminamos este búfer, ya no tiene sentido que se ejecute el temporizador, así que lo eliminaremos con una función hook añadida a kill-buffer-hook (para que se ejecute al matar el búfer).

Implementación

La función, que acepta la orden a ejecutar (command) y los segundos entre ejecuciones (interval), es así:

(defun watch (command interval) "Run shell COMMAND every INTERVAL seconds showing the results in a dedicated buffer." (interactive (list (read-shell-command "Shell command: " nil nil) (read-number "Update interval (seconds): "))) (let* ((buffer-name (format "*Watch: %s*" command)) (buffer (generate-new-buffer buffer-name))) (switch-to-buffer buffer) (lexical-let ((timer (run-with-timer 0 interval 'watch--callback buffer command))) (add-hook 'kill-buffer-hook (lambda () (cancel-timer timer)) nil t))))

Analicémosla punto por punto:

La función watch--callback, que es la que se ejecuta cada vez que salta el temporizador, lo único que hace es llamar a la orden con async-shell-command:

(defun watch--callback (buffer command) (async-shell-command command buffer))ime

Esto tiene el problema de que se muestra el búfer cada vez que salta el temporizador, lo que puede ser molesto. Para evitarlo, hacemos lo que recomienda la documentación de async-shell-command:

(push '("^\*Watch: .*\*" display-buffer-no-window) display-buffer-alist)

Ejecutamos esto solamente una vez. El elemento que hemos añadido a display-buffer-alist es una lista con dos campos: una expresión regular que casa con el esquema de nombres que hemos usado para nuestros búferes y la función display-buffer-no-window, que se encarga de que no se muestre el búfer si ya está oculto.

Problemas pendientes

Cada vez termina la ejecución del programa externo con async-shell-command, aparece un mensaje en el minibúfer. Esto puede ser muy molesto, así que buscaremos una forma de evitarlo.

No estamos permitiendo una forma sencilla de parar el temporizador o cambiar el intervalo entre ejecuciones. Podemos usar nuestro nuevo y flamante gestor de temporizadores, pero es mejor ofrecer esa funcionalidad directamente.


Categorías: Informática

Permalink: https://sgcg.es/articulos/2015/07/23/emacs-lisp-en-casos-practicos-7-ejecutar-un-programa-externo-periodicamente-1/