# Emacs Lisp Best Practices for Package Development This document covers best practices for writing Emacs Lisp packages, specifically targeting org-babel language implementations. ## Table of Contents - [File Structure and Headers](#file-structure-and-headers) - [Naming Conventions](#naming-conventions) - [Variables and Customization](#variables-and-customization) - [Function Definitions](#function-definitions) - [Error Handling](#error-handling) - [Documentation](#documentation) - [Dependencies and Loading](#dependencies-and-loading) - [References](#references) --- ## File Structure and Headers Every Emacs Lisp package file should follow a standard structure: ```elisp ;;; ob-elixir.el --- Org Babel functions for Elixir evaluation -*- lexical-binding: t; -*- ;; Copyright (C) 2024 Your Name ;; Author: Your Name ;; URL: https://github.com/username/ob-elixir ;; Keywords: literate programming, reproducible research, elixir ;; Version: 1.0.0 ;; Package-Requires: ((emacs "27.1") (org "9.4")) ;; This file is not part of GNU Emacs. ;; 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. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; This package provides Org Babel support for evaluating Elixir code blocks. ;; ;; Features: ;; - Execute Elixir code in org-mode source blocks ;; - Support for Mix project context ;; - Session support via IEx ;; - Variable passing between code blocks ;; ;; Usage: ;; Add (elixir . t) to `org-babel-load-languages': ;; ;; (org-babel-do-load-languages ;; 'org-babel-load-languages ;; '((elixir . t))) ;;; Code: (require 'ob) (require 'ob-ref) (require 'ob-comint) (require 'ob-eval) ;; ... your code here ... (provide 'ob-elixir) ;;; ob-elixir.el ends here ``` ### Key Header Elements | Element | Purpose | Example | |----------------------|--------------------------------------------|-----------------------| | `lexical-binding: t` | Enable lexical scoping (always use this) | First line comment | | `Package-Requires` | Declare dependencies with minimum versions | `((emacs "27.1"))` | | `Keywords` | Help with package discovery | Standard keyword list | | `Version` | Semantic versioning | `1.0.0` | --- ## Naming Conventions ### Package Prefix All symbols (functions, variables, constants) must be prefixed with the package name to avoid namespace collisions: ```elisp ;; GOOD - properly prefixed (defvar ob-elixir-command "elixir") (defun ob-elixir-evaluate (body params) ...) (defconst ob-elixir-eoe-indicator "---ob-elixir-eoe---") ;; BAD - no prefix, will collide (defvar elixir-command "elixir") (defun evaluate-elixir (body) ...) ``` ### Naming Patterns for org-babel Org-babel uses specific naming conventions that must be followed: ```elisp ;; Required function - note the colon syntax (defun org-babel-execute:elixir (body params) "Execute a block of Elixir code with Babel.") ;; Optional session function (defun org-babel-elixir-initiate-session (&optional session params) "Create or return an Elixir session buffer.") ;; Variable assignments function (defun org-babel-variable-assignments:elixir (params) "Return Elixir code to assign variables.") ;; Default header arguments variable (defvar org-babel-default-header-args:elixir '() "Default header arguments for Elixir code blocks.") ;; Prep session function (defun org-babel-prep-session:elixir (session params) "Prepare SESSION with PARAMS.") ``` ### Private Functions Use double hyphens for internal/private functions: ```elisp ;; Public API (defun ob-elixir-evaluate (body params) "Evaluate BODY with PARAMS.") ;; Internal helper - note the double hyphen (defun ob-elixir--format-value (value) "Internal: Format VALUE for Elixir.") (defun ob-elixir--clean-output (output) "Internal: Remove ANSI codes from OUTPUT.") ``` --- ## Variables and Customization ### User-Customizable Variables Use `defcustom` for variables users should configure: ```elisp (defgroup ob-elixir nil "Org Babel support for Elixir." :group 'org-babel :prefix "ob-elixir-") (defcustom ob-elixir-command "elixir" "Command to invoke Elixir. Can be a full path or just the command name if in PATH." :type 'string :group 'ob-elixir) (defcustom ob-elixir-iex-command "iex" "Command to invoke IEx for session mode." :type 'string :group 'ob-elixir) (defcustom ob-elixir-mix-command "mix" "Command to invoke Mix." :type 'string :group 'ob-elixir) (defcustom ob-elixir-default-session-timeout 30 "Default timeout in seconds for session operations." :type 'integer :group 'ob-elixir) ;; For options with specific choices (defcustom ob-elixir-output-format 'value "Default output format for results." :type '(choice (const :tag "Return value" value) (const :tag "Standard output" output)) :group 'ob-elixir) ``` ### Internal Variables Use `defvar` for internal state: ```elisp (defvar ob-elixir--session-buffer nil "Current active session buffer. This is internal state and should not be modified directly.") (defvar ob-elixir--pending-output "" "Accumulated output waiting to be processed.") ``` ### Constants Use `defconst` for values that never change: ```elisp (defconst ob-elixir-eoe-indicator "__ob_elixir_eoe__" "String used to indicate end of evaluation output.") (defconst ob-elixir-error-regexp "\\*\\* (\\w+Error)" "Regexp to match Elixir error messages.") ``` --- ## Function Definitions ### Function Documentation Always include comprehensive docstrings: ```elisp (defun ob-elixir-evaluate (body params) "Evaluate BODY as Elixir code according to PARAMS. BODY is the source code string to execute. PARAMS is an alist of header arguments. Supported header arguments: :session - Name of IEx session to use, or \"none\" :results - How to handle results (value, output) :var - Variables to inject into the code Returns the result as a string, or nil if execution failed. Example: (ob-elixir-evaluate \"1 + 1\" \\='((:results . \"value\")))" ...) ``` ### Interactive Commands Mark user-facing commands as interactive: ```elisp (defun ob-elixir-check-installation () "Check if Elixir is properly installed and accessible. Display version information in the minibuffer." (interactive) (let ((version (shell-command-to-string (format "%s --version" ob-elixir-command)))) (message "Elixir: %s" (string-trim version)))) ``` ### Using `cl-lib` Properly Prefer `cl-lib` functions over deprecated `cl` package: ```elisp (require 'cl-lib) ;; Use cl- prefixed versions (cl-defun ob-elixir-parse-result (output &key (format 'value)) "Parse OUTPUT according to FORMAT." ...) (cl-loop for pair in params when (eq (car pair) :var) collect (cdr pair)) (cl-destructuring-bind (name . value) variable ...) ``` --- ## Error Handling ### Signaling Errors Use appropriate error functions: ```elisp ;; For user errors (incorrect usage) (user-error "No Elixir session found for %s" session-name) ;; For programming errors (error "Invalid parameter: %S" param) ;; Custom error types (define-error 'ob-elixir-error "Ob-elixir error") (define-error 'ob-elixir-execution-error "Elixir execution error" 'ob-elixir-error) (define-error 'ob-elixir-session-error "Elixir session error" 'ob-elixir-error) ;; Signaling custom errors (signal 'ob-elixir-execution-error (list "Compilation failed" output)) ``` ### Handling Errors Gracefully ```elisp (defun ob-elixir-safe-evaluate (body params) "Safely evaluate BODY, handling errors gracefully." (condition-case err (ob-elixir-evaluate body params) (ob-elixir-execution-error (message "Elixir error: %s" (cadr err)) nil) (file-error (user-error "Cannot access Elixir command: %s" (error-message-string err))) (error (message "Unexpected error: %s" (error-message-string err)) nil))) ``` ### Input Validation ```elisp (defun ob-elixir-evaluate (body params) "Evaluate BODY with PARAMS." ;; Validate inputs early (unless (stringp body) (error "BODY must be a string, got %S" (type-of body))) (unless (listp params) (error "PARAMS must be an alist")) (when (string-empty-p body) (user-error "Cannot evaluate empty code block")) ...) ``` --- ## Documentation ### Commentary Section Write helpful commentary: ```elisp ;;; Commentary: ;; ob-elixir provides Org Babel support for the Elixir programming language. ;; ;; Installation: ;; ;; 1. Ensure Elixir is installed and in your PATH ;; 2. Add to your init.el: ;; ;; (require 'ob-elixir) ;; (org-babel-do-load-languages ;; 'org-babel-load-languages ;; '((elixir . t))) ;; ;; Usage: ;; ;; In an org file, create a source block: ;; ;; #+BEGIN_SRC elixir ;; IO.puts("Hello from Elixir!") ;; 1 + 1 ;; #+END_SRC ;; ;; Press C-c C-c to evaluate. ;; ;; Header Arguments: ;; ;; - :session NAME Use a persistent IEx session ;; - :results value Return the last expression's value ;; - :results output Capture stdout ;; - :mix PROJECT Execute in Mix project context ;; ;; See the README for complete documentation. ``` ### Info Manual Integration (Optional) For larger packages, consider an info manual: ```elisp (defun ob-elixir-info () "Open the ob-elixir info manual." (interactive) (info "(ob-elixir)")) ``` --- ## Dependencies and Loading ### Requiring Dependencies ```elisp ;;; Code: ;; Required dependencies (require 'ob) (require 'ob-ref) (require 'ob-comint) (require 'ob-eval) ;; Soft dependencies (optional features) (require 'elixir-mode nil t) ; t = noerror ;; Check for optional dependency (defvar ob-elixir-has-elixir-mode (featurep 'elixir-mode) "Non-nil if elixir-mode is available.") ``` ### Autoloads For functions that should be available before the package loads: ```elisp ;;;###autoload (defun org-babel-execute:elixir (body params) "Execute a block of Elixir code with Babel." ...) ;;;###autoload (eval-after-load 'org '(add-to-list 'org-src-lang-modes '("elixir" . elixir))) ``` ### Provide Statement Always end with provide: ```elisp (provide 'ob-elixir) ;;; ob-elixir.el ends here ``` --- ## References - [GNU Emacs Lisp Reference Manual](https://www.gnu.org/software/emacs/manual/elisp.html) - [Emacs Lisp Style Guide](https://github.com/bbatsov/emacs-lisp-style-guide) - [Packaging Guidelines (MELPA)](https://github.com/melpa/melpa/blob/master/CONTRIBUTING.org) - [Writing GNU Emacs Extensions](https://www.gnu.org/software/emacs/manual/html_node/eintr/) - [Org Mode Source Code](https://git.savannah.gnu.org/cgit/emacs/org-mode.git/) - [Elisp Conventions (Emacs Wiki)](https://www.emacswiki.org/emacs/ElispConventions)