Repository: Wilfred/ht.el
Branch: master
Commit: 1c49aad1c820
Files: 9
Total size: 32.9 KB
Directory structure:
gitextract_bbdlbrez/
├── .github/
│ └── workflows/
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── Cask
├── Makefile
├── README.md
├── ht.el
└── test/
├── ht-test.el
└── test-helper.el
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/test.yml
================================================
name: test
on:
pull_request:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
emacs_version:
- 25.3
- 26.3
- 27.2
- 28.2
- snapshot
steps:
- uses: purcell/setup-emacs@master
with:
version: ${{ matrix.emacs_version }}
- uses: conao3/setup-cask@master
- uses: actions/checkout@v3
- run: cask
- name: Run tests
run: |
emacs --version
make test
================================================
FILE: .gitignore
================================================
*.elc
/.cask
================================================
FILE: CHANGELOG.md
================================================
## v2.4 (not yet tagged)
Switched to GitHub Actions for automated testing.
### Features
* Added `ht-update-with!`. This is like Racket's `hash-update!`.
### Bug Fixes
* `ht-select-keys` can now select keys whose values happen to be the symbol `key-not-found`.
* Corrected the documentation for `ht-reject!`.
## v2.3
### Features
* Added `ht-empty-p` as an alias of `ht-empty?`.
* Many functions have been marked as `side-effect-free`, improving
performance and byte-compiler warnings.
* `ht-get` and `ht-get*` can now be used with `setf`, e.g.
```
(setf (ht-get my-table my-key) new-value)
```
### Bug Fixes
* `ht<-plist` now keeps the first item in the list, if there are
duplicates. This makes it consistent with `ht<-alist` and
`plist-get`.
* `ht-get*` now handles an empty list of keys correctly, and will no
longer stack overflow on large lists of keys.
* `ht-contains-p` now even supports hash tables that contain the key
`ht--not-found`.
## v2.2
* Added `ht-select-keys`
* Added `ht-get*`
ht.el now uses `defsubst` for many of its functions, which may improve
performance in byte-compiled code.
## v2.1
* `ht<-alist` and `ht<-plist` gained an optional argument `test` to
specify the equality predicate.
* Added `ht-equal?`.
## v2.0 -- API Change
Functions names have been changed to be more explicit and consistent.
Note that ht.el includes aliases, so v2.0 is fully backwards
compatible.
Mutation functions now always end with `!`, and `ht-delete-if` has
been renamed for consistency with its non-mutating equivalent
`ht-reject`.
* `ht-set` -> `ht-set!`
* `ht-update` -> `ht-update!`
* `ht-remove` -> `ht-remove!`
* `ht-clear` -> `ht-clear!`
* `ht-delete-if` -> `ht-reject!`
Predicates now always end with `?`.
* `ht-p` -> `ht?`
* `ht-contains-p` -> `ht-contains?`
Conversion functions now use `<-` and `->`.
* `ht-to-alist` -> `ht->alist`
* `ht-to-plist` -> `ht->plist`
* `ht-from-alist` -> `ht<-alist`
* `ht-from-plist` -> `ht<-plist`
## v1.6
* Added `ht-reject` and `ht-select`
* Added `ht-delete-if`
* Added `ht-find`
* Added `ht-empty?` and `ht-size`
Also added Travis configuration.
## v1.5
* `ht-aeach` now evaluates to `nil` as it should (use `ht-amap` if you
want the resulting hash table).
## v1.4
* Added `ht-merge`.
## v1.3
* Removed runtime dependency on `cl`.
## v1.2
* Fixed various `void-variable` crashes due to `ht-amap` only being
declared after its usage.
## v1.1
* Added `ht-contains-p`.
## v1.0 -- API Change
* `ht-map` now returns a list, as you'd expect a map function to do.
* Added `ht-each` for when you're only interested in side-effects.
* Added an anaphoric version of `ht-each`, `ht-aeach`.
## v0.11
* Added `ht-map` and an anaphoric version `ht-amap`.
## v0.10
* Added `ht-p`, an alias of `hash-table-p`, (mainly for completeness).
## v0.9
* Added `ht-update`.
## v0.8
* Added the `ht` macro to make hash table literals easy
## v0.7
* Added `ht-to-alist` and `ht-to-plist`
## v0.6
* Fixed a bug where `ht-from-alist` would overwrite the latest key-value
association with older ones
## v0.5
* Added `ht-from-plist`
## v0.4
* Added `ht-from-alist`
## v0.3
* Added ht-copy
## v0.2
* Changed functions from hm-* to ht-* (Emacs doesn't use the term hash map)
## v0.1
* Initial release
================================================
FILE: Cask
================================================
(source gnu)
(source melpa)
(package-file "ht.el")
(development
(depends-on "f")
(depends-on "cl-lib")
(depends-on "ert-runner"))
================================================
FILE: Makefile
================================================
EMACS ?= emacs
CASK ?= cask
all: test
test: clean-elc
${MAKE} unit
${MAKE} compile
${MAKE} unit
${MAKE} clean-elc
unit:
${CASK} exec ert-runner
compile:
${CASK} exec ${EMACS} -Q -batch -f batch-byte-compile ht.el
clean-elc:
rm -f ht.elc
.PHONY: all test unit compile
================================================
FILE: README.md
================================================
# ht.el
The missing hash table library for Emacs.
[](http://melpa.org/#/ht)
[](http://stable.melpa.org/#/ht)
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-generate-toc again -->
**Table of Contents**
- [ht.el](#htel)
- [Functions](#functions)
- [Return a hash table](#return-a-hash-table)
- [Accessing the hash table](#accessing-the-hash-table)
- [Mutating the hash table](#mutating-the-hash-table)
- [Iterating over the hash table](#iterating-over-the-hash-table)
- [Predicates](#predicates)
- [Converting from a hash table](#converting-from-a-hash-table)
- [Converting to a hash table](#converting-to-a-hash-table)
- [Macros](#macros)
- [Returning a hash table](#returning-a-hash-table)
- [Iterating over the hash table (anaphoric)](#iterating-over-the-hash-table-anaphoric)
- [Examples](#examples)
- [Why?](#why)
- [Similar libraries](#similar-libraries)
- [Installation](#installation)
- [Changelog](#changelog)
- [Running tests](#running-tests)
- [What's an alist/plist?](#whats-an-alistplist)
<!-- markdown-toc end -->
## Functions
### Return a hash table
* `ht-create` `(test?)`
* `ht-merge` `(&rest tables)`
* `ht-copy` `(table)`
* `ht-select` `(function table)`
* `ht-reject` `(function table)`
* `ht-select-keys` `(table keys)`
### Accessing the hash table
* `ht-get` `(table key default?)`
* `ht-get*` `(table &rest keys)`
* `ht-keys` `(table)`
* `ht-values` `(table)`
* `ht-items` `(table)`
* `ht-find` `(function table)`
* `ht-size` `(table)`
### Mutating the hash table
* `ht-set!` `(table key value)`
* `ht-update!` `(table table)`
* `ht-update-with!` `(table key updater default?)`
* `ht-remove!` `(table key)`
* `ht-clear!` `(table)`
* `ht-reject!` `(function table)`
### Iterating over the hash table
* `ht-map` `(function table)`
* `ht-each` `(function table)`
### Predicates
* `ht?` `(table-or-object)`
* `ht-contains?` `(table key)`
* `ht-equal?` `(table1 table2)`
* `ht-empty?` `(table)`
### Converting from a hash table
* `ht->alist` `(table)`
* `ht->plist` `(table)`
### Converting to a hash table
* `ht<-alist` `(alist test?)`
* `ht<-plist` `(plist test?)`
## Macros
### Returning a hash table
* `ht` `(&rest pairs)`
### Iterating over the hash table (anaphoric)
* `ht-amap` `(form table)`
* `ht-aeach` `(form table)`
## Examples
Creating a hash table and accessing it:
``` emacs-lisp
(require 'ht)
(defun say-hello (name)
(let ((greetings (ht ("Bob" "Hey bob!")
("Chris" "Hi Chris!"))))
(ht-get greetings name "Hello stranger!")))
```
This could be alternatively written as:
``` emacs-lisp
(require 'ht)
(defun say-hello (name)
(let ((greetings (ht-create)))
(ht-set! greetings "Bob" "Hey Bob!")
(ht-set! greetings "Chris" "Hi Chris!")
(ht-get greetings name "Hello stranger!")))
```
Accessing nested hash tables:
``` emacs-lisp
(let ((alphabets (ht ("Greek" (ht (1 (ht ('letter "α")
('name "alpha")))
(2 (ht ('letter "β")
('name "beta")))))
("English" (ht (1 (ht ('letter "a")
('name "A")))
(2 (ht ('letter "b")
('name "B"))))))))
(ht-get* alphabets "Greek" 1 'letter)) ; => "α"
```
`ht-get` and `ht-get*` have gv-setters and so will work with `setf`:
``` emacs-lisp
(let ((table (ht-create)))
(ht-set! table 1 "A"))
```
is equivalent to
``` emacs-lisp
(let ((table (ht-create)))
(setf (ht-get table 1) "A"))
```
and
``` emacs-lisp
(let ((table (ht (1 (ht (2 (ht (3 "three"))))))))
(ht-set! (ht-get (ht-get table 1) 2) 3 :three))
```
is equivalent to
``` emacs-lisp
(let ((table (ht (1 (ht (2 (ht (3 "three"))))))))
(setf (ht-get* table 1 2 3) :three))
```
Updating values with a function using `ht-update-with!`:
``` emacs-lisp
(let ((table (ht ("a" (list "a" "b")))))
(ht-update-with!
table "a"
(lambda (list)
(cons "c" list)))
(ht-get table "a")) ; '("c" "a" "b"))
```
is equivalent to
``` emacs-lisp
(let ((table (ht ("a" (list "a" "b")))))
(setf (ht-get table "a")
(cons "c" (ht-get table "a")))
(ht-get table "a")) ; '("c" "a" "b"))
```
## Why?
Libraries like [s.el](https://github.com/magnars/s.el) (strings) and
[dash.el](https://github.com/magnars/dash.el) (lists) have shown how
much nicer Emacs lisp programming can be with good libraries. ht.el
aims to similarly simplify working with hash tables.
Common operations with hash tables (e.g. enumerate the keys) are too
difficult in Emacs lisp.
ht.el offers:
* A consistent naming scheme (contrast `make-hash-table` with `puthash`)
* A more natural argument ordering
* Mutation functions always return `nil`
* A more comprehensive range of hash table operations, including a
conventional map (`ht-map` returns a list, elisp's `maphash` returns
nil).
### Similar libraries
* [kv.el](https://github.com/nicferrier/emacs-kv) (focuses more on
alists)
* [mon-hash-utils](http://www.emacswiki.org/emacs/mon-hash-utils.el)
## Installation
ht.el is available on [MELPA](https://melpa.org/) (recommended) and
[Marmalade](http://marmalade-repo.org/).
Add MELPA to your .emacs.d/init.el:
``` emacs-lisp
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
```
then run `M-x package-install <RET> ht <RET>`.
## Changelog
ht.el uses semantic versioning, so an incompatible API change will
result in the major version increasing. See
[CHANGELOG.md](CHANGELOG.md) for a history of all changes.
## Running tests
`M-x ht-run-tests`
## What's an alist/plist?
An alist is an association list, which is a list of pairs. It looks like this:
((key1 . value1)
(key2 . value2)
(key3 . value3))
An alist can also look like this:
((key1 . value1)
(key2 . value2)
(key1 . oldvalue))
A plist is a property list, which is a flat list with an even number
of items. It looks like this:
(key1 value1
key2 value2
key3 value3)
Both of these are slow. ht.el provides `ht<-alist` and
`ht<-plist` to help you convert to hash tables. If you need to
work with an alist or plist, use the functions `ht->alist` and
`ht->plist` to convert an hash table to those formats.
================================================
FILE: ht.el
================================================
;;; ht.el --- The missing hash table library for Emacs -*- lexical-binding: t; -*-
;; Copyright (C) 2013 Wilfred Hughes
;; Author: Wilfred Hughes <me@wilfred.me.uk>
;; Version: 2.4
;; Keywords: hash table, hash map, hash
;; Package-Requires: ((dash "2.12.0"))
;; 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:
;; The missing hash table library for Emacs.
;;
;; See documentation at https://github.com/Wilfred/ht.el
;;; Code:
(require 'dash)
(require 'gv)
(eval-when-compile
(require 'inline))
(defmacro ht (&rest pairs)
"Create a hash table with the key-value pairs given.
Keys are compared with `equal'.
\(fn (KEY-1 VALUE-1) (KEY-2 VALUE-2) ...)"
(let* ((table-symbol (make-symbol "ht-temp"))
(assignments
(mapcar
(lambda (pair) `(ht-set! ,table-symbol ,@pair))
pairs)))
`(let ((,table-symbol (ht-create)))
,@assignments
,table-symbol)))
(define-inline ht-set! (table key value)
"Associate KEY in TABLE with VALUE."
(inline-quote
(prog1 nil
(puthash ,key ,value ,table))))
(defalias 'ht-set 'ht-set!)
(define-inline ht-create (&optional test)
"Create an empty hash table.
TEST indicates the function used to compare the hash
keys. Default is `equal'. It can be `eq', `eql', `equal' or a
user-supplied test created via `define-hash-table-test'."
(declare (side-effect-free t))
(inline-quote (make-hash-table :test (or ,test 'equal))))
(defun ht<-alist (alist &optional test)
"Create a hash table with initial values according to ALIST.
TEST indicates the function used to compare the hash
keys. Default is `equal'. It can be `eq', `eql', `equal' or a
user-supplied test created via `define-hash-table-test'."
(declare (side-effect-free t))
(let ((h (ht-create test)))
;; the first key-value pair in an alist gets precedence, so we
;; start from the end of the list:
(dolist (pair (reverse alist) h)
(let ((key (car pair))
(value (cdr pair)))
(ht-set! h key value)))))
(defalias 'ht-from-alist 'ht<-alist)
(defun ht<-plist (plist &optional test)
"Create a hash table with initial values according to PLIST.
TEST indicates the function used to compare the hash
keys. Default is `equal'. It can be `eq', `eql', `equal' or a
user-supplied test created via `define-hash-table-test'."
(declare (side-effect-free t))
(let ((h (ht-create test)))
(dolist (pair (nreverse (-partition 2 plist)) h)
(let ((key (car pair))
(value (cadr pair)))
(ht-set! h key value)))))
(defalias 'ht-from-plist 'ht<-plist)
(define-inline ht-get (table key &optional default)
"Look up KEY in TABLE, and return the matching value.
If KEY isn't present, return DEFAULT (nil if not specified)."
(declare (side-effect-free t))
(inline-quote
(gethash ,key ,table ,default)))
;; Don't use `ht-set!' here, gv setter was assumed to return the value
;; to be set.
(gv-define-setter ht-get (value table key) `(puthash ,key ,value ,table))
(define-inline ht-get* (table &rest keys)
"Look up KEYS in nested hash tables, starting with TABLE.
The lookup for each key should return another hash table, except
for the final key, which may return any value."
(declare (side-effect-free t))
(inline-letevals (table keys)
(inline-quote
(progn
(while ,keys
(setf ,table (ht-get ,table (pop ,keys))))
,table))))
(put 'ht-get* 'compiler-macro
(lambda (_ table &rest keys)
(--reduce-from `(ht-get ,acc ,it) table keys)))
(defun ht-update! (table from-table)
"Update TABLE according to every key-value pair in FROM-TABLE."
(maphash
(lambda (key value) (puthash key value table))
from-table)
nil)
(defalias 'ht-update 'ht-update!)
(define-inline ht-update-with! (table key updater &optional default)
"Update the value of KEY in TABLE with UPDATER.
If the value does not exist, do nothing, unless DEFAULT is
non-nil, in which case act as if the value is DEFAULT.
UPDATER receives one argument, the value, and its return value
becomes the new value of KEY."
(inline-quote
(let* ((not-found-symbol (make-symbol "ht--not-found"))
(v (gethash ,key ,table
(or ,default not-found-symbol))))
(unless (eq v not-found-symbol)
(prog1 nil
(puthash ,key (funcall ,updater v) ,table))))))
(defun ht-merge (&rest tables)
"Crete a new table that includes all the key-value pairs from TABLES.
If multiple tables have the same key, the value in the last
table is used."
(let ((merged (ht-create)))
(mapc (lambda (table) (ht-update! merged table)) tables)
merged))
(define-inline ht-remove! (table key)
"Remove KEY from TABLE."
(inline-quote (remhash ,key ,table)))
(defalias 'ht-remove 'ht-remove!)
(define-inline ht-clear! (table)
"Remove all keys from TABLE."
(inline-quote
(prog1 nil
(clrhash ,table))))
(defalias 'ht-clear 'ht-clear!)
(defun ht-map (function table)
"Apply FUNCTION to each key-value pair of TABLE, and make a list of the results.
FUNCTION is called with two arguments, KEY and VALUE."
(let (results)
(maphash
(lambda (key value)
(push (funcall function key value) results))
table)
results))
(defmacro ht-amap (form table)
"Anaphoric version of `ht-map'.
For every key-value pair in TABLE, evaluate FORM with the
variables KEY and VALUE bound. If you don't use both of
these variables, then use `ht-map' to avoid warnings."
`(ht-map (lambda (key value) ,form) ,table))
(defun ht-keys (table)
"Return a list of all the keys in TABLE."
(declare (side-effect-free t))
(ht-map (lambda (key _value) key) table))
(defun ht-values (table)
"Return a list of all the values in TABLE."
(declare (side-effect-free t))
(ht-map (lambda (_key value) value) table))
(defun ht-items (table)
"Return a list of two-element lists \\='(key value) from TABLE."
(declare (side-effect-free t))
(ht-amap (list key value) table))
(defalias 'ht-each 'maphash
"Apply FUNCTION to each key-value pair of TABLE.
Returns nil, used for side-effects only.")
(defmacro ht-aeach (form table)
"Anaphoric version of `ht-each'.
For every key-value pair in TABLE, evaluate FORM with the
variables key and value bound."
`(ht-each (lambda (key value) ,form) ,table))
(defun ht-select-keys (table keys)
"Return a copy of TABLE with only the specified KEYS."
(declare (side-effect-free t))
(let ((not-found-symbol (make-symbol "ht--not-found"))
result)
(setq result (make-hash-table :test (hash-table-test table)))
(dolist (key keys result)
(if (not (equal (gethash key table not-found-symbol) not-found-symbol))
(puthash key (gethash key table) result)))))
(defun ht->plist (table)
"Return a flat list \\='(key1 value1 key2 value2...) from TABLE.
Note that hash tables are unordered, so this cannot be an exact
inverse of `ht<-plist'. The following is not guaranteed:
\(let ((data \\='(a b c d)))
(equalp data
(ht->plist (ht<-plist data))))"
(declare (side-effect-free t))
(apply 'append (ht-items table)))
(defalias 'ht-to-plist 'ht->plist)
(define-inline ht-copy (table)
"Return a shallow copy of TABLE (keys and values are shared)."
(declare (side-effect-free t))
(inline-quote (copy-hash-table ,table)))
(defun ht->alist (table)
"Return a list of two-element lists \\='(key . value) from TABLE.
Note that hash tables are unordered, so this cannot be an exact
inverse of `ht<-alist'. The following is not guaranteed:
\(let ((data \\='((a . b) (c . d))))
(equalp data
(ht->alist (ht<-alist data))))"
(declare (side-effect-free t))
(ht-amap (cons key value) table))
(defalias 'ht-to-alist 'ht->alist)
(defalias 'ht? 'hash-table-p)
(defalias 'ht-p 'hash-table-p)
(define-inline ht-contains? (table key)
"Return \\='t if TABLE contains KEY."
(declare (side-effect-free t))
(inline-quote
(let ((not-found-symbol (make-symbol "ht--not-found")))
(not (eq (ht-get ,table ,key not-found-symbol) not-found-symbol)))))
(defalias 'ht-contains-p 'ht-contains?)
(define-inline ht-size (table)
"Return the actual number of entries in TABLE."
(declare (side-effect-free t))
(inline-quote
(hash-table-count ,table)))
(define-inline ht-empty? (table)
"Return true if the actual number of entries in TABLE is zero."
(declare (side-effect-free t))
(inline-quote
(zerop (ht-size ,table))))
(defalias 'ht-empty-p 'ht-empty?)
(defun ht-select (function table)
"Return a hash table containing all entries in TABLE for which
FUNCTION returns a truthy value.
FUNCTION is called with two arguments, KEY and VALUE."
(let ((results (ht-create)))
(ht-each
(lambda (key value)
(when (funcall function key value)
(ht-set! results key value)))
table)
results))
(defun ht-reject (function table)
"Return a hash table containing all entries in TABLE for which
FUNCTION returns a falsy value.
FUNCTION is called with two arguments, KEY and VALUE."
(let ((results (ht-create)))
(ht-each
(lambda (key value)
(unless (funcall function key value)
(ht-set! results key value)))
table)
results))
(defun ht-reject! (function table)
"Delete entries from TABLE for which FUNCTION returns non-nil.
FUNCTION is called with two arguments, KEY and VALUE."
(ht-each
(lambda (key value)
(when (funcall function key value)
(remhash key table)))
table)
nil)
(defalias 'ht-delete-if 'ht-reject!)
(defun ht-find (function table)
"Return (key, value) from TABLE for which FUNCTION returns a truthy value.
Return nil otherwise.
FUNCTION is called with two arguments, KEY and VALUE."
(catch 'break
(ht-each
(lambda (key value)
(when (funcall function key value)
(throw 'break (list key value))))
table)))
(defun ht-equal? (table1 table2)
"Return t if TABLE1 and TABLE2 have the same keys and values.
Does not compare equality predicates."
(declare (side-effect-free t))
(let ((keys1 (ht-keys table1))
(keys2 (ht-keys table2))
(sentinel (make-symbol "ht-sentinel")))
(and (equal (length keys1) (length keys2))
(--all?
(if (ht-p (ht-get table1 it))
(ht-equal-p (ht-get table1 it)
(ht-get table2 it))
(equal (ht-get table1 it)
(ht-get table2 it sentinel)))
keys1))))
(defalias 'ht-equal-p 'ht-equal?)
(provide 'ht)
;;; ht.el ends here
================================================
FILE: test/ht-test.el
================================================
(require 'ht)
(ert-deftest ht-test-ht ()
(let ((test-table (ht (1 2) ("foo" (1+ 2)))))
(should (and (member 1 (ht-keys test-table))
(member "foo" (ht-keys test-table))
(member 2 (ht-values test-table))
(member 3 (ht-values test-table))))))
(ert-deftest ht-test-create ()
(should (hash-table-p (ht-create))))
(ert-deftest ht-test-set-then-get ()
(let ((test-table (ht-create)))
(ht-set test-table "foo" "bar")
(should (equal (ht-get test-table "foo") "bar"))))
(ert-deftest ht-test-get-default ()
(let ((test-table (ht-create)))
(should (equal (ht-get test-table "foo" "default") "default"))))
(ert-deftest ht-test-get* ()
(let ((alphabets (ht ("Greek" (ht (1 (ht ('letter "α")
('name "alpha")))
(2 (ht ('letter "β")
('name "beta")))))
("English" (ht (1 (ht ('letter "a")
('name "A")))
(2 (ht ('letter "b")
('name "B"))))))))
;; Nested
(should (equal (ht-get* alphabets "English" 1 'letter)
"a"))
;; Non-nested
(should (equal (ht-get* (ht (1 "one")) 1)
"one"))
;; Base case (no keys)
(should (equal (ht-get* alphabets)
alphabets))
;; Works with apply, see #38
(should (equal (apply #'ht-get* (list (ht) 1))
nil))))
(ert-deftest ht-test-setf-ht-get ()
(let ((test-table (ht (1 "one"))))
(should (equal (setf (ht-get test-table 1) "alpha") "alpha")))
(let ((test-table (ht (1 "one") (2 "two"))))
(setf (ht-get test-table 1) "alpha")
(should (equal (ht-get test-table 1)
"alpha"))))
(ert-deftest ht-test-setf-ht-get* ()
(let ((test-table (ht (1 (ht (2 (ht (3 "three"))))))))
(should (equal (setf (ht-get* test-table 1 2 3) "gamma")
"gamma")))
;; nested tables { 1 : { 2 : { 3 : three } } }
(let ((test-table (ht (1 (ht (2 (ht (3 "three"))))))))
(setf (ht-get* test-table 1 2 3) "gamma")
(should (equal (ht-get* test-table 1 2 3)
"gamma")))
;; nested tables { 1 : { 2 : two } }
(let ((test-table (ht (1 (ht (2 "two"))))))
(setf (ht-get* test-table 1 2) "beta")
(should (equal (ht-get* test-table 1 2)
"beta")))
;; Non-nested table { 1 : one }
(let ((test-table (ht (1 "one"))))
(setf (ht-get* test-table 1) "alpha")
(should (equal (ht-get* test-table 1)
"alpha"))))
(ert-deftest ht-test-update-with! ()
(let ((test-table (ht ("foo" 1))))
;; Do it
(ht-update-with! test-table "foo" #'1+)
(should
(equal (ht-get test-table "foo") 2))
;; Keys stay unset
(ht-update-with! test-table "bar" #'1+)
(should-not
(ht-contains? test-table "bar"))
;; Default value
(ht-update-with! test-table "bar" #'1+ 0)
(should
(equal (ht-get test-table "bar") 1))))
(ert-deftest ht-test-update ()
(let ((test-table (ht ("foo" 1))))
(ht-update test-table (ht ("bar" 2)))
(should
(equal
(list "bar" "foo")
(sort (ht-keys test-table) 'string<)))))
(ert-deftest ht-test-merge ()
(let ((table1 (ht ("foo" 1)))
(table2 (ht ("bar" 2))))
(should
(equal
(list "bar" "foo")
(sort (ht-keys (ht-merge table1 table2)) 'string<)))))
(ert-deftest ht-test-create-non-default-test ()
(let ((test-table (ht-create 'eq)))
(should (equal (hash-table-test test-table) 'eq))))
(ert-deftest ht-test-remove ()
(let ((test-table (ht-create)))
(ht-set test-table "foo" "bar")
(ht-remove test-table "foo")
(should (equal (ht-get test-table "foo") nil))))
(ert-deftest ht-test-clear ()
(let ((test-table (ht-create)))
(ht-set test-table "foo" "bar")
(ht-set test-table "biz" "baz")
(ht-clear test-table)
(should (equal (ht-items test-table) nil))))
(ert-deftest ht-test-keys ()
(let ((test-table (ht-create)))
(ht-set test-table "foo" "bar")
(should (equal (ht-keys test-table) (list "foo")))))
(ert-deftest ht-test-values ()
(let ((test-table (ht-create)))
(ht-set test-table "foo" "bar")
(should (equal (ht-values test-table) (list "bar")))))
(ert-deftest ht-test-items ()
(let ((test-table (ht-create)))
(ht-set test-table "key1" "value1")
(should (equal (ht-items test-table) '(("key1" "value1"))))))
(ert-deftest ht-test-map ()
(let ((total 0))
(ht-map
(lambda (key value) (setq total (+ total value)))
(ht ("foo" 1) ("bar" 2)))
(should
(equal total 3))))
(ert-deftest ht-test-map-returns-list ()
(should
(equal
(sort
(ht-map
(lambda (key value) (+ 1 value))
(ht ("foo" 1) ("bar" 2)))
'<)
(list 2 3))))
(ert-deftest ht-test-amap ()
(let ((total 0))
(ht-amap
(setq total (+ total value))
(ht ("foo" 1) ("bar" 2)))
(should
(equal total 3))))
(ert-deftest ht-test-each ()
(let ((total 0))
(ht-each
(lambda (key value) (setq total (+ total value)))
(ht ("foo" 1) ("bar" 2)))
(should
(equal total 3))))
(ert-deftest ht-test-aeach ()
(let ((total 0))
(ht-aeach
(setq total (+ total value))
(ht ("foo" 1) ("bar" 2)))
(should
(equal total 3))))
(ert-deftest ht-test-aeach-nil ()
"ht-aeach should return nil"
(let ((total 0))
(should
(equal
(ht-aeach
(setq total (+ total value))
(ht ("foo" 1) ("bar" 2)))
nil))))
(ert-deftest ht-test-select-keys-empty ()
"ht-select-keys should return an empty table if the keys list is empty"
(let ((table (ht (:foo 1) (:bar 3))))
(should (ht-empty? (ht-select-keys table '())))))
(ert-deftest ht-test-select-keys ()
"size of returned table should be the same as the keys list"
(let ((table (ht (:foo 1) (:bar 3))))
(should (equal (ht-size (ht-select-keys table '(:foo :bar))) 2))))
(ert-deftest ht-test-select-keys-not-found ()
"if the key is not found, it doesn't occur in the returned table"
(let ((table (ht (:foo 1) (:bar 3))))
(should (equal (ht-size (ht-select-keys table '(:foo :baz))) 1))))
(ert-deftest ht-test-from-alist ()
(let* ((alist '(("key1" . "value1")))
(test-table (ht-from-alist alist)))
(should (equal (ht-items test-table) '(("key1" "value1"))))))
(ert-deftest ht-test-from-alist-masked-values ()
(let* ((alist '(("key1" . "value1") ("key1" . "value2")))
(test-table (ht-from-alist alist)))
(should (equal (ht-items test-table) '(("key1" "value1"))))))
(ert-deftest ht-test-from-plist ()
(let* ((plist '("key1" "value1"))
(test-table (ht-from-plist plist)))
(should (equal (ht-items test-table) '(("key1" "value1"))))))
(ert-deftest ht-test-from-plist-masked-values ()
(let* ((plist '("key1" "value1" "key2" "value2" "key1" "value3"))
(test-table (ht-from-plist plist)))
(should (equal (ht-get test-table "key1") "value1"))))
(ert-deftest ht-test-to-alist ()
(let* ((alist '(("key1" . "value1") ("key2" . "value2")))
(test-table (ht-from-alist alist)))
(should (or (equal (ht-to-alist test-table) alist)
(equal (ht-to-alist test-table) (reverse alist))))))
(ert-deftest ht-test-to-plist ()
(let* ((test-table (ht-create)))
(ht-set test-table "foo" "bar")
(should (equal (ht-to-plist test-table) '("foo" "bar")))))
(ert-deftest ht-test-p ()
"Ensure `ht-p' only returns t for hash-tables."
(should (ht-p (ht)))
(should-not (ht-p nil)))
(ert-deftest ht-test-contains-p ()
(should (ht-contains-p (ht ("key" nil)) "key"))
(should-not (ht-contains-p (ht) "key")))
(ert-deftest ht-test-size ()
(should (= (ht-size (ht)) 0))
(should (= (ht-size (ht ("foo" "bar"))) 1))
(should (= (ht-size (ht ("foo" "bar")
("baz" "qux"))) 2)))
(ert-deftest ht-test-empty ()
(should (ht-empty? (ht)))
(should-not (ht-empty? (ht ("foo" "bar"))))
(should-not (ht-empty? (ht ("foo" "bar")
("baz" "qux")))))
(ert-deftest ht-test-select ()
(let ((results
(ht-select
(lambda (key value)
(= (% value 2) 0))
(ht
("foo" 1)
("bar" 2)
("baz" 3)
("qux" 4)))))
(should (= (ht-size results) 2))
(should (= (ht-get results "bar") 2))
(should (= (ht-get results "qux") 4))))
(ert-deftest ht-test-reject ()
(let ((results
(ht-reject
(lambda (key value)
(= (% value 2) 0))
(ht
("foo" 1)
("bar" 2)
("baz" 3)
("qux" 4)))))
(should (= (ht-size results) 2))
(should (= (ht-get results "foo") 1))
(should (= (ht-get results "baz") 3))))
(ert-deftest ht-test-delete-if ()
(let* ((table (ht
("foo" 1)
("bar" 2)
("baz" 3)
("qux" 4)))
(results
(ht-delete-if
(lambda (key value)
(= (% value 2) 0))
table)))
(should-not results)
(should (= (ht-size table) 2))
(should (= (ht-get table "foo") 1))
(should (= (ht-get table "baz") 3))
(should-not (ht-get table "bar"))
(should-not (ht-get table "qux"))))
(ert-deftest ht-test-find ()
(let* ((table (ht
("baz" 3)
("qux" 4)))
(result
(ht-find
(lambda (key value)
(= (% value 2) 0))
table)))
(should (equal result '("qux" 4)))))
(ert-deftest ht-test-find-nil ()
(let* ((table (ht
("baz" 3)
("qux" 4)))
(result
(ht-find
(lambda (key value)
nil)
table)))
(should (equal result nil))))
(ert-deftest ht-test-equal ()
;; Same keys and values.
(should (ht-equal-p (ht) (ht)))
(should (ht-equal-p (ht (1 2)) (ht (1 2))))
;; Different values.
(should (not (ht-equal-p (ht (1 2)) (ht (1 3)))))
;; Different keys.
(should (not (ht-equal-p (ht (1 2)) (ht (2 2)))))
;; Different amount of keys.
(should (not (ht-equal-p (ht (1 2)) (ht (1 2) (3 4)))))
(should (not (ht-equal-p (ht (1 2) (3 4)) (ht (1 2)))))
;; Nested
(should (ht-equal-p (ht ("foo" (ht ("bar" "baz"))))
(ht ("foo" (ht ("bar" "baz"))))))
(should (ht-equal-p (ht ("foo" (ht ("bar" (ht ("baz" "qux"))))))
(ht ("foo" (ht ("bar" (ht ("baz" "qux")))))))))
(ert-deftest ht-test-two-name-style-predicator ()
(let ((real-definition (lambda (sym)
(let ((def sym))
(while (and def (symbolp def))
(setq def (symbol-function def)))
def))))
(mapatoms
(lambda (sym)
(let ((sym-name (symbol-name sym)))
(cond
;; Ignore tests
((string-match-p "\\`ht-test" sym-name)
nil)
((string-match "\\`\\(ht.*\\)\\(?:-p\\|\\?\\)\\'" sym-name)
(let* ((name-body (match-string 1 sym-name))
(scheme-style-sym (intern (format "%s?" name-body)))
(cl-style-sym (intern (format "%s-p" name-body))))
(should (eq (funcall real-definition scheme-style-sym)
(funcall real-definition cl-style-sym)))))))))))
(defun ht-run-tests ()
(interactive)
(ert-run-tests-interactively "ht-test-"))
================================================
FILE: test/test-helper.el
================================================
(require 'f)
(defvar ht-test/test-path
(f-parent (f-this-file)))
(defvar ht-test/root-path
(f-parent ht-test/test-path))
(require 'ert)
(require 'ht (f-expand "ht" ht-test/root-path))
gitextract_bbdlbrez/
├── .github/
│ └── workflows/
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── Cask
├── Makefile
├── README.md
├── ht.el
└── test/
├── ht-test.el
└── test-helper.el
Condensed preview — 9 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (36K chars).
[
{
"path": ".github/workflows/test.yml",
"chars": 541,
"preview": "name: test\n\non:\n pull_request:\n push:\n branches:\n - master\n\njobs:\n build:\n runs-on: ubuntu-latest\n stra"
},
{
"path": ".gitignore",
"chars": 13,
"preview": "*.elc\n/.cask\n"
},
{
"path": "CHANGELOG.md",
"chars": 3297,
"preview": "## v2.4 (not yet tagged)\n\nSwitched to GitHub Actions for automated testing.\n\n### Features\n\n* Added `ht-update-with!`. Th"
},
{
"path": "Cask",
"chars": 135,
"preview": "(source gnu)\n(source melpa)\n\n(package-file \"ht.el\")\n\n(development\n (depends-on \"f\")\n (depends-on \"cl-lib\")\n (depends-on "
},
{
"path": "Makefile",
"chars": 280,
"preview": "EMACS ?= emacs\nCASK ?= cask\n\nall: test\n\ntest: clean-elc\n\t${MAKE} unit\n\t${MAKE} compile\n\t${MAKE} unit\n\t${MAKE} clean-elc\n"
},
{
"path": "README.md",
"chars": 6564,
"preview": "# ht.el\n\nThe missing hash table library for Emacs.\n\n[](http://melpa.org/"
},
{
"path": "ht.el",
"chars": 11117,
"preview": ";;; ht.el --- The missing hash table library for Emacs -*- lexical-binding: t; -*-\n\n;; Copyright (C) 2013 Wilfred Hughe"
},
{
"path": "test/ht-test.el",
"chars": 11579,
"preview": "(require 'ht)\n\n(ert-deftest ht-test-ht ()\n (let ((test-table (ht (1 2) (\"foo\" (1+ 2)))))\n (should (and (member 1 (ht"
},
{
"path": "test/test-helper.el",
"chars": 191,
"preview": "(require 'f)\n\n(defvar ht-test/test-path\n (f-parent (f-this-file)))\n\n(defvar ht-test/root-path\n (f-parent ht-test/test-"
}
]
About this extraction
This page contains the full source code of the Wilfred/ht.el GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 9 files (32.9 KB), approximately 10.1k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.