ruby_object
ruby_object

Reputation: 1266

Emacs Lisp - How do I handle an edge case where a function returns a string or nil?

I'm in the process of modifying an Emacs mode. https://github.com/bigos/git-auto-commit-mode

I am new to Emacs lisp and I can't find elegant solution to following problem.

I have this code fragment where function gac-raw-branches returns a string or nil and I can't find good solution to avoid errors where split expects a string. I use helper functions like gac-split-for-current shown in the following example. But, is there a better way to do it?

(defun gac-split-for-current-branch (raw-branches)
  (split-string raw-branches "\n"))

(defun gac-current-branch (filename)
  "Current git branch of FILENAME."
  (let ((res)
        (raw-branches (gac-raw-branches filename)))
    (if raw-branches
        (dolist (el
                 (gac-split-for-current-branch raw-branches)
                 res)
      (if (string-match "^\\* .*" el)
          (setq res (substring el 2))))
        nil)))

Upvotes: 2

Views: 246

Answers (2)

abo-abo
abo-abo

Reputation: 20342

Here's the good-looking (IMO) code:

(defun gac-current-branch (filename)
  "Current git branch of FILENAME."
  (let (res)
    (dolist (el (ignore-errors
                  (split-string
                   (gac-raw-branches filename)
                   "\n" t)))
      (when (string-match "^\\* +\\(.*\\)$" el)
        (setq res (match-string 1 el))))
    res))

I also kind of like functional style, although it's not recommended for Elisp:

(defun gac-current-branch (filename)
  "Current git branch of FILENAME."
  (cl-reduce
   (lambda (res el)
     (or (and (string-match "^\\* +\\(.*\\)$" el)
              (match-string 1 el))
         res))
   (ignore-errors
     (split-string (gac-raw-branches filename) "\n" t))))

Some points:

  1. I don't like to define functions that are just partial application of one function, like gac-split-for-current-branch. It makes the code less clear.
  2. (ignore-errors (split-string x)) always returns a list, nil at worst. It's fine to iterate on nil with either dolist or mapcar or cl-reduce.
  3. No need to write nil in else clause of if statement: it's automatic.

Upvotes: 1

Kyle Meyer
Kyle Meyer

Reputation: 1586

If a function returns a string or nil, checking that the value is non-nil (using if, when, or and, depending on the context) is a fine way to deal with that (at least I don't find it inelegant).


Here is one way to rewrite your function (assuming gac-raw-branches is the output of git branch).

(defun gac-current-branch (filename)
  "Current git branch of FILENAME."
  (let ((gb-output (gac-raw-branches filename)))
    (when gb-output
      (with-temp-buffer
        (insert gb-output)
        (goto-char (point-min))
        (and (re-search-forward "^\\*\\s-+\\(.*\\)" nil t)
             (match-string 1))))))

Note the when in the fourth line that is used to make sure there is a non-nil value. The and in the eigth line serves the same purpose.


If you use dash, -if-let and -when-let are nice for these situations.

(defun gac-current-branch (filename)
  "Current git branch of FILENAME."
  (-when-let (gb-output (gac-raw-branches filename))
    (with-temp-buffer
      (insert gb-output)
      (goto-char (point-min))
      (and (re-search-forward "^\\*\\s-+\\(.*\\)" nil t)
           (match-string 1)))))

Unrelated to the question, but have you looked into using Magit for this? It has a backup mode, but even if that doesn't behave as you want, you could take advantage of its git interface.

Upvotes: 1

Related Questions