223 lines
6.4 KiB
EmacsLisp
223 lines
6.4 KiB
EmacsLisp
;;; ob-elixir.el --- Org Babel functions for Elixir -*- lexical-binding: t; -*-
|
|
|
|
;; Copyright (C) 2024 Your Name
|
|
|
|
;; Author: Your Name <your.email@example.com>
|
|
;; URL: https://github.com/username/ob-elixir
|
|
;; Keywords: literate programming, reproducible research, elixir
|
|
;; Version: 0.1.0
|
|
;; Package-Requires: ((emacs "27.1") (org "9.4"))
|
|
|
|
;; 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.
|
|
|
|
;;; Commentary:
|
|
|
|
;; Org Babel support for evaluating Elixir code blocks.
|
|
;;
|
|
;; Features:
|
|
;; - Execute Elixir code in org-mode source blocks
|
|
;; - Support for :results value and :results output
|
|
;; - Variable passing with :var header argument
|
|
;; - Mix project context support
|
|
;;
|
|
;; Usage:
|
|
;; Add (elixir . t) to `org-babel-load-languages':
|
|
;;
|
|
;; (org-babel-do-load-languages
|
|
;; 'org-babel-load-languages
|
|
;; '((elixir . t)))
|
|
|
|
;;; Code:
|
|
|
|
(require 'ob)
|
|
(require 'ob-eval)
|
|
|
|
;;; Customization
|
|
|
|
(defgroup ob-elixir nil
|
|
"Org Babel support for Elixir."
|
|
:group 'org-babel
|
|
:prefix "ob-elixir-")
|
|
|
|
(defcustom ob-elixir-command "elixir"
|
|
"Command to execute Elixir code.
|
|
Can be a full path or command name if in PATH."
|
|
:type 'string
|
|
:group 'ob-elixir
|
|
:safe #'stringp)
|
|
|
|
;;; Header Arguments
|
|
|
|
(defvar org-babel-default-header-args:elixir
|
|
'((:results . "value")
|
|
(:session . "none"))
|
|
"Default header arguments for Elixir code blocks.")
|
|
|
|
;;; Language Registration
|
|
|
|
;; File extension for tangling
|
|
(add-to-list 'org-babel-tangle-lang-exts '("elixir" . "ex"))
|
|
|
|
;; Associate with elixir-mode for syntax highlighting (if available)
|
|
(with-eval-after-load 'org-src
|
|
(add-to-list 'org-src-lang-modes '("elixir" . elixir)))
|
|
|
|
;;; Type Conversion
|
|
|
|
(defun ob-elixir--escape-string (str)
|
|
"Escape special characters in STR for Elixir string literal."
|
|
(let ((result str))
|
|
;; Escape backslashes first
|
|
(setq result (replace-regexp-in-string "\\\\" "\\\\\\\\" result))
|
|
;; Escape double quotes
|
|
(setq result (replace-regexp-in-string "\"" "\\\\\"" result))
|
|
;; Escape newlines
|
|
(setq result (replace-regexp-in-string "\n" "\\\\n" result))
|
|
;; Escape tabs
|
|
(setq result (replace-regexp-in-string "\t" "\\\\t" result))
|
|
result))
|
|
|
|
(defun ob-elixir--elisp-to-elixir (value)
|
|
"Convert Elisp VALUE to Elixir literal syntax.
|
|
|
|
Handles:
|
|
- nil -> nil
|
|
- t -> true
|
|
- numbers -> numbers
|
|
- strings -> quoted strings
|
|
- symbols -> atoms
|
|
- lists -> Elixir lists
|
|
- vectors -> tuples"
|
|
(cond
|
|
;; nil
|
|
((null value) "nil")
|
|
|
|
;; Boolean true
|
|
((eq value t) "true")
|
|
|
|
;; Numbers
|
|
((numberp value)
|
|
(number-to-string value))
|
|
|
|
;; Strings
|
|
((stringp value)
|
|
(format "\"%s\"" (ob-elixir--escape-string value)))
|
|
|
|
;; Symbols become atoms (except special ones)
|
|
((symbolp value)
|
|
(let ((name (symbol-name value)))
|
|
(cond
|
|
((string= name "hline") ":hline")
|
|
((string-match-p "^[a-z_][a-zA-Z0-9_]*[?!]?$" name)
|
|
(format ":%s" name))
|
|
(t (format ":\"%s\"" name)))))
|
|
|
|
;; Vectors become tuples
|
|
((vectorp value)
|
|
(format "{%s}"
|
|
(mapconcat #'ob-elixir--elisp-to-elixir
|
|
(append value nil) ", ")))
|
|
|
|
;; Lists
|
|
((listp value)
|
|
(format "[%s]"
|
|
(mapconcat #'ob-elixir--elisp-to-elixir value ", ")))
|
|
|
|
;; Fallback
|
|
(t (format "%S" value))))
|
|
|
|
;;; Variable Handling
|
|
|
|
(defun ob-elixir--var-name (name)
|
|
"Convert NAME to a valid Elixir variable name.
|
|
|
|
Elixir variables must start with lowercase or underscore."
|
|
(let ((str (if (symbolp name) (symbol-name name) name)))
|
|
;; Ensure starts with lowercase or underscore
|
|
(if (string-match-p "^[a-z_]" str)
|
|
str
|
|
(concat "_" str))))
|
|
|
|
(defun org-babel-variable-assignments:elixir (params)
|
|
"Return list of Elixir statements assigning variables from PARAMS.
|
|
|
|
Each statement has the form: var_name = value"
|
|
(mapcar
|
|
(lambda (pair)
|
|
(let ((name (car pair))
|
|
(value (cdr pair)))
|
|
(format "%s = %s"
|
|
(ob-elixir--var-name name)
|
|
(ob-elixir--elisp-to-elixir value))))
|
|
(org-babel--get-vars params)))
|
|
|
|
;;; Execution
|
|
|
|
(defconst ob-elixir--value-wrapper
|
|
"result = (
|
|
%s
|
|
)
|
|
IO.puts(inspect(result, limit: :infinity, printable_limit: :infinity, charlists: :as_lists))
|
|
"
|
|
"Wrapper template for capturing Elixir expression value.
|
|
%s is replaced with the user's code.")
|
|
|
|
(defun ob-elixir--wrap-for-value (body)
|
|
"Wrap BODY to capture its return value.
|
|
|
|
The wrapper evaluates BODY, then prints the result using
|
|
`inspect/2` with infinite limits to avoid truncation."
|
|
(format ob-elixir--value-wrapper body))
|
|
|
|
(defun ob-elixir--execute (body result-type)
|
|
"Execute BODY as Elixir code.
|
|
|
|
RESULT-TYPE is either `value' or `output'.
|
|
For `value', wraps code to capture return value.
|
|
For `output', captures stdout directly.
|
|
|
|
Returns the result as a string."
|
|
(let* ((tmp-file (org-babel-temp-file "ob-elixir-" ".exs"))
|
|
(code (if (eq result-type 'value)
|
|
(ob-elixir--wrap-for-value body)
|
|
body)))
|
|
(with-temp-file tmp-file
|
|
(insert code))
|
|
(let ((result (org-babel-eval
|
|
(format "%s %s"
|
|
ob-elixir-command
|
|
(org-babel-process-file-name tmp-file))
|
|
"")))
|
|
(string-trim result))))
|
|
|
|
(defun org-babel-execute:elixir (body params)
|
|
"Execute a block of Elixir code with org-babel.
|
|
|
|
BODY is the Elixir code to execute.
|
|
PARAMS is an alist of header arguments.
|
|
|
|
This function is called by `org-babel-execute-src-block'."
|
|
(let* ((result-type (cdr (assq :result-type params)))
|
|
(result-params (cdr (assq :result-params params)))
|
|
;; Expand body with variable assignments
|
|
(full-body (org-babel-expand-body:generic
|
|
body params
|
|
(org-babel-variable-assignments:elixir params)))
|
|
(result (ob-elixir--execute full-body result-type)))
|
|
(org-babel-reassemble-table
|
|
(org-babel-result-cond result-params
|
|
result
|
|
(org-babel-script-escape result))
|
|
(org-babel-pick-name (cdr (assq :colname-names params))
|
|
(cdr (assq :colnames params)))
|
|
(org-babel-pick-name (cdr (assq :rowname-names params))
|
|
(cdr (assq :rownames params))))))
|
|
|
|
(provide 'ob-elixir)
|
|
;;; ob-elixir.el ends here
|