1

I use org babel to load my literate configuration file "config.org". But sometimes when editing that org file, I introduce some error -- say, a missed parenthesis -- and then restart emacs. With all the various deferred loading and such, I don't get a backtrace; emacs somehow starts, appears normal -- but as I use it, I see that much of my configuration didn't load.

What I would like is to run check-parens (or any other function to check my code) when saving or tangling the elisp code from my configuration file. It seems like that ought to run on the generated .el file, though, and not on the .org file I'm saving.

Is there some way to do this, to get some kind of automated code linter / checking? Some kind of file-local variable that runs?

1

2 Answers 2

1

Here's another option, which just now showed up: the Emacs Elements channel demonstrated emacs batch mode for checking your initialization. You can run something like

emacs -batch -l ~/.emacs.d/init.el

from a shell to start emacs, using the above initialization, and just immediately exit. If your configuration is bad, you'll see error messages about. And because it happens outside your existing emacs session, you can fix the errors inside emacs.

But naturally we want to try that right from within emacs:

(defun my-check-init-batch-mode ()
  "Use batch mode to check my emacs initialization.
Inspired by https://www.youtube.com/watch?v=5Vume2SETNM."
  (interactive)
  (shell-command "emacs -batch -l ~/.emacs.d/init.el"))

That's nice because, for me at least, I get lots of warnings, and it's nice to have the shell output buffer to look through and fix things up.

I can confirm this works -- I added the above function to my config, ran it, then made a change from one of the warnings -- and made a typo in my lisp code, and fixed it right away. 🙂

3
  • One nice thing to do is add a local variable that reminds you to run this: (defun after-save-hook-message () (message "Consider my-check-init-batch-mode to check your configuration.")) and then add a file-local variable to your init/config files: set after-save-hook in those files to (after-save-hook-message).
    – Dan Drake
    Sep 28, 2023 at 11:11
  • 1
    I wonder if running it automatically is worth it? Debouncing it maybe.. Oct 3, 2023 at 22:56
  • Running it automatically sounds like a good idea -- just replace the message call in the after-save hook to actually run the function. One thing I've noticed is that, on Windows, the above function is super slow. (But, emacs on Windows is slow when interacting with outside programs -- magit, in particular, is agonizing.) I'd want to check to make sure that auto-saves don't trigger that hook, though.
    – Dan Drake
    Oct 4, 2023 at 11:20
0

I don't have an automated way yet, but here's a function one can use:

(defun check-parens-in-tangled-file ()
  "Tangle the current buffer, then run `check-parens' in the output tangled buffer."
  (interactive)
  (let ((tangled-file (car (org-babel-tangle))))
    (with-current-buffer (find-file-noselect tangled-file)
      (check-parens)))

One could also do further checks by also calling, say, eval-buffer.

For an automated version, one tricky thing is that check-parens doesn't have a useful return value; if the check passes, it returns nil, and otherwise throws an error. You can wrap that, though, and do various things based on the return value:

(defun bad-parens-p ()
  "Return `t' if `check-parens' indicates unbalanced parens or quotes; otherwise `nil'"
  (interactive)
  (condition-case err
      (check-parens)
    (error (message "%s" (error-message-string err)) t)))

(defun check-parens-in-tangled-file ()
  "Tangle the current buffer, then run `check-parens' in the output tangled buffer."
  (interactive)
  (let ((tangled-file (car (org-babel-tangle))))
    (with-current-buffer (find-file-noselect tangled-file)
      (if (bad-parens-p)
          (message "Tangled file has unbalanced parentheses or quotes")
        (message "`check-parens' is satisfied with the tangled file.")))))

One approach could be to use write-contents-functions -- inspired by this emacs.sx answer, and looking at the documentation:

 This works just like ‘write-file-functions’, but it is intended for
 hooks that pertain to the buffer’s contents, not to the particular
 visited file or its location, and can be used to create arbitrary
 save processes for buffers that aren’t visiting files at all.  Such
 hooks are usually set up by major modes, as buffer-local bindings
 for this variable.  This variable automatically becomes
 buffer-local whenever it is set; switching to a new major mode
 always resets this variable, but calling ‘set-visited-file-name’
 does not.

 If any of the functions in this hook returns non-‘nil’, the file is
 considered already written and the rest are not called and neither
 are the functions in ‘write-file-functions’.

So in your literate config, you could run a function that returns non-nil when something is wrong with the output .el file.

For the moment, though, just having a function I can run that does some kind of check is a good start.

3
  • Doesn't your bad-parens-p function reproduce exactly the same nil-or-error problem that check-parens exhibits? Your answer suggests to me that you intend it to return a useful result, not error out. Sep 26, 2023 at 20:00
  • bad-parens-p is working as expected, for me: I can make an .el file, and in that evaluate (bad-parens-p) and it returns t or nil as expected. It's condition-case that does that -- instead of throwing a backtrace, it calls error and then returns t.
    – Dan Drake
    Sep 28, 2023 at 11:06
  • I missed that t. Sep 29, 2023 at 8:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.