Cómo manejar con gracia los errores en el archivo init

20

Quisiera una forma de detectar errores al ejecutar mi archivo init y luego manejarlos con gracia. Muchas de mis personalizaciones y combinaciones de teclas más importantes aparecen al final de mi archivo init para asegurarme de que no se apliquen otras configuraciones por encima de ellas. El problema es que cuando la inicialización se interrumpe temprano, me siento totalmente paralizado tratando de depurar el problema sin que se apliquen mis configuraciones y enlaces de teclas familiares.

¿Hay alguna forma de finalizar con gracia el proceso de inicialización cuando se produce un error?

nispio
fuente

Respuestas:

9

Me vienen a la mente dos opciones, ninguna de las cuales es perfecta. Primero, puede envolver la mayor parte de su código de inicialización temprana (es decir, antes de que llegue a sus personalizaciones) (ignore-errors ...). Si no hay errores, sin embargo, no será una gran cantidad de comentarios - ignore-errorsle devuelva nil.

Una opción más compleja sería envolver el código potencialmente defectuoso en una combinación de unwind-protecty with-demoted-errors(con debug-on-errorset a nil). Este último generará un error con gracia en el primer error que encuentre e informará el mensaje de error al *Messages*búfer para su inspección. Mientras tanto, unwind-protectse evaluará el resto del cuerpo (presumiblemente sus personalizaciones). Entonces, por ejemplo:

(unwind-protect
    (let ((debug-on-error nil))
      (with-demoted-errors
        (message "The world is about to end")
        (sleep-for 2)
        (/ 10 0)                        ; divide by zero signals an error
        (message "This will never evaluate")
        (sleep-for 2)
        (setq some-var 5)))
  (message "Here are the unwind forms that will always evaluate")
  (sleep-for 2)
  (setq some-var 10)
  (setq another-var "puppies")
  (message "All done!"))
Dan
fuente
1
Bien, no lo hice with-demoted-errors. Puede agregarle un argumento de cadena "LOOK OVER HERE!!! %s", por lo que es menos probable que pierda el error en el búfer de mensajes.
Malabarba
@Malabarba Esta forma de with-demoted-errorssolo está disponible en 24.4
lunaryorn
@lunaryorn Gracias, no lo sabía.
Malabarba
En realidad, la versión en la que estoy es 24.3.1.
Dan
8

@Dan describió bien cómo puede convertir los errores en mensajes. También puede hacer lo que quiera con errores mediante el uso condition-case. Otra opción más es usar unwind-protect.

Me quedaré condition-caseaquí, sin ninguna razón.

Capturando el error

Esto siempre debería garantizar que sus definiciones clave sean evaluadas, independientemente de lo que sucedió en su interior condition-case. Cualquier error se almacena dentro init-error.

(defvar init-error nil 
  "The error which happened.")

(condition-case the-error
    (progn
      ;; Do the dangerous stuff here.
      (require 'what-I-want))
  (error
   ;; This is only evaluated if there's an error.
   (setq init-error the-error)))

;;; Do the safe stuff here.
(define-key uncrippling-map "\C-h" 'help!)

Arrojándolo de vuelta

Luego, simplemente arroje el error nuevamente. Hay varias formas de hacerlo, aquí hay una.

;;; Throw the error again here.
(when init-error
  (funcall #'signal (car init-error) (cdr init-error)))
Malabarba
fuente
unwind-protecthace que el error se vuelva a generar inmediatamente, después de ejecutar el código que haya puesto en su cláusula de rescate. Es como finallyen un lenguaje como Java, en lugar de catch.
sanityinc
2

Las otras respuestas han cubierto bastante bien las facilidades de manejo de errores de bajo nivel que serán útiles en un caso como este. Otro enfoque que puede ayudar es la modularidad. Por ejemplo, divido mi archivo de inicialización en varios archivos diferentes (usando providesegún corresponda), y los cargo usando esta función en lugar de require:

(defun my/require-softly (feature &optional filename)
  "As `require', but instead of an error just print a message.

If there is an error, its message will be included in the message
printed.

Like `require', the return value will be FEATURE if the load was
successful (or unnecessary) and nil if not."
  (condition-case err
      (require feature filename) 
    (error (message "Error loading %s: \"%s\""
                    (if filename (format "%s (%s)" feature filename) feature)
                    (error-message-string err))
           nil)))

Un error al cargar un archivo de esta manera seguirá imprimiendo un mensaje, pero no impedirá la ejecución de nada fuera del archivo donde realmente ocurrió el error.

Por supuesto, esta función no es realmente tan diferente de terminar una requirellamada with-demoted-errors(la escribí antes de saberlo with-demoted-errors), pero el punto importante es que esencialmente puede implementar algo como la combinación de Dan sin with-demoted-errorsy con unwind-protectajuste (potencialmente muy largo) bloques de código

Aaron Harris
fuente
Esta función era exactamente lo que buscaba. Mi emacs arranca ahora a pesar de informar un error. Después de eso, acabo de cargar mi archivo de inicio y eval-buffer. Gracias por publicarlo.
Kevin