emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* Babel partial support for SAS language
@ 2017-10-11 15:10 Pierre-André Cornillon
  0 siblings, 0 replies; only message in thread
From: Pierre-André Cornillon @ 2017-10-11 15:10 UTC (permalink / raw)
  To: Org Mode List

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

Dear orgmode users

as the best way to submit a Babel support for a new language is this
mailing list, I submit this partial support for SAS
language (using ESS).

This is partial as only few header arguments are supported
:results output
:output graphics
:session
arguments are supported but one of the most important, :output value, is
not supported (as I am not able to figure out what does it really mean
with SAS).

I am not proud of it but I have added a
:results odsgraphics
for odsgraphics (which are different from sas/graph classical graphics).


If someone is interested in that support, please see the attached file
(as I don't actually know anything about the orgmode mailing list
policy about attached file, I hope it will be still attached...)

Comments and suggestions are welcome.

Best regards
Pierre-Andre

[-- Attachment #2: ob-sas.el --]
[-- Type: text/x-emacs-lisp, Size: 13345 bytes --]

;;; ob-sas.el --- org-babel functions for sas code evaluation

;; Copyright (C) 2017 P.A. Cornillon
;; Author: P.A. Cornillon
;;      G. Jay Kerns
;;      Eric Schulte
;;      Dan Davison


;; 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, 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 GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;; The file provides Org-Babel support for evaluating sas code.  It is
;; basically result of find-and-replace "sas" for "julia" in
;; ob-julia.el by G. Jay Kerns.
;; 1) Parameter ":results output" needs a
;; to give the right filename for SAS in the  Parameter ":results value"
;; does not work (I'm not sure it makes sense or is useful).  Parameter
;; ":session" works with default value using ESS
;; 

;;; Requirements:
;; Sas: http://sas.com
;; ESS: http://ess.r-project.org

;;; Code:
(require 'ob)
(require 'cl-lib)

(declare-function orgtbl-to-csv "org-table" (table params))
(declare-function sas "ext:ess-sas" (&optional start-args))
(declare-function inferior-ess-send-string "ext:ess-inf" ())
(declare-function ess-make-buffer-current "ext:ess-inf" ())
(declare-function ess-eval-buffer "ext:ess-inf" (vis))
(declare-function org-number-sequence "org-compat" (from &optional to inc))
;;;;;;;;;;;;;;;; could be useful to increase or decrease timeout ....
(defcustom org-babel-sas-timeout 1000
  "Timeout (in sec) used when waiting output from a submitted src block (to sas) with argument :session."
  :group 'org-babel
  :type 'integer)
;;;;;;;;;;;;;;;; could be useful to tweak printing page size
(defcustom org-babel-sas-print-options "options formdlim='' pagesize=max;\n"
  "general options used to have the maximum page size"
  :group 'org-babel
  :type 'string)

;;;;;;;;;;;;;;;; where is SAS
(defcustom org-babel-sas-command "/usr/local/bin/sas_u8"
;  inferior-SAS-program-name
  "Name of command to use for executing sas code."
  :group 'org-babel
  :type 'string)

;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar org-babel-sas-lepoint 1)
(defvar org-babel-sas-logfile-name "log_sas.log")
(defvar org-babel-sas-eoe-indicator "data eoe_org_data;\n nbabelvareoe=1;\nrun;\nOPTIONS NODATE NONUMBER;\nTITLE1;\nTITLE2;\nproc print data=eoe_org_data;\nrun;")
(defvar org-babel-sas-eoe-output "Obs.    nbabelvareoe\n\n                                1           1")
(defvar org-babel-sas-boe-output "$ tty\n/dev/pts/[0-9]+\n\\$")
(defconst org-babel-header-args:sas
  '((hsize		 . :any)
    (vsize		 . :any)
    (xpixels		 . :any)
    (ypixels		 . :any)
    (border		 . :any)
    (width		 . :any)
    (height		 . :any)
    (results             . ((file list vector table scalar verbatim)
			    (raw org html latex code pp wrap)
			    (replace silent append prepend)
                            ;; NOTE: not sure 'value' makes sense in sas
                            ;; we may want to remove it from the list
			    (output graphics))))
  "sas-specific header arguments.")

(add-to-list 'org-babel-tangle-lang-exts '("sas" . "sas"))

;; session using ESS is the way to go, so make that the default
(defvar org-babel-default-header-args:sas '((:results . "output") (:session . nil)))

;; trim white space and garbage
(defun org-babel-sas-trim-white (s)
  "replace S by empty string if S is whitespace only"
  (if (string-match "\\`[ \t\n\r]+\\'" s)
      (replace-match "" t t s)
    s))
;; let's go: main function
(defun org-babel-execute:sas (body params)
  "Execute a block of sas code.
This function is called by `org-babel-execute-src-block'."
  (save-excursion
    (let* ((result-params (cdr (assq :result-params params)))
	   (result-type (cdr (assq :result-type params)))
          (session (org-babel-sas-initiate-session
		    (cdr (assq :session params)) params))
	  (graphics-file (org-babel-sas-graphical-output-file params))
	  (graphics-type (or (member "odsgraphics" (cdr (assq :result-params params))) (member "graphics" (cdr (assq :result-params params)))))
	  (full-body (org-babel-expand-body:sas body params graphics-file graphics-type))
	  (result
	   (org-babel-sas-evaluate
	    session full-body result-type result-params)))
      (if graphics-file nil result))))

(defvar ess-ask-for-ess-directory) ; dynamically scoped

(defun org-babel-sas-initiate-session (session params)
  "If there is not a current sas process then create one."
  (unless (string= session "none")
    (let ((session (or session "*SAS*"))
	  (ess-ask-for-ess-directory
	   (and (and (boundp 'ess-ask-for-ess-directory) ess-ask-for-ess-directory)
		(not (cdr (assq :dir params))))))
      (if (org-babel-comint-buffer-livep session)
	  session
	(save-window-excursion
	  (require 'ess) (SAS)
	  (rename-buffer
	   (if (bufferp session)
	       (buffer-name session)
	     (if (stringp session)
		 session
	       (buffer-name))))
	  (current-buffer))))))

(defun org-babel-sas-associate-session (session)
  "Associate sas code buffer with a sas session.
Make SESSION be the inferior ESS process associated with the
current code buffer."
  (setq ess-local-process-name
	(process-name (get-buffer-process session)))
  (ess-make-buffer-current))

(defun org-babel-load-session:sas (session body params)
  "Load BODY into SESSION."
  (save-window-excursion
    (let ((buffer (org-babel-prep-session:sas session params)))
      (with-current-buffer buffer
        (goto-char (process-mark (get-buffer-process (current-buffer))))
        (insert (org-babel-chomp body)))
      buffer)))

(defun org-babel-sas-graphical-output-file (params)
  "Name of file to which sas should send graphical output."
  (and (or (member "graphics" (cdr (assq :result-params params)))
	   (member "odsgraphics" (cdr (assq :result-params params))))
       (cdr (assq :file params))))

(defun org-babel-expand-body:sas (body params &optional graphics-file graphics-type)
  "Expand BODY according to PARAMS, return the expanded body."
  (let ((graphics-file
	 (or graphics-file
	     (org-babel-sas-graphical-output-file params)))
	(graphics-type
	 (or graphics-type
	     (or (member "odsgraphics" (cdr (assq :result-params params)))
		 (member "graphics" (cdr (assq :result-params params)))))))
    (concat org-babel-sas-print-options
     (if graphics-file
	   (org-babel-sas-construct-graphics-device-call
	    graphics-file graphics-type params)
       "") body (if graphics-file
		    (if (string-equal (car graphics-type) "odsgraphics")
			"quit;\nods graphics off;\n"
		      "quit;\n")))))

(defvar org-babel-sas-graphics-devices
  '((:bmp "bmp")
    (:emf "emf")
    (:tiff "tiff")
    (:png "png")
    (:png300 "png300")
    (:svg "svg")
    (:pdf "pdf")
    (:ps "pscolor")
    (:postscript "pscolor"))
  "An alist mapping graphics file types to SAS devices.

Each member of this list is a list with three members:
1. the file extension of the graphics file, as an elisp :keyword
2. the SAS device function to call to generate such a file")

;; we need the following twolines with sas/graph :graphics
;; example of svg device
;; filename sortie "toto.svg";
;; goptions  device=svg gsfname=sortie
;; or this line with ODS graphics :odsgraphics
;; ods graphics on /  imagefmt=png imagename="barplot" border=off width=10cm;
(defun org-babel-sas-construct-graphics-device-call (out-file graphics-type params)
  "Construct the string for choosing device and saving graphic file"
  (let* ((allowed-args '(:hsize :vsize :xpixels :ypixels :border :width :height))
	 (device (file-name-extension out-file))
	 (device-info (or (assq (intern (concat ":" device))
				org-babel-sas-graphics-devices)
                          (assq :png org-babel-sas-graphics-devices)))
	 (extra-args (cdr (assq :SAS-dev-args params))) filearg args)
    (setq device (nth 1 device-info))
    (setq args (mapconcat
		(lambda (pair)
		  (if (member (car pair) allowed-args)
		      (format " %s=%S"
			      (substring (symbol-name (car pair)) 1)
			      (cdr pair)) ""))
		params ""))
    (if (string-equal (car graphics-type) "odsgraphics")
	(format "ods graphics on / imagename=\"%s\" imagefmt=%s %s;\n"
		(file-name-sans-extension out-file) device args
		(if extra-args " " "") (or extra-args ""))
      (format "filename outfob \"%s\";\ngoptions  device=%s gsfname= outfob %s;\n"
	    out-file device args
	    (if extra-args " " "") (or extra-args "")))))


(defun org-babel-sas-evaluate
  (session body result-type result-params)
  "Evaluate sas code in BODY."
  (if session
      (org-babel-sas-evaluate-session
       session body result-type result-params)
    (org-babel-sas-evaluate-external-process
     body result-type result-params)))

(defun org-babel-sas-evaluate-external-process
  (body result-type result-params)
  "Evaluate BODY in external sas process.
If RESULT-TYPE equals 'output then return standard output as a
string.  If RESULT-TYPE equals 'value then return the value of the
last statement in BODY, as elisp."
  (cl-case result-type
    (value
     (message "%s" "no ':results value' in SAS, please use ':results output' instead"))
    (output
     ;; org-babel-eval does pass external argument...
     (let ((tmp-file "sas-file4677846547.sas")
	   (directory-sas ""))
       (with-current-buffer
	   (switch-to-buffer (find-file-noselect tmp-file))
	 (insert body)
	 (save-buffer 0))
       (message "options %s" (format "%s -log %s -nonews -nodms %s" org-babel-sas-command org-babel-sas-logfile-name tmp-file))
       (shell-command (format "%s -log %s -nonews -nodms %s" org-babel-sas-command org-babel-sas-logfile-name tmp-file) nil nil)
       (kill-buffer "sas-file4677846547.sas")
       (delete-file "sas-file4677846547.sas")
       (setq directory-sas (file-name-directory (buffer-file-name (get-buffer org-babel-sas-logfile-name))))
       (message "directory: %s" directory-sas)
       (if (file-readable-p "sas-file4677846547.lst")
	   (progn
	     (with-current-buffer
		 (switch-to-buffer (find-file-noselect "sas-file4677846547.lst"))
	       (beginning-of-buffer)
	       (setq body (buffer-string)))
	     (delete-file "sas-file4677846547.lst")
	     (kill-buffer "sas-file4677846547.lst")
	     body)
	 (progn
	   (if (get-buffer org-babel-sas-logfile-name)
	       (with-current-buffer (get-buffer org-babel-sas-logfile-name)
		 (revert-buffer :ignore-auto :noconfirm :preserve-modes))
	     (save-window-excursion (pop-to-buffer-same-window (find-file-noselect org-babel-sas-logfile-name))))
	   (format "Errors, please see [[file://%s/%s][log file]] (in Buffer list)" directory-sas org-babel-sas-logfile-name)))))))

(defun org-babel-sas-evaluate-session
    (session body result-type result-params)
  "Evaluate BODY in SESSION.
If RESULT-TYPE equals 'output then return standard output as a
string.  If RESULT-TYPE equals 'value then return the value of the
last statement in BODY, as elisp."
  (cl-case result-type
    (value
     (message "%s" "no ':results value' in SAS, please use ':results output' instead"))
    (output
     ;; submit body through a temp buffer (in order to not go
     ;; beyond the limit of 500 bytes)
     ;; see 
     ;; https://stat.ethz.ch/pipermail/ess-help/2015-April/010518.html
    (let ((org-babel-sas-ess-process-name  (process-name (get-buffer-process session))))
      (with-temp-buffer
	(insert body)
	(let ((ess-local-process-name
	       (process-name (get-buffer-process session)))
	      (ess-eval-visibly-p nil))
	  (ess-eval-buffer nil)))
      (ess-send-string (get-process org-babel-sas-ess-process-name) org-babel-sas-eoe-indicator)
      ;;    excursion for cut/paste results from output buffer
      ;;   as output buffer is not the same as session buffer
      ;; org-babel-comint-with-output cannot be used 
      (save-excursion
      	(set-buffer (format "*%s.lst*" org-babel-sas-ess-process-name))
      	(let* ((a 0) (b 0) (ancienpoint org-babel-sas-lepoint))
      	 (while (< a org-babel-sas-timeout)
      	   (setq b a)
      	   (goto-char org-babel-sas-lepoint)
      	   (setq a (re-search-forward (regexp-quote org-babel-sas-eoe-output) nil t))
      	   (if a
      	       (progn (setq a org-babel-sas-timeout)
      	 	      (goto-char org-babel-sas-lepoint)
      	 	      (setq ancienpoint org-babel-sas-lepoint)
		      ;; well well, this is embarassing but
		      ;; as there's not history like in comint
		      ;; the last point is saved in this global
		      ;; variable (that will be used the
		      ;; next time)
      	 	      (setf org-babel-sas-lepoint (point-max)))
      	     (setq a (+ b 1)))
      	   (sit-for 0.01))
       	 (org-babel-sas-trim-white (replace-regexp-in-string (concat "\\(\f\\)\\|\\(" org-babel-sas-boe-output "\\)\\|\\(" org-babel-sas-eoe-output "\\)") "" (buffer-substring ancienpoint org-babel-sas-lepoint)))))))))

(provide 'ob-sas)

;;; ob-sas.el ends here

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2017-10-11 15:11 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-10-11 15:10 Babel partial support for SAS language Pierre-André Cornillon

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).