diff --git a/README.org b/README.org new file mode 100644 index 0000000..374ab4e --- /dev/null +++ b/README.org @@ -0,0 +1,345 @@ +#+TITLE: ob-elixir +#+AUTHOR: Luis Eduardo Bueso de Barrio +#+LANGUAGE: en + +Org Babel support for evaluating Elixir code blocks in Emacs org-mode. + +* Features + +- Execute Elixir code directly in org-mode source blocks +- Support for =:results value= and =:results output= +- Variable passing with =:var= header argument +- Persistent IEx sessions with =:session= +- Mix dependency management via =#+BEGIN_DEPS= blocks +- Shared imports/aliases via =#+BEGIN_IMPORTS= blocks +- Module definitions with =:module= header argument +- Automatic conversion of Elixir lists to org tables +- Configurable error handling and warning display + +* Requirements + +- Emacs 27.1 or later +- Org-mode 9.4 or later +- Elixir installed and available in PATH + +* Installation + +** With straight.el + +#+begin_src emacs-lisp +(straight-use-package + '(ob-elixir :type git :host github :repo "username/ob-elixir")) +#+end_src + +** With use-package and straight.el + +#+begin_src emacs-lisp +(use-package ob-elixir + :straight (:type git :host github :repo "username/ob-elixir")) +#+end_src + +** Manual Installation + +Clone the repository and add it to your load path: + +#+begin_src emacs-lisp +(add-to-list 'load-path "/path/to/ob-elixir") +(require 'ob-elixir) +#+end_src + +* Configuration + +Enable Elixir in org-babel: + +#+begin_src emacs-lisp +(org-babel-do-load-languages + 'org-babel-load-languages + '((elixir . t))) +#+end_src + +* Usage + +** Basic Execution + +Execute Elixir code with =C-c C-c=: + +#+begin_example +,#+begin_src elixir +1 + 2 +,#+end_src + +,#+RESULTS: +: 3 +#+end_example + +** Results: Value vs Output + +By default, blocks return the value of the last expression (=:results value=): + +#+begin_example +,#+begin_src elixir :results value +Enum.map([1, 2, 3], fn x -> x * 2 end) +,#+end_src + +,#+RESULTS: +| 2 | 4 | 6 | +#+end_example + +Use =:results output= to capture printed output: + +#+begin_example +,#+begin_src elixir :results output +IO.puts("Hello, World!") +IO.puts("Line 2") +,#+end_src + +,#+RESULTS: +: Hello, World! +: Line 2 +#+end_example + +** Variables + +Pass variables to Elixir code with =:var=: + +#+begin_example +,#+begin_src elixir :var name="Elixir" count=3 +String.duplicate(name, count) +,#+end_src + +,#+RESULTS: +: ElixirElixirElixir +#+end_example + +Variables are automatically converted: +- Elisp strings become Elixir strings +- Elisp numbers become Elixir numbers +- Elisp lists become Elixir lists +- Elisp vectors become Elixir tuples +- Elisp =t= becomes =true=, =nil= becomes =nil= +- Elisp symbols become atoms + +** Sessions + +Use =:session= to maintain state across blocks: + +#+begin_example +,#+begin_src elixir :session my-session +x = 10 +,#+end_src + +,#+RESULTS: +: 10 + +,#+begin_src elixir :session my-session +x * 2 +,#+end_src + +,#+RESULTS: +: 20 +#+end_example + +Manage sessions with: +- =M-x ob-elixir-kill-session= - Kill a specific session +- =M-x ob-elixir-kill-all-sessions= - Kill all sessions + +** Dependencies + +Define Mix dependencies with =#+BEGIN_DEPS= blocks. Dependencies apply to all subsequent Elixir blocks in the document: + +#+begin_example +,#+BEGIN_DEPS elixir +[ + {:jason, "~> 1.4"}, + {:decimal, "~> 2.0"} +] +,#+END_DEPS + +,#+begin_src elixir +Jason.encode!(%{hello: "world"}) +,#+end_src + +,#+RESULTS: +: {"hello":"world"} +#+end_example + +Dependencies are cached in =~/.cache/ob-elixir/= (configurable via =ob-elixir-deps-cache-dir=). The first execution fetches and compiles dependencies; subsequent executions reuse the cache. + +Manage dependency projects with: +- =M-x ob-elixir-list-deps-projects= - List cached projects +- =M-x ob-elixir-cleanup-deps-projects= - Delete all cached projects + +** Imports + +Define shared imports, aliases, and requires with =#+BEGIN_IMPORTS= blocks: + +#+begin_example +,#+BEGIN_IMPORTS elixir +import Enum, only: [map: 2, filter: 2] +alias String, as: S +,#+END_IMPORTS + +,#+begin_src elixir +map([1, 2, 3], &(&1 * 2)) |> filter(&(&1 > 2)) +,#+end_src + +,#+RESULTS: +| 4 | 6 | + +,#+begin_src elixir +S.upcase("hello") +,#+end_src + +,#+RESULTS: +: HELLO +#+end_example + +** Module Definitions + +Define reusable modules with the =:module= header argument: + +#+begin_example +,#+begin_src elixir :module MyMath +def add(a, b), do: a + b +def multiply(a, b), do: a * b +,#+end_src + +,#+RESULTS: +: Module MyMath: functions defined +#+end_example + +Multiple blocks with the same =:module= name merge their contents: + +#+begin_example +,#+begin_src elixir :module MyMath +def subtract(a, b), do: a - b +,#+end_src +#+end_example + +Use the module in subsequent blocks (requires explicit import): + +#+begin_example +,#+BEGIN_IMPORTS elixir +import MyMath +,#+END_IMPORTS + +,#+begin_src elixir +add(1, 2) |> multiply(3) +,#+end_src + +,#+RESULTS: +: 9 +#+end_example + +** Tables + +Elixir lists are automatically converted to org tables: + +#+begin_example +,#+begin_src elixir +[ + ["Name", "Age"], + ["Alice", 30], + ["Bob", 25] +] +,#+end_src + +,#+RESULTS: +| Name | Age | +| Alice | 30 | +| Bob | 25 | +#+end_example + +Tuples are also supported: + +#+begin_example +,#+begin_src elixir +{:ok, "success"} +,#+end_src + +,#+RESULTS: +| ok | success | +#+end_example + +* Customization + +** Commands + +- =ob-elixir-command= (default: ="elixir"=) - Command to execute Elixir code +- =ob-elixir-iex-command= (default: ="iex"=) - Command to start IEx sessions +- =ob-elixir-mix-command= (default: ="mix"=) - Command to run Mix + +** Behavior + +- =ob-elixir-signal-errors= (default: =t=) - When non-nil, Elixir errors are signaled as Emacs errors. When nil, errors are returned as result strings. +- =ob-elixir-show-warnings= (default: =t=) - When non-nil, Elixir warnings are included in output. + +** Paths + +- =ob-elixir-deps-cache-dir= (default: =~/.cache/ob-elixir/=) - Directory for caching temporary Mix projects created for dependencies. + +** Example Configuration + +#+begin_src emacs-lisp +(use-package ob-elixir + :after org + :config + (setq ob-elixir-signal-errors nil) ; Return errors as results + (setq ob-elixir-show-warnings nil) ; Hide warnings + (org-babel-do-load-languages + 'org-babel-load-languages + '((elixir . t)))) +#+end_src + +* Development + +** Running Tests + +Using Make: + +#+begin_src sh +make test +#+end_src + +Using Eldev: + +#+begin_src sh +eldev test +#+end_src + +** Byte Compilation + +#+begin_src sh +make compile +#+end_src + +** Linting + +#+begin_src sh +make lint +#+end_src + +** Project Structure + +#+begin_example +ob-elixir/ +├── ob-elixir.el # Main source file +├── Makefile # Build and test commands +├── Eldev # Eldev configuration +├── test/ # Test suite +│ ├── test-ob-elixir.el +│ ├── test-ob-elixir-core.el +│ ├── test-ob-elixir-vars.el +│ ├── test-ob-elixir-results.el +│ ├── test-ob-elixir-errors.el +│ ├── test-ob-elixir-org.el +│ ├── test-ob-elixir-deps.el +│ ├── test-ob-elixir-imports.el +│ ├── test-ob-elixir-modules.el +│ └── test-ob-elixir-sessions.el +└── docs/ # Implementation documentation +#+end_example + +* License + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.