Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Porting ob-julia for org 9.2.5 and Julia 1.1.1 #16

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.juliahistory
*~

/ob-julia.elc
207 changes: 127 additions & 80 deletions ob-julia.el
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@
;; Org-Babel support for evaluating julia code

;;; Code:
(require 'cl-lib)
(require 'ob)
(eval-when-compile (require 'cl))

(declare-function orgtbl-to-csv "org-table" (table params))
(declare-function julia "ext:ess-julia" (&optional start-args))
(declare-function inferior-ess-send-input "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))
(declare-function org-remove-if-not "org" (predicate seq))
(declare-function ess-wait-for-process "ext:ess-inf"
(&optional proc sec-prompt wait force-redisplay))

(defconst org-babel-header-args:julia
(defvar org-babel-header-args:julia
'((width . :any)
(horizontal . :any)
(results . ((file list vector table scalar verbatim)
Expand All @@ -30,55 +30,68 @@

(defvar org-babel-default-header-args:julia '())

(defcustom org-babel-julia-command inferior-julia-program-name
(defcustom org-babel-julia-command "julia"
"Name of command to use for executing julia code."
:group 'org-babel
:version "24.3"
:type 'string)

(defvar ess-current-process-name) ; dynamically scoped
(defvar ess-local-process-name) ; dynamically scoped
(defun org-babel-edit-prep:julia (info)
(let ((session (cdr (assoc :session (nth 2 info)))))
(when (and session (string-match "^\\*\\(.+?\\)\\*$" session))
(save-match-data (org-babel-julia-initiate-session session nil)))))
(let ((session (cdr (assq :session (nth 2 info)))))
(when (and session
(string-prefix-p "*" session)
(string-suffix-p "*" session))
(org-babel-julia-initiate-session session nil))))

(defun org-babel-expand-body:julia (body params &optional graphics-file)
(defun org-babel-expand-body:julia (body params &optional _graphics-file)
"Expand BODY according to PARAMS, return the expanded body."
(let ((graphics-file
(or graphics-file (org-babel-julia-graphical-output-file params))))
(mapconcat
#'identity
((lambda (inside)
(if graphics-file
inside
inside)
)
(append (org-babel-variable-assignments:julia params)
(list body))) "\n")))
(mapconcat 'identity
(append
(when (cdr (assq :prologue params))
(list (cdr (assq :prologue params))))
(org-babel-variable-assignments:julia params)
(list body)
(when (cdr (assq :epilogue params))
(list (cdr (assq :epilogue params)))))
"\n"))

(defun org-babel-execute:julia (body params)
"Execute a block of julia code.
This function is called by `org-babel-execute-src-block'."
(save-excursion
(let* ((result-params (cdr (assoc :result-params params)))
(result-type (cdr (assoc :result-type params)))
(let* ((result-params (cdr (assq :result-params params)))
(result-type (cdr (assq :result-type params)))
(session (org-babel-julia-initiate-session
(cdr (assoc :session params)) params))
(colnames-p (cdr (assoc :colnames params)))
(rownames-p (cdr (assoc :rownames params)))
(graphics-file (org-babel-julia-graphical-output-file params))
(cdr (assq :session params)) params))
(graphics-file (and (member "graphics" (assq :result-params params))
(org-babel-graphical-output-file params)))
(colnames-p (unless graphics-file (cdr (assq :colnames params))))
(rownames-p (unless graphics-file (cdr (assq :rownames params))))
(full-body (org-babel-expand-body:julia body params graphics-file))
(result
(org-babel-julia-evaluate
session full-body result-type result-params
(or (equal "yes" colnames-p)
(org-babel-pick-name
(cdr (assoc :colname-names params)) colnames-p))
(cdr (assq :colname-names params)) colnames-p))
(or (equal "yes" rownames-p)
(org-babel-pick-name
(cdr (assoc :rowname-names params)) rownames-p)))))
(cdr (assq :rowname-names params)) rownames-p)))))
(if graphics-file nil result))))

; Code from https://github.com/gjkerns/ob-julia/pull/14
; (if graphics-file nil (if (and result (sequencep result))
; (org-babel-normalize-newline result)
; result)))))

(defun org-babel-normalize-newline (result)
(replace-regexp-in-string
"\\(\n\r?\\)\\{2,\\}"
"\n"
result))

(defun org-babel-prep-session:julia (session params)
"Prepare SESSION according to the header arguments specified in PARAMS."
(let* ((session (org-babel-julia-initiate-session session params))
Expand All @@ -102,23 +115,21 @@ This function is called by `org-babel-execute-src-block'."

(defun org-babel-variable-assignments:julia (params)
"Return list of julia statements assigning the block's variables."
(let ((vars (if (fboundp 'org-babel-get-header)
(mapcar #'cdr (org-babel-get-header params :var))
(mapcar #'cdr (org-babel--get-vars params)))))
(let ((vars (org-babel--get-vars params)))
(mapcar
(lambda (pair)
(org-babel-julia-assign-elisp
(car pair) (cdr pair)
(equal "yes" (cdr (assoc :colnames params)))
(equal "yes" (cdr (assoc :rownames params)))))
(equal "yes" (cdr (assq :colnames params)))
(equal "yes" (cdr (assq :rownames params)))))
(mapcar
(lambda (i)
(cons (car (nth i vars))
(org-babel-reassemble-table
(cdr (nth i vars))
(cdr (nth i (cdr (assoc :colname-names params))))
(cdr (nth i (cdr (assoc :rowname-names params)))))))
(org-number-sequence 0 (1- (length vars)))))))
(cdr (nth i (cdr (assq :colname-names params))))
(cdr (nth i (cdr (assq :rowname-names params)))))))
(number-sequence 0 (1- (length vars)))))))

(defun org-babel-julia-quote-csv-field (s)
"Quote field S for export to julia."
Expand All @@ -129,40 +140,43 @@ This function is called by `org-babel-execute-src-block'."
(defun org-babel-julia-assign-elisp (name value colnames-p rownames-p)
"Construct julia code assigning the elisp VALUE to a variable named NAME."
(if (listp value)
(let ((max (apply #'max (mapcar #'length (org-remove-if-not
#'sequencep value))))
(min (apply #'min (mapcar #'length (org-remove-if-not
#'sequencep value))))
(transition-file (org-babel-temp-file "julia-import-")))
;; ensure VALUE has an orgtbl structure (depth of at least 2)
(let* ((lengths (mapcar 'length (cl-remove-if-not 'sequencep value)))
(max (if lengths (apply 'max lengths) 0))
(min (if lengths (apply 'min lengths) 0)))
;; Ensure VALUE has an orgtbl structure (depth of at least 2).
(unless (listp (car value)) (setq value (list value)))
(with-temp-file transition-file
(insert
(orgtbl-to-csv value '(:fmt org-babel-julia-quote-csv-field))
"\n"))
(let ((file (org-babel-process-file-name transition-file 'noquote))
(header (if (or (eq (nth 1 value) 'hline) colnames-p)
"TRUE" "FALSE"))
(row-names (if rownames-p "1" "NULL")))
(if (= max min)
(format "%s = readcsv(\"%s\")" name file)
(format "%s = readcsv(\"%s\")"
name file))))
(let ((file (orgtbl-to-tsv value '(:fmt org-babel-julia-quote-tsv-field)))
(header (if (or (eq (nth 1 value) 'hline) colnames-p)
"TRUE" "FALSE"))
(row-names (if rownames-p "1" "NULL")))
(if (= max min)
(format "%s = begin
using CSV
CSV.read(\"%s\")
end" name file)
(format "%s = begin
using CSV
CSV.read(\"%s\")
end"
name file))))
(format "%s = %s" name (org-babel-julia-quote-csv-field value))))

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

(defun org-babel-julia-initiate-session (session params)
"If there is not a current julia process then create one."
(unless (string= session "none")
(let ((session (or session "*julia*"))
(let ((session (or session "*Julia*"))
(ess-ask-for-ess-directory
(and (and (boundp 'ess-ask-for-ess-directory) ess-ask-for-ess-directory)
(not (cdr (assoc :dir params))))))
(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) (julia)
(when (get-buffer session)
;; Session buffer exists, but with dead process
(set-buffer session))
(require 'ess) (set-buffer (julia))
(rename-buffer
(if (bufferp session)
(buffer-name session)
Expand All @@ -171,27 +185,54 @@ This function is called by `org-babel-execute-src-block'."
(buffer-name))))
(current-buffer))))))

(defun org-babel-julia-associate-session (session)
"Associate julia code buffer with a julia 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-julia-associate-session (session)
; "Associate julia code buffer with a julia 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-julia-graphical-output-file (params)
"Name of file to which julia should send graphical output."
(and (member "graphics" (cdr (assq :result-params params)))
(cdr (assq :file params))))

(defvar org-babel-julia-eoe-indicator "print(\"org_babel_julia_eoe\")")
(defvar org-babel-julia-eoe-output "org_babel_julia_eoe")
(defconst org-babel-julia-eoe-indicator "print(\"org_babel_julia_eoe\")")
(defconst org-babel-julia-eoe-output "org_babel_julia_eoe")

(defconst org-babel-julia-write-object-command "begin
local p_ans = %s
local p_tmp_file = \"%s\"

try
using CSV, DataFrames

if typeof(p_ans) <: DataFrame
p_ans_df = p_ans
else
p_ans_df = DataFrame(:ans => p_ans)
end

(defvar org-babel-julia-write-object-command "writecsv(\"%s\",%s)")
CSV.write(p_tmp_file,
p_ans_df,
writeheader = %s,
transform = (col, val) -> something(val, missing),
missingstring = \"nil\",
quotestrings = false)
p_ans
catch e
err_msg = \"Source block evaluation failed. $e\"
CSV.write(p_tmp_file,
DataFrame(:ans => err_msg),
writeheader = false,
transform = (col, val) -> something(val, missing),
missingstring = \"nil\",
quotestrings = false)

;; The following was a very complicated write object command
;; The replacement needs to add error catching
;(defvar org-babel-julia-write-object-command "{function(object,transfer.file){object;invisible(if(inherits(try({tfile<-tempfile();write.table(object,file=tfile,sep=\"\\t\",na=\"nil\",row.names=%s,col.names=%s,quote=FALSE);file.rename(tfile,transfer.file)},silent=TRUE),\"try-error\")){if(!file.exists(transfer.file))file.create(transfer.file)})}}(object=%s,transfer.file=\"%s\")")
err_msg
end
end")

(defun org-babel-julia-evaluate
(session body result-type result-params column-names-p row-names-p)
Expand All @@ -208,13 +249,15 @@ current code buffer."
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."
(case result-type
(cl-case result-type
(value
(let ((tmp-file (org-babel-temp-file "julia-")))
(org-babel-eval org-babel-julia-command
(format org-babel-julia-write-object-command
(format "begin %s end" body)
(org-babel-process-file-name tmp-file 'noquote)
(format "begin\n%s\nend" body)))
(if column-names-p "true" "false")
))
(org-babel-julia-process-value-result
(org-babel-result-cond result-params
(with-temp-buffer
Expand All @@ -230,7 +273,7 @@ last statement in BODY, as elisp."
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."
(case result-type
(cl-case result-type
(value
(with-temp-buffer
(insert (org-babel-chomp body))
Expand All @@ -242,7 +285,10 @@ last statement in BODY, as elisp."
(org-babel-comint-eval-invisibly-and-wait-for-file
session tmp-file
(format org-babel-julia-write-object-command
(org-babel-process-file-name tmp-file 'noquote) "ans"))
"ans"
(org-babel-process-file-name tmp-file 'noquote)
(if column-names-p "true" "false")
))
(org-babel-julia-process-value-result
(org-babel-result-cond result-params
(with-temp-buffer
Expand All @@ -252,22 +298,23 @@ last statement in BODY, as elisp."
column-names-p)))
(output
(mapconcat
#'org-babel-chomp
'org-babel-chomp
(butlast
(delq nil
(mapcar
(lambda (line) (when (> (length line) 0) line))
(mapcar
(lambda (line) ;; cleanup extra prompts left in output
(if (string-match
"^\\([ ]*[>+\\.][ ]?\\)+\\([[0-9]+\\|[ ]\\)" line)
"^\\([>+.]\\([ ][>.+]\\)*[ ]\\)"
(car (split-string line "\n")))
(substring line (match-end 1))
line))
(org-babel-comint-with-output (session org-babel-julia-eoe-output)
(insert (mapconcat #'org-babel-chomp
(insert (mapconcat 'org-babel-chomp
(list body org-babel-julia-eoe-indicator)
"\n"))
(inferior-ess-send-input)))))) "\n"))))
(inferior-ess-send-input)))))) "\n"))))

(defun org-babel-julia-process-value-result (result column-names-p)
"julia-specific processing of return value.
Expand Down