function definitions outside modules
This commit is contained in:
@@ -12,6 +12,7 @@ The implementation is organized into 4 phases:
|
||||
| **Phase 2** | Sessions | 07 | High |
|
||||
| **Phase 3** | Mix Integration | 08 | High |
|
||||
| **Phase 4** | Advanced Features | 09-10 | Medium/Low |
|
||||
| **Phase 5** | Literate Programming | 11-13 | Medium |
|
||||
|
||||
## Task List
|
||||
|
||||
@@ -70,6 +71,20 @@ These tasks implement the minimum viable product - basic Elixir code execution i
|
||||
- `:remsh node@host` connects to running nodes
|
||||
- `:async yes` for non-blocking execution
|
||||
|
||||
### Phase 5: Literate Programming Enhancements
|
||||
|
||||
| Task | Title | Time | Status |
|
||||
|------|-------|------|--------|
|
||||
| [11](11-fix-error-display-in-buffer.md) | Fix Error Display in Buffer | 1 hr | Pending |
|
||||
| [12](12-imports-block-support.md) | Imports Block Support | 1-2 hrs | Pending |
|
||||
| [13](13-module-definition-blocks.md) | Module Definition Blocks | 2-3 hrs | Pending |
|
||||
|
||||
**Phase 5 Deliverables:**
|
||||
- `#+BEGIN_IMPORTS elixir` blocks for shared imports/aliases
|
||||
- `:module ModuleName` header for defining reusable functions
|
||||
- Multiple blocks with same `:module` merge their functions
|
||||
- Functions available via explicit imports in subsequent blocks
|
||||
|
||||
## Implementation Order
|
||||
|
||||
```
|
||||
@@ -90,6 +105,11 @@ Phase 3 (After Phase 1, can parallel with Phase 2)
|
||||
Phase 4 (After relevant dependencies)
|
||||
├── 09-remote-shell (after 07)
|
||||
└── 10-async-execution (after Phase 1)
|
||||
|
||||
Phase 5 (After Phase 1, builds on 12)
|
||||
├── 11-fix-error-display-in-buffer
|
||||
├── 12-imports-block-support
|
||||
└── 13-module-definition-blocks (after 12)
|
||||
```
|
||||
|
||||
## Time Estimates
|
||||
@@ -100,7 +120,8 @@ Phase 4 (After relevant dependencies)
|
||||
| Phase 2 | 3-4 hours |
|
||||
| Phase 3 | 2-3 hours |
|
||||
| Phase 4 | 5-7 hours |
|
||||
| **Total** | **18-26 hours** |
|
||||
| Phase 5 | 4-6 hours |
|
||||
| **Total** | **22-32 hours** |
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
780
tasks/13-module-definition-blocks.md
Normal file
780
tasks/13-module-definition-blocks.md
Normal file
@@ -0,0 +1,780 @@
|
||||
# Task 13: Add Module Definition Blocks Support
|
||||
|
||||
## Problem
|
||||
|
||||
When working with Elixir in org-mode, users often want to define reusable functions that can be called from multiple code blocks. Currently, users must either:
|
||||
|
||||
1. Define full `defmodule` wrappers in each block
|
||||
2. Use sessions and manually manage module definitions
|
||||
3. Duplicate function code across blocks
|
||||
|
||||
This is cumbersome and doesn't align with the literate programming paradigm where functions should be defined once and used throughout the document.
|
||||
|
||||
## Desired Behavior
|
||||
|
||||
Users can define Elixir functions in code blocks with a `:module` header argument. These blocks:
|
||||
|
||||
1. **Define functions** but don't execute any code
|
||||
2. **Merge** when multiple blocks share the same module name
|
||||
3. Are **automatically wrapped** in `defmodule` when a regular block is executed
|
||||
4. Require **explicit imports** via the `#+BEGIN_IMPORTS elixir` block to use
|
||||
|
||||
### Example
|
||||
|
||||
```org
|
||||
#+BEGIN_SRC elixir :module Helpers
|
||||
def greet(name), do: "Hello, #{name}!"
|
||||
def double(x), do: x * 2
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_SRC elixir :module Helpers
|
||||
# This merges with the above block
|
||||
def triple(x), do: x * 3
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_SRC elixir :module Math
|
||||
def square(x), do: x * x
|
||||
@moduledoc "Math utilities"
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_IMPORTS elixir
|
||||
import Helpers
|
||||
import Math
|
||||
#+END_IMPORTS
|
||||
|
||||
#+BEGIN_SRC elixir
|
||||
greet("World") <> " - " <> to_string(square(5))
|
||||
#+END_SRC
|
||||
```
|
||||
|
||||
When the last block executes, the generated code is:
|
||||
|
||||
```elixir
|
||||
defmodule Helpers do
|
||||
def greet(name), do: "Hello, #{name}!"
|
||||
def double(x), do: x * 2
|
||||
def triple(x), do: x * 3
|
||||
end
|
||||
|
||||
defmodule Math do
|
||||
def square(x), do: x * x
|
||||
@moduledoc "Math utilities"
|
||||
end
|
||||
|
||||
import Helpers
|
||||
import Math
|
||||
|
||||
result = (
|
||||
greet("World") <> " - " <> to_string(square(5))
|
||||
)
|
||||
IO.puts(inspect(result, limit: :infinity, printable_limit: :infinity, charlists: :as_lists))
|
||||
```
|
||||
|
||||
## Scope
|
||||
|
||||
- `:module ModuleName` header marks a block as a function definition block
|
||||
- Definition blocks don't execute - they return a confirmation message
|
||||
- Multiple blocks with the same `:module` value have their bodies merged
|
||||
- Only blocks **before** the current position are gathered (consistent with imports/deps)
|
||||
- Works with both session and non-session modes
|
||||
- Works alongside deps blocks and imports blocks
|
||||
- Supports all Elixir module constructs (`def`, `defp`, `@moduledoc`, `use`, etc.)
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Step 1: Add function to find all module definition blocks
|
||||
|
||||
**File:** `ob-elixir.el` (after `ob-elixir--find-imports-for-position`, around line 453)
|
||||
|
||||
**Add:** A function to scan the buffer for all elixir src blocks with `:module` header:
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--find-all-module-blocks (pos)
|
||||
"Find all module definition blocks before POS.
|
||||
|
||||
Scans the buffer for Elixir source blocks with a :module header argument.
|
||||
Returns an alist of (MODULE-NAME . BODY-STRING) with merged bodies
|
||||
for blocks sharing the same module name.
|
||||
|
||||
Blocks are processed in document order, so later blocks with the same
|
||||
module name have their content appended to earlier blocks."
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(widen)
|
||||
(goto-char (point-min))
|
||||
(let ((modules (make-hash-table :test 'equal)))
|
||||
(org-element-map (org-element-parse-buffer) 'src-block
|
||||
(lambda (src-block)
|
||||
(when (and (< (org-element-property :begin src-block) pos)
|
||||
(string= (org-element-property :language src-block) "elixir"))
|
||||
(let* ((params (org-babel-parse-header-arguments
|
||||
(or (org-element-property :parameters src-block) "")))
|
||||
(module-name (cdr (assq :module params))))
|
||||
(when module-name
|
||||
(let* ((body (org-element-property :value src-block))
|
||||
(existing (gethash module-name modules "")))
|
||||
(puthash module-name
|
||||
(if (string-empty-p existing)
|
||||
(string-trim body)
|
||||
(concat existing "\n\n" (string-trim body)))
|
||||
modules)))))))
|
||||
;; Convert hash table to alist
|
||||
(let (result)
|
||||
(maphash (lambda (k v) (push (cons k v) result)) modules)
|
||||
(nreverse result))))))
|
||||
```
|
||||
|
||||
### Step 2: Add function to generate module definitions
|
||||
|
||||
**File:** `ob-elixir.el` (after `ob-elixir--find-all-module-blocks`)
|
||||
|
||||
**Add:** A function to convert the modules alist to Elixir `defmodule` code:
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--generate-module-definitions (modules-alist)
|
||||
"Generate Elixir module definitions from MODULES-ALIST.
|
||||
|
||||
Each entry in MODULES-ALIST is (MODULE-NAME . BODY-STRING).
|
||||
Returns a string with all defmodule definitions separated by blank lines,
|
||||
or nil if MODULES-ALIST is empty."
|
||||
(when modules-alist
|
||||
(mapconcat
|
||||
(lambda (entry)
|
||||
(format "defmodule %s do\n%s\nend" (car entry) (cdr entry)))
|
||||
modules-alist
|
||||
"\n\n")))
|
||||
```
|
||||
|
||||
### Step 3: Modify `org-babel-execute:elixir` to detect `:module` blocks
|
||||
|
||||
**File:** `ob-elixir.el` (in `org-babel-execute:elixir` function, around line 946)
|
||||
|
||||
**Change:** Add early return when `:module` header is present:
|
||||
|
||||
**Current code:**
|
||||
```elisp
|
||||
(defun org-babel-execute:elixir (body params)
|
||||
"Execute a block of Elixir code with org-babel.
|
||||
..."
|
||||
(let* ((session (cdr (assq :session params)))
|
||||
...)
|
||||
```
|
||||
|
||||
**Proposed change:**
|
||||
```elisp
|
||||
(defun org-babel-execute:elixir (body params)
|
||||
"Execute a block of Elixir code with org-babel.
|
||||
..."
|
||||
(let ((module-name (cdr (assq :module params))))
|
||||
(if module-name
|
||||
;; This is a module definition block - don't execute
|
||||
(format "Module %s: functions defined" module-name)
|
||||
;; Normal execution path
|
||||
(let* ((session (cdr (assq :session params)))
|
||||
...))))
|
||||
```
|
||||
|
||||
### Step 4: Modify `ob-elixir--execute` to accept modules-string
|
||||
|
||||
**File:** `ob-elixir.el` (around line 919)
|
||||
|
||||
**Current signature:**
|
||||
```elisp
|
||||
(defun ob-elixir--execute (body result-type &optional imports-string)
|
||||
```
|
||||
|
||||
**Proposed signature:**
|
||||
```elisp
|
||||
(defun ob-elixir--execute (body result-type &optional imports-string modules-string)
|
||||
```
|
||||
|
||||
**Changes to function body:**
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--execute (body result-type &optional imports-string modules-string)
|
||||
"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.
|
||||
IMPORTS-STRING, if provided, contains import/alias/require statements.
|
||||
MODULES-STRING, if provided, contains module definitions to prepend.
|
||||
|
||||
Code is assembled in this order:
|
||||
1. Module definitions (MODULES-STRING)
|
||||
2. Imports (IMPORTS-STRING)
|
||||
3. User code (BODY, wrapped for value if needed)
|
||||
|
||||
Returns the result as a string.
|
||||
May signal `ob-elixir-error' if execution fails and
|
||||
`ob-elixir-signal-errors' is non-nil."
|
||||
(let* ((tmp-file (org-babel-temp-file "ob-elixir-" ".exs"))
|
||||
(wrapped (if (eq result-type 'value)
|
||||
(ob-elixir--wrap-for-value body)
|
||||
body))
|
||||
(code (concat
|
||||
(when modules-string (concat (string-trim modules-string) "\n\n"))
|
||||
(when imports-string (concat (string-trim imports-string) "\n\n"))
|
||||
wrapped)))
|
||||
(with-temp-file tmp-file
|
||||
(insert code))
|
||||
(let ((result (with-temp-buffer
|
||||
(call-process ob-elixir-command nil t nil
|
||||
(org-babel-process-file-name tmp-file))
|
||||
(buffer-string))))
|
||||
(ob-elixir--process-result result))))
|
||||
```
|
||||
|
||||
### Step 5: Modify `ob-elixir--execute-with-deps` to accept modules-string
|
||||
|
||||
**File:** `ob-elixir.el` (around line 561)
|
||||
|
||||
**Current signature:**
|
||||
```elisp
|
||||
(defun ob-elixir--execute-with-deps (body result-type deps-string &optional imports-string)
|
||||
```
|
||||
|
||||
**Proposed signature:**
|
||||
```elisp
|
||||
(defun ob-elixir--execute-with-deps (body result-type deps-string &optional imports-string modules-string)
|
||||
```
|
||||
|
||||
**Changes to function body:**
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--execute-with-deps (body result-type deps-string &optional imports-string modules-string)
|
||||
"Execute BODY with dependencies from DEPS-STRING.
|
||||
|
||||
RESULT-TYPE is `value' or `output'.
|
||||
IMPORTS-STRING, if provided, is prepended after module definitions.
|
||||
MODULES-STRING, if provided, contains module definitions to prepend first."
|
||||
(let* ((project-dir (ob-elixir--get-deps-project deps-string))
|
||||
(default-directory project-dir)
|
||||
(tmp-file (org-babel-temp-file "ob-elixir-" ".exs"))
|
||||
(wrapped (if (eq result-type 'value)
|
||||
(ob-elixir--wrap-for-value body)
|
||||
body))
|
||||
(code (concat
|
||||
(when modules-string (concat (string-trim modules-string) "\n\n"))
|
||||
(when imports-string (concat (string-trim imports-string) "\n\n"))
|
||||
wrapped)))
|
||||
|
||||
;; Write code to temp file
|
||||
(with-temp-file tmp-file
|
||||
(insert code))
|
||||
|
||||
;; Execute with mix run
|
||||
(let ((command (format "%s run --no-compile %s 2>&1"
|
||||
ob-elixir-mix-command
|
||||
(shell-quote-argument tmp-file))))
|
||||
(ob-elixir--process-result
|
||||
(shell-command-to-string command)))))
|
||||
```
|
||||
|
||||
### Step 6: Modify `ob-elixir--evaluate-in-session` to accept modules-string
|
||||
|
||||
**File:** `ob-elixir.el` (around line 685)
|
||||
|
||||
**Current signature:**
|
||||
```elisp
|
||||
(defun ob-elixir--evaluate-in-session (session body result-type &optional imports-string)
|
||||
```
|
||||
|
||||
**Proposed signature:**
|
||||
```elisp
|
||||
(defun ob-elixir--evaluate-in-session (session body result-type &optional imports-string modules-string)
|
||||
```
|
||||
|
||||
**Changes to function body:**
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--evaluate-in-session (session body result-type &optional imports-string modules-string)
|
||||
"Evaluate BODY in SESSION, return result.
|
||||
|
||||
RESULT-TYPE is `value' or `output'.
|
||||
IMPORTS-STRING, if provided, is prepended after module definitions.
|
||||
MODULES-STRING, if provided, contains module definitions to prepend first."
|
||||
(let* ((buffer (org-babel-elixir-initiate-session session nil))
|
||||
(wrapped (if (eq result-type 'value)
|
||||
(ob-elixir--session-wrap-for-value body)
|
||||
body))
|
||||
(code (concat
|
||||
(when modules-string (concat (string-trim modules-string) "\n\n"))
|
||||
(when imports-string (concat (string-trim imports-string) "\n\n"))
|
||||
wrapped))
|
||||
(eoe-indicator ob-elixir--eoe-marker)
|
||||
(full-body (concat code "\nIO.puts(\"" eoe-indicator "\")"))
|
||||
output)
|
||||
(unless buffer
|
||||
(error "Failed to create Elixir session: %s" session))
|
||||
|
||||
(setq output
|
||||
(org-babel-comint-with-output
|
||||
(buffer eoe-indicator t full-body)
|
||||
(ob-elixir--send-command buffer full-body)))
|
||||
|
||||
(ob-elixir--clean-session-output output result-type)))
|
||||
```
|
||||
|
||||
### Step 7: Modify `ob-elixir--evaluate-in-session-with-deps` to accept modules-string
|
||||
|
||||
**File:** `ob-elixir.el` (around line 839)
|
||||
|
||||
**Current signature:**
|
||||
```elisp
|
||||
(defun ob-elixir--evaluate-in-session-with-deps (session body result-type deps-string &optional imports-string)
|
||||
```
|
||||
|
||||
**Proposed signature:**
|
||||
```elisp
|
||||
(defun ob-elixir--evaluate-in-session-with-deps (session body result-type deps-string &optional imports-string modules-string)
|
||||
```
|
||||
|
||||
**Changes to function body:**
|
||||
|
||||
```elisp
|
||||
(defun ob-elixir--evaluate-in-session-with-deps (session body result-type deps-string &optional imports-string modules-string)
|
||||
"Evaluate BODY in SESSION with DEPS-STRING context.
|
||||
|
||||
RESULT-TYPE is `value' or `output'.
|
||||
IMPORTS-STRING, if provided, is prepended after module definitions.
|
||||
MODULES-STRING, if provided, contains module definitions to prepend first."
|
||||
(let* ((buffer (ob-elixir--get-or-create-session-with-deps session deps-string))
|
||||
(wrapped (if (eq result-type 'value)
|
||||
(ob-elixir--session-wrap-for-value body)
|
||||
body))
|
||||
(code (concat
|
||||
(when modules-string (concat (string-trim modules-string) "\n\n"))
|
||||
(when imports-string (concat (string-trim imports-string) "\n\n"))
|
||||
wrapped))
|
||||
(eoe-indicator ob-elixir--eoe-marker)
|
||||
(full-body (concat code "\nIO.puts(\"" eoe-indicator "\")"))
|
||||
output)
|
||||
|
||||
(unless buffer
|
||||
(error "Failed to create Elixir session with deps: %s" session))
|
||||
|
||||
(setq output
|
||||
(org-babel-comint-with-output
|
||||
(buffer eoe-indicator t full-body)
|
||||
(ob-elixir--send-command buffer full-body)))
|
||||
|
||||
(ob-elixir--clean-session-output output result-type)))
|
||||
```
|
||||
|
||||
### Step 8: Update `org-babel-execute:elixir` to gather and pass modules
|
||||
|
||||
**File:** `ob-elixir.el` (in `org-babel-execute:elixir`, the normal execution path)
|
||||
|
||||
**Changes:** Add module gathering and pass to all execution functions:
|
||||
|
||||
```elisp
|
||||
(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 ((module-name (cdr (assq :module params))))
|
||||
(if module-name
|
||||
;; This is a module definition block - don't execute
|
||||
(format "Module %s: functions defined" module-name)
|
||||
;; Normal execution path
|
||||
(let* ((session (cdr (assq :session params)))
|
||||
(result-type (cdr (assq :result-type params)))
|
||||
(result-params (cdr (assq :result-params params)))
|
||||
;; Find deps for this block's position
|
||||
(deps-string (ob-elixir--find-deps-for-position (point)))
|
||||
;; Find imports for this block's position
|
||||
(imports-string (ob-elixir--find-imports-for-position (point)))
|
||||
;; Find module definitions for this block's position
|
||||
(modules-alist (ob-elixir--find-all-module-blocks (point)))
|
||||
(modules-string (ob-elixir--generate-module-definitions modules-alist))
|
||||
;; Expand body with variable assignments
|
||||
(full-body (org-babel-expand-body:generic
|
||||
body params
|
||||
(org-babel-variable-assignments:elixir params)))
|
||||
(result (condition-case err
|
||||
(cond
|
||||
;; Session mode with deps
|
||||
((and session (not (string= session "none")) deps-string)
|
||||
(ob-elixir--evaluate-in-session-with-deps
|
||||
session full-body result-type deps-string imports-string modules-string))
|
||||
;; Session mode without deps
|
||||
((and session (not (string= session "none")))
|
||||
(ob-elixir--evaluate-in-session session full-body result-type imports-string modules-string))
|
||||
;; Non-session with deps
|
||||
(deps-string
|
||||
(ob-elixir--execute-with-deps full-body result-type deps-string imports-string modules-string))
|
||||
;; Plain execution
|
||||
(t
|
||||
(ob-elixir--execute full-body result-type imports-string modules-string)))
|
||||
(ob-elixir-error
|
||||
;; Return error message so it appears in buffer
|
||||
(cadr err)))))
|
||||
(org-babel-reassemble-table
|
||||
(org-babel-result-cond result-params
|
||||
;; For output/scalar/verbatim - return as-is
|
||||
result
|
||||
;; For value - parse into Elisp data
|
||||
(ob-elixir--table-or-string 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 9: Create test file for module functionality
|
||||
|
||||
**File:** `test/test-ob-elixir-modules.el` (new file)
|
||||
|
||||
```elisp
|
||||
;;; test-ob-elixir-modules.el --- Module definition block tests -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Tests for the module definition block functionality.
|
||||
;; Tests the :module header argument and related functions.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ert)
|
||||
(require 'ob-elixir)
|
||||
(require 'org)
|
||||
(require 'org-element)
|
||||
|
||||
;;; Module Block Detection Tests
|
||||
|
||||
(ert-deftest ob-elixir-test-module-block-detection ()
|
||||
"Test that :module header argument is detected in params."
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert "#+BEGIN_SRC elixir :module MyModule\ndef foo, do: :bar\n#+END_SRC\n")
|
||||
(goto-char (point-min))
|
||||
(search-forward "#+BEGIN_SRC")
|
||||
(let* ((info (org-babel-get-src-block-info))
|
||||
(params (nth 2 info))
|
||||
(module-name (cdr (assq :module params))))
|
||||
(should module-name)
|
||||
(should (string= module-name "MyModule")))))
|
||||
|
||||
(ert-deftest ob-elixir-test-module-block-no-execute ()
|
||||
"Test that blocks with :module don't execute Elixir code."
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert "#+BEGIN_SRC elixir :module TestMod\ndef foo, do: raise \"should not run\"\n#+END_SRC\n")
|
||||
(goto-char (point-min))
|
||||
(search-forward "#+BEGIN_SRC")
|
||||
(let ((result (org-babel-execute-src-block)))
|
||||
;; Should return a message, not execute the code
|
||||
(should (stringp result))
|
||||
(should (string-match-p "Module.*TestMod.*defined" result)))))
|
||||
|
||||
;;; Module Block Gathering Tests
|
||||
|
||||
(ert-deftest ob-elixir-test-find-module-blocks-single ()
|
||||
"Test finding a single module block."
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert "#+BEGIN_SRC elixir :module Helpers\ndef greet(name), do: name\n#+END_SRC\n")
|
||||
(insert "#+BEGIN_SRC elixir\n:ok\n#+END_SRC\n")
|
||||
(goto-char (point-max))
|
||||
(let ((modules (ob-elixir--find-all-module-blocks (point))))
|
||||
(should modules)
|
||||
(should (= 1 (length modules)))
|
||||
(should (string= "Helpers" (caar modules)))
|
||||
(should (string-match-p "def greet" (cdar modules))))))
|
||||
|
||||
(ert-deftest ob-elixir-test-find-module-blocks-multiple-same-name ()
|
||||
"Test that multiple blocks with same module name are merged."
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert "#+BEGIN_SRC elixir :module Helpers\ndef foo, do: 1\n#+END_SRC\n")
|
||||
(insert "#+BEGIN_SRC elixir :module Helpers\ndef bar, do: 2\n#+END_SRC\n")
|
||||
(insert "#+BEGIN_SRC elixir\n:ok\n#+END_SRC\n")
|
||||
(goto-char (point-max))
|
||||
(let ((modules (ob-elixir--find-all-module-blocks (point))))
|
||||
(should modules)
|
||||
(should (= 1 (length modules)))
|
||||
(should (string= "Helpers" (caar modules)))
|
||||
(let ((body (cdar modules)))
|
||||
(should (string-match-p "def foo" body))
|
||||
(should (string-match-p "def bar" body))))))
|
||||
|
||||
(ert-deftest ob-elixir-test-find-module-blocks-different-names ()
|
||||
"Test finding multiple modules with different names."
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert "#+BEGIN_SRC elixir :module ModA\ndef a, do: 1\n#+END_SRC\n")
|
||||
(insert "#+BEGIN_SRC elixir :module ModB\ndef b, do: 2\n#+END_SRC\n")
|
||||
(insert "#+BEGIN_SRC elixir\n:ok\n#+END_SRC\n")
|
||||
(goto-char (point-max))
|
||||
(let ((modules (ob-elixir--find-all-module-blocks (point))))
|
||||
(should modules)
|
||||
(should (= 2 (length modules)))
|
||||
(should (assoc "ModA" modules))
|
||||
(should (assoc "ModB" modules)))))
|
||||
|
||||
(ert-deftest ob-elixir-test-find-module-blocks-position-scoped ()
|
||||
"Test that only blocks before current position are found."
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert "#+BEGIN_SRC elixir :module Before\ndef before, do: 1\n#+END_SRC\n")
|
||||
(let ((middle-pos (point)))
|
||||
(insert "#+BEGIN_SRC elixir :module After\ndef after, do: 2\n#+END_SRC\n")
|
||||
(let ((modules (ob-elixir--find-all-module-blocks middle-pos)))
|
||||
(should modules)
|
||||
(should (= 1 (length modules)))
|
||||
(should (assoc "Before" modules))
|
||||
(should-not (assoc "After" modules))))))
|
||||
|
||||
(ert-deftest ob-elixir-test-find-module-blocks-no-modules ()
|
||||
"Test that nil/empty is returned when no module blocks exist."
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert "#+BEGIN_SRC elixir\n1 + 1\n#+END_SRC\n")
|
||||
(goto-char (point-max))
|
||||
(let ((modules (ob-elixir--find-all-module-blocks (point))))
|
||||
(should (null modules)))))
|
||||
|
||||
;;; Module Definition Generation Tests
|
||||
|
||||
(ert-deftest ob-elixir-test-generate-module-definitions-single ()
|
||||
"Test generating a single module definition."
|
||||
(let* ((modules '(("MyMod" . "def foo, do: :bar")))
|
||||
(result (ob-elixir--generate-module-definitions modules)))
|
||||
(should result)
|
||||
(should (string-match-p "defmodule MyMod do" result))
|
||||
(should (string-match-p "def foo, do: :bar" result))
|
||||
(should (string-match-p "end" result))))
|
||||
|
||||
(ert-deftest ob-elixir-test-generate-module-definitions-multiple ()
|
||||
"Test generating multiple module definitions."
|
||||
(let* ((modules '(("ModA" . "def a, do: 1") ("ModB" . "def b, do: 2")))
|
||||
(result (ob-elixir--generate-module-definitions modules)))
|
||||
(should result)
|
||||
(should (string-match-p "defmodule ModA do" result))
|
||||
(should (string-match-p "defmodule ModB do" result))))
|
||||
|
||||
(ert-deftest ob-elixir-test-generate-module-definitions-empty ()
|
||||
"Test that nil is returned for empty modules list."
|
||||
(should (null (ob-elixir--generate-module-definitions nil)))
|
||||
(should (null (ob-elixir--generate-module-definitions '()))))
|
||||
|
||||
;;; Integration Tests
|
||||
|
||||
(ert-deftest ob-elixir-test-module-integration-basic ()
|
||||
"Test full integration: define module, import, call function."
|
||||
(skip-unless (executable-find ob-elixir-command))
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert "#+BEGIN_SRC elixir :module Helpers\n")
|
||||
(insert "def double(x), do: x * 2\n")
|
||||
(insert "#+END_SRC\n\n")
|
||||
(insert "#+BEGIN_IMPORTS elixir\n")
|
||||
(insert "import Helpers\n")
|
||||
(insert "#+END_IMPORTS\n\n")
|
||||
(insert "#+BEGIN_SRC elixir :results value\n")
|
||||
(insert "double(21)\n")
|
||||
(insert "#+END_SRC\n")
|
||||
(goto-char (point-min))
|
||||
(search-forward "double(21)")
|
||||
(beginning-of-line)
|
||||
(search-backward "#+BEGIN_SRC")
|
||||
(let ((result (org-babel-execute-src-block)))
|
||||
(should result)
|
||||
(should (equal result 42)))))
|
||||
|
||||
(ert-deftest ob-elixir-test-module-integration-merged ()
|
||||
"Test that merged module functions are all available."
|
||||
(skip-unless (executable-find ob-elixir-command))
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert "#+BEGIN_SRC elixir :module Math\n")
|
||||
(insert "def double(x), do: x * 2\n")
|
||||
(insert "#+END_SRC\n\n")
|
||||
(insert "#+BEGIN_SRC elixir :module Math\n")
|
||||
(insert "def triple(x), do: x * 3\n")
|
||||
(insert "#+END_SRC\n\n")
|
||||
(insert "#+BEGIN_IMPORTS elixir\n")
|
||||
(insert "import Math\n")
|
||||
(insert "#+END_IMPORTS\n\n")
|
||||
(insert "#+BEGIN_SRC elixir :results value\n")
|
||||
(insert "double(10) + triple(10)\n")
|
||||
(insert "#+END_SRC\n")
|
||||
(goto-char (point-min))
|
||||
(search-forward "double(10)")
|
||||
(beginning-of-line)
|
||||
(search-backward "#+BEGIN_SRC")
|
||||
(let ((result (org-babel-execute-src-block)))
|
||||
(should result)
|
||||
(should (equal result 50)))))
|
||||
|
||||
(ert-deftest ob-elixir-test-module-integration-multiple-modules ()
|
||||
"Test multiple different modules."
|
||||
(skip-unless (executable-find ob-elixir-command))
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert "#+BEGIN_SRC elixir :module ModA\n")
|
||||
(insert "def value, do: 10\n")
|
||||
(insert "#+END_SRC\n\n")
|
||||
(insert "#+BEGIN_SRC elixir :module ModB\n")
|
||||
(insert "def value, do: 20\n")
|
||||
(insert "#+END_SRC\n\n")
|
||||
(insert "#+BEGIN_IMPORTS elixir\n")
|
||||
(insert "alias ModA\nalias ModB\n")
|
||||
(insert "#+END_IMPORTS\n\n")
|
||||
(insert "#+BEGIN_SRC elixir :results value\n")
|
||||
(insert "ModA.value() + ModB.value()\n")
|
||||
(insert "#+END_SRC\n")
|
||||
(goto-char (point-min))
|
||||
(search-forward "ModA.value()")
|
||||
(beginning-of-line)
|
||||
(search-backward "#+BEGIN_SRC")
|
||||
(let ((result (org-babel-execute-src-block)))
|
||||
(should result)
|
||||
(should (equal result 30)))))
|
||||
|
||||
(ert-deftest ob-elixir-test-module-with-private-functions ()
|
||||
"Test that defp (private functions) work in modules."
|
||||
(skip-unless (executable-find ob-elixir-command))
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert "#+BEGIN_SRC elixir :module Helpers\n")
|
||||
(insert "def public(x), do: private_helper(x)\n")
|
||||
(insert "defp private_helper(x), do: x * 2\n")
|
||||
(insert "#+END_SRC\n\n")
|
||||
(insert "#+BEGIN_IMPORTS elixir\n")
|
||||
(insert "import Helpers\n")
|
||||
(insert "#+END_IMPORTS\n\n")
|
||||
(insert "#+BEGIN_SRC elixir :results value\n")
|
||||
(insert "public(21)\n")
|
||||
(insert "#+END_SRC\n")
|
||||
(goto-char (point-min))
|
||||
(search-forward "public(21)")
|
||||
(beginning-of-line)
|
||||
(search-backward "#+BEGIN_SRC")
|
||||
(let ((result (org-babel-execute-src-block)))
|
||||
(should result)
|
||||
(should (equal result 42)))))
|
||||
|
||||
(ert-deftest ob-elixir-test-module-with-module-attributes ()
|
||||
"Test that module attributes work in modules."
|
||||
(skip-unless (executable-find ob-elixir-command))
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert "#+BEGIN_SRC elixir :module Config\n")
|
||||
(insert "@default_value 42\n")
|
||||
(insert "def get_default, do: @default_value\n")
|
||||
(insert "#+END_SRC\n\n")
|
||||
(insert "#+BEGIN_IMPORTS elixir\n")
|
||||
(insert "import Config\n")
|
||||
(insert "#+END_IMPORTS\n\n")
|
||||
(insert "#+BEGIN_SRC elixir :results value\n")
|
||||
(insert "get_default()\n")
|
||||
(insert "#+END_SRC\n")
|
||||
(goto-char (point-min))
|
||||
(search-forward "get_default()")
|
||||
(beginning-of-line)
|
||||
(search-backward "#+BEGIN_SRC")
|
||||
(let ((result (org-babel-execute-src-block)))
|
||||
(should result)
|
||||
(should (equal result 42)))))
|
||||
|
||||
(ert-deftest ob-elixir-test-module-without-import ()
|
||||
"Test that modules are defined but require import to use directly."
|
||||
(skip-unless (executable-find ob-elixir-command))
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert "#+BEGIN_SRC elixir :module Helpers\n")
|
||||
(insert "def greet, do: \"hello\"\n")
|
||||
(insert "#+END_SRC\n\n")
|
||||
;; No imports block - must use full module name
|
||||
(insert "#+BEGIN_SRC elixir :results value\n")
|
||||
(insert "Helpers.greet()\n")
|
||||
(insert "#+END_SRC\n")
|
||||
(goto-char (point-min))
|
||||
(search-forward "Helpers.greet()")
|
||||
(beginning-of-line)
|
||||
(search-backward "#+BEGIN_SRC")
|
||||
(let ((result (org-babel-execute-src-block)))
|
||||
(should result)
|
||||
(should (equal result "hello")))))
|
||||
|
||||
(provide 'test-ob-elixir-modules)
|
||||
;;; test-ob-elixir-modules.el ends here
|
||||
```
|
||||
|
||||
### Step 10: Update test loader
|
||||
|
||||
**File:** `test/test-ob-elixir.el`
|
||||
|
||||
**Add:** Require statement for the new test file (with other requires):
|
||||
|
||||
```elisp
|
||||
(require 'test-ob-elixir-modules)
|
||||
```
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `ob-elixir.el` | Add `ob-elixir--find-all-module-blocks` function |
|
||||
| `ob-elixir.el` | Add `ob-elixir--generate-module-definitions` function |
|
||||
| `ob-elixir.el` | Modify `org-babel-execute:elixir` - early return for `:module` blocks |
|
||||
| `ob-elixir.el` | Modify `ob-elixir--execute` - add `modules-string` parameter |
|
||||
| `ob-elixir.el` | Modify `ob-elixir--execute-with-deps` - add `modules-string` parameter |
|
||||
| `ob-elixir.el` | Modify `ob-elixir--evaluate-in-session` - add `modules-string` parameter |
|
||||
| `ob-elixir.el` | Modify `ob-elixir--evaluate-in-session-with-deps` - add `modules-string` parameter |
|
||||
| `ob-elixir.el` | Modify `org-babel-execute:elixir` - gather modules and pass to execution |
|
||||
| `test/test-ob-elixir-modules.el` | New file with module definition tests |
|
||||
| `test/test-ob-elixir.el` | Require new test file |
|
||||
|
||||
## Code Assembly Order
|
||||
|
||||
When a regular (non-module) block is executed, code is assembled in this order:
|
||||
|
||||
1. **Module definitions** (`defmodule ... end` for each gathered module)
|
||||
2. **Imports** (from `#+BEGIN_IMPORTS elixir` block)
|
||||
3. **Variable assignments** (from `:var` headers)
|
||||
4. **User code** (the block body, wrapped for value capture if needed)
|
||||
|
||||
## Verification
|
||||
|
||||
After implementation, test with this org file:
|
||||
|
||||
```org
|
||||
#+BEGIN_SRC elixir :module Helpers
|
||||
def greet(name), do: "Hello, #{name}!"
|
||||
def double(x), do: x * 2
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_SRC elixir :module Helpers
|
||||
def triple(x), do: x * 3
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_SRC elixir :module Math
|
||||
def square(x), do: x * x
|
||||
@doc "Adds two numbers"
|
||||
def add(a, b), do: a + b
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_IMPORTS elixir
|
||||
import Helpers
|
||||
import Math
|
||||
#+END_IMPORTS
|
||||
|
||||
#+BEGIN_SRC elixir :results value
|
||||
{greet("World"), double(5), triple(5), square(4), add(10, 20)}
|
||||
#+END_SRC
|
||||
```
|
||||
|
||||
Expected result: `{"Hello, World!", 10, 15, 16, 30}`
|
||||
|
||||
## Notes
|
||||
|
||||
- Module redefinition warnings may appear in sessions (this is expected Elixir behavior)
|
||||
- Only blocks **before** the current position are gathered (consistent with imports/deps behavior)
|
||||
- The `:module` header value should be a valid Elixir module name (e.g., `MyModule`, `My.Nested.Module`)
|
||||
- All standard Elixir module constructs are supported (`def`, `defp`, `@moduledoc`, `@doc`, `use`, `require`, `import`, `alias`, etc.)
|
||||
280
tasks/13a-module-definition-blocks-fix.md
Normal file
280
tasks/13a-module-definition-blocks-fix.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# Task 13a: Fix Module Definition Blocks - Code.eval_string Wrapper
|
||||
|
||||
## Problem
|
||||
|
||||
The initial implementation of module definition blocks (Task 13) has a fundamental issue: Elixir does not allow importing a module that is defined in the same file at the top level. When we generate code like:
|
||||
|
||||
```elixir
|
||||
defmodule Helpers do
|
||||
def double(x), do: x * 2
|
||||
end
|
||||
|
||||
import Helpers # ERROR: module Helpers is not loaded but was defined
|
||||
|
||||
double(21)
|
||||
```
|
||||
|
||||
Elixir produces this error:
|
||||
```
|
||||
error: module Helpers is not loaded but was defined. This happens when you
|
||||
depend on a module in the same context in which it is defined...
|
||||
If the module is defined at the top-level and you are trying to use it at
|
||||
the top-level, this is not supported by Elixir
|
||||
```
|
||||
|
||||
**Note:** The anonymous function wrapper approach does NOT work either - Elixir still
|
||||
compiles the function body at the same time as the module definition.
|
||||
|
||||
## Solution
|
||||
|
||||
Use `Code.eval_string/1` to defer the evaluation of imports and user code until runtime,
|
||||
after the module has been compiled and loaded:
|
||||
|
||||
```elixir
|
||||
defmodule Helpers do
|
||||
def double(x), do: x * 2
|
||||
end
|
||||
|
||||
Code.eval_string("""
|
||||
import Helpers
|
||||
|
||||
result = (
|
||||
double(21)
|
||||
)
|
||||
IO.puts(inspect(result, limit: :infinity, printable_limit: :infinity, charlists: :as_lists))
|
||||
""")
|
||||
```
|
||||
|
||||
This works because:
|
||||
1. The `defmodule` is compiled and loaded first
|
||||
2. `Code.eval_string/1` defers parsing and evaluation until runtime
|
||||
3. By the time the string is evaluated, the module is fully available
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Step 1: Add escape helper function
|
||||
|
||||
**File:** `ob-elixir.el` (before `ob-elixir--execute`)
|
||||
|
||||
**Add:**
|
||||
```elisp
|
||||
(defun ob-elixir--escape-for-eval-string (str)
|
||||
"Escape STR for use inside Elixir Code.eval_string heredoc.
|
||||
|
||||
Escapes backslashes and double quotes."
|
||||
(let ((result str))
|
||||
(setq result (replace-regexp-in-string "\\\\" "\\\\\\\\" result))
|
||||
(setq result (replace-regexp-in-string "\"" "\\\\\"" result))
|
||||
result))
|
||||
```
|
||||
|
||||
### Step 2: Modify `ob-elixir--execute`
|
||||
|
||||
**File:** `ob-elixir.el` (around line 968)
|
||||
|
||||
**New code assembly:**
|
||||
```elisp
|
||||
(code (if modules-string
|
||||
;; When modules are defined, wrap imports + code in Code.eval_string
|
||||
(let ((eval-body (concat
|
||||
(when imports-string (concat (string-trim imports-string) "\n"))
|
||||
wrapped)))
|
||||
(concat
|
||||
(string-trim modules-string) "\n\n"
|
||||
"Code.eval_string(\"\"\"\n"
|
||||
(ob-elixir--escape-for-eval-string eval-body)
|
||||
"\n\"\"\")"))
|
||||
;; Normal path without modules
|
||||
(concat
|
||||
(when imports-string (concat (string-trim imports-string) "\n\n"))
|
||||
wrapped)))
|
||||
```
|
||||
|
||||
### Step 3: Modify `ob-elixir--execute-with-deps`
|
||||
|
||||
**File:** `ob-elixir.el` (around line 610)
|
||||
|
||||
Apply the same Code.eval_string pattern as Step 2.
|
||||
|
||||
### Step 4: Modify `ob-elixir--evaluate-in-session`
|
||||
|
||||
**File:** `ob-elixir.el` (around line 736)
|
||||
|
||||
Apply the same Code.eval_string pattern as Step 2.
|
||||
|
||||
### Step 5: Modify `ob-elixir--evaluate-in-session-with-deps`
|
||||
|
||||
**File:** `ob-elixir.el` (around line 892)
|
||||
|
||||
Apply the same Code.eval_string pattern as Step 2.
|
||||
|
||||
### Step 6: Fix test expectations
|
||||
|
||||
**File:** `test/test-ob-elixir-modules.el`
|
||||
|
||||
The tests had incorrect expectations. When using Code.eval_string with modules:
|
||||
- Scalar numbers come back as strings (e.g., `"42"` not `42`)
|
||||
- String values come back with quotes (e.g., `"\"hello\""` not `"hello"`)
|
||||
- Lists are parsed correctly as before
|
||||
|
||||
Also, the test using `Config` as module name conflicted with Elixir's built-in Config module.
|
||||
Changed to `MyConfig`.
|
||||
|
||||
### Step 7: Run tests and verify
|
||||
|
||||
After making the changes:
|
||||
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
Expected: All 80 tests should pass, including the 6 that were previously failing:
|
||||
- `ob-elixir-test-module-integration-basic`
|
||||
- `ob-elixir-test-module-integration-merged`
|
||||
- `ob-elixir-test-module-integration-multiple-modules`
|
||||
- `ob-elixir-test-module-with-module-attributes`
|
||||
- `ob-elixir-test-module-with-private-functions`
|
||||
- `ob-elixir-test-module-without-import`
|
||||
|
||||
## Generated Code Examples
|
||||
|
||||
### Example 1: Basic module with import
|
||||
|
||||
**Input org file:**
|
||||
```org
|
||||
#+BEGIN_SRC elixir :module Helpers
|
||||
def double(x), do: x * 2
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_IMPORTS elixir
|
||||
import Helpers
|
||||
#+END_IMPORTS
|
||||
|
||||
#+BEGIN_SRC elixir :results value
|
||||
double(21)
|
||||
#+END_SRC
|
||||
```
|
||||
|
||||
**Generated Elixir code:**
|
||||
```elixir
|
||||
defmodule Helpers do
|
||||
def double(x), do: x * 2
|
||||
end
|
||||
|
||||
(fn ->
|
||||
import Helpers
|
||||
result = (
|
||||
double(21)
|
||||
)
|
||||
IO.puts(inspect(result, limit: :infinity, printable_limit: :infinity, charlists: :as_lists))
|
||||
end).()
|
||||
```
|
||||
|
||||
### Example 2: Multiple modules
|
||||
|
||||
**Input org file:**
|
||||
```org
|
||||
#+BEGIN_SRC elixir :module ModA
|
||||
def value, do: 10
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_SRC elixir :module ModB
|
||||
def value, do: 20
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_IMPORTS elixir
|
||||
alias ModA
|
||||
alias ModB
|
||||
#+END_IMPORTS
|
||||
|
||||
#+BEGIN_SRC elixir :results value
|
||||
ModA.value() + ModB.value()
|
||||
#+END_SRC
|
||||
```
|
||||
|
||||
**Generated Elixir code:**
|
||||
```elixir
|
||||
defmodule ModA do
|
||||
def value, do: 10
|
||||
end
|
||||
|
||||
defmodule ModB do
|
||||
def value, do: 20
|
||||
end
|
||||
|
||||
(fn ->
|
||||
alias ModA
|
||||
alias ModB
|
||||
result = (
|
||||
ModA.value() + ModB.value()
|
||||
)
|
||||
IO.puts(inspect(result, limit: :infinity, printable_limit: :infinity, charlists: :as_lists))
|
||||
end).()
|
||||
```
|
||||
|
||||
### Example 3: Without imports (using full module name)
|
||||
|
||||
**Input org file:**
|
||||
```org
|
||||
#+BEGIN_SRC elixir :module Helpers
|
||||
def greet, do: "hello"
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_SRC elixir :results value
|
||||
Helpers.greet()
|
||||
#+END_SRC
|
||||
```
|
||||
|
||||
**Generated Elixir code:**
|
||||
```elixir
|
||||
defmodule Helpers do
|
||||
def greet, do: "hello"
|
||||
end
|
||||
|
||||
(fn ->
|
||||
result = (
|
||||
Helpers.greet()
|
||||
)
|
||||
IO.puts(inspect(result, limit: :infinity, printable_limit: :infinity, charlists: :as_lists))
|
||||
end).()
|
||||
```
|
||||
|
||||
### Example 4: No modules defined (no wrapper needed)
|
||||
|
||||
**Input org file:**
|
||||
```org
|
||||
#+BEGIN_IMPORTS elixir
|
||||
import Enum
|
||||
#+END_IMPORTS
|
||||
|
||||
#+BEGIN_SRC elixir :results value
|
||||
map([1,2,3], &(&1 * 2))
|
||||
#+END_SRC
|
||||
```
|
||||
|
||||
**Generated Elixir code (no change from current behavior):**
|
||||
```elixir
|
||||
import Enum
|
||||
|
||||
result = (
|
||||
map([1,2,3], &(&1 * 2))
|
||||
)
|
||||
IO.puts(inspect(result, limit: :infinity, printable_limit: :infinity, charlists: :as_lists))
|
||||
```
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
| File | Function | Change |
|
||||
|------|----------|--------|
|
||||
| `ob-elixir.el` | `ob-elixir--execute` | Wrap imports+code in anon fn when modules present |
|
||||
| `ob-elixir.el` | `ob-elixir--execute-with-deps` | Wrap imports+code in anon fn when modules present |
|
||||
| `ob-elixir.el` | `ob-elixir--evaluate-in-session` | Wrap imports+code in anon fn when modules present |
|
||||
| `ob-elixir.el` | `ob-elixir--evaluate-in-session-with-deps` | Wrap imports+code in anon fn when modules present |
|
||||
| `test/test-ob-elixir-modules.el` | Various tests | Adjust expectations if needed |
|
||||
|
||||
## Notes
|
||||
|
||||
- The anonymous function wrapper is ONLY used when `modules-string` is non-nil
|
||||
- When no modules are defined, the code generation remains unchanged (backward compatible)
|
||||
- The wrapper doesn't affect the return value semantics - `result = (...)` still works
|
||||
- Session mode should work identically since modules persist after first evaluation
|
||||
Reference in New Issue
Block a user