Copyright (C) 2022 SPDX-License-Identifier: MIT Author: Benjamin Kästner Keywords: convenience
WIP. Not tested enough yet. Handle with care. The usual stuff.
(defun detangle-file (filename)
"Detangle the given Emacs Lisp file into an `org-mode' file."
(interactive "f")
(copy-file filename (concat filename ".org"))
(with-current-buffer (find-file-noselect (concat filename ".org"))
(goto-char (point-min))
This function uses a very simple internal state machine with two states, `text’ and `code’. State transition from `code’ to `text’ is triggered by pure comment lines, i.e. lines that only contain a comment. State transition from `text’ to `code’ is triggered on the first non-empty non-comment line.
(let ((state 'text) ; initial state is text
(clevel 1)) ; initial current org-mode level is 1
(while (not (eobp))
(cond
Warning: This pattern uses [ \t] after the semicolons by choice, as :space: also contains whitespace and would thus join multiple, otherwise empty comment lines together.
((looking-at "^\\([[:space:]]*\\(;+\\)[ \t]*\\)")
(if (eq state 'code)
End of code transition
(progn
(insert "#+END_SRC")
(newline)
We need go go backward after the newline, otherwise we jump behind the comment at the end of the loop.
(forward-line -1)
(setq state 'text))
(let ((level (- (match-end 2) (match-beginning 2) 2)))
(replace-match "")
(when (> level 0)
TODO: Consider `org-current-level’ instead?
(beginning-of-line)
(org-insert-heading)
(while (> level clevel)
(org-demote)
(cl-incf clevel))
(while (< level clevel)
(org-promote)
(cl-decf clevel))))))
Keep and skip empty lines
((looking-at-p "^[[:space:]]*$") nil)
(t
We hit a non-empty, non-comment line and transition into the `code’ state.
(when (eq state 'text)
(insert "#+BEGIN_SRC emacs-lisp")
(newline)
(setq state 'code))))
(forward-line))
(when (eq state 'code)
(insert "#+END_SRC")))))
(provide 'detangle)