docs and tasks
This commit is contained in:
644
docs/04-elixir-integration-strategies.md
Normal file
644
docs/04-elixir-integration-strategies.md
Normal file
@@ -0,0 +1,644 @@
|
||||
# Elixir Integration Strategies
|
||||
|
||||
This document covers strategies for integrating Elixir with Emacs and org-babel, including execution modes, data conversion, and Mix project support.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Execution Modes](#execution-modes)
|
||||
- [One-Shot Execution](#one-shot-execution)
|
||||
- [IEx Session Management](#iex-session-management)
|
||||
- [Mix Project Context](#mix-project-context)
|
||||
- [Remote Shell (remsh)](#remote-shell-remsh)
|
||||
- [Data Type Conversion](#data-type-conversion)
|
||||
- [Error Handling](#error-handling)
|
||||
- [elixir-mode Integration](#elixir-mode-integration)
|
||||
- [Performance Considerations](#performance-considerations)
|
||||
- [References](#references)
|
||||
|
||||
---
|
||||
|
||||
## Execution Modes
|
||||
|
||||
### Overview of Execution Strategies
|
||||
|
||||
| Mode | Command | Use Case | Startup Time | State |
|
||||
|--------------|-------------------|----------------|--------------|-------------|
|
||||
| One-shot | `elixir -e` | Simple scripts | ~500ms | None |
|
||||
| Script file | `elixir file.exs` | Larger code | ~500ms | None |
|
||||
| IEx session | `iex` | Interactive | Once | Persistent |
|
||||
| Mix context | `iex -S mix` | Projects | Slower | Project |
|
||||
| Remote shell | `iex --remsh` | Production | Fast | Remote node |
|
||||
|
||||
### Recommended Approach
|
||||
|
||||
For org-babel, we recommend:
|
||||
|
||||
1. **Default**: Script file execution (reliable, predictable)
|
||||
2. **With `:session`**: IEx via comint (persistent state)
|
||||
3. **With `:mix-project`**: Mix context execution
|
||||
|
||||
---
|
||||
|
||||
## One-Shot Execution
|
||||
|
||||
### Using `elixir -e`
|
||||
|
||||
For simple, single expressions:
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--eval-simple (code)
|
||||
"Evaluate CODE using elixir -e."
|
||||
(shell-command-to-string
|
||||
(format "elixir -e %s"
|
||||
(shell-quote-argument code))))
|
||||
```
|
||||
|
||||
**Pros**: Simple, no temp files
|
||||
**Cons**: Limited code size, quoting issues
|
||||
|
||||
### Using Script Files (Recommended)
|
||||
|
||||
More robust for complex code:
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--eval-script (code)
|
||||
"Evaluate CODE using a temporary script file."
|
||||
(let ((script-file (org-babel-temp-file "elixir-" ".exs")))
|
||||
(with-temp-file script-file
|
||||
(insert code))
|
||||
(org-babel-eval
|
||||
(format "elixir %s" (org-babel-process-file-name script-file))
|
||||
"")))
|
||||
```
|
||||
|
||||
### Capturing Return Values vs Output
|
||||
|
||||
```elisp
|
||||
;; For :results output - capture stdout
|
||||
(defun ob-elixir--eval-output (code)
|
||||
"Execute CODE and capture stdout."
|
||||
(ob-elixir--eval-script code))
|
||||
|
||||
;; For :results value - wrap to capture return value
|
||||
(defconst ob-elixir--value-wrapper
|
||||
"
|
||||
_result_ = (
|
||||
%s
|
||||
)
|
||||
IO.puts(inspect(_result_, limit: :infinity, printable_limit: :infinity, charlists: :as_lists))
|
||||
"
|
||||
"Wrapper to capture the return value of code.")
|
||||
|
||||
(defun ob-elixir--eval-value (code)
|
||||
"Execute CODE and return its value."
|
||||
(let ((wrapped (format ob-elixir--value-wrapper code)))
|
||||
(string-trim (ob-elixir--eval-script wrapped))))
|
||||
```
|
||||
|
||||
### Handling Multiline Output
|
||||
|
||||
```elisp
|
||||
(defconst ob-elixir--value-wrapper-with-marker
|
||||
"
|
||||
_result_ = (
|
||||
%s
|
||||
)
|
||||
IO.puts(\"__OB_ELIXIR_RESULT_START__\")
|
||||
IO.puts(inspect(_result_, limit: :infinity, printable_limit: :infinity))
|
||||
IO.puts(\"__OB_ELIXIR_RESULT_END__\")
|
||||
"
|
||||
"Wrapper with markers for reliable output parsing.")
|
||||
|
||||
(defun ob-elixir--extract-result (output)
|
||||
"Extract result from OUTPUT between markers."
|
||||
(when (string-match "__OB_ELIXIR_RESULT_START__\n\\(.*\\)\n__OB_ELIXIR_RESULT_END__"
|
||||
output)
|
||||
(match-string 1 output)))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## IEx Session Management
|
||||
|
||||
### Starting an IEx Session
|
||||
|
||||
```elisp
|
||||
(defvar ob-elixir-iex-buffer-name "*ob-elixir-iex*"
|
||||
"Buffer name for the IEx process.")
|
||||
|
||||
(defvar ob-elixir-prompt-regexp "^iex\\([0-9]+\\)> "
|
||||
"Regexp matching the IEx prompt.")
|
||||
|
||||
(defvar ob-elixir-continued-prompt-regexp "^\\.\\.\\.\\([0-9]+\\)> "
|
||||
"Regexp matching the IEx continuation prompt.")
|
||||
|
||||
(defun ob-elixir--start-iex-session (session-name &optional params)
|
||||
"Start an IEx session named SESSION-NAME."
|
||||
(let* ((buffer-name (format "*ob-elixir-%s*" session-name))
|
||||
(buffer (get-buffer-create buffer-name))
|
||||
(mix-project (when params (cdr (assq :mix-project params))))
|
||||
(iex-args (when params (cdr (assq :iex-args params)))))
|
||||
|
||||
(unless (comint-check-proc buffer)
|
||||
(with-current-buffer buffer
|
||||
;; Set up environment
|
||||
(setenv "TERM" "dumb")
|
||||
(setenv "IEX_WITH_WERL" nil)
|
||||
|
||||
;; Start the process
|
||||
(apply #'make-comint-in-buffer
|
||||
(format "ob-elixir-%s" session-name)
|
||||
buffer
|
||||
"iex"
|
||||
nil
|
||||
(append
|
||||
(when mix-project
|
||||
(list "-S" "mix"))
|
||||
(when iex-args
|
||||
(split-string iex-args))))
|
||||
|
||||
;; Wait for initial prompt
|
||||
(ob-elixir--wait-for-prompt buffer 10)
|
||||
|
||||
;; Configure IEx for programmatic use
|
||||
(ob-elixir--configure-iex-session buffer)))
|
||||
buffer))
|
||||
```
|
||||
|
||||
### Configuring IEx for Non-Interactive Use
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--configure-iex-session (buffer)
|
||||
"Configure IEx in BUFFER for non-interactive use."
|
||||
(with-current-buffer buffer
|
||||
(let ((config-commands
|
||||
'("IEx.configure(colors: [enabled: false])"
|
||||
"IEx.configure(inspect: [limit: :infinity, printable_limit: :infinity])"
|
||||
"Application.put_env(:elixir, :ansi_enabled, false)")))
|
||||
(dolist (cmd config-commands)
|
||||
(ob-elixir--send-to-iex buffer cmd)
|
||||
(ob-elixir--wait-for-prompt buffer 5)))))
|
||||
```
|
||||
|
||||
### Sending Code to IEx
|
||||
|
||||
```elisp
|
||||
(defvar ob-elixir-eoe-marker "__OB_ELIXIR_EOE__"
|
||||
"End-of-evaluation marker.")
|
||||
|
||||
(defun ob-elixir--send-to-iex (buffer code)
|
||||
"Send CODE to IEx process in BUFFER."
|
||||
(with-current-buffer buffer
|
||||
(goto-char (point-max))
|
||||
(insert code)
|
||||
(comint-send-input nil t)))
|
||||
|
||||
(defun ob-elixir--eval-in-session (session code)
|
||||
"Evaluate CODE in SESSION, return output."
|
||||
(let* ((buffer (ob-elixir--get-or-create-session session))
|
||||
(start-marker nil))
|
||||
(with-current-buffer buffer
|
||||
(goto-char (point-max))
|
||||
(setq start-marker (point-marker))
|
||||
|
||||
;; Send the code
|
||||
(ob-elixir--send-to-iex buffer code)
|
||||
(ob-elixir--wait-for-prompt buffer 30)
|
||||
|
||||
;; Send EOE marker to clearly delineate output
|
||||
(ob-elixir--send-to-iex buffer (format "\"%s\"" ob-elixir-eoe-marker))
|
||||
(ob-elixir--wait-for-prompt buffer 5)
|
||||
|
||||
;; Extract output
|
||||
(ob-elixir--extract-session-output buffer start-marker))))
|
||||
|
||||
(defun ob-elixir--wait-for-prompt (buffer timeout)
|
||||
"Wait for IEx prompt in BUFFER with TIMEOUT seconds."
|
||||
(with-current-buffer buffer
|
||||
(let ((end-time (+ (float-time) timeout)))
|
||||
(while (and (< (float-time) end-time)
|
||||
(not (ob-elixir--at-prompt-p)))
|
||||
(accept-process-output (get-buffer-process buffer) 0.1)
|
||||
(goto-char (point-max))))))
|
||||
|
||||
(defun ob-elixir--at-prompt-p ()
|
||||
"Return t if point is at an IEx prompt."
|
||||
(save-excursion
|
||||
(beginning-of-line)
|
||||
(looking-at ob-elixir-prompt-regexp)))
|
||||
```
|
||||
|
||||
### Cleaning Session Output
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--clean-iex-output (output)
|
||||
"Clean OUTPUT from IEx session."
|
||||
(let ((cleaned output))
|
||||
;; Remove ANSI escape codes
|
||||
(setq cleaned (ansi-color-filter-apply cleaned))
|
||||
;; Remove prompts
|
||||
(setq cleaned (replace-regexp-in-string
|
||||
"^iex([0-9]+)> " "" cleaned))
|
||||
(setq cleaned (replace-regexp-in-string
|
||||
"^\\.\\.\\.([0-9]+)> " "" cleaned))
|
||||
;; Remove EOE marker
|
||||
(setq cleaned (replace-regexp-in-string
|
||||
(regexp-quote (format "\"%s\"" ob-elixir-eoe-marker))
|
||||
"" cleaned))
|
||||
;; Remove trailing whitespace
|
||||
(string-trim cleaned)))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mix Project Context
|
||||
|
||||
### Detecting Mix Projects
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--find-mix-project (dir)
|
||||
"Find mix.exs file starting from DIR, searching up."
|
||||
(let ((mix-file (locate-dominating-file dir "mix.exs")))
|
||||
(when mix-file
|
||||
(file-name-directory mix-file))))
|
||||
|
||||
(defun ob-elixir--in-mix-project-p ()
|
||||
"Return t if current buffer is in a Mix project."
|
||||
(ob-elixir--find-mix-project default-directory))
|
||||
```
|
||||
|
||||
### Executing in Mix Context
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--eval-with-mix (code project-dir &optional mix-env)
|
||||
"Evaluate CODE in the context of Mix project at PROJECT-DIR."
|
||||
(let* ((default-directory project-dir)
|
||||
(script-file (org-babel-temp-file "elixir-" ".exs"))
|
||||
(env-vars (when mix-env
|
||||
(format "MIX_ENV=%s " mix-env))))
|
||||
(with-temp-file script-file
|
||||
(insert code))
|
||||
(shell-command-to-string
|
||||
(format "%smix run %s"
|
||||
(or env-vars "")
|
||||
(org-babel-process-file-name script-file)))))
|
||||
```
|
||||
|
||||
### Compiling Before Execution
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--ensure-compiled (project-dir)
|
||||
"Ensure Mix project at PROJECT-DIR is compiled."
|
||||
(let ((default-directory project-dir))
|
||||
(shell-command-to-string "mix compile --force-check")))
|
||||
|
||||
(defun ob-elixir--eval-with-compilation (code project-dir)
|
||||
"Compile and evaluate CODE in PROJECT-DIR."
|
||||
(ob-elixir--ensure-compiled project-dir)
|
||||
(ob-elixir--eval-with-mix code project-dir))
|
||||
```
|
||||
|
||||
### Using Mix Aliases
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--run-mix-task (task project-dir &optional args)
|
||||
"Run Mix TASK in PROJECT-DIR with ARGS."
|
||||
(let ((default-directory project-dir))
|
||||
(shell-command-to-string
|
||||
(format "mix %s %s" task (or args "")))))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Remote Shell (remsh)
|
||||
|
||||
### Connecting to Running Nodes
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--start-remsh-session (node-name &optional cookie sname)
|
||||
"Connect to remote Elixir node NODE-NAME.
|
||||
|
||||
COOKIE is the Erlang cookie (optional).
|
||||
SNAME is the short name for the local node."
|
||||
(let* ((local-name (or sname (format "ob_elixir_%d" (random 10000))))
|
||||
(buffer-name (format "*ob-elixir-remsh-%s*" node-name))
|
||||
(buffer (get-buffer-create buffer-name))
|
||||
(args (append
|
||||
(list "--sname" local-name)
|
||||
(when cookie (list "--cookie" cookie))
|
||||
(list "--remsh" node-name))))
|
||||
|
||||
(unless (comint-check-proc buffer)
|
||||
(with-current-buffer buffer
|
||||
(apply #'make-comint-in-buffer
|
||||
(format "ob-elixir-remsh-%s" node-name)
|
||||
buffer
|
||||
"iex"
|
||||
nil
|
||||
args)
|
||||
(ob-elixir--wait-for-prompt buffer 30)
|
||||
(ob-elixir--configure-iex-session buffer)))
|
||||
buffer))
|
||||
```
|
||||
|
||||
### Remote Evaluation
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--eval-remote (code node-name &optional cookie)
|
||||
"Evaluate CODE on remote NODE-NAME."
|
||||
(let ((session (ob-elixir--start-remsh-session node-name cookie)))
|
||||
(ob-elixir--eval-in-session session code)))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Type Conversion
|
||||
|
||||
### Elisp to Elixir
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--elisp-to-elixir (value)
|
||||
"Convert Elisp VALUE to Elixir literal syntax."
|
||||
(pcase value
|
||||
('nil "nil")
|
||||
('t "true")
|
||||
((pred numberp) (number-to-string value))
|
||||
((pred stringp) (ob-elixir--format-string value))
|
||||
((pred symbolp) (ob-elixir--format-atom value))
|
||||
((pred vectorp) (ob-elixir--format-tuple value))
|
||||
((pred listp)
|
||||
(cond
|
||||
((ob-elixir--alist-p value) (ob-elixir--format-keyword-list value))
|
||||
((ob-elixir--plist-p value) (ob-elixir--format-map value))
|
||||
(t (ob-elixir--format-list value))))
|
||||
(_ (format "%S" value))))
|
||||
|
||||
(defun ob-elixir--format-string (str)
|
||||
"Format STR as Elixir string."
|
||||
(format "\"%s\""
|
||||
(replace-regexp-in-string
|
||||
"\\\\" "\\\\\\\\"
|
||||
(replace-regexp-in-string
|
||||
"\"" "\\\\\""
|
||||
(replace-regexp-in-string
|
||||
"\n" "\\\\n"
|
||||
str)))))
|
||||
|
||||
(defun ob-elixir--format-atom (sym)
|
||||
"Format symbol SYM as Elixir atom."
|
||||
(let ((name (symbol-name sym)))
|
||||
(if (string-match-p "^[a-z_][a-zA-Z0-9_]*[?!]?$" name)
|
||||
(format ":%s" name)
|
||||
(format ":\"%s\"" name))))
|
||||
|
||||
(defun ob-elixir--format-list (lst)
|
||||
"Format LST as Elixir list."
|
||||
(format "[%s]"
|
||||
(mapconcat #'ob-elixir--elisp-to-elixir lst ", ")))
|
||||
|
||||
(defun ob-elixir--format-tuple (vec)
|
||||
"Format vector VEC as Elixir tuple."
|
||||
(format "{%s}"
|
||||
(mapconcat #'ob-elixir--elisp-to-elixir (append vec nil) ", ")))
|
||||
|
||||
(defun ob-elixir--format-keyword-list (alist)
|
||||
"Format ALIST as Elixir keyword list."
|
||||
(format "[%s]"
|
||||
(mapconcat
|
||||
(lambda (pair)
|
||||
(format "%s: %s"
|
||||
(car pair)
|
||||
(ob-elixir--elisp-to-elixir (cdr pair))))
|
||||
alist ", ")))
|
||||
|
||||
(defun ob-elixir--format-map (plist)
|
||||
"Format PLIST as Elixir map."
|
||||
(let ((pairs '()))
|
||||
(while plist
|
||||
(push (format "%s => %s"
|
||||
(ob-elixir--elisp-to-elixir (car plist))
|
||||
(ob-elixir--elisp-to-elixir (cadr plist)))
|
||||
pairs)
|
||||
(setq plist (cddr plist)))
|
||||
(format "%%{%s}" (mapconcat #'identity (nreverse pairs) ", "))))
|
||||
```
|
||||
|
||||
### Elixir to Elisp
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--parse-result (output)
|
||||
"Parse Elixir OUTPUT into Elisp value."
|
||||
(let ((trimmed (string-trim output)))
|
||||
(cond
|
||||
;; nil
|
||||
((string= trimmed "nil") nil)
|
||||
|
||||
;; Booleans
|
||||
((string= trimmed "true") t)
|
||||
((string= trimmed "false") nil)
|
||||
|
||||
;; Numbers
|
||||
((string-match-p "^-?[0-9]+$" trimmed)
|
||||
(string-to-number trimmed))
|
||||
((string-match-p "^-?[0-9]+\\.[0-9]+\\(e[+-]?[0-9]+\\)?$" trimmed)
|
||||
(string-to-number trimmed))
|
||||
|
||||
;; Atoms (convert to symbols)
|
||||
((string-match "^:\\([a-zA-Z_][a-zA-Z0-9_]*\\)$" trimmed)
|
||||
(intern (match-string 1 trimmed)))
|
||||
|
||||
;; Strings
|
||||
((string-match "^\"\\(.*\\)\"$" trimmed)
|
||||
(match-string 1 trimmed))
|
||||
|
||||
;; Lists/tuples - use org-babel-script-escape
|
||||
((string-match-p "^\\[.*\\]$" trimmed)
|
||||
(org-babel-script-escape trimmed))
|
||||
((string-match-p "^{.*}$" trimmed)
|
||||
(org-babel-script-escape
|
||||
(replace-regexp-in-string "^{\\(.*\\)}$" "[\\1]" trimmed)))
|
||||
|
||||
;; Maps - convert to alist
|
||||
((string-match-p "^%{.*}$" trimmed)
|
||||
(ob-elixir--parse-map trimmed))
|
||||
|
||||
;; Default: return as string
|
||||
(t trimmed))))
|
||||
|
||||
(defun ob-elixir--parse-map (map-string)
|
||||
"Parse Elixir MAP-STRING to alist."
|
||||
;; Simplified - for complex maps, use JSON encoding
|
||||
(let ((content (substring map-string 2 -1))) ; Remove %{ and }
|
||||
(mapcar
|
||||
(lambda (pair)
|
||||
(when (string-match "\\(.+?\\) => \\(.+\\)" pair)
|
||||
(cons (ob-elixir--parse-result (match-string 1 pair))
|
||||
(ob-elixir--parse-result (match-string 2 pair)))))
|
||||
(split-string content ", "))))
|
||||
```
|
||||
|
||||
### Using JSON for Complex Data
|
||||
|
||||
For complex nested structures, JSON is more reliable:
|
||||
|
||||
```elisp
|
||||
(defconst ob-elixir--json-wrapper
|
||||
"
|
||||
_result_ = (
|
||||
%s
|
||||
)
|
||||
IO.puts(Jason.encode!(_result_))
|
||||
"
|
||||
"Wrapper that outputs result as JSON.")
|
||||
|
||||
(defun ob-elixir--eval-as-json (code)
|
||||
"Evaluate CODE and parse result as JSON."
|
||||
(let* ((wrapped (format ob-elixir--json-wrapper code))
|
||||
(output (ob-elixir--eval-script wrapped)))
|
||||
(json-read-from-string (string-trim output))))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Detecting Elixir Errors
|
||||
|
||||
```elisp
|
||||
(defconst ob-elixir-error-patterns
|
||||
'("^\\*\\* (\\(\\w+Error\\))" ; ** (RuntimeError) ...
|
||||
"^\\*\\* (\\(\\w+\\)) \\(.+\\)" ; ** (exit) ...
|
||||
"^\\(CompileError\\)" ; CompileError ...
|
||||
"^\\(SyntaxError\\)") ; SyntaxError ...
|
||||
"Patterns matching Elixir error output.")
|
||||
|
||||
(defun ob-elixir--error-p (output)
|
||||
"Return error info if OUTPUT contains an error, nil otherwise."
|
||||
(catch 'found
|
||||
(dolist (pattern ob-elixir-error-patterns)
|
||||
(when (string-match pattern output)
|
||||
(throw 'found (list :type (match-string 1 output)
|
||||
:message output))))))
|
||||
```
|
||||
|
||||
### Signaling Errors to Org
|
||||
|
||||
```elisp
|
||||
(define-error 'ob-elixir-error "Elixir evaluation error")
|
||||
(define-error 'ob-elixir-compile-error "Elixir compilation error" 'ob-elixir-error)
|
||||
(define-error 'ob-elixir-runtime-error "Elixir runtime error" 'ob-elixir-error)
|
||||
|
||||
(defun ob-elixir--handle-error (output)
|
||||
"Handle error in OUTPUT, signaling appropriate condition."
|
||||
(when-let ((error-info (ob-elixir--error-p output)))
|
||||
(let ((type (plist-get error-info :type))
|
||||
(message (plist-get error-info :message)))
|
||||
(cond
|
||||
((member type '("CompileError" "SyntaxError" "TokenMissingError"))
|
||||
(signal 'ob-elixir-compile-error (list message)))
|
||||
(t
|
||||
(signal 'ob-elixir-runtime-error (list message)))))))
|
||||
```
|
||||
|
||||
### Timeout Handling
|
||||
|
||||
```elisp
|
||||
(defcustom ob-elixir-timeout 30
|
||||
"Default timeout in seconds for Elixir evaluation."
|
||||
:type 'integer
|
||||
:group 'ob-elixir)
|
||||
|
||||
(defun ob-elixir--eval-with-timeout (code timeout)
|
||||
"Evaluate CODE with TIMEOUT seconds limit."
|
||||
(with-timeout (timeout
|
||||
(error "Elixir evaluation timed out after %d seconds" timeout))
|
||||
(ob-elixir--eval-script code)))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## elixir-mode Integration
|
||||
|
||||
### Syntax Highlighting
|
||||
|
||||
```elisp
|
||||
;; Register with org-src for editing
|
||||
(add-to-list 'org-src-lang-modes '("elixir" . elixir))
|
||||
|
||||
;; If elixir-mode isn't available, use a fallback
|
||||
(unless (fboundp 'elixir-mode)
|
||||
(add-to-list 'org-src-lang-modes '("elixir" . prog)))
|
||||
```
|
||||
|
||||
### Using elixir-mode Functions
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--format-code (code)
|
||||
"Format CODE using mix format if available."
|
||||
(when (and (executable-find "mix")
|
||||
(> (length code) 0))
|
||||
(with-temp-buffer
|
||||
(insert code)
|
||||
(let ((temp-file (make-temp-file "elixir-format" nil ".ex")))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(write-region (point-min) (point-max) temp-file)
|
||||
(when (= 0 (call-process "mix" nil nil nil
|
||||
"format" temp-file))
|
||||
(erase-buffer)
|
||||
(insert-file-contents temp-file)
|
||||
(buffer-string)))
|
||||
(delete-file temp-file))))))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Caching Compilation
|
||||
|
||||
```elisp
|
||||
(defvar ob-elixir--module-cache (make-hash-table :test 'equal)
|
||||
"Cache of compiled Elixir modules.")
|
||||
|
||||
(defun ob-elixir--get-cached-module (code)
|
||||
"Get cached module for CODE, or compile and cache."
|
||||
(let ((hash (md5 code)))
|
||||
(or (gethash hash ob-elixir--module-cache)
|
||||
(let ((module-name (format "ObElixir_%s" (substring hash 0 8))))
|
||||
(ob-elixir--compile-module module-name code)
|
||||
(puthash hash module-name ob-elixir--module-cache)
|
||||
module-name))))
|
||||
```
|
||||
|
||||
### Reusing Sessions
|
||||
|
||||
```elisp
|
||||
(defcustom ob-elixir-reuse-sessions t
|
||||
"Whether to reuse sessions between evaluations.
|
||||
When non-nil, sessions persist until explicitly killed."
|
||||
:type 'boolean
|
||||
:group 'ob-elixir)
|
||||
```
|
||||
|
||||
### Startup Time Optimization
|
||||
|
||||
```elisp
|
||||
;; Pre-start a session on package load (optional)
|
||||
(defun ob-elixir-warm-up ()
|
||||
"Pre-start an IEx session for faster first evaluation."
|
||||
(interactive)
|
||||
(ob-elixir--start-iex-session "warmup"))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Elixir Documentation](https://elixir-lang.org/docs.html)
|
||||
- [IEx Documentation](https://hexdocs.pm/iex/IEx.html)
|
||||
- [Mix Documentation](https://hexdocs.pm/mix/Mix.html)
|
||||
- [Erlang Distribution Protocol](https://www.erlang.org/doc/reference_manual/distributed.html)
|
||||
- [elixir-mode GitHub](https://github.com/elixir-editors/emacs-elixir)
|
||||
- [inf-elixir for REPL](https://github.com/J3RN/inf-elixir)
|
||||
- [Elixir LS (Language Server)](https://github.com/elixir-lsp/elixir-ls)
|
||||
Reference in New Issue
Block a user