6.2 KiB
6.2 KiB
Task 02: Basic Code Execution
Phase: 1 - Core (MVP) Priority: Critical Estimated Time: 1-2 hours Dependencies: Task 01 (Project Setup)
Objective
Implement the core org-babel-execute:elixir function that can execute Elixir code blocks using external process (one-shot execution).
Prerequisites
- Task 01 completed
- Elixir installed and accessible via
elixircommand
Steps
Step 1: Add customization group and variables
Add to ob-elixir.el:
;;; 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)
Step 2: Add default header arguments
;;; Header Arguments
(defvar org-babel-default-header-args:elixir
'((:results . "value")
(:session . "none"))
"Default header arguments for Elixir code blocks.")
Step 3: Register the language
;;; 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)))
Step 4: Implement the execute function
;;; Execution
(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)))
(result (ob-elixir--execute 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))))))
Step 5: Implement the internal execute function
(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))))
Step 6: Implement the value wrapper
(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))
Step 7: Add tests
Add to test/test-ob-elixir.el:
(ert-deftest ob-elixir-test-elixir-available ()
"Test that Elixir is available."
(should (executable-find ob-elixir-command)))
(ert-deftest ob-elixir-test-simple-value ()
"Test simple value evaluation."
(skip-unless (executable-find ob-elixir-command))
(let ((result (ob-elixir--execute "1 + 1" 'value)))
(should (equal "2" result))))
(ert-deftest ob-elixir-test-simple-output ()
"Test simple output evaluation."
(skip-unless (executable-find ob-elixir-command))
(let ((result (ob-elixir--execute "IO.puts(\"hello\")" 'output)))
(should (equal "hello" result))))
(ert-deftest ob-elixir-test-multiline-value ()
"Test multiline code value evaluation."
(skip-unless (executable-find ob-elixir-command))
(let ((result (ob-elixir--execute "x = 10\ny = 20\nx + y" 'value)))
(should (equal "30" result))))
(ert-deftest ob-elixir-test-list-result ()
"Test list result."
(skip-unless (executable-find ob-elixir-command))
(let ((result (ob-elixir--execute "[1, 2, 3]" 'value)))
(should (equal "[1, 2, 3]" result))))
(ert-deftest ob-elixir-test-map-result ()
"Test map result."
(skip-unless (executable-find ob-elixir-command))
(let ((result (ob-elixir--execute "%{a: 1, b: 2}" 'value)))
(should (string-match-p "%{a: 1, b: 2}" result))))
Step 8: Test in an org buffer
Create a test org file test.org:
* Test ob-elixir
** Basic arithmetic (value)
#+BEGIN_SRC elixir
1 + 1
#+END_SRC
** Output test
#+BEGIN_SRC elixir :results output
IO.puts("Hello, World!")
#+END_SRC
** List manipulation
#+BEGIN_SRC elixir
Enum.map([1, 2, 3], fn x -> x * 2 end)
#+END_SRC
Press C-c C-c on each block to test.
Acceptance Criteria
org-babel-execute:elixirfunction exists- Simple expressions evaluate correctly:
1 + 1returns2 :results valuecaptures return value (default):results outputcaptures stdout- Multiline code executes correctly
- Lists and maps are returned in Elixir format
- All tests pass:
make test
Troubleshooting
"Cannot find elixir"
Ensure Elixir is in PATH:
which elixir
elixir --version
Or set the full path:
(setq ob-elixir-command "/usr/local/bin/elixir")
Results are truncated
The wrapper uses limit: :infinity to prevent truncation. If still truncated, check for very large outputs.
ANSI codes in output
We'll handle this in a later task. For now, output should be clean with the current approach.
Files Modified
ob-elixir.el- Add execution functionstest/test-ob-elixir.el- Add execution tests