From 853516b47e568490956d6b9c433ea4ea4263e052 Mon Sep 17 00:00:00 2001 From: Luis Eduardo Bueso de Barrio Date: Sat, 24 Jan 2026 17:40:13 +0100 Subject: [PATCH] tasks/05-result-formatting.md done --- ob-elixir.el | 106 ++++++++++++++++++++++++++++++++++++++++- test/test-ob-elixir.el | 45 +++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) diff --git a/ob-elixir.el b/ob-elixir.el index e7affad..2e456b3 100644 --- a/ob-elixir.el +++ b/ob-elixir.el @@ -236,6 +236,108 @@ Handles: ;; Fallback (t (format "%S" value)))) +;;; Result Formatting + +(defvar ob-elixir-nil-to 'hline + "Elisp value to use for Elixir nil in table cells. + +When nil appears in an Elixir list that becomes a table, +it is replaced with this value. Use `hline' for org table +horizontal lines, or nil for empty cells.") + +(defun ob-elixir--parse-value (str) + "Parse STR as a simple Elixir value." + (let ((trimmed (string-trim str))) + (cond + ((string= trimmed "nil") nil) + ((string= trimmed "true") t) + ((string= trimmed "false") nil) + ((string-match-p "^[0-9]+$" trimmed) + (string-to-number trimmed)) + ((string-match-p "^[0-9]+\\.[0-9]+$" trimmed) + (string-to-number trimmed)) + ((string-match-p "^\".*\"$" trimmed) + (substring trimmed 1 -1)) + ((string-match-p "^:.*$" trimmed) + (intern (substring trimmed 1))) + (t trimmed)))) + +(defun ob-elixir--parse-keyword-list (str) + "Parse STR as Elixir keyword list into alist. + +Handles format like: [a: 1, b: 2]" + (when (string-match "^\\[\\(.*\\)\\]$" str) + (let ((content (match-string 1 str))) + (when (string-match-p "^[a-z_]+:" content) + (let ((pairs '())) + (dolist (part (split-string content ", ")) + (when (string-match "^\\([a-z_]+\\):\\s-*\\(.+\\)$" part) + (push (cons (intern (match-string 1 part)) + (ob-elixir--parse-value (match-string 2 part))) + pairs))) + (nreverse pairs)))))) + +(defun ob-elixir--sanitize-row (row) + "Sanitize a single ROW for table display." + (if (listp row) + (mapcar (lambda (cell) + (cond + ((null cell) ob-elixir-nil-to) + ((eq cell 'nil) ob-elixir-nil-to) + (t cell))) + row) + row)) + +(defun ob-elixir--sanitize-table (data) + "Sanitize DATA for use as an org table. + +Replaces nil values according to `ob-elixir-nil-to'. +Ensures consistent structure for table rendering." + (cond + ;; Not a list - return as-is + ((not (listp data)) data) + + ;; Empty list + ((null data) nil) + + ;; List of lists - could be table + ((and (listp (car data)) (not (null (car data)))) + (mapcar #'ob-elixir--sanitize-row data)) + + ;; Simple list - single row + (t (ob-elixir--sanitize-row data)))) + +(defun ob-elixir--table-or-string (result) + "Convert RESULT to Emacs table or string. + +If RESULT looks like a list, parse it into an Elisp list. +Otherwise return as string. + +Uses `org-babel-script-escape' for parsing." + (let ((trimmed (string-trim result))) + (cond + ;; Empty result + ((string-empty-p trimmed) nil) + + ;; Looks like a list - try to parse + ((string-match-p "^\\[.*\\]$" trimmed) + (condition-case nil + (let ((parsed (org-babel-script-escape trimmed))) + (ob-elixir--sanitize-table parsed)) + (error trimmed))) + + ;; Looks like a tuple - convert to list first + ((string-match-p "^{.*}$" trimmed) + (condition-case nil + (let* ((as-list (replace-regexp-in-string + "^{\\(.*\\)}$" "[\\1]" trimmed)) + (parsed (org-babel-script-escape as-list))) + (ob-elixir--sanitize-table parsed)) + (error trimmed))) + + ;; Scalar value + (t trimmed)))) + ;;; Variable Handling (defun ob-elixir--var-name (name) @@ -318,8 +420,10 @@ This function is called by `org-babel-execute-src-block'." (result (ob-elixir--execute full-body result-type))) (org-babel-reassemble-table (org-babel-result-cond result-params + ;; For output/scalar/verbatim - return as-is result - (org-babel-script-escape 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)) diff --git a/test/test-ob-elixir.el b/test/test-ob-elixir.el index 4ca8a3c..4b5724c 100644 --- a/test/test-ob-elixir.el +++ b/test/test-ob-elixir.el @@ -176,5 +176,50 @@ (let ((result (ob-elixir--execute "undefined_function()" 'value))) (should (string-match-p "\\(UndefinedFunctionError\\|CompileError\\)" result))))) +;;; Result Formatting Tests + +(ert-deftest ob-elixir-test-parse-simple-list () + "Test parsing simple list result." + (should (equal '(1 2 3) (ob-elixir--table-or-string "[1, 2, 3]")))) + +(ert-deftest ob-elixir-test-parse-nested-list () + "Test parsing nested list (table) result." + (should (equal '((1 2) (3 4)) + (ob-elixir--table-or-string "[[1, 2], [3, 4]]")))) + +(ert-deftest ob-elixir-test-parse-tuple () + "Test parsing tuple result." + (should (equal '(1 2 3) (ob-elixir--table-or-string "{1, 2, 3}")))) + +(ert-deftest ob-elixir-test-parse-scalar () + "Test that scalars are returned as strings." + (should (equal "42" (ob-elixir--table-or-string "42"))) + (should (equal ":ok" (ob-elixir--table-or-string ":ok")))) + +(ert-deftest ob-elixir-test-parse-string () + "Test parsing string result." + (should (equal "\"hello\"" (ob-elixir--table-or-string "\"hello\"")))) + +(ert-deftest ob-elixir-test-sanitize-table-nil () + "Test that nil values are sanitized in tables." + (let ((ob-elixir-nil-to 'hline)) + (should (equal '((1 hline) (hline 2)) + (ob-elixir--sanitize-table '((1 nil) (nil 2))))))) + +(ert-deftest ob-elixir-test-execution-returns-table () + "Test that list results become tables." + (skip-unless (executable-find ob-elixir-command)) + (let ((result (ob-elixir--table-or-string + (ob-elixir--execute "[[1, 2], [3, 4]]" 'value)))) + (should (equal '((1 2) (3 4)) result)))) + +(ert-deftest ob-elixir-test-mixed-list () + "Test parsing mixed-type list." + (skip-unless (executable-find ob-elixir-command)) + (let ((result (ob-elixir--table-or-string + (ob-elixir--execute "[1, \"two\", :three]" 'value)))) + (should (listp result)) + (should (= 3 (length result))))) + (provide 'test-ob-elixir) ;;; test-ob-elixir.el ends here