* 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 external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.