Compare commits

..

6 commits

Author SHA1 Message Date
Howard Abrams
a351eeadfa Better search for a header in a demo 2025-06-09 09:48:27 -07:00
Howard Abrams
27eb55b154 Autocomplete is quite annoying
As one can't actually fix some mistakes on the command line.
2025-06-09 09:48:27 -07:00
Howard Abrams
f1e282d713 Remove lsp-rename that doesn't exist 2025-06-08 19:53:06 -07:00
Howard Abrams
a4ce781544 Use a fix-width font on exported code blocks 2025-06-08 19:51:48 -07:00
Howard Abrams
d598ec1e46 Fix connection bug with pud-login 2025-06-08 19:50:26 -07:00
Howard Abrams
cf79016484 Use community RSS for Org instead of my copy 2025-06-08 14:41:49 -07:00
7 changed files with 122 additions and 486 deletions

View file

@ -1,414 +0,0 @@
;;; ox-rss.el --- RSS 2.0 Back-End for Org Export Engine
;; Copyright (C) 2013-2015 Bastien Guerry
;; Author: Bastien Guerry <bzg@gnu.org>
;; Keywords: org, wp, blog, feed, rss
;; This file is not yet 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This library implements a RSS 2.0 back-end for Org exporter, based on
;; the `html' back-end.
;;
;; It requires Emacs 24.1 at least.
;;
;; It provides two commands for export, depending on the desired output:
;; `org-rss-export-as-rss' (temporary buffer) and `org-rss-export-to-rss'
;; (as a ".xml" file).
;;
;; This backend understands two new option keywords:
;;
;; #+RSS_EXTENSION: xml
;; #+RSS_IMAGE_URL: http://myblog.org/mypicture.jpg
;;
;; It uses #+HTML_LINK_HOME: to set the base url of the feed.
;;
;; Exporting an Org file to RSS modifies each top-level entry by adding a
;; PUBDATE property. If `org-rss-use-entry-url-as-guid', it will also add
;; an ID property, later used as the guid for the feed's item.
;;
;; The top-level headline is used as the title of each RSS item unless
;; an RSS_TITLE property is set on the headline.
;;
;; You typically want to use it within a publishing project like this:
;;
;; (add-to-list
;; 'org-publish-project-alist
;; '("homepage_rss"
;; :base-directory "~/myhomepage/"
;; :base-extension "org"
;; :rss-image-url "http://lumiere.ens.fr/~guerry/images/faces/15.png"
;; :html-link-home "http://lumiere.ens.fr/~guerry/"
;; :html-link-use-abs-url t
;; :rss-extension "xml"
;; :publishing-directory "/home/guerry/public_html/"
;; :publishing-function (org-rss-publish-to-rss)
;; :section-numbers nil
;; :exclude ".*" ;; To exclude all files...
;; :include ("index.org") ;; ... except index.org.
;; :table-of-contents nil))
;;
;; ... then rsync /home/guerry/public_html/ with your server.
;;
;; By default, the permalink for a blog entry points to the headline.
;; You can specify a different one by using the :RSS_PERMALINK:
;; property within an entry.
;;; Code:
(require 'ox-html)
(declare-function url-encode-url "url-util" (url))
;;; Variables and options
(defgroup org-export-rss nil
"Options specific to RSS export back-end."
:tag "Org RSS"
:group 'org-export
:version "24.4"
:package-version '(Org . "8.0"))
(defcustom org-rss-image-url "http://orgmode.org/img/org-mode-unicorn-logo.png"
"The URL of the an image for the RSS feed."
:group 'org-export-rss
:type 'string)
(defcustom org-rss-extension "xml"
"File extension for the RSS 2.0 feed."
:group 'org-export-rss
:type 'string)
(defcustom org-rss-categories 'from-tags
"Where to extract items category information from.
The default is to extract categories from the tags of the
headlines. When set to another value, extract the category
from the :CATEGORY: property of the entry."
:group 'org-export-rss
:type '(choice
(const :tag "From tags" from-tags)
(const :tag "From the category property" from-category)))
(defcustom org-rss-use-entry-url-as-guid t
"Use the URL for the <guid> metatag?
When nil, Org will create ids using `org-icalendar-create-uid'."
:group 'org-export-rss
:type 'boolean)
;;; Define backend
(org-export-define-derived-backend 'rss 'html
:menu-entry
'(?r "Export to RSS"
((?R "As RSS buffer"
(lambda (a s v b) (org-rss-export-as-rss a s v)))
(?r "As RSS file" (lambda (a s v b) (org-rss-export-to-rss a s v)))
(?o "As RSS file and open"
(lambda (a s v b)
(if a (org-rss-export-to-rss t s v)
(org-open-file (org-rss-export-to-rss nil s v)))))))
:options-alist
'((:description "DESCRIPTION" nil nil newline)
(:keywords "KEYWORDS" nil nil space)
(:with-toc nil nil nil) ;; Never include HTML's toc
(:rss-extension "RSS_EXTENSION" nil org-rss-extension)
(:rss-image-url "RSS_IMAGE_URL" nil org-rss-image-url)
(:rss-categories nil nil org-rss-categories))
:filters-alist '((:filter-final-output . org-rss-final-function))
:translate-alist '((headline . org-rss-headline)
(comment . (lambda (&rest args) ""))
(comment-block . (lambda (&rest args) ""))
(timestamp . (lambda (&rest args) ""))
(plain-text . org-rss-plain-text)
(section . org-rss-section)
(template . org-rss-template)))
;;; Export functions
;;;###autoload
(defun org-rss-export-as-rss (&optional async subtreep visible-only)
"Export current buffer to a RSS buffer.
If narrowing is active in the current buffer, only export its
narrowed part.
If a region is active, export that region.
A non-nil optional argument ASYNC means the process should happen
asynchronously. The resulting buffer should be accessible
through the `org-export-stack' interface.
When optional argument SUBTREEP is non-nil, export the sub-tree
at point, extracting information from the headline properties
first.
When optional argument VISIBLE-ONLY is non-nil, don't export
contents of hidden elements.
Export is done in a buffer named \"*Org RSS Export*\", which will
be displayed when `org-export-show-temporary-export-buffer' is
non-nil."
(interactive)
(let ((file (buffer-file-name (buffer-base-buffer))))
(org-icalendar-create-uid file 'warn-user)
(org-rss-add-pubdate-property))
(org-export-to-buffer 'rss "*Org RSS Export*"
async subtreep visible-only nil nil (lambda () (text-mode))))
;;;###autoload
(defun org-rss-export-to-rss (&optional async subtreep visible-only)
"Export current buffer to a RSS file.
If narrowing is active in the current buffer, only export its
narrowed part.
If a region is active, export that region.
A non-nil optional argument ASYNC means the process should happen
asynchronously. The resulting file should be accessible through
the `org-export-stack' interface.
When optional argument SUBTREEP is non-nil, export the sub-tree
at point, extracting information from the headline properties
first.
When optional argument VISIBLE-ONLY is non-nil, don't export
contents of hidden elements.
Return output file's name."
(interactive)
(let ((file (buffer-file-name (buffer-base-buffer))))
(org-icalendar-create-uid file 'warn-user)
(org-rss-add-pubdate-property))
(let ((outfile (org-export-output-file-name
(concat "." org-rss-extension) subtreep)))
(org-export-to-file 'rss outfile async subtreep visible-only)))
;;;###autoload
(defun org-rss-publish-to-rss (plist filename pub-dir)
"Publish an org file to RSS.
FILENAME is the filename of the Org file to be published. PLIST
is the property list for the given project. PUB-DIR is the
publishing directory.
Return output file name."
(let ((bf (get-file-buffer filename)))
(if bf
(with-current-buffer bf
(org-icalendar-create-uid filename 'warn-user)
(org-rss-add-pubdate-property)
(write-file filename))
(find-file filename)
(org-icalendar-create-uid filename 'warn-user)
(org-rss-add-pubdate-property)
(write-file filename) (kill-buffer)))
(org-publish-org-to
'rss filename (concat "." org-rss-extension) plist pub-dir))
;;; Main transcoding functions
(defun org-rss-headline (headline contents info)
"Transcode HEADLINE element into RSS format.
CONTENTS is the headline contents. INFO is a plist used as a
communication channel."
(unless (or (org-element-property :footnote-section-p headline)
;; Only consider first-level headlines
(> (org-export-get-relative-level headline info) 1))
(let* ((author (and (plist-get info :with-author)
(let ((auth (plist-get info :author)))
(and auth (org-export-data auth info)))))
(htmlext (plist-get info :html-extension))
(hl-number (org-export-get-headline-number headline info))
(hl-home (file-name-as-directory (plist-get info :html-link-home)))
(hl-pdir (plist-get info :publishing-directory))
(hl-perm (org-element-property :RSS_PERMALINK headline))
(anchor (org-export-get-reference headline info))
(category (org-rss-plain-text
(or (org-element-property :CATEGORY headline) "") info))
(pubdate0 (org-element-property :PUBDATE headline))
(pubdate (let ((system-time-locale "C"))
(if pubdate0
(format-time-string
"%a, %d %b %Y %H:%M:%S %z"
(org-time-string-to-time pubdate0)))))
(title (or (org-element-property :RSS_TITLE headline)
(replace-regexp-in-string
org-bracket-link-regexp
(lambda (m) (or (match-string 3 m)
(match-string 1 m)))
(org-element-property :raw-value headline))))
(publink
(or (and hl-perm (concat (or hl-home hl-pdir) hl-perm))
(concat
(or hl-home hl-pdir)
(file-name-nondirectory
(file-name-sans-extension
(plist-get info :input-file))) "." htmlext "#" anchor)))
(guid (if org-rss-use-entry-url-as-guid
publink
(org-rss-plain-text
(or (org-element-property :ID headline)
(org-element-property :CUSTOM_ID headline)
publink)
info))))
(if (not pubdate0) "" ;; Skip entries with no PUBDATE prop
(format
(concat
"<item>\n"
"<title>%s</title>\n"
"<link>%s</link>\n"
"<author>%s</author>\n"
"<guid isPermaLink=\"false\">%s</guid>\n"
"<pubDate>%s</pubDate>\n"
(org-rss-build-categories headline info) "\n"
"<description><![CDATA[%s]]></description>\n"
"</item>\n")
title publink author guid pubdate contents)))))
(defun org-rss-build-categories (headline info)
"Build categories for the RSS item."
(if (eq (plist-get info :rss-categories) 'from-tags)
(mapconcat
(lambda (c) (format "<category><![CDATA[%s]]></category>" c))
(org-element-property :tags headline)
"\n")
(let ((c (org-element-property :CATEGORY headline)))
(format "<category><![CDATA[%s]]></category>" c))))
(defun org-rss-template (contents info)
"Return complete document string after RSS conversion.
CONTENTS is the transcoded contents string. INFO is a plist used
as a communication channel."
(concat
(format "<?xml version=\"1.0\" encoding=\"%s\"?>"
(symbol-name org-html-coding-system))
"\n<rss version=\"2.0\"
xmlns:content=\"http://purl.org/rss/1.0/modules/content/\"
xmlns:wfw=\"http://wellformedweb.org/CommentAPI/\"
xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
xmlns:atom=\"http://www.w3.org/2005/Atom\"
xmlns:sy=\"http://purl.org/rss/1.0/modules/syndication/\"
xmlns:slash=\"http://purl.org/rss/1.0/modules/slash/\"
xmlns:georss=\"http://www.georss.org/georss\"
xmlns:geo=\"http://www.w3.org/2003/01/geo/wgs84_pos#\"
xmlns:media=\"http://search.yahoo.com/mrss/\">"
"<channel>"
(org-rss-build-channel-info info) "\n"
contents
"</channel>\n"
"</rss>"))
(defun org-rss-build-channel-info (info)
"Build the RSS channel information."
(let* ((system-time-locale "C")
(title (plist-get info :title))
(email (org-export-data (plist-get info :email) info))
(author (and (plist-get info :with-author)
(let ((auth (plist-get info :author)))
(and auth (org-export-data auth info)))))
(date (format-time-string "%a, %d %b %Y %H:%M:%S %z")) ;; RFC 882
(description (org-export-data (plist-get info :description) info))
(lang (plist-get info :language))
(keywords (plist-get info :keywords))
(rssext (plist-get info :rss-extension))
(blogurl (or (plist-get info :html-link-home)
(plist-get info :publishing-directory)))
(image (url-encode-url (plist-get info :rss-image-url)))
(ifile (plist-get info :input-file))
(publink
(concat (file-name-as-directory blogurl)
(file-name-nondirectory
(file-name-sans-extension ifile))
"." rssext)))
(format
"\n<title>%s</title>
<atom:link href=\"%s\" rel=\"self\" type=\"application/rss+xml\" />
<link>%s</link>
<description><![CDATA[%s]]></description>
<language>%s</language>
<pubDate>%s</pubDate>
<lastBuildDate>%s</lastBuildDate>
<generator>%s</generator>
<webMaster>%s (%s)</webMaster>
<image>
<url>%s</url>
<title>%s</title>
<link>%s</link>
</image>
"
title publink blogurl description lang date date
(concat (format "Emacs %d.%d"
emacs-major-version
emacs-minor-version)
" Org-mode " (org-version))
email author image title blogurl)))
(defun org-rss-section (section contents info)
"Transcode SECTION element into RSS format.
CONTENTS is the section contents. INFO is a plist used as
a communication channel."
contents)
(defun org-rss-timestamp (timestamp contents info)
"Transcode a TIMESTAMP object from Org to RSS.
CONTENTS is nil. INFO is a plist holding contextual
information."
(org-html-encode-plain-text
(org-timestamp-translate timestamp)))
(defun org-rss-plain-text (contents info)
"Convert plain text into RSS encoded text."
(let (output)
(setq output (org-html-encode-plain-text contents)
output (org-export-activate-smart-quotes
output :html info))))
;;; Filters
(defun org-rss-final-function (contents backend info)
"Prettify the RSS output."
(with-temp-buffer
(xml-mode)
(insert contents)
(indent-region (point-min) (point-max))
(buffer-substring-no-properties (point-min) (point-max))))
;;; Miscellaneous
(defun org-rss-add-pubdate-property ()
"Set the PUBDATE property for top-level headlines."
(let (msg)
(org-map-entries
(lambda ()
(let* ((entry (org-element-at-point))
(level (org-element-property :level entry)))
(when (= level 1)
(unless (org-entry-get (point) "PUBDATE")
(setq msg t)
(org-set-property
"PUBDATE" (format-time-string
(cdr org-time-stamp-formats)))))))
nil nil 'comment 'archive)
(when msg
(message "Property PUBDATE added to top-level entries in %s"
(buffer-file-name))
(sit-for 2))))
(provide 'ox-rss)
;;; ox-rss.el ends here

View file

@ -446,7 +446,11 @@ These interactive functions scroll the “notes” in the other window in anothe
(call-interactively 'org-find-file))
(setq ha-slide-presentation (buffer-name))
(when initial-heading
(imenu initial-heading))
(goto-char (point-min))
(re-search-forward (rx bol
(one-or-more "*")
(one-or-more space)
(literal initial-heading))))
(cond
((fboundp #'dslide-deck-forward) (call-interactively 'dslide-deck-start))
((fboundp #'org-present-next) (call-interactively 'org-present))
@ -510,7 +514,7 @@ To make the contents of the expression easier to write, the =define-ha-demo= as
Probably best to explain this in an example:
\(define-demo demo1
\(define-ha-demo demo1
\(:buffer \"demonstrations.py\") \(message \"In a buffer\"\)
\(:mode 'dired-mode\) \(message \"In a dired\"\)
\(:heading \"Raven Civilizations\"\) \(message \"In an org file\"\)\)

View file

@ -27,13 +27,26 @@ A literate programming file for publishing my website using org.
* Introduction
While the Emacs community have a plethora of options for generating a static website from org-formatted files, I keep my pretty simple, and use the standard =org-publish= feature.
The RSS needs UUIDs:
#+BEGIN_SRC emacs-lisp results silent
(use-package uuidgen
:straight (:host github :repo "emacsmirror/uuidgen"))
(defun org-icalendar-create-uid (&rest ignored)
"Returns a UUID."
(uuidgen-1))
#+END_SRC
While the following packages come with Emacs, they aren't necessarily loaded:
#+begin_src emacs-lisp :results silent
(use-package ox-rss
:straight (:host github :repo "emacsmirror/ox-rss"))
(use-package org
:config
(require 'ox-html)
(require 'ox-rss)
(require 'ox-publish))
#+end_src

View file

@ -3,7 +3,7 @@
#+date: 2020-09-18
#+tags: emacs org
#+startup: inlineimages
#+lastmod: [2025-03-11 Tue]
#+lastmod: [2025-04-17 Thu]
A literate programming file for configuring org-mode and those files.
@ -771,11 +771,14 @@ Splitting out HTML snippets is often a way that I can transfer org-formatted con
(:link (@ :rel "stylesheet"
:type "text/css"
:href "https://fonts.googleapis.com/css2?family=Overpass:ital,wght@0,300;0,600;1,300;1,600&display=swap"))
(:link (@ :rel "stylesheet"
:type "text/css"
:href "https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap"))
(:style ,(string-join '(
"body { font-family: 'Literata', sans-serif; color: #333; }"
"h1,h2,h3,h4,h5 { font-family: 'Overpass', sans-serif; color: #333; }"
"code { color: steelblue }"
"pre { background-color: #eee; border-color: #aaa; }"
"code { font-family: 'Source Code Pro'; color: steelblue }"
"pre { font-family: 'Source Code Pro'; background-color: #eee; border-color: #aaa; }"
"a { text-decoration-style: dotted }"
"@media (prefers-color-scheme: dark) {"
" body { background-color: #1d1f21; color: white; }"
@ -871,7 +874,6 @@ Since this auto-correction needs to happen in /insert/ mode, I have bound a few
Of course I need a thesaurus, and I'm installing [[https://github.com/SavchenkoValeriy/emacs-powerthesaurus][powerthesaurus]]:
#+begin_src emacs-lisp
(use-package powerthesaurus
:bind ("s-t" . powerthesaurus-lookup-dwim)
:config
(ha-leader
"s t" '(:ignore t :which-key "thesaurus")
@ -882,14 +884,19 @@ Of course I need a thesaurus, and I'm installing [[https://github.com/SavchenkoV
"s t u" '("usages" . powerthesaurus-lookup-sentences-dwim)))
#+end_src
The key-bindings, keystrokes, and key-connections work well with ~M-T~ (notice the Shift), but to jump to specifics, we use a leader.
The key-bindings, keystrokes, and key-connections work well with a hyper-command, but to jump to specifics, I use a leader.
#+BEGIN_SRC emacs-lisp
(use-package powerthesaurus
:bind ("s-t" . powerthesaurus-lookup-dwim))
#+END_SRC
*** Definitions
Since the /definitions/ do not work, so let's use the [[https://github.com/abo-abo/define-word][define-word]] project:
#+begin_src emacs-lisp
(use-package define-word
:bind ("s-d" . define-word-at-point)
:config
(ha-leader :keymaps 'text-mode-map
"s d" '(:ignore t :which-key "dictionary")
@ -897,6 +904,13 @@ Since the /definitions/ do not work, so let's use the [[https://github.com/abo-a
"s d a" '("define any word" . define-word)))
#+end_src
And what about a binding when Im in insert mode:
#+BEGIN_SRC emacs-lisp
(use-package define-word
:bind ("s-d" . define-word-at-point))
#+END_SRC
After my enamoring of Noah Websters 1913 dictionary (originally due to reading [[https://janusworx.com/blog/thank-god-for-noah/][this essay]] by Mario Jason Braganza who referred to James Somers original [[https://jsomers.net/blog/dictionary][2014 blog entry]]), I easily followed the instructions from [[https://github.com/ponychicken/WebsterParser][WebsterParser]], a Github project, with the dictionary:
1. Download [[https://github.com/ponychicken/WebsterParser/releases/latest/download/websters-1913.dictionary.zip][the dictionary]] file.
2. Unzip the archive … have a *Finder* window open to the =.dictionary= file.
@ -935,9 +949,7 @@ The [[https://github.com/bnbeckwith/writegood-mode][writegood-mode]] is effectiv
#+end_src
And it reports obnoxious messages.
Hrm::hook ((org-mode . writegood-mode)
(gfm-mode . writegood-mode)
(markdown-mode) . writegood-mode)
Note: Instead of hooking the =writegood-mode= to Org files, I will hook it to =flycheck= instead.
We install the =write-good= NPM:
#+begin_src shell

View file

@ -347,8 +347,7 @@ Now that the [[file:ha-programming.org::*Language Server Protocol (LSP) Integrat
("Server"
(("l" python-lsp/body "LSP..."))
"Edit"
(("r" lsp-rename "Rename")
("=" lsp-format-region "Format"))
(("=" lsp-format-region "Format"))
"Navigate"
(("A" lsp-workspace-folders-add "Add Folder")
("R" lsp-workspace-folders-remove "Remove Folder"))

119
pud.org
View file

@ -2,7 +2,7 @@
#+author: Howard X. Abrams
#+date: 2025-01-18
#+filetags: emacs hamacs
#+lastmod: [2025-03-21 Fri]
#+lastmod: [2025-06-08 Sun]
A literate programming file for a Comint-based MUD client.
@ -30,17 +30,31 @@ A literate programming file for a Comint-based MUD client.
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 =telnet= (at the moment) for the connection, so you will need to install that first. On Mac, this would be:
This uses eithr =ssh= or good ol =telnet= for the connection. Surprised that one can still install in on a Mac, like:
#+BEGIN_SRC sh
brew install telent
brew install telnet
#+END_SRC
And use a similar command on Linux.
Use your favorite way to install Emacs packages, for instance, with Emacs 30, once can install it, and customize some settings in one go with =use-package=:
#+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
You may want to customize your connections to more worlds.
The default connects to *Moss n Puddles*, my own MUD which I invite you to join.
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
@ -88,27 +102,12 @@ The default connects to *Moss n Puddles*, my own MUD which I invite you to jo
:group 'pud)
#+END_SRC
For instance:
For instance, you could set up a call to =setopt= or customize the =pud-worlds= variable, as in:
#+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
Hidden:
#+BEGIN_SRC emacs-lisp :tangle no :eval no
(setq pud-worlds
'(["Moss-n-Puddles" ssh "howardabrams.com" 4004 "howard" "" "\\nconnect %s %s\\n"]
["Moss-n-Puddles" ssh "howardabrams.com" 4004 "rick" "" "\\nconnect %s %s\\n"]
(setopt pud-worlds
'(["Moss-n-Puddles" ssh "howardabrams.com" 4004 "howard" "" "connect %s %s"]
["Moss-n-Puddles" ssh "howardabrams.com" 4004 "rick" "" "connect %s %s"]
["Local-Moss" telnet "localhost" 4000 "howard" "" ""]
["Local-Moss" telnet "localhost" 4000 "rick" "" ""]))
#+END_SRC
@ -129,7 +128,7 @@ Next, open [[file:~/.authinfo.gpg][your authinfo file]], and insert the followin
machine howardabrams.com login [name] port 4000 password [pass]
#+END_SRC
Now, lets play! Type =run-pud=, and optionally select a world. If you get disconnected, re-run it, or even =pud-reconnect=.
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
@ -163,7 +162,6 @@ Choosing a world… er, connection using a =completing-read= allowing you to cho
(t (customize-option 'pud-worlds)))))
#+END_SRC
The following functions are accessibility functions to the world entry.
#+BEGIN_SRC emacs-lisp
@ -182,7 +180,7 @@ The following functions are accessibility functions to the world entry.
(defun pud-world-creds (world)
"Return the username and password from WORLD.
Multiple search queries for the .authinfo file."
(seq-let (label host port user) world
(seq-let (label conn-type host port user) world
(if-let ((auth-results (first (auth-source-search
:host host
:port port
@ -200,8 +198,8 @@ And some basic functions I should expand.
(ert-deftest pud-world-name-test ()
(should (string-equal (pud-world-name "foobar") "foobar"))
(should (string-equal (pud-world-name ["foobar" "localhost" "4000"]) "foobar"))
(should (string-equal (pud-world-name ["foobar" "localhost" "4000" nil]) "foobar"))
(should (string-equal (pud-world-name ["foobar" "localhost" "4000" ""]) "foobar"))
(should (string-equal (pud-world-name ["foobar" "localhost" "4000" nil]) "foobar"))
(should (string-equal (pud-world-name ["foobar" "localhost" "4000" "guest" "guest"]) "guest@foobar")))
(ert-deftest pud-world-network-test ()
@ -221,9 +219,6 @@ And some basic functions I should expand.
#+END_SRC
* Basics
:LOGBOOK:
CLOCK: [2025-03-03 Mon 11:57]--[2025-03-03 Mon 12:10] => 0:13
:END:
Using Comint, and hoping to have the ANSI colors displayed.
#+BEGIN_SRC emacs-lisp
@ -235,12 +230,12 @@ Im going to use good ol fashion =telnet= for the connection:
#+BEGIN_SRC emacs-lisp
(defcustom pud-telnet-path "telnet"
"Path to the program used by `run-pud' to connect using telnet."
"Path to the program used by `pud-run' to connect using telnet."
:type '(string)
:group 'pud)
(defcustom pud-ssh-path "ssh"
"Path to the program used by `run-pud' to connect using ssh."
"Path to the program used by `pud-run' to connect using ssh."
:type '(string)
:group 'pud)
#+END_SRC
@ -259,7 +254,6 @@ Command string to use, given a =world= with a connection type:
"Return a command string to pass to the shell.
The WORLD is a vector with the hostname, see `pud-worlds'."
(seq-let (host port) (pud-world-network world)
(message "Dealing with: %s %s %s" host port (aref world 1))
(cl-case (aref world 1)
(telnet (append (cons pud-telnet-path pud-cli-arguments)
(list host port)))
@ -286,14 +280,14 @@ The empty and currently disused mode map for storing our custom keybindings inhe
(let ((map (nconc (make-sparse-keymap) comint-mode-map)))
(define-key map "\t" 'completion-at-point)
map)
"Basic mode map for `run-pud'.")
"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 "" ; "^\\(?:\\[[^@]+@[^@]+\\]\\)"
"Prompt for `run-pud'.")
"Prompt for `pud-run'.")
#+END_SRC
The name of the buffer:
@ -305,13 +299,14 @@ The name of the buffer:
#+END_SRC
** Run and Connect
The main entry point to the program is the =run-pud= function:
The main entry point to the program is the =pud-run= function:
#+BEGIN_SRC emacs-lisp
(defun run-pud (world)
(defun pud-run (world)
"Run an inferior instance of `pud-cli' inside Emacs.
The WORLD should be vector containing the following:
- label for the world
- server type, 'ssh or 'telnet
- server hostname
- server port
- username (can be overridden)
@ -328,7 +323,7 @@ The main entry point to the program is the =run-pud= function:
(apply 'make-comint-in-buffer "Pud" buffer (car pud-cli) nil (cdr pud-cli))
(pud-mode)
(visual-line-mode 1)
(pud-reconnect world)))
(pud-login world)))
;; Regardless, provided we have a valid buffer, we pop to it.
(when buffer
(pop-to-buffer buffer))))
@ -337,29 +332,57 @@ The main entry point to the program is the =run-pud= function:
Connection and/or re-connection:
#+BEGIN_SRC emacs-lisp
(defun pud-reconnect (world)
(defun pud-login (world)
"Collect and send a `connect' sequence to WORLD.
Where WORLD is a vector of world information. NOP if the buffer has no
connection or no password could be found."
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)
(message "Attempting to log in...")
(length world)
(message "Attempting to log in to %s..." (pud-world-name world))
(seq-let (username password) (pud-world-creds world)
(let* ((conn-str (if (length> world 5)
(aref world 5)
(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))))
(message "proc: %s str: '%s'" process conn-full)
(goto-char (point-max))
(if process
(comint-send-string process conn-full)
(insert conn-full)))))
#+END_SRC
(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
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.
@ -370,7 +393,7 @@ Note that =comint-process-echoes=, depending on the mode and the circumstances,
(setq comint-use-prompt-regexp nil))
(define-derived-mode pud-mode comint-mode "Pud"
"Major mode for `run-pud'.
"Major mode for `pud-run'.
\\<pud-mode-map>"
;; this sets up the prompt so it matches things like: [foo@bar]

View file

@ -18,7 +18,7 @@ This creates the following files:
#!/usr/bin/env zsh
#
# My complete Zshell configuration. Don't edit this file.
# Instead edit: ~/technical/zshell.org and tangle it.
# Instead edit: zshell.org and tangle it.
#
#+END_SRC
@ -26,7 +26,7 @@ This creates the following files:
#!/usr/bin/env zsh
#
# Non-interactive, mostly easily settable environment variables. Don't
# edit this file. # Instead edit: ~/technical/zshell.org and tangle.
# edit this file. Instead edit: zshell.org and tangle.
#
#+END_SRC
@ -133,9 +133,9 @@ If you type something wrong, Zshell, by default, prompts to see if you wanted to
ENABLE_CORRECTION="true"
#+END_SRC
What about just /fixing it/? For this, we update the [[https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html][ZShell line editor]]:
What about just /fixing it/? For this, I thought to update the [[https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html][ZShell line editor]] with something like:
#+BEGIN_SRC zsh
#+BEGIN_SRC zsh :tangle no
autocorrect() {
zle .spell-word
zle .$WIDGET
@ -145,12 +145,7 @@ What about just /fixing it/? For this, we update the [[https://zsh.sourceforge.i
zle -N magic-space autocorrect
#+END_SRC
Bind the ability to auto-correct the word to the left with =Space= or =Enter=:
#+BEGIN_SRC zsh
bindkey ' ' magic-space
#+END_SRC
Now you cant insert a space as it attempts to correct it. Not worth the space savings.
** Waiting Indication
Display red dots whilst waiting for commands to complete.
@ -374,3 +369,7 @@ To let us know we read the =~/.zshrc= file:
#+options: num:nil toc:t todo:nil tasks:nil tags:nil date:nil
#+options: skip:nil author:nil email:nil creator:nil timestamp:nil
#+infojs_opt: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
# Local Variables:
# eval: (add-hook 'after-save-hook #'org-babel-tangle t t)
# End: