hamacs/pud.org

679 lines
24 KiB
Org Mode
Raw Permalink Normal View History

#+title: Moss and Puddles
#+author: Howard X. Abrams
#+date: 2025-01-18
#+filetags: emacs hamacs
2025-09-02 05:42:50 +00:00
#+lastmod: [2025-08-23 Sat]
A literate programming file for a Comint-based MUD client.
#+begin_src emacs-lisp :exports none
;;; pud --- a MUD client -*- lexical-binding: t; -*-
;;
;; © 2025 Howard X. Abrams
;; Licensed under a Creative Commons Attribution 4.0 International License.
;; See http://creativecommons.org/licenses/by/4.0/
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
;; Maintainer: Howard X. Abrams
;; Created: January 18, 2025
;;
;; While obvious, GNU Emacs does not include this file or project.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; /Users/howard/src/hamacs/pud.org
;; And tangle the file to recreate this one.
;;
;;; Code:
#+end_src
* Introduction
This project is a simple MUD client for Emacs, based on COM-INT MUD client I learn about on Mickey Petersens [[https://www.masteringemacs.org/article/comint-writing-command-interpreter][essay on Comint]].
This uses either =ssh= or good ol =telnet= for the connection. Surprised that one can still install =telnet= on a Mac, like:
#+BEGIN_SRC sh
2025-06-09 02:50:26 +00:00
brew install telnet
#+END_SRC
Use your favorite way to install Emacs packages, for instance, with Emacs 30, one could install and customize settings in one go with =use-package=:
2025-06-09 02:50:26 +00:00
#+BEGIN_SRC emacs-lisp :tangle no :eval no
(use-package pud
:vc (:url "https://howardabrams.com/git/howard/pud")
:custom
(pud-worlds
'(["Remote Moss-n-Puddles" 'ssh "howardabrams.com" 4000 "george"]
; ↑ No password? Should be in .authinfo.gpg
["Local Root" 'telnet "localhost" 4000 "darol" "some-pass"]
; ↑ This has the password in your custom settings.
; ↓ Password from authinfo, special connection string:
["Local User" 'telnet "localhost" 4000 "rick" nil "login %s %s"])))
#+END_SRC
** Customization
2025-06-09 02:50:26 +00:00
You will want to customize your connections to the connections, as this program defaults to *Moss n Puddles*, my own MUD which I invite you to join.
#+BEGIN_SRC emacs-lisp
(defgroup pud nil
"An overly simplistic MUD client that works with Evennia."
:group 'processes)
(defcustom pud-worlds
'(["Moss-n-Puddles" telnet "howardabrams.com" 4000])
"List of worlds you play in.
You need to define the worlds you play in before you can get
started. In most worlds, you can start playing using a guest account.
Each element WORLD of the list has the following form:
\[CONN-TYPE NAME HOST PORT CHARACTER PASSWORD LOGIN-STR]
NAME identifies the connection, HOST and PORT specify the network
connection, CHARACTER and PASSWORD are used to connect automatically.
The CONN-TYPE can be either 'telnet or 'ssh.
The LOGIN-STR is a string with two `%s' where this substitutes
the username and password respectively. Sends this to the server after
establishing a connection. This can be blank for the default.
If given, make sure to have a trailing `\n' to automatically send.
Note that this will be saved in your `custom-file' -- including your
passwords! If you don't want that, specify nil as your password."
:type '(repeat
(vector :tag "Server World"
(string :tag "Name")
(radio :tag "Type"
(const :tag "Telnet" :value telnet)
(const :tag "SSH" :value ssh))
(string :tag "Hostname")
(integer :tag "Port num")
(string :tag "Username" :value "guest")
(string :tag "Password")
(string :tag "Login String"
:format "%t: %v%h"
:doc "The login string to send after connection.
This should probably have a \`\\n' at the end to submit it.
If blank or nil, use the \`pud-default-connection-string'.
For example: connect %s %s\\n")))
:group 'pud)
#+END_SRC
2025-06-09 02:50:26 +00:00
For instance, you could set up a call to =setopt= or customize the =pud-worlds= variable, as in:
2025-03-30 23:10:44 +00:00
#+BEGIN_SRC emacs-lisp :tangle no :eval no
(use-package pud
:custom
(pud-worlds
'(["Remote Moss-n-Puddles" 'ssh "howardabrams.com" 4000 "bobby"]
; ↑ No password? Should be in .authinfo.gpg
["Local Root" 'telnet "localhost" 4000 "suzy" "some-pass"]
; ↑ This has the password in your custom settings.
; ↓ Password from authinfo, special connection string:
["Local User" 'telnet "localhost" 4000 "rick" nil "login %s %s"])))
#+END_SRC
Or set up the customization in the =init.el= file:
#+BEGIN_SRC emacs-lisp :tangle no :eval no
2025-06-09 02:50:26 +00:00
(setopt pud-worlds
2025-03-30 23:10:44 +00:00
'(["Moss-n-Puddles" ssh "howardabrams.com" 4004 "howard" "" "\\nconnect %s %s\\n"]
["Moss-n-Puddles" ssh "howardabrams.com" 4004 "rick" "" "\\nconnect %s %s\\n"]
["Moss-n-Puddles" ssh "howardabrams.com" 4004 "darol" "" "\\nconnect %s %s\\n"]
["Local-Moss" telnet "localhost" 4000 "howard" "" ""]
["Local-Moss" telnet "localhost" 4000 "rick" "" ""]))
#+END_SRC
Seems like MUDs have a standard login sequence, but they dont have to. Here is the default that a user can override in their =pud-worlds= listing:
#+BEGIN_SRC emacs-lisp
(defcustom pud-default-connection-string "connect %s %s\n"
"The standard connection string to substitute the username and password."
:type '(string)
:group 'pud)
#+END_SRC
** User Credentials
Next, open [[file:~/.authinfo.gpg][your authinfo file]], and insert the following line, substituting =[user]= and =[pass]= with your credentials as well as the first entry to match each host name in your =pud-worlds= entries:
#+BEGIN_SRC conf :tangle no :eval no
machine howardabrams.com login [name] port 4000 password [pass]
#+END_SRC
2025-07-30 22:57:43 +00:00
Now, lets play! Type =pud-run=, and optionally select a world. If you get disconnected, re-run it, or even =pud-reconnect=.
The rest of this file describes the code implementing this project.
* Code
Choosing a world… er, connection using a =completing-read= allowing you to choose a world. If =pud-worlds= contains a single value, might as well just return that.
#+BEGIN_SRC emacs-lisp
(defvar pud-world-history nil
"History for `pud-get-world'.")
(defun pud-get-world ()
"Let the user choose a world from `pud-worlds'.
The return value is a cons cell, the car is the name of the connection,
the cdr holds the connection defails from `pud-worlds'."
(if (length= pud-worlds 1)
(seq-first pud-worlds))
(let ((world-completions
(mapcar (lambda (w)
(cons (pud-world-name w) w))
pud-worlds)))
(cond
((and world-completions (length= world-completions 1))
(thread-first world-completions
(first)
(cdr)))
(world-completions
(thread-first
(completing-read "World: " world-completions nil t nil pud-world-history)
(assoc world-completions)
(cdr)))
(t (customize-option 'pud-worlds)))))
#+END_SRC
The following functions are accessibility functions to the world entry.
#+BEGIN_SRC emacs-lisp
(defun pud-world-name (world)
"Return the name for WORLD as a string."
(if (vectorp world)
(if (or (length< world 5) (null (aref world 4)) (string-blank-p (aref world 4)))
(aref world 0)
(concat (aref world 4) "@" (aref world 0)))
world))
(defun pud-world-network (world)
2025-03-30 23:10:44 +00:00
"Return the network details for WORLD as a list.
[Connect-Type Host Port]."
(list (aref world 1) (aref world 2) (format "%s" (aref world 3))))
(defun pud-world-creds (world)
"Return the username and password from WORLD.
Multiple search queries for the .authinfo file."
2025-06-09 02:50:26 +00:00
(seq-let (label conn-type host port user) world
(if-let ((auth-results (first (auth-source-search
:host host
:port port
:user user
:max 1))))
(list (plist-get auth-results :user)
(funcall (plist-get auth-results :secret)))
;; No match? Just return values from world:
(list (aref world 4) (aref world 5)))))
#+END_SRC
And some basic functions I should expand.
#+BEGIN_SRC emacs-lisp :tangle no
(ert-deftest pud-world-name-test ()
(should (string-equal (pud-world-name "foobar") "foobar"))
2025-03-30 23:10:44 +00:00
(should (string-equal (pud-world-name ["foobar" 'telnet "localhost" "4000"]) "foobar"))
(should (string-equal (pud-world-name ["foobar" 'telnet "localhost" "4000" nil]) "foobar"))
(should (string-equal (pud-world-name ["foobar" 'telnet "localhost" "4000" ""]) "foobar"))
(should (string-equal (pud-world-name ["foobar" 'telnet "localhost" "4000" "guest" "guest"]) "guest@foobar")))
(ert-deftest pud-world-network-test ()
2025-03-30 23:10:44 +00:00
(should (equal (pud-world-network ["foobar" telnet "overthere" "4000" "guest" "guest"]) '(telnet "overthere" "4000")))
(should (equal (pud-world-network ["foobar" ssh "overthere" 4000 "guest" "guest"]) '(ssh "overthere" "4000"))))
(ert-deftest pud-world-creds-test ()
;; Test with no match in authinfo!
(should (equal
(pud-world-creds ["some-place" telnet "some-home" 4000 "a-user" "a-pass"])
'("a-user" "a-pass")))
;; This test works if the following line is in .authinfo:
;; machine localhost port 4000 login george password testpass
(should (equal
(pud-world-creds ["nudder-place" ssh "localhost" 4000 "george"])
'("george" "testpass"))))
#+END_SRC
* Basics
Using Comint, and hoping to have the ANSI colors displayed.
#+BEGIN_SRC emacs-lisp
(require 'comint)
(load "ansi-color" t)
#+END_SRC
2025-09-02 05:42:50 +00:00
Get full colors using the [[https://github.com/atomontage/xterm-color][xterm-color]] project:
#+BEGIN_SRC emacs-lisp
(use-package xterm-color)
#+END_SRC
Im going to use good ol fashion =telnet= for the connection:
#+BEGIN_SRC emacs-lisp
(defcustom pud-telnet-path "telnet"
2025-06-09 02:50:26 +00:00
"Path to the program used by `pud-run' to connect using telnet."
:type '(string)
:group 'pud)
(defcustom pud-ssh-path "ssh"
2025-06-09 02:50:26 +00:00
"Path to the program used by `pud-run' to connect using ssh."
:type '(string)
:group 'pud)
#+END_SRC
The pud-cli-arguments, holds a list of commandline arguments: the port.
2025-02-15 04:39:13 +00:00
#+BEGIN_SRC emacs-lisp
(defvar pud-cli-arguments nil
"A list of arguments to use before the connection.")
#+END_SRC
Command string to use, given a =world= with a connection type:
#+BEGIN_SRC emacs-lisp
(defun pud-cli-command (world)
"Return a command string to pass to the shell.
The WORLD is a vector with the hostname, see `pud-worlds'."
2025-03-30 23:10:44 +00:00
(seq-let (connection-type host port) (pud-world-network world)
(message "Dealing with: %s %s %s" connection-type host port)
(cl-case connection-type
(telnet (append (cons pud-telnet-path pud-cli-arguments)
(list host port)))
2025-03-30 17:00:06 +00:00
(ssh (append (cons pud-ssh-path pud-cli-arguments)
(list "-p" port host)))
(t (error "Unsupported connection type")))))
2025-02-15 04:39:13 +00:00
#+END_SRC
Some tests:
#+BEGIN_SRC emacs-lisp :tangle no
(ert-deftest pud-cli-command-test ()
(should (equal (pud-cli-command ["some-world" telnet "world.r.us" 4000])
'("telnet" "world.r.us" "4000")))
(should (equal (pud-cli-command ["nudder-world" ssh "world.r.us" 4004])
'("ssh" "-p" "4004" "world.r.us"))))
#+END_SRC
The empty and currently disused mode map for storing our custom keybindings inherits from =comint-mode-map=, so we get the same keys exposed in =comint-mode=.
#+BEGIN_SRC emacs-lisp
(defvar pud-mode-map
(let ((map (nconc (make-sparse-keymap) comint-mode-map)))
(define-key map "\t" 'completion-at-point)
map)
2025-06-09 02:50:26 +00:00
"Basic mode map for `pud-run'.")
#+END_SRC
This holds a regular expression that matches the prompt style for the MUD. Not sure if this is going to work, since MUDs typically dont have prompts.
#+BEGIN_SRC emacs-lisp
(defvar pud-prompt-regexp "" ; "^\\(?:\\[[^@]+@[^@]+\\]\\)"
2025-06-09 02:50:26 +00:00
"Prompt for `pud-run'.")
#+END_SRC
The name of the buffer:
#+BEGIN_SRC emacs-lisp
(defun pud-buffer-name (world)
"Return the buffer name associated with WORLD."
(format "*%s*" (pud-world-name world)))
#+END_SRC
** Run and Connect
2025-06-09 02:50:26 +00:00
The main entry point to the program is the =pud-run= function:
#+BEGIN_SRC emacs-lisp
2025-06-09 02:50:26 +00:00
(defun pud-run (world)
2025-02-15 04:39:13 +00:00
"Run an inferior instance of `pud-cli' inside Emacs.
The WORLD should be vector containing the following:
- label for the world
2025-06-09 02:50:26 +00:00
- server type, 'ssh or 'telnet
2025-02-15 04:39:13 +00:00
- server hostname
- server port
- username (can be overridden)
- password (should be overridden)"
(interactive (list (pud-get-world)))
(let* ((pud-cli (pud-cli-command world))
(buffer (get-buffer-create (pud-buffer-name world)))
(proc-alive (comint-check-proc buffer))
(process (get-buffer-process buffer)))
;; if the process is dead then re-create the process and reset the
;; mode.
2025-03-30 23:10:44 +00:00
(message "Gonna %s with %s" (car pud-cli) (cdr pud-cli))
(unless proc-alive
(with-current-buffer buffer
(apply 'make-comint-in-buffer "Pud" buffer (car pud-cli) nil (cdr pud-cli))
(pud-mode)
(visual-line-mode 1)
2025-06-09 02:50:26 +00:00
(pud-login world)))
;; Regardless, provided we have a valid buffer, we pop to it.
(when buffer
(pop-to-buffer buffer))))
#+END_SRC
Connection and/or re-connection:
#+BEGIN_SRC emacs-lisp
2025-06-09 02:50:26 +00:00
(defun pud-login (world)
2025-02-15 04:39:13 +00:00
"Collect and send a `connect' sequence to WORLD.
2025-06-09 02:50:26 +00:00
Where WORLD is a vector of world information. NOP if the buffer
has no connection or no password could be found."
(interactive (list (pud-get-world)))
(when (called-interactively-p)
(pop-to-buffer (pud-buffer-name world)))
(sit-for 1)
2025-06-09 02:50:26 +00:00
(length world)
(message "Attempting to log in to %s..." (pud-world-name world))
(seq-let (username password) (pud-world-creds world)
2025-06-09 02:50:26 +00:00
(let* ((local-conn (when (length> world 6)
(aref world 6 )))
(conn-str (if (and local-conn
(not (string-blank-p local-conn)))
local-conn
pud-default-connection-string))
(conn-full (format conn-str username password))
(process (get-buffer-process (current-buffer))))
(goto-char (point-max))
(if process
(comint-send-string process conn-full)
(insert conn-full)))))
#+END_SRC
2025-06-09 02:50:26 +00:00
(setq world (pud-get-world))
** Reconnect
Force a kill process, and restart.
#+BEGIN_SRC emacs-lisp
(defun pud-reconnect (world)
"Force stop an inferior instance of `pud-cli'.
The WORLD should be vector containing the following:
- label for the world
- server hostname
- server port
- username (can be overridden)
- password (should be overridden)"
(interactive (list (pud-get-world)))
(let* ((pud-cli (pud-cli-command world))
(buffer (get-buffer-create (pud-buffer-name world)))
(proc-alive (comint-check-proc buffer))
(process (get-buffer-process buffer)))
(when (processp process)
(kill-process process))
(pud-run world)))
#+END_SRC
* Pud Mode
2025-02-15 04:39:13 +00:00
Note that =comint-process-echoes=, depending on the mode and the circumstances, may result in prompts appearing twice. Setting =comint-process-echoes= to =t= helps with that.
#+BEGIN_SRC emacs-lisp
(defun pud--initialize ()
"Helper function to initialize Pud."
(setq comint-process-echoes t)
(setq comint-use-prompt-regexp nil))
(define-derived-mode pud-mode comint-mode "Pud"
2025-06-09 02:50:26 +00:00
"Major mode for `pud-run'.
2025-09-02 05:42:50 +00:00
\\<pud-mode-map>"
;; this sets up the prompt so it matches things like: [foo@bar]
;; (setq comint-prompt-regexp pud-prompt-regexp)
;; this makes it read only; a contentious subject as some prefer the
;; buffer to be overwritable.
(setq comint-prompt-read-only t)
;; this makes it so commands like M-{ and M-} work.
;; (set (make-local-variable 'paragraph-separate) "\\'")
;; (set (make-local-variable 'font-lock-defaults) '(pud-font-lock-keywords t))
;; (set (make-local-variable 'paragraph-start) pud-prompt-regexp)
2025-09-02 05:42:50 +00:00
;; Get the xterm colorization on:
(require 'xterm-color)
(font-lock-mode -1)
;; Prevent font-locking from being re-enabled in this buffer
(make-local-variable 'font-lock-function)
(setq font-lock-function (lambda (_) nil))
(add-hook 'comint-preoutput-filter-functions 'xterm-color-filter nil t))
(add-hook 'pud-mode-hook 'pud--initialize)
(defconst pud-keywords
'("connect" "get" "look" "use")
"List of keywords to highlight in `pud-font-lock-keywords'.")
(defvar pud-font-lock-keywords
(list
;; highlight all the reserved commands.
2025-02-15 04:39:13 +00:00
`(,(concat (rx bol (optional "@")) (regexp-opt pud-keywords)) . font-lock-keyword-face)
`(,(rx bol "@" (one-or-more)))
)
"Additional expressions to highlight in `pud-mode'.")
#+END_SRC
* Org Babel
Wouldnt it be nice to be able to write commands in an Org file, and send the command to the connected Mud?
#+begin_src emacs-lisp :exports none :tangle ~/.emacs.d/elisp/ob-evennia.el
;;; ob-evennia --- Evennia source blocks in Org -*- lexical-binding: t; -*-
;;
;; © 2025 Howard X. Abrams
;; Licensed under a Creative Commons Attribution 4.0 International License.
;; See http://creativecommons.org/licenses/by/4.0/
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
;; Maintainer: Howard X. Abrams
;; Created: January 18, 2025
;;
;; While obvious, GNU Emacs does not include this file or project.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; /Users/howard/src/hamacs/pud.org
;; And tangle the file to recreate this one.
;;
;;; Code:
#+end_src
Since Im connected to more than one MUD, or at least, I often log in with two different characters as two different characters. Lets have a function that can return all PUD buffers:
#+BEGIN_SRC emacs-lisp :tangle ~/.emacs.d/elisp/ob-evennia.el
(defun pud-get-all-buffers ()
"Return a list of all buffers with a live PUD connection."
(save-window-excursion
(seq-filter (lambda (buf)
(switch-to-buffer buf)
(and
(eq major-mode 'pud-mode)
(get-buffer-process (current-buffer))))
(buffer-list))))
#+END_SRC
And a wrapper around =completing-read= for choosing one of the buffers:
#+BEGIN_SRC emacs-lisp :tangle ~/.emacs.d/elisp/ob-evennia.el
(defun pud-current-world ()
"Return buffer based on user choice of current PUD connections."
(let ((pud-buffers (pud-get-all-buffers)))
(cond
((null pud-buffers) nil)
((length= pud-buffers 1) (car pud-buffers))
(t
(completing-read "Choose connection: "
(seq-map (lambda (buf) (buffer-name buf))
pud-buffers))))))
#+END_SRC
Given a buffer and a string, use the =comint-send-string=:
#+BEGIN_SRC emacs-lisp :tangle ~/.emacs.d/elisp/ob-evennia.el
(defun pud-send-string (buf-name text)
"Send TEXT to a comint buffer, BUF-NAME."
(save-window-excursion
(save-excursion
(pop-to-buffer buf-name)
(goto-char (point-max))
(comint-send-string (get-buffer-process (current-buffer))
(format "%s\n" text)))))
#+END_SRC
Lets send the current line or region.
#+BEGIN_SRC emacs-lisp :tangle ~/.emacs.d/elisp/ob-evennia.el
(defun pud-send-line (world)
"Send the current line or region to WORLD."
(interactive (list (pud-current-world)))
(unless world
(error "No current MUD connection."))
(let ((text (buffer-substring-no-properties
(if (region-active-p) (region-beginning)
(beginning-of-line-text) (point))
(if (region-active-p) (region-end)
(end-of-line) (point)))))
(pud-send-string world text)))
(global-set-key (kbd "<f6>") 'pud-send-line)
#+END_SRC
Lets be able to send the current Org block, where all lines in the block are smooshed together to create a single line:
#+BEGIN_SRC emacs-lisp :tangle ~/.emacs.d/elisp/ob-evennia.el
(defun pud-send-block (world)
"Send the current Org block to WORLD."
(interactive (list (pud-current-world)))
(unless world
(error "No current MUD connection."))
(let ((text (thread-last (org-element-at-point)
(org-src--contents-area)
(nth 2))))
(pud-send-string world
(replace-regexp-in-string
(rx (one-or-more space)) " " text))))
#+END_SRC
And code so that we can =(require 'ob-evennia)= to get font-locking working in blocks.
#+BEGIN_SRC emacs-lisp :tangle ~/.emacs.d/elisp/ob-evennia.el
(require 'ob)
(defvar org-babel-default-header-args:evennia '())
(defun org-babel-execute:evennia (body params)
"Execute evennia BODY.
PARAMS can contain the following:
:session - The buffer to send the block
Called by `org-babel-execute-src-block'."
(let* ((session (cdr (assq :session params)))
(buffer (or session (pud-current-world))))
(if session
(setq buffer (format "*%s*" session)))
(pud-send-string buffer
(replace-regexp-in-string
(rx (one-or-more space)) " " body))
(message "No connected world.")))
(defun org-babel-prep-session:evennia (_session _params)
"Signal error; Evennia does not (currently) support sessions."
(error "Evennia sessions are nonsensical"))
(provide 'ob-evennia)
#+END_SRC
This should allow this client to simply =require= it:
#+BEGIN_SRC emacs-lisp
(require 'ob-evennia)
#+END_SRC
2025-02-15 04:39:13 +00:00
* Evennia Mode
#+begin_src emacs-lisp :exports none :tangle ~/.emacs.d/elisp/evennia-mode.el
;;; evennia-mode --- Syntax coloring for Evennia code -*- lexical-binding: t; -*-
;;
;; © 2025 Howard X. Abrams
;; Licensed under a Creative Commons Attribution 4.0 International License.
;; See http://creativecommons.org/licenses/by/4.0/
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
;; Maintainer: Howard X. Abrams
;; Created: January 18, 2025
;;
;; While obvious, GNU Emacs does not include this file or project.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; /Users/howard/src/hamacs/pud.org
;; And tangle the file to recreate this one.
;;
;;; Code:
#+end_src
2025-02-15 04:39:13 +00:00
Make a simple mode for basic highlighting of =ev= code. Based on =ruby= (which seems to be close enough).
#+BEGIN_SRC emacs-lisp :tangle ~/.emacs.d/elisp/evennia-mode.el
(define-derived-mode evennia-mode ruby-mode "Evennia"
2025-02-15 04:39:13 +00:00
"Major mode for editing evennia batch command files.
\\{evennia-mode-map}
Turning on Evennia mode runs the normal hook `evennia-mode-hook'."
(setq-local require-final-newline mode-require-final-newline)
2025-02-15 04:39:13 +00:00
(setq-local comment-start "# ")
(setq-local comment-start-skip "#+\\s-*"))
#+END_SRC
2025-02-15 04:39:13 +00:00
And add it to org blocks:
#+BEGIN_SRC emacs-lisp :tangle no
(add-to-list 'org-babel-load-languages '(evennia . t))
#+END_SRC
Final stuff to require to include this major-mode:
2025-02-15 04:39:13 +00:00
#+BEGIN_SRC emacs-lisp :tangle ~/.emacs.d/elisp/evennia-mode.el
;; Add the mode to the auto-mode-alist for specific file extensions
(add-to-list 'auto-mode-alist '("\\.ev\\'" . evennia-mode))
;; Provide the mode for use
(provide 'evennia-mode)
#+END_SRC
How does this look?
#+BEGIN_SRC evennia :tangle /tmp/testing.ev
# Comments, while not used much are comments.
@one two = "three" :four
2025-02-15 04:39:13 +00:00
#+END_SRC
This client can =require= to depend on this mode.
#+BEGIN_SRC emacs-lisp export none
(require 'evennia-mode)
#+END_SRC
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file:
#+begin_src emacs-lisp :exports none
(provide 'pud)
;;; pud.el ends here
#+end_src
#+DESCRIPTION: a MUD client
#+PROPERTY: header-args:sh :tangle no
#+PROPERTY: header-args:emacs-lisp :tangle yes
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js