With the GNU Emacs package rcd-hash-edit.el one can visually edit Emacs Lisp hashes. Use it as a form editing convenience.

Tip
This file requires the GNU Emacs package: rcd-utilities.el:
https://gnu.support/gnu-emacs/packages/rcd-utilities-el.html
Caution
This package edits Emacs Lisp hashes created with the :test 'equal and is not well tested. Help is needed to expand package to handle hashes with keys as symbols. Maybe it works, maybe not. Who knows.

Key bindings

  • Use D to remove key and value from hash

  • Use a to add new key and value to the hash

  • Use d to nullify the existing value

  • Use e to edit value of a key

  • Use g to refresh

  • Use s to save hash

  • Use h, j, k, l as in Vi editor bindings

  • Use q to burry buffer

Possible applications

  1. Create a hash or edit existing hash with command M-x rcd-hash-edit. You may as well invoke hash editing programmatically with the function rcd-hash-edit-hash.

  2. Use rcd-utilities.el to save hash or read hash from file. Send file to other people for collaboration, receive hash back by email or other communication line. Store your hash on a disk. Collaborate on Emacs Lisp hash. Send back and forth by using chat.

  3. For example M-x rcd-save-symbol-to-file may be used to save the symbol value to file. Or you may use M-x rcd-hash-save

  4. You may read the hash into memory by using: M-x rcd-read-symbol-from-file or M-x rcd-hash-load

  5. Convert a single database column to hash and send it to collaborator for editing or viewing, then receive it back modified if you wish.

  6. Use it as a mini and quick database, create fields and values, save it for later. Load again, save it again.

  7. Build a system for your personal notes. You may easily create bunch of notes by using this system, save it all in the file, read from file when you want. Use alist type of lists of hashes. Fetch information from database, let the user edit it and save it back into the database.

  8. Prepare one hash with empty values as a template, send it to collaborator to insert new information for you. Receive it back and continue processing your data. Use it for form editing.

Source for Emacs Lisp package rcd-hash-edit

;;; rcd-hash-edit.el --- Edit hashes visually and collaboratively  -*- lexical-binding: t; -*-

;; Copyright (C) 2021 by Jean Louis

;; Author: Jean Louis <bugs@gnu.support>
;; Version: 0.2
;; Package-Requires: (rcd-utilities)
;; Keywords: convenience
;; URL: https://hyperscope.link/3/7/6/6/1/Emacs-Lisp-package-rcd-hash-edit-37661.html

;; 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 <http://www.gnu.org/licenses/>.

;;; Commentary:

;; 1. Create a hash any how or use M-x rcd-hash-edit

;; (setq hash (make-hash-table :test #'equal))
;; (puthash "ID" 29 hash)
;; (rcd-hash-edit-hash 'hash)
;;
;; M-x rcd-hash-edit
;; choose the hash to edit
;; if hash does not exist, new one can be created
;;
;; Key bindings:
;;
;; d - nullify the value
;; D - remove the key and value from hash
;; a - add new value to hash
;; e - edit value
;;
;; This file requires the GNU Emacs package: rcd-utilities.el:
;; https://gnu.support/gnu-emacs/packages/rcd-utilities-el.html
;;
;; Use `rcd-utilities' to save hash or read hash from file.  Send file
;; to other people to collaborate on the hash structure, let them send
;; it back by email.
;;
;; For example M-x rcd-save-symbol-to-file may be used to save the
;; symbol value to file.
;;
;; You may read the hash into memory by using:
;; M-x rcd-read-symbol-from-file
;;
;; Send file to your collaborators.  Receive the file from
;; collaborators.  Collaborate on Emacs Lisp hash.  Send back and forth
;; by using chat.
;;
;; Example uses:
;;
;; - Convert a single database column and send to collaborator to edit
;;   it and send it back.
;;
;; - Use it as a mini database, create fields and values, save it for
;;   later.  Load again, save it again.
;;
;; - Prepare one template empty hash, send it to collaborator to
;;   insert new information for you. Receive it back and continue
;;   processing your data.

;; RCD is acronym for Reach, Connect, Deliver, my personal
;; principle and formula for Wealth.

;;; Change Log:

;;; Code:

(require 'rcd-utilities nil t)

(defvar-local rcd-hash-current-hash nil
  "Buffer local variable to designate the symbol of edited hash.")

(define-derived-mode rcd-hash-list-mode tabulated-list-mode "RCD Hash List"
  "RCD Hash List is derived from `tabulated-list-mode'.")

(defvar rcd-hash-mode-map
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map tabulated-list-mode-map)
    (define-key map "D" #'rcd-hash-delete-key-value)
    (define-key map "a" #'rcd-hash-add-key)
    (define-key map "d" #'rcd-hash-nullify-entry)
    (define-key map "e" #'rcd-hash-edit-entry)
    (define-key map "g" #'rcd-hash-refresh)
    (define-key map "h" #'backward-char)
    (define-key map "j" #'next-line)
    (define-key map "k" #'previous-line)
    (define-key map "l" #'forward-char)
    (define-key map "q" #'quit-window)
    (define-key map "s" #'(lambda () (interactive) (rcd-hash-save rcd-hash-current-hash)))
    map)
  "The RCD hash keymap.")

(defun rcd-hash-entries (hash)
  "Convert symbol or hash HASH to `tabulated-list-entries'."
  (let* ((entries '())
	 (type (type-of hash))
	 (hash (cond ((eq type 'symbol) (symbol-value hash))
		     ((eq type 'hash-table) hash)
		     (t hash))))
    (if (eq (type-of hash) 'hash-table)
	(progn
	  (maphash
	   (lambda (key value)
	     (push (list key (vector
			      (string-or-empty-string key)
			      (string-or-empty-string (prin1-to-string value))))
		   entries))
	   hash)
	  (reverse entries))
      (error "%s is not hash or symbol for hash" hash))))

(defun rcd-hash-edit-hash (rcd-hash)
  "Edit HASH with `rcd-hash-list-mode'."
  (let ((rcd-hash-current-hash rcd-hash)
	(entries (rcd-hash-entries rcd-hash)))
    (rcd-hash-report "Edit hash" entries [("Key" 20 t :right-align t :pad-right 3) ("Value" 40 t)] rcd-hash 'rcd-hash-refresh)))

(defun rcd-hash-edit ()
  "Interactively choose a hash to edit.
Optionally provide symbol RCD-HASH for editing or creation."
  (interactive)
  (let ((rcd-hash (read--expression "Hash to edit: ")))
    (if (boundp rcd-hash)
	(if (eq (type-of (symbol-value rcd-hash)) 'hash-table)
	    (rcd-hash-edit-hash rcd-hash)
	  (message "Symbol is not a hash"))
      (progn
	(rcd-hash-create rcd-hash)
	;; (puthash "ID" "ID" (symbol-value rcd-hash))
	;; (remhash "ID" (symbol-value rcd-hash))
	(rcd-hash-edit-hash rcd-hash)))))

(defun rcd-hash-create (symbol)
  "Create hash NAME.
Argument SYMBOL will be created in global space."
  (eval `(defvar ,(intern (symbol-name symbol)) (make-hash-table :test 'equal))))

(defun rcd-hash-edit-entry-1 (hash key)
  "Edit HASH value by using KEY."
  (let* ((hash (symbol-value hash))
	 (value (gethash key hash))
	 (prompt (format "Value for key `%s': " (if (not (stringp key)) (prin1-to-string key) key)))
	 (type (type-of value))
	 (new-value (cond  ((eq type 'cons) (read--expression prompt (prin1-to-string value)))
			   ((or (eq type 'integer)
				(eq type 'float))
			    (read-number prompt value))
			   ((eq type 'string) (read-from-minibuffer prompt value nil nil nil value))
			   (t (error "Not recognized type %s" type)))))
    (puthash key new-value hash)))

(defun rcd-hash-add-key ()
  "Add key to edited hash."
  (interactive)
  (let* ((key (read-from-minibuffer "New key: "))
	 (completion-ignore-case t)
	 (type (completing-read "Type: " '("Number" "String" "Cons") nil t))
	 (prompt (format "Value for key `%s': " key))
	 (value (cond ((string= type "Number") (read-number prompt))
		      ((string= type "String") (read-from-minibuffer prompt))
		      ((string= type "Cons") (read--expression prompt))
		      (t (read-from-minibuffer prompt)))))
    (when (and key value)
      (let ((point (point))
	    (rcd-hash rcd-hash-current-hash))
	(setf (gethash key (symbol-value rcd-hash-current-hash)) value)
	(kill-this-buffer)
	(rcd-hash-edit-hash rcd-hash)
	(goto-char point)))))

(defun rcd-hash-report (title entries format rcd-hash &optional refresh)
  "Handles visual HASH editing.
TITLE is used for the name of buffer.
ENTRIES is in the format of `tabulated-list-entries'.
FORMAT is is in the format of `tabulated-list-format'."
  (let* ((buffer (generate-new-buffer-name (concat "*RCD Hash Editing: " title "*"))))
    (let* ((buffer (get-buffer-create buffer)))
      (switch-to-buffer buffer)
      (setq tabulated-list-format format)
      (setq tabulated-list-entries entries)
      (setq rcd-tabulated-refresh-function refresh)
      (rcd-hash-list-mode)
      (use-local-map rcd-hash-mode-map)
      (hl-line-mode 1)
      (setq rcd-hash-current-hash rcd-hash)
      (setq tabulated-list-padding 1)
      (tabulated-list-init-header))
    (tabulated-list-print t)))

(defun rcd-hash-delete-key-value ()
  "Delete the key and value from edited hash."
  (interactive)
  (let* ((key (tabulated-list-get-id)))
    (when key
      (let ((point (point))
	    (rcd-hash rcd-hash-current-hash))
	(when (y-or-n-p (format "Remove key `%s'? " key))
	  (remhash key (symbol-value rcd-hash))
	  (kill-this-buffer)
	  (rcd-hash-edit-hash rcd-hash)
	  (goto-char point))))))

(defun rcd-hash-refresh ()
  "Refresh the `rcd-hash-list-mode'."
  (interactive)
  (when (eq major-mode 'rcd-hash-list-mode)
    (let ((rcd-hash rcd-hash-current-hash)
	  (point (point)))
      (kill-this-buffer)
      (rcd-hash-edit-hash rcd-hash)
      (goto-char point))))

(defun rcd-hash-edit-entry ()
  "Edit entry."
  (interactive)
  (let* ((key (tabulated-list-get-id))
	 (hash rcd-hash-current-hash)
	 (point (point)))
    (when (and key rcd-hash-current-hash)
      (rcd-hash-edit-entry-1 rcd-hash-current-hash key)
      (kill-this-buffer)
      (rcd-hash-edit-hash hash)
      (goto-char point))))

(defun rcd-hash-nullify-entry ()
  "Nullify the entry for the specific hash key."
  (interactive)
  (let* ((key (tabulated-list-get-id))
	 (entry (tabulated-list-get-entry)))
    (when (and key entry)
      (let* ((point (point))
	     (rcd-hash rcd-hash-current-hash)
	     (value (gethash key (symbol-value rcd-hash-current-hash))))
	(when (y-or-n-p (format "Delete `%s'? " value))
	  (puthash key (rcd-hash-nullify-by-type value) (symbol-value rcd-hash-current-hash))
	  (kill-this-buffer)
	  (rcd-hash-edit-hash rcd-hash)
	  (goto-char point))))))

(defun rcd-hash-nullify-by-type (value)
  "Return the nullified value depending of the type of VALUE."
  (let ((type (type-of value)))
    (cond  ((or (eq type 'cons) (car (read-from-string "nil"))))
	   ((or (eq type 'symbol) (car (read-from-string "nil"))))
	   ((or (eq type 'integer)
		(eq type 'float))
	    0)
	   ((eq type 'string) "")
	   (t (error "Not recognized type %s" type)))))

(defalias 'rcd-hash-load 'rcd-read-symbol-from-file
  "Uses the package `rcd-utilities' to read hash from file.")

(defalias 'rcd-hash-save 'rcd-save-symbol-to-file
  "Uses the package `rcd-utilities' to save hash into file")

;;; rcd-hash-edit.el ends here

GNU General Public License Version 3

Copyright © 2021-05-18 13:13:47.580285+02 by Jean Louis

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 https://www.gnu.org/licenses/.