# Task 12: Add Imports Block Support ## Problem When using common imports (`import`, `alias`, `use`, `require`) across multiple Elixir code blocks in an org file, users must manually add these lines to each block, leading to repetition and maintenance burden. ## Desired Behavior Users can define an imports block that automatically prepends its content to all subsequent Elixir code blocks: ```org #+BEGIN_IMPORTS elixir import Enum alias MyApp.Helpers, as: H require Logger #+END_IMPORTS #+begin_src elixir # This block will have the imports prepended automatically map([1, 2, 3], &(&1 * 2)) #+end_src ``` ## Scope - Imports block applies to all elixir blocks **after** it until the next imports block (or end of file) - Works with both session and non-session modes - Works with deps blocks (imports prepended after deps are loaded) ## Implementation Plan ### Step 1: Define the imports block regexp **File:** `ob-elixir.el` (near line 418, after `ob-elixir--deps-block-regexp`) **Add:** A new constant for matching imports blocks: ```elisp (defconst ob-elixir--imports-block-regexp "^[ \t]*#\\+BEGIN_IMPORTS[ \t]+elixir[ \t]*\n\\(\\(?:.*\n\\)*?\\)[ \t]*#\\+END_IMPORTS" "Regexp matching an imports block. Group 1 captures the imports content.") ``` ### Step 2: Add function to find imports for a position **File:** `ob-elixir.el` (after `ob-elixir--find-deps-for-position`) **Add:** A function to find the most recent imports block before a given position: ```elisp (defun ob-elixir--find-imports-for-position (pos) "Find the most recent imports block before POS. Returns the imports content as a string, or nil if no imports block found." (save-excursion (goto-char pos) (let ((found nil)) (while (and (not found) (re-search-backward ob-elixir--imports-block-regexp nil t)) (when (< (match-end 0) pos) (setq found (match-string-no-properties 1)))) found))) ``` ### Step 3: Modify `org-babel-execute:elixir` to prepend imports **File:** `ob-elixir.el` (in `org-babel-execute:elixir` function, around line 923) **Current code:** ```elisp (deps-string (ob-elixir--find-deps-for-position (point))) ;; Expand body with variable assignments (full-body (org-babel-expand-body:generic body params (org-babel-variable-assignments:elixir params))) ``` **Proposed change:** ```elisp (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))) ;; Expand body with variable assignments (full-body (let ((expanded (org-babel-expand-body:generic body params (org-babel-variable-assignments:elixir params)))) ;; Prepend imports if present (if imports-string (concat (string-trim imports-string) "\n\n" expanded) expanded))) ``` **Rationale:** - Imports are found before body expansion - Imports are prepended to the fully expanded body (after variable assignments) - The `string-trim` ensures no extra whitespace issues - A blank line separates imports from the user's code for readability ### Step 4: Add test file for imports functionality **File:** `test/test-ob-elixir-imports.el` (new file) ```elisp ;;; test-ob-elixir-imports.el --- Imports block tests -*- lexical-binding: t; -*- ;;; Commentary: ;; Tests for the imports block functionality. ;;; Code: (require 'ert) (require 'ob-elixir) ;;; Imports Block Parsing Tests (ert-deftest ob-elixir-test-imports-block-parsing () "Test that imports blocks are correctly parsed." (with-temp-buffer (insert "#+BEGIN_IMPORTS elixir\nimport Enum\nalias Foo\n#+END_IMPORTS\n") (goto-char (point-max)) (let ((imports (ob-elixir--find-imports-for-position (point)))) (should imports) (should (string-match-p "import Enum" imports)) (should (string-match-p "alias Foo" imports))))) (ert-deftest ob-elixir-test-no-imports-block () "Test that nil is returned when no imports block exists." (with-temp-buffer (insert "#+begin_src elixir\n1 + 1\n#+end_src\n") (should (null (ob-elixir--find-imports-for-position (point)))))) (ert-deftest ob-elixir-test-imports-block-before-position () "Test that imports block must be before position." (with-temp-buffer (insert "#+begin_src elixir\n1 + 1\n#+end_src\n") (let ((pos (point))) (insert "#+BEGIN_IMPORTS elixir\nimport Enum\n#+END_IMPORTS\n") (should (null (ob-elixir--find-imports-for-position pos)))))) (ert-deftest ob-elixir-test-imports-block-override () "Test that later imports blocks override earlier ones." (with-temp-buffer (insert "#+BEGIN_IMPORTS elixir\nimport Enum\n#+END_IMPORTS\n") (insert "#+BEGIN_IMPORTS elixir\nimport String\n#+END_IMPORTS\n") (goto-char (point-max)) (let ((imports (ob-elixir--find-imports-for-position (point)))) (should imports) (should (string-match-p "import String" imports)) (should-not (string-match-p "import Enum" imports))))) ;;; Imports Execution Tests (ert-deftest ob-elixir-test-imports-execution () "Test that imports are applied during execution." (skip-unless (executable-find ob-elixir-command)) (with-temp-buffer (org-mode) (insert "#+BEGIN_IMPORTS elixir\nimport Enum\n#+END_IMPORTS\n\n") (insert "#+begin_src elixir :results value\nmap([1,2,3], &(&1 * 2))\n#+end_src\n") (goto-char (point-min)) (search-forward "#+begin_src") (let ((result (org-babel-execute-src-block))) ;; Without import, this would fail because map/2 requires Enum prefix (should result) (should (equal result '(2 4 6)))))) (ert-deftest ob-elixir-test-imports-with-alias () "Test that alias works in imports block." (skip-unless (executable-find ob-elixir-command)) (with-temp-buffer (org-mode) (insert "#+BEGIN_IMPORTS elixir\nalias String, as: S\n#+END_IMPORTS\n\n") (insert "#+begin_src elixir :results value\nS.upcase(\"hello\")\n#+end_src\n") (goto-char (point-min)) (search-forward "#+begin_src") (let ((result (org-babel-execute-src-block))) (should (equal result "HELLO"))))) (provide 'test-ob-elixir-imports) ;;; test-ob-elixir-imports.el ends here ``` ### Step 5: 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-imports) ``` ## Summary of Changes | File | Change | |------|--------| | `ob-elixir.el` | Add `ob-elixir--imports-block-regexp` constant | | `ob-elixir.el` | Add `ob-elixir--find-imports-for-position` function | | `ob-elixir.el` | Modify `org-babel-execute:elixir` to prepend imports | | `test/test-ob-elixir-imports.el` | New file with imports tests | | `test/test-ob-elixir.el` | Require new test file | ## Files NOT Modified - Session-related code (imports will work automatically since they're prepended to body) - Deps handling (imports are independent, applied after variable expansion) ## Verification After implementation, test with: ```org #+BEGIN_IMPORTS elixir import Enum alias String, as: S #+END_IMPORTS #+begin_src elixir # Both of these should work without prefixes result = map([1, 2, 3], &(&1 * 2)) S.upcase("hello") #+end_src ``` Expected: The code block executes successfully using the imported `map/2` function and the `S` alias.