docs and tasks
This commit is contained in:
242
tasks/02-basic-execution.md
Normal file
242
tasks/02-basic-execution.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# 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 `elixir` command
|
||||
|
||||
## Steps
|
||||
|
||||
### Step 1: Add customization group and variables
|
||||
|
||||
Add to `ob-elixir.el`:
|
||||
|
||||
```elisp
|
||||
;;; 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
|
||||
|
||||
```elisp
|
||||
;;; 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
|
||||
|
||||
```elisp
|
||||
;;; 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
|
||||
|
||||
```elisp
|
||||
;;; 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
|
||||
|
||||
```elisp
|
||||
(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
|
||||
|
||||
```elisp
|
||||
(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`:
|
||||
|
||||
```elisp
|
||||
(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`:
|
||||
|
||||
```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:elixir` function exists
|
||||
- [ ] Simple expressions evaluate correctly: `1 + 1` returns `2`
|
||||
- [ ] `:results value` captures return value (default)
|
||||
- [ ] `:results output` captures 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:
|
||||
```bash
|
||||
which elixir
|
||||
elixir --version
|
||||
```
|
||||
|
||||
Or set the full path:
|
||||
```elisp
|
||||
(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 functions
|
||||
- `test/test-ob-elixir.el` - Add execution tests
|
||||
|
||||
## References
|
||||
|
||||
- [docs/03-org-babel-implementation-guide.md](../docs/03-org-babel-implementation-guide.md)
|
||||
- [docs/04-elixir-integration-strategies.md](../docs/04-elixir-integration-strategies.md)
|
||||
Reference in New Issue
Block a user