emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: Akater <nuclearspace@gmail.com>
To: emacs-orgmode@gnu.org
Subject: Feature suggestion and code review request: org-babel-cycle-src-block-header
Date: Wed, 28 Feb 2018 10:59:12 +0000	[thread overview]
Message-ID: <87muztqt1b.fsf@gmail.com> (raw)

[-- Attachment #1: Type: text/plain, Size: 6060 bytes --]

When I have a chance, I enjoy the following development workflow: the
code is written in org files and is tangled into conventional source
code files more or less regularly.

I find that source blocks mostly fall into three categories, numbered
here for further reference:
- examples/test cases/desiderata, like
  `(my-implemented-or-desired-function x y)' (type 1) 
- drafts, failed attempts at implementations and other snippets better
  left as is, or as a warning (type 2)
- working implementations, to be tangled (type 3)

Hence I end up using only a handful of header argument strings. An
example corresponding to this 3-cases setup is found below. So it would
be nice to have a function that cycles between those, much like we can
cycle through org TODO sequence now using a standard function, and set
up this sequence per Org file.

I'm fairly bad at Emacs Lisp, so I'm interested in feedback about my
implementation of cycling function. It operates with strings, mostly
because I failed to make it work with lists of alists of header
arguments as ob-core.el suggests. On the other hand, given that Emacs
Lisp is more string-oriented than it is object-oriented, it might not be
a really bad idea.

So what do you think? How can this implementation be improved? (Sans
using rotate and tracking position in a smarter way.) Does it make sense
to include this feature in Org mode? Maybe I missed some existing
well-estabilished solutions? This is something akin to “literate
programming”; I'm not a fan of this idea---at least the way it is
usually presented---but it is somewhat popular a topic. I have some
other feature in mind I'd love to see implemented in Org-Babel:
convenient export of src blocks of type 1 (see above) into unit tests
(as test cases) and into documentation sources (as examples) but this
one is heavily target-language dependent and probably deserves its own
thread.

#+begin_src emacs-lisp
(cl-defun next-maybe-cycled (elem list &key (test #'equal))
  "Returns the element in `list' next to the first `elem' found. If `elem' is found at `list''s very tail, returns `list''s car. `next-maybe-cycled' provides no way to distinguish between \"found nil\" and \"found nothing\"."
  (let ((sublist (cl-member elem list :test test)))
    (and sublist
	 (if (cdr sublist)
	     (cadr sublist)
	   (car list)))))

(defun shrink-whitespace (string)
  "Transforms all whitespace instances into single spaces. Trims whitespace at beginning and end. No argument type checking."
  (cl-reduce (lambda (string rule)
	       (replace-regexp-in-string (car rule) (cdr rule) string))
	     '(("[[:blank:]]+" . " ") ("^[[:blank:]]*" . "") ("[[:blank:]]*$" . ""))
	     :initial-value string))

(defun string-equal-modulo-whitespace (x y)
  (string-equal (shrink-whitespace x) (shrink-whitespace y)))

(defun org-babel-cycle-src-block-header-string (header-strings)
  "Cycle through given `header-strings' if currently in Org Babel source code block. If current src-block header is not found in `header-strings', switch header to the car of `header-strings'.

`header-strings' must be a non-empty list of strings. All whitespace in them is shrinked.

If UNDO-ed, cursor position is not guaranteed to be preserved."
  (interactive)
  (cond
   ((not (and header-strings (listp header-strings)))
    (error "No Org Babel header strings list found to cycle through. %S found intstead." header-strings))
   ((not (every #'stringp header-strings))
    (error "Malformed list of Org Babel header strings: not all elements are strings in %S." header-strings))
   (t
    (let ((initial-position (point)))
      (org-babel-goto-src-block-head)
      ;; here we rely on `org-babel-goto-src-block-head'
      ;; signalling an error if not in source code block
      (forward-char (length "#+BEGIN_SRC"))
      (let* ((fallback-position (point))
	     (we-were-before-replacement-zone (<= initial-position
						  fallback-position)))
	(let ((default-position-to-return-to initial-position)
	      (old-header-string (delete-and-extract-region (point)
							    (line-end-position))))
	  (unless we-were-before-replacement-zone
	    (incf default-position-to-return-to (- (length old-header-string))))
	  (let ((new-header-string
		 (concatenate 'string
			      " "
			      (shrink-whitespace
			       (or (next-maybe-cycled old-header-string
						      header-strings
						      :test #'string-equal-modulo-whitespace)
				   (car header-strings))))))
	    (insert new-header-string)
	    (unless we-were-before-replacement-zone
	      (incf default-position-to-return-to (length new-header-string)))
	    (goto-char (if (<= fallback-position
			       default-position-to-return-to
			       (+ fallback-position (length new-header-string)))
			   fallback-position
			 default-position-to-return-to)))))))))

;; example for mailing list
;; Common Lisp assumed!
(defun akater/org-babel-cycle-header nil
  (interactive)  
  (org-babel-cycle-src-block-header-string
   '("lisp :tangle no :results none"   ;; type 2 above
     "lisp :tangle yes :results none"  ;; type 3 above
     "lisp :results type verbatim"     ;; type 1 above
     )))
#+end_src

Ideally, I envision something along these lines (some specific choices
below don't really make sense):
#+begin_src emacs-lisp
(defcustom org-babel-standard-header-sequences-alist
  '((development-setup-1
     (lisp
      (((:tangle . "no") 
	(:results . "none"))
       ((:tangle . "yes") 
	(:results . "none"))
       ((:results . "type verbatim"))))
     (python
      (((:tangle . "no") 
	(:results . "none"))
       ((:tangle . "yes") 
	(:results . "none"))
       ((:results . "type output"))))
     )
    (development-setup-2
     (C
      (((:tangle . "no") 
	(:results . "none"))
       ((:tangle . "yes") 
	(:results . "raw"))))
     (julia
      (((:tangle . "no") 
	(:results . "none"))
       ((:tangle . "yes") 
	(:results . "none")))))))
#+end_src

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 800 bytes --]

             reply	other threads:[~2018-02-28 11:03 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-02-28 10:59 Akater [this message]
2018-03-03  0:37 ` Feature suggestion and code review request: org-babel-cycle-src-block-header John Kitchin
2018-03-03 14:26   ` Akater
2018-03-03 19:52   ` Thorsten Jolitz
2018-03-03 20:26     ` Thorsten Jolitz
2018-03-04 23:09       ` John Kitchin
2018-03-05  0:21         ` Thorsten Jolitz
2018-03-05  4:12           ` John Kitchin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87muztqt1b.fsf@gmail.com \
    --to=nuclearspace@gmail.com \
    --cc=emacs-orgmode@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).