Skip to content

Instantly share code, notes, and snippets.

@camsaul
Last active March 24, 2021 09:08
Show Gist options
  • Save camsaul/047f72f518218ed23acf0f2b7f4003f9 to your computer and use it in GitHub Desktop.
Save camsaul/047f72f518218ed23acf0f2b7f4003f9 to your computer and use it in GitHub Desktop.
Emacs Lisp convert Clojure expectations form to clojure.test deftest form
(defun cam/-next-sexp-on-current-line-p ()
"Whether the next sexp after point is one the current line."
(let ((next-sexp (save-excursion
(paredit-forward)
(current-line))))
(= next-sexp (current-line))))
(defun cam/-clojure-in-expect-form-p ()
"Whether we are currently in a expect form."
(save-excursion
(beginning-of-defun)
(paredit-forward-down)
(eq (sexp-at-point) 'expect)))
(defun cam/-clojure-convert-expect-to-deftest ()
"Convert the current expect form to a deftest form."
(interactive)
(unless (cam/-clojure-in-expect-form-p)
(error "Not in an expect form"))
;; comments below denote cursor position with _
;; _(expect x y)
(beginning-of-defun)
;; (_expect x y)
(paredit-forward-down)
;; (_ x y)
(kill-sexp)
(case (paredit-count-sexps-forward)
(1
;; (is_ assertion)
(insert "is")
;; make sure assertion is on the same line as is
(unless (cam/-next-sexp-on-current-line-p)
(join-line -1)))
(2
;; (=_ expected actual)
(insert "=")
;; make sure expected is on the same line as =
(unless (cam/-next-sexp-on-current-line-p)
(join-line -1))
;; (= expected_ actual)
(paredit-forward)
;; put actual on its own line if its not already
(when (cam/-next-sexp-on-current-line-p)
(paredit-newline))
;; _(= expected actual)
(paredit-backward-up)
;; (_(= expected actual)
(paredit-wrap-sexp)
;; (is _(= expected actual)
(insert "is ")))
;; _(is ...)
(paredit-backward-up)
;; (_(is ...))
(paredit-wrap-sexp)
;; (deftest a-test _(is ...))
(insert "deftest a-test ")
(paredit-newline)
;; _(deftest ...)
(beginning-of-defun))
(defun cam/-clojure-delete-comment-line ()
"Delete a full-line comment line, and return the deleted text, excluding initial semicolons and whitespace."
(beginning-of-line)
(search-forward " ")
(prog1 (buffer-substring-no-properties (point) (point-at-eol))
(delete-region (point-at-bol) (point-at-bol 2))))
(defun cam/-clojure-in-long-string-p ()
"Whether we are currently inside of a long string that goes past `fill-column'."
(and (paredit-in-string-p)
(save-excursion
;; "..."_
(paredit-forward-up)
(> (current-column) fill-column))))
(defun cam/clojure-split-long-string ()
"Split a long string into multiple lines with a str form."
(interactive)
(when (cam/-clojure-in-long-string-p)
;; _"..."
(paredit-backward-up)
;; (_"...")
(paredit-wrap-sexp)
;; (str _"...")
(insert "str ")
;; "_..."
(paredit-forward-down)
(while (cam/-clojure-in-long-string-p)
;; move forward to the first word after the fill column
(let ((original-line (current-line)))
(while (and (= (current-line) original-line)
(< (current-column) fill-column))
(paredit-forward)))
;; now back one word, where we'll split it.
(paredit-backward)
;; kill rest of line
(paredit-kill)
;; "..."_
(paredit-forward-up)
;; insert new line then new string with what we just killed.
(paredit-newline)
(paredit-doublequote)
(yank)
;; _"..."
(paredit-backward-up)
;; "_word ..."
(paredit-forward-down))
;; _"..."
(paredit-backward-up)))
(defun cam/clojure-convert-comment-to-testing ()
"Convert the comment line into a testing form in the deftest form that follows it."
(interactive)
(when-let ((comment-string (cam/-clojure-delete-comment-line)))
;; _(deftest ...)
(paredit-forward-down)
(paredit-forward-down)
(paredit-backward-up)
(paredit-wrap-sexp)
(insert "testing ")
;; (testing "_" ...)
(paredit-doublequote)
(insert comment-string)
(cam/clojure-split-long-string)
(paredit-forward-up)
(paredit-newline)))
(defun cam/-lisp-line-is-comment-p ()
"Whether the current line is a full-line comment."
(string-match-p
(rx line-start (one-or-more ";"))
(buffer-substring-no-properties (point-at-bol) (min (+ (point-at-bol) 4)
(point-at-eol)))))
(defun cam/-lisp-previous-line-is-comment-p ()
"Whether the previous line is a full-line comment."
(save-excursion
(forward-line -1)
(beginning-of-line)
(cam/-lisp-line-is-comment-p)))
(defun cam/clojure-collapse-comments ()
"Collapse the current comment line and all ones directly above it into a single line."
(interactive)
(beginning-of-line)
(when (cam/-lisp-previous-line-is-comment-p)
(let ((comment-string (cam/-clojure-delete-comment-line)))
;; move to end of previous line, insert space, and the text of the comment line we just deleted
(end-of-line 0)
(insert " ")
(insert comment-string))
(cam/clojure-collapse-comments)))
(defun cam/-reindent-clojure-defun ()
"Reinident the current form."
(let ((end (save-excursion
(end-of-defun)
(point))))
(clojure-align (point) end)))
(defun cam/clojure-expect-to-deftest ()
"Convert the expect form under `point' to a deftest form."
(interactive)
(cam/-clojure-convert-expect-to-deftest)
(when (cam/-lisp-previous-line-is-comment-p)
(forward-line -1)
(cam/clojure-collapse-comments)
(cam/clojure-convert-comment-to-testing))
(cam/-reindent-clojure-defun))
(defun cam/clojure-next-expect-to-deftest ()
"Convert the next expect form to a deftest form. Throws error if no more deftest forms exist."
(interactive)
(search-forward "(expect")
(cam/clojure-expect-to-deftest))
(defun cam/clojure-all-expect-forms-to-deftest ()
"Convert all the expect forms in the current buffer after `point' to deftest forms."
(interactive)
(ignore-errors
(while (ignore-errors
(cam/clojure-next-expect-to-deftest)
t))))
(defun cam/clojure-move-is-down-one-level ()
(interactive)
(beginning-of-defun)
(search-forward "(is")
(paredit-forward-down)
(paredit-forward)
(paredit-forward)
(paredit-backward)
(paredit-kill)
(paredit-raise-sexp)
(paredit-raise-sexp)
(paredit-forward-down)
(paredit-backward-up)
(paredit-forward)
(paredit-backward-down)
(paredit-backward)
(paredit-wrap-sexp)
(insert "= ")
(yank)
(paredit-newline)
(paredit-backward-up)
(paredit-wrap-sexp)
(insert "is " )
(cam/-reindent-clojure-defun))
@camsaul
Copy link
Author

camsaul commented Mar 23, 2021

Requires paredit and clojure-mode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment