gjkerns / ob-julia

36 stars 25 forks source link

WIP: Major rework. #15

Closed nico202 closed 5 years ago

nico202 commented 5 years ago

Please, let me know if it works for you (I mainly tested it with the :session param), julia 1.1.0, ess 17.11 and org-mode 9.2.3.

It's compatible with julia 1.1.0 and should be easy to add custom formatters for specific types.

To display dataframes: after 'using DataFrames' be sure to use the ':results raw' option.

You can pass table to julia like normal variables (:var x=table y=1 z="string"). If table have headers: The variable `org-babel-julia-table-as-dict' controls how to import tables (either as dicts or as namedtuple (default)). Then you can pass them to DataFrame to get the dataframe. else: They are imported as Matrix{Any,2}.

Plots are supported. Just use the :file output.{svg,png} option. FileIO is required for the :file parameter to work.

nico202 commented 5 years ago

Forgot to mention, for now the content of org-babel-julia-setup must be in the julia startup file (~/.julia/config/startup.jl). I'll see how to load it automatically later:

# org-mode
import Base.display, Base.show, Base.Multimedia.showable
struct OrgEmacs <: AbstractDisplay
    outfile::String
end
display(d::OrgEmacs, x) = display(d::OrgEmacs, MIME("text/org"), x)
function display(d::OrgEmacs, ::MIME"text/org", x)
    open(d.outfile, "w") do f
        show(f, MIME("text/org"), x)
    end
end
# Generic fallback
show(io::IO, ::MIME"text/org", i) = show(io, i)
# Overload types
show(io::IO, ::MIME"text/org", t::Tuple) = print(io, join(t, ','))
function show(io::IO, ::MIME"text/org", t::NamedTuple)
    print(io, join(string.(keys(t)), ','))
    println(io)
    print(io, join(t, ','))
end
show(io::IO, ::MIME"text/org", ::Nothing) = show(io, "")
show(io::IO, ::MIME"text/org", a::Array{T,1}) where T <: Any = print(io, join(a, ","))
function show(io::IO, ::MIME"text/org", i::Array{T,2}) where T <: Any
    out = eachrow(i) |> x -> join([join(l, ",") for l in x], "\n")
    print(io, out)
end

function org_reload()
    "Defines show method based on loaded packages"
    let pkg = :DataFrames
        if isdefined(Main, pkg) && isa(getfield(Main, pkg), Module)
            @eval function show(io::IO, ::MIME"text/org", d::DataFrame)
                out = '|' * join(string.(names(d)), '|')
                out *= "\n|" * repeat("-|", length(names(d))) * '\n'
                out *= join(['|' * join(x, '|') * '|' for x in eachrow(d) .|> collect], '\n')
                print(io, out)
            end
        end
    end
end

edit: added method for namedtuple and Array{T,1}

gjkerns commented 5 years ago

This is very cool, but I am not qualified to review these changes---I haven't used julia for some time now. Why don't you fork this and develop until your heart's content?

nico202 commented 5 years ago

Fine! Sorry for the noise then! :)

non-Jedi commented 5 years ago

If you decide to maintain a fork of this, please announce it on the julia discourse. I know there are several emacs+julia users who would be interested.

sebastianpech commented 5 years ago

There is also an emacs slack channel for julia. To be honest I completely stopped using ob-julia and switched to emacs-jupyter. It utilises all the output and autocompletion features that jupyter offers. Maybe have a look at that first, the package is of very high quality. I use it on a daily basis and have not had any problems so far.

nico202 commented 5 years ago

@non-Jedi Sure, I'll do @sebastianpech I don't like to have to setup jupyiter, that opens to vulnerabilities. Also if I'm not wrong there's the need to install the IJulia.jl package, so fiddling with the DEPOT_PATH is not something I could do easily anymore

sebastianpech commented 5 years ago

Yes of course. So this would be the lightweight solution then.

nnicandro commented 5 years ago

@nico202 any progress on forking? I would like to generalize the org-babel-julia-quote-csv-field function which I see you have already made some progress on. I was thinking of doing something like

this ```emacs-lisp (defun org-babel-julia-convert-value (val) "Quote field S for export to julia." (cond ((stringp val) ;; TODO: Escape quotes of val if it contains triple quotes (concat "\"\"\"" val "\"\"\"")) ;; TODO: false? ((null val) "nothing") ((eq val t) "true") ((symbolp val) (format ":%S" val)) ((consp val) (cond ;; 2-element tuple ((not (consp (cdr val))) (concat "(" (org-babel-julia-convert-value (car val)) ", " (org-babel-julia-convert-value (cdr val)) ")")) ;; Dict ((cl-every (lambda (x) (and (consp x) (or (not (consp (cdr x))) (= (length x) 2)))) val) (concat "Dict([" (mapconcat (lambda (x) (concat "(" (org-babel-julia-convert-value (car x)) "," (org-babel-julia-convert-value (if (consp (cdr x)) (cadr x) (cdr x))) ")") ) val ", ") "])")) ;; Matrix ((and (consp (car val)) (consp (cdar val)) (let ((len (length (car val)))) (cl-every (lambda (x) (and (consp x) (consp (cdr x)) (= (length x) len))) val))) (concat "[" (mapconcat (lambda (x) (mapconcat #'org-babel-julia-convert-value x " ")) val "\n") "]")) ;; Vector (t (concat "[" (mapconcat #'org-babel-julia-convert-value val ", ") "]")))) (t (format "%S" val)))) ```

Your approach of using the Display machinery makes sense. I think the next steps would be to figure out why ESS is needed, since I think comint can be used instead. We could probably just have both methods and let the user choose if they want to use ESS or not. Also we should probably setup a test framework to verify behavior.

I mainly use emacs-jupyter, but would like the org-babel-variable-assignments:julia function to work properly out of the box, so I would be willing to help maintain the project, e.g. reviewing changes and help setup the test framework, if you fork it. Then maybe we can go to the org mailing list and have them use the updated version once its stable enough.