From 1b81d6e07bc5ac78fe68674c0269c7dacd6b543f Mon Sep 17 00:00:00 2001 From: stardiviner Date: Thu, 31 May 2018 23:07:47 +0200 Subject: [PATCH] ob-eshell.el: Add Eshell support for Babel * lisp/ob-eshell.el (org-babel-execute:eshell): Execute Eshell code in Babel. (org-babel-prep-session:eshell): (ob-eshell-session-live-p): (org-babel-eshell-initiate-session): (org-babel-variable-assignments:eshell): (org-babel-load-session:eshell): * testing/test-ob-eshell.el: Write test for ob-eshell. * doc/org-manual.org (Languages): Add Babel language eshell identity. --- doc/org-manual.org | 46 +++++++-------- lisp/ob-eshell.el | 105 +++++++++++++++++++++++++++++++++ testing/lisp/test-ob-eshell.el | 73 +++++++++++++++++++++++ 3 files changed, 201 insertions(+), 23 deletions(-) create mode 100644 lisp/ob-eshell.el create mode 100644 testing/lisp/test-ob-eshell.el diff --git a/doc/org-manual.org b/doc/org-manual.org index 27131c805..535aa9d7f 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -17600,29 +17600,29 @@ code block header arguments: Code blocks in the following languages are supported. #+attr_texinfo: :columns 0.20 0.35 0.20 0.20 -| Language | Identifier | Language | Identifier | -|------------+---------------+---------------+--------------| -| Asymptote | =asymptote= | Lua | =lua= | -| Awk | =awk= | MATLAB | =matlab= | -| C | =C= | Mscgen | =mscgen= | -| C++ | =C++=[fn:139] | OCaml | =ocaml= | -| Clojure | =clojure= | Octave | =octave= | -| CSS | =css= | Org mode | =org= | -| D | =D=[fn:140] | Oz | =oz= | -| ditaa | =ditaa= | Perl | =perl= | -| Emacs Calc | =calc= | Plantuml | =plantuml= | -| Emacs Lisp | =emacs-lisp= | Processing.js | =processing= | -| Fortran | =fortran= | Python | =python= | -| Gnuplot | =gnuplot= | R | =R= | -| GNU Screen | =screen= | Ruby | =ruby= | -| Graphviz | =dot= | Sass | =sass= | -| Haskell | =haskell= | Scheme | =scheme= | -| Java | =java= | Sed | =sed= | -| Javascript | =js= | shell | =sh= | -| LaTeX | =latex= | SQL | =sql= | -| Ledger | =ledger= | SQLite | =sqlite= | -| Lilypond | =lilypond= | Vala | =vala= | -| Lisp | =lisp= | | | +| Language | Identifier | Language | Identifier | +|------------+---------------+----------------+--------------| +| Asymptote | =asymptote= | Lisp | =lisp= | +| Awk | =awk= | Lua | =lua= | +| C | =C= | MATLAB | =matlab= | +| C++ | =C++=[fn:135] | Mscgen | =mscgen= | +| Clojure | =clojure= | Objective Caml | =ocaml= | +| CSS | =css= | Octave | =octave= | +| D | =D=[fn:136] | Org mode | =org= | +| ditaa | =ditaa= | Oz | =oz= | +| Emacs Calc | =calc= | Perl | =perl= | +| Emacs Lisp | =emacs-lisp= | Plantuml | =plantuml= | +| Eshell | =eshell= | Processing.js | =processing= | +| Fortran | =fortran= | Python | =python= | +| Gnuplot | =gnuplot= | R | =R= | +| GNU Screen | =screen= | Ruby | =ruby= | +| Graphviz | =dot= | Sass | =sass= | +| Haskell | =haskell= | Scheme | =scheme= | +| Java | =java= | Sed | =sed= | +| Javascript | =js= | shell | =sh= | +| LaTeX | =latex= | SQL | =sql= | +| Ledger | =ledger= | SQLite | =sqlite= | +| Lilypond | =lilypond= | Vala | =vala= | Additional documentation for some languages is at https://orgmode.org/worg/org-contrib/babel/languages.html. diff --git a/lisp/ob-eshell.el b/lisp/ob-eshell.el new file mode 100644 index 000000000..45eb33855 --- /dev/null +++ b/lisp/ob-eshell.el @@ -0,0 +1,105 @@ +;;; ob-eshell.el --- Babel Functions for Eshell -*- lexical-binding: t; -*- + +;; Copyright (C) 2018 Free Software Foundation, Inc. + +;; Author: stardiviner +;; Keywords: literate programming, reproducible research +;; Homepage: https://orgmode.org + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Commentary: + +;; Org Babel support for evaluating Eshell source code. + +;;; Code: +(require 'ob) +(require 'eshell) + +(defvar org-babel-default-header-args:eshell '()) + +(defun org-babel-execute:eshell (body params) + "Execute a block of Eshell code BODY with PARAMS. +This function is called by `org-babel-execute-src-block'. + +The BODY can be any code which allowed executed in Eshell. +Eshell allow to execute normal shell command and Elisp code. +More details please reference Eshell Info. + +The PARAMS are variables assignments." + (let* ((session (org-babel-eshell-initiate-session + (cdr (assq :session params)))) + (full-body (org-babel-expand-body:generic + body params (org-babel-variable-assignments:eshell params)))) + (if session + (progn + (with-current-buffer session + (dolist (line (split-string full-body "\n")) + (goto-char eshell-last-output-end) + (insert line) + (eshell-send-input)) + ;; get output of last input + ;; TODO: collect all output instead of last command's output. + (goto-char eshell-last-input-end) + (buffer-substring-no-properties (point) eshell-last-output-start))) + (with-temp-buffer + (eshell-command full-body t) + (buffer-string))))) + +(defun org-babel-prep-session:eshell (session params) + "Prepare SESSION according to the header arguments specified in PARAMS." + (let* ((session (org-babel-eshell-initiate-session session)) + ;; Eshell session buffer is read from variable `eshell-buffer-name'. + (eshell-buffer-name session) + (var-lines (org-babel-variable-assignments:eshell params))) + (call-interactively #'eshell) + (mapc #'eshell-command var-lines) + session)) + +(defun ob-eshell-session-live-p (session) + "Non-nil if Eshell SESSION exists." + (get-buffer session)) + +(defun org-babel-eshell-initiate-session (&optional session params) + "Initiate a session named SESSION according to PARAMS." + (when (and session (not (string= session "none"))) + (save-window-excursion + (or (ob-eshell-session-live-p session) + (progn + (let ((eshell-buffer-name session)) + (eshell)) + (get-buffer (current-buffer))))) + session)) + +(defun org-babel-variable-assignments:eshell (params) + "Convert ob-eshell :var specified variables into Eshell variables assignments." + (mapcar + (lambda (pair) + (format "(setq %s %S)" (car pair) (cdr pair))) + (org-babel--get-vars params))) + +(defun org-babel-load-session:eshell (session body params) + "Load BODY into SESSION with PARAMS." + (save-window-excursion + (let ((buffer (org-babel-prep-session:eshell session params))) + (with-current-buffer buffer + (goto-char (point-max)) + (insert (org-babel-chomp body))) + buffer))) + +(provide 'ob-eshell) + +;;; ob-eshell.el ends here diff --git a/testing/lisp/test-ob-eshell.el b/testing/lisp/test-ob-eshell.el new file mode 100644 index 000000000..5b0eb27c1 --- /dev/null +++ b/testing/lisp/test-ob-eshell.el @@ -0,0 +1,73 @@ +;;; test-ob-eshell.el + +;; Copyright (c) 2018 stardiviner +;; Authors: stardiviner + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Comment: + +;; Template test file for Org tests + +;;; Code: +(unless (featurep 'ob-eshell) + (signal 'missing-test-dependency "Support for Eshell code blocks")) + +(ert-deftest ob-eshell/execute () + "Test ob-eshell execute." + (should + (string= + (org-test-with-temp-text + "#+begin_src eshell +echo 2 +#+end_src" + (org-babel-execute-src-block)) + ": 2"))) + +(ert-deftest ob-eshell/variables-assignment () + "Test ob-eshell variables assignment." + (should + (string= + (org-test-with-temp-text + "#+begin_src eshell :var hi=\"hello, world\" +echo $hi +#+end_src" + (org-babel-execute-src-block)) + ": hello, world"))) + +(ert-deftest ob-eshell/session () + "Test ob-eshell session." + (should + (string= + (org-test-with-temp-text + "#+begin_src eshell :session +(setq hi \"hello, world\") +#+end_src + +#+begin_src eshell :session +echo $hi +#+end_src" + (org-babel-execute-src-block) + (org-babel-next-src-block) + (org-babel-execute-src-block) + (goto-char (org-babel-where-is-src-block-result)) + (forward-line) + (buffer-substring-no-properties (point) (line-end-position))) + ": hello, world"))) + +(provide 'test-ob-eshell) + +;;; test-ob-eshell.el ends here