docs and tasks
This commit is contained in:
435
docs/05-existing-implementations-analysis.md
Normal file
435
docs/05-existing-implementations-analysis.md
Normal file
@@ -0,0 +1,435 @@
|
||||
# Existing Implementations Analysis
|
||||
|
||||
This document analyzes existing org-babel Elixir implementations and related packages, identifying gaps and improvement opportunities.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [zweifisch/ob-elixir Analysis](#zweifischob-elixir-analysis)
|
||||
- [Comparison with Other ob-* Packages](#comparison-with-other-ob--packages)
|
||||
- [Feature Gap Analysis](#feature-gap-analysis)
|
||||
- [Recommendations](#recommendations)
|
||||
- [References](#references)
|
||||
|
||||
---
|
||||
|
||||
## zweifisch/ob-elixir Analysis
|
||||
|
||||
### Repository Information
|
||||
|
||||
- **URL**: https://github.com/zweifisch/ob-elixir
|
||||
- **Stars**: 29 (as of 2024)
|
||||
- **Last Updated**: Limited recent activity
|
||||
- **License**: GPL-3.0
|
||||
|
||||
### Source Code Review
|
||||
|
||||
```elisp
|
||||
;;; Current implementation (simplified)
|
||||
|
||||
(defvar ob-elixir-process-output "")
|
||||
|
||||
(defconst org-babel-header-args:elixir
|
||||
'((cookie . :any)
|
||||
(name . :any)
|
||||
(remsh . :any)
|
||||
(sname . :any))
|
||||
"elixir header arguments")
|
||||
|
||||
(defvar ob-elixir-eoe "\u2029") ; Unicode paragraph separator
|
||||
|
||||
(defun org-babel-execute:elixir (body params)
|
||||
(let ((session (cdr (assoc :session params)))
|
||||
(tmp (org-babel-temp-file "elixir-")))
|
||||
(ob-elixir-ensure-session session params)
|
||||
(with-temp-file tmp (insert body))
|
||||
(ob-elixir-eval session (format "import_file(\"%s\")" tmp))))
|
||||
|
||||
(defun ob-elixir-eval (session body)
|
||||
(let ((result (ob-elixir-eval-in-repl session body)))
|
||||
;; Heavy regex cleanup of IEx output
|
||||
(replace-regexp-in-string
|
||||
"^\\(import_file([^)]+)\\)+\n" ""
|
||||
(replace-regexp-in-string
|
||||
"\r" ""
|
||||
(replace-regexp-in-string
|
||||
"\n\\(\\(iex\\|[.]+\\)\\(([^@]+@[^)]+)[0-9]+\\|([0-9]+)\\)> \\)+" ""
|
||||
(replace-regexp-in-string
|
||||
"\e\\[[0-9;]*[A-Za-z]" ""
|
||||
(replace-regexp-in-string
|
||||
"\"\\\\u2029\"" ""
|
||||
result)))))))
|
||||
```
|
||||
|
||||
### Strengths
|
||||
|
||||
| Feature | Description |
|
||||
|---------------------|--------------------------------------------------------|
|
||||
| **Session support** | Uses IEx sessions for persistent state |
|
||||
| **Remote shell** | Supports `--remsh` for connecting to running nodes |
|
||||
| **Node naming** | Supports `--sname` and `--name` for distributed Erlang |
|
||||
| **Cookie support** | Can specify Erlang cookie for authentication |
|
||||
| **Simple design** | Minimal code, easy to understand |
|
||||
|
||||
### Weaknesses
|
||||
|
||||
| Issue | Description | Impact |
|
||||
|----------------------------|-----------------------------------------------------|----------------------------|
|
||||
| **Always uses sessions** | No one-shot execution mode | Slow for simple scripts |
|
||||
| **No `:results value`** | Only captures output, not return values | Limited functionality |
|
||||
| **No `:var` support** | Cannot pass variables to code blocks | Poor org-babel integration |
|
||||
| **Fragile output parsing** | Multiple regex replacements to clean IEx output | Unreliable |
|
||||
| **Uses `import_file`** | Relies on IEx-specific command | Tight coupling to IEx |
|
||||
| **Global state** | Uses `ob-elixir-process-output` global variable | Not thread-safe |
|
||||
| **No Mix support** | Cannot execute in Mix project context | Limited for real projects |
|
||||
| **Hardcoded `sit-for`** | Uses fixed delays instead of proper synchronization | Timing issues |
|
||||
| **No error handling** | Errors not properly detected or reported | Poor UX |
|
||||
| **No tests** | No test suite | Quality concerns |
|
||||
| **No `defcustom`** | Settings not customizable via Customize | Poor UX |
|
||||
|
||||
### Specific Issues in Code
|
||||
|
||||
1. **Timing-based synchronization**:
|
||||
```elisp
|
||||
(sit-for 0.5) ; Wait 500ms and hope IEx is ready
|
||||
;; This is fragile - slow systems may fail
|
||||
```
|
||||
|
||||
2. **No prompt detection**:
|
||||
```elisp
|
||||
(defun ob-elixir-wait ()
|
||||
(while (not (string-match-p ob-elixir-eoe ob-elixir-process-output))
|
||||
(sit-for 0.2)))
|
||||
;; Busy-waiting instead of using process filter properly
|
||||
```
|
||||
|
||||
3. **Heavy output cleanup**:
|
||||
```elisp
|
||||
;; 5 nested regex replacements - fragile and hard to debug
|
||||
(replace-regexp-in-string "pattern1" ""
|
||||
(replace-regexp-in-string "pattern2" ""
|
||||
(replace-regexp-in-string "pattern3" ""
|
||||
...)))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comparison with Other ob-* Packages
|
||||
|
||||
### ob-python (Standard Library)
|
||||
|
||||
**Features we should match:**
|
||||
|
||||
```elisp
|
||||
;; Multiple execution modes
|
||||
(defcustom org-babel-python-command "python3"
|
||||
"Command to execute Python code.")
|
||||
|
||||
;; Proper result handling
|
||||
(defun org-babel-execute:python (body params)
|
||||
(let* ((session (org-babel-python-initiate-session
|
||||
(cdr (assq :session params)) params))
|
||||
(result-params (cdr (assq :result-params params)))
|
||||
(result-type (cdr (assq :result-type params)))
|
||||
(full-body (org-babel-expand-body:generic ...))
|
||||
(result (org-babel-python-evaluate ...)))
|
||||
;; Proper result formatting
|
||||
(org-babel-reassemble-table
|
||||
(org-babel-result-cond result-params
|
||||
result
|
||||
(org-babel-python-table-or-string result))
|
||||
...)))
|
||||
|
||||
;; Variable injection
|
||||
(defun org-babel-variable-assignments:python (params)
|
||||
(mapcar (lambda (pair)
|
||||
(format "%s=%s" (car pair)
|
||||
(org-babel-python-var-to-python (cdr pair))))
|
||||
(org-babel--get-vars params)))
|
||||
|
||||
;; Both session and non-session evaluation
|
||||
(defun org-babel-python-evaluate (session body &optional result-type result-params)
|
||||
(if session
|
||||
(org-babel-python-evaluate-session ...)
|
||||
(org-babel-python-evaluate-external-process ...)))
|
||||
```
|
||||
|
||||
### ob-ruby (Standard Library)
|
||||
|
||||
**Patterns to adopt:**
|
||||
|
||||
```elisp
|
||||
;; Clean wrapper for value capture
|
||||
(defconst org-babel-ruby-wrapper-method
|
||||
"
|
||||
def main
|
||||
%s
|
||||
end
|
||||
puts main.inspect
|
||||
")
|
||||
|
||||
;; Proper session management with comint
|
||||
(defun org-babel-ruby-initiate-session (&optional session params)
|
||||
(unless (string= session "none")
|
||||
(let ((session-buffer (or (get-buffer ...) ...)))
|
||||
(if (org-babel-comint-buffer-livep session-buffer)
|
||||
session-buffer
|
||||
(org-babel-ruby-initiate-session session)))))
|
||||
|
||||
;; Type conversion
|
||||
(defun org-babel-ruby-var-to-ruby (var)
|
||||
(if (listp var)
|
||||
(concat "[" (mapconcat #'org-babel-ruby-var-to-ruby var ", ") "]")
|
||||
(format "%S" var)))
|
||||
```
|
||||
|
||||
### ob-shell (Standard Library)
|
||||
|
||||
**Useful patterns:**
|
||||
|
||||
```elisp
|
||||
;; Multiple shell types
|
||||
(defcustom org-babel-shell-names
|
||||
'("sh" "bash" "zsh" "fish" ...)
|
||||
"Shells to register with org-babel.")
|
||||
|
||||
;; Header arg for shell selection
|
||||
(defconst org-babel-header-args:shell
|
||||
'((shebang . :any)))
|
||||
|
||||
;; Proper prep-session
|
||||
(defun org-babel-prep-session:shell (session params)
|
||||
(let* ((session (org-babel-sh-initiate-session session))
|
||||
(var-lines (org-babel-variable-assignments:shell params)))
|
||||
(org-babel-comint-in-buffer session
|
||||
(mapc (lambda (var)
|
||||
(insert var) (comint-send-input nil t)
|
||||
(org-babel-comint-wait-for-output session))
|
||||
var-lines))
|
||||
session))
|
||||
```
|
||||
|
||||
### Feature Comparison Matrix
|
||||
|
||||
| Feature | ob-python | ob-ruby | ob-shell | ob-elixir (zweifisch) |
|
||||
|--------------------|-----------|---------|----------|-----------------------|
|
||||
| One-shot execution | Yes | Yes | Yes | No |
|
||||
| Session support | Yes | Yes | Yes | Yes |
|
||||
| `:results value` | Yes | Yes | Yes | No |
|
||||
| `:results output` | Yes | Yes | Yes | Yes (only) |
|
||||
| Variable injection | Yes | Yes | Yes | No |
|
||||
| Table output | Yes | Yes | Yes | No |
|
||||
| Error handling | Yes | Yes | Yes | No |
|
||||
| Customization | Yes | Yes | Yes | No |
|
||||
| Tests | Yes | Yes | Yes | No |
|
||||
| Graphics support | Yes | No | No | No |
|
||||
| Async support | Yes | No | No | No |
|
||||
|
||||
---
|
||||
|
||||
## Feature Gap Analysis
|
||||
|
||||
### Critical Gaps (Must Fix)
|
||||
|
||||
1. **No `:results value` support**
|
||||
- Cannot capture return value of expressions
|
||||
- Users must use `IO.inspect` workarounds
|
||||
- **Fix**: Implement wrapper method pattern
|
||||
|
||||
2. **No variable injection**
|
||||
- Cannot use `:var` header argument
|
||||
- Breaks org-babel's data passing paradigm
|
||||
- **Fix**: Implement `org-babel-variable-assignments:elixir`
|
||||
|
||||
3. **No one-shot execution**
|
||||
- Every evaluation starts/uses IEx session
|
||||
- Slow for simple scripts
|
||||
- **Fix**: Add external process execution mode
|
||||
|
||||
4. **Fragile output parsing**
|
||||
- Multiple regex replacements prone to breaking
|
||||
- **Fix**: Use markers or JSON encoding
|
||||
|
||||
### Important Gaps (Should Fix)
|
||||
|
||||
1. **No Mix project support**
|
||||
- Cannot run code in project context
|
||||
- No access to project dependencies
|
||||
- **Fix**: Add `:mix-project` header argument
|
||||
|
||||
2. **No proper error handling**
|
||||
- Errors appear as raw output
|
||||
- No structured error information
|
||||
- **Fix**: Detect and signal Elixir errors
|
||||
|
||||
3. **No customization**
|
||||
- Hardcoded paths and settings
|
||||
- **Fix**: Add `defcustom` for all configurable values
|
||||
|
||||
4. **No tests**
|
||||
- No way to verify correctness
|
||||
- **Fix**: Add comprehensive test suite
|
||||
|
||||
### Nice-to-Have Gaps (Could Fix)
|
||||
|
||||
1. **No async execution**
|
||||
- Long-running code blocks UI
|
||||
- Consider for future versions
|
||||
|
||||
2. **No graphics support**
|
||||
- Elixir has visualization libraries (VegaLite)
|
||||
- Could add in future
|
||||
|
||||
3. **No LSP integration**
|
||||
- Could integrate with ElixirLS for completions
|
||||
- Future enhancement
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Approach: New Implementation
|
||||
|
||||
Given the significant gaps and architectural issues in zweifisch/ob-elixir, we recommend **creating a new implementation** rather than forking. Reasons:
|
||||
|
||||
1. Need to change fundamental architecture (session-only → dual mode)
|
||||
2. Core data flow needs redesign
|
||||
3. Limited active maintenance upstream
|
||||
4. Cleaner API design possible with fresh start
|
||||
|
||||
### Proposed Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ org-babel-execute:elixir │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Session Mode │ │ External Mode │
|
||||
│ (IEx/comint) │ │ (elixir cmd) │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ With Mix │ │ With Mix │
|
||||
│ (iex -S mix) │ │ (mix run) │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
### Implementation Phases
|
||||
|
||||
#### Phase 1: Core (MVP)
|
||||
- [x] One-shot execution with `elixir` command
|
||||
- [x] `:results value` and `:results output` support
|
||||
- [x] Variable injection with `:var`
|
||||
- [x] Basic error detection
|
||||
- [x] Customizable commands
|
||||
- [x] Test suite foundation
|
||||
|
||||
#### Phase 2: Sessions
|
||||
- [ ] IEx session support via comint
|
||||
- [ ] Session persistence
|
||||
- [ ] Proper prompt detection
|
||||
- [ ] Session cleanup
|
||||
|
||||
#### Phase 3: Mix Integration
|
||||
- [ ] `:mix-project` header argument
|
||||
- [ ] Automatic Mix project detection
|
||||
- [ ] `iex -S mix` for sessions
|
||||
- [ ] `mix run` for one-shot
|
||||
|
||||
#### Phase 4: Advanced Features
|
||||
- [ ] Remote shell (remsh) support
|
||||
- [ ] Table input/output
|
||||
- [ ] Async execution
|
||||
- [ ] Better error messages with line numbers
|
||||
|
||||
### Header Arguments to Support
|
||||
|
||||
```elisp
|
||||
(defconst org-babel-header-args:elixir
|
||||
'(;; Standard org-babel args work automatically
|
||||
;; Language-specific:
|
||||
(mix-project . :any) ; Path to mix.exs directory
|
||||
(mix-env . :any) ; MIX_ENV (dev, test, prod)
|
||||
(iex-args . :any) ; Extra IEx arguments
|
||||
(timeout . :any) ; Execution timeout
|
||||
(node-name . :any) ; --name for distributed
|
||||
(node-sname . :any) ; --sname for distributed
|
||||
(cookie . :any) ; Erlang cookie
|
||||
(remsh . :any)) ; Remote shell node
|
||||
"Elixir-specific header arguments.")
|
||||
```
|
||||
|
||||
### Example Usage (Target)
|
||||
|
||||
```org
|
||||
* Basic Evaluation
|
||||
|
||||
#+BEGIN_SRC elixir
|
||||
Enum.map([1, 2, 3], &(&1 * 2))
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
| 2 | 4 | 6 |
|
||||
|
||||
* With Variables
|
||||
|
||||
#+NAME: my-data
|
||||
| a | 1 |
|
||||
| b | 2 |
|
||||
|
||||
#+BEGIN_SRC elixir :var data=my-data
|
||||
Enum.map(data, fn [k, v] -> {k, v * 10} end)
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
| a | 10 |
|
||||
| b | 20 |
|
||||
|
||||
* In Mix Project
|
||||
|
||||
#+BEGIN_SRC elixir :mix-project ~/my_app :mix-env test
|
||||
MyApp.some_function()
|
||||
#+END_SRC
|
||||
|
||||
* With Session
|
||||
|
||||
#+BEGIN_SRC elixir :session my-session
|
||||
defmodule Helper do
|
||||
def double(x), do: x * 2
|
||||
end
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_SRC elixir :session my-session
|
||||
Helper.double(21)
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
: 42
|
||||
|
||||
* Capturing Output
|
||||
|
||||
#+BEGIN_SRC elixir :results output
|
||||
IO.puts("Hello")
|
||||
IO.puts("World")
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
: Hello
|
||||
: World
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [zweifisch/ob-elixir](https://github.com/zweifisch/ob-elixir)
|
||||
- [Org Mode ob-python.el](https://git.savannah.gnu.org/cgit/emacs/org-mode.git/tree/lisp/ob-python.el)
|
||||
- [Org Mode ob-ruby.el](https://git.savannah.gnu.org/cgit/emacs/org-mode.git/tree/lisp/ob-ruby.el)
|
||||
- [Org Mode ob-shell.el](https://git.savannah.gnu.org/cgit/emacs/org-mode.git/tree/lisp/ob-shell.el)
|
||||
- [Org Mode ob-emacs-lisp.el](https://git.savannah.gnu.org/cgit/emacs/org-mode.git/tree/lisp/ob-emacs-lisp.el)
|
||||
- [elixir-editors/emacs-elixir](https://github.com/elixir-editors/emacs-elixir)
|
||||
- [Worg Babel Languages](https://orgmode.org/worg/org-contrib/babel/languages/)
|
||||
Reference in New Issue
Block a user