[
  {
    "path": ".github/workflows/test.yml",
    "content": "name: test\n\non:\n  pull_request:\n  push:\n    branches:\n      - master\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        emacs_version:\n          - 25.3\n          - 26.3\n          - 27.2\n          - 28.2\n          - snapshot\n    steps:\n      - uses: purcell/setup-emacs@master\n        with:\n          version: ${{ matrix.emacs_version }}\n      - uses: conao3/setup-cask@master\n      - uses: actions/checkout@v3\n      - run: cask\n      - name: Run tests\n        run: |\n          emacs --version\n          make test\n"
  },
  {
    "path": ".gitignore",
    "content": "*.elc\n/.cask\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## v2.4 (not yet tagged)\n\nSwitched to GitHub Actions for automated testing.\n\n### Features\n\n* Added `ht-update-with!`. This is like Racket's `hash-update!`.\n\n### Bug Fixes\n\n* `ht-select-keys` can now select keys whose values happen to be the symbol `key-not-found`.\n\n* Corrected the documentation for `ht-reject!`.\n\n## v2.3\n\n### Features\n\n* Added `ht-empty-p` as an alias of `ht-empty?`.\n\n* Many functions have been marked as `side-effect-free`, improving\n  performance and byte-compiler warnings.\n\n* `ht-get` and `ht-get*` can now be used with `setf`, e.g.\n\n```\n(setf (ht-get my-table my-key) new-value)\n```\n\n### Bug Fixes\n\n* `ht<-plist` now keeps the first item in the list, if there are\nduplicates. This makes it consistent with `ht<-alist` and\n`plist-get`.\n\n* `ht-get*` now handles an empty list of keys correctly, and will no\n  longer stack overflow on large lists of keys.\n\n* `ht-contains-p` now even supports hash tables that contain the key\n`ht--not-found`.\n\n## v2.2\n\n* Added `ht-select-keys`\n* Added `ht-get*`\n\nht.el now uses `defsubst` for many of its functions, which may improve\nperformance in byte-compiled code.\n\n## v2.1\n\n* `ht<-alist` and `ht<-plist` gained an optional argument `test` to\nspecify the equality predicate.\n* Added `ht-equal?`.\n\n## v2.0 -- API Change\n\nFunctions names have been changed to be more explicit and consistent.\n\nNote that ht.el includes aliases, so v2.0 is fully backwards\ncompatible.\n\nMutation functions now always end with `!`, and `ht-delete-if` has\nbeen renamed for consistency with its non-mutating equivalent\n`ht-reject`.\n\n* `ht-set` -> `ht-set!`\n* `ht-update` -> `ht-update!`\n* `ht-remove` -> `ht-remove!`\n* `ht-clear` -> `ht-clear!`\n* `ht-delete-if` -> `ht-reject!`\n\nPredicates now always end with `?`.\n\n* `ht-p` -> `ht?`\n* `ht-contains-p` -> `ht-contains?`\n\nConversion functions now use `<-` and `->`.\n\n* `ht-to-alist` -> `ht->alist`\n* `ht-to-plist` -> `ht->plist`\n* `ht-from-alist` -> `ht<-alist`\n* `ht-from-plist` -> `ht<-plist`\n\n## v1.6\n\n* Added `ht-reject` and `ht-select`\n* Added `ht-delete-if`\n* Added `ht-find`\n* Added `ht-empty?` and `ht-size`\n\nAlso added Travis configuration.\n\n## v1.5\n\n* `ht-aeach` now evaluates to `nil` as it should (use `ht-amap` if you\n  want the resulting hash table).\n\n## v1.4\n\n* Added `ht-merge`.\n\n## v1.3\n\n* Removed runtime dependency on `cl`.\n\n## v1.2\n\n* Fixed various `void-variable` crashes due to `ht-amap` only being\n  declared after its usage.\n\n## v1.1\n\n* Added `ht-contains-p`.\n\n## v1.0 -- API Change\n\n* `ht-map` now returns a list, as you'd expect a map function to do.\n* Added `ht-each` for when you're only interested in side-effects.\n* Added an anaphoric version of `ht-each`, `ht-aeach`.\n\n## v0.11\n\n* Added `ht-map` and an anaphoric version `ht-amap`.\n\n## v0.10\n\n* Added `ht-p`, an alias of `hash-table-p`, (mainly for completeness).\n\n## v0.9\n\n* Added `ht-update`.\n\n## v0.8\n\n* Added the `ht` macro to make hash table literals easy\n\n## v0.7\n\n* Added `ht-to-alist` and `ht-to-plist`\n\n## v0.6\n\n* Fixed a bug where `ht-from-alist` would overwrite the latest key-value\n  association with older ones\n\n## v0.5\n\n* Added `ht-from-plist`\n\n## v0.4\n\n* Added `ht-from-alist`\n\n## v0.3\n\n* Added ht-copy\n\n## v0.2\n\n* Changed functions from hm-* to ht-* (Emacs doesn't use the term hash map)\n\n## v0.1\n\n* Initial release\n"
  },
  {
    "path": "Cask",
    "content": "(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 \"ert-runner\"))\n"
  },
  {
    "path": "Makefile",
    "content": "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\nunit:\n\t${CASK} exec ert-runner\n\ncompile:\n\t${CASK} exec ${EMACS} -Q -batch -f batch-byte-compile ht.el\n\nclean-elc:\n\trm -f ht.elc\n\n.PHONY:\tall test unit compile\n"
  },
  {
    "path": "README.md",
    "content": "# ht.el\n\nThe missing hash table library for Emacs.\n\n[![MELPA](http://melpa.org/packages/ht-badge.svg)](http://melpa.org/#/ht)\n[![MELPA Stable](http://stable.melpa.org/packages/ht-badge.svg)](http://stable.melpa.org/#/ht)\n\n<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-generate-toc again -->\n**Table of Contents**\n\n- [ht.el](#htel)\n    - [Functions](#functions)\n        - [Return a hash table](#return-a-hash-table)\n        - [Accessing the hash table](#accessing-the-hash-table)\n        - [Mutating the hash table](#mutating-the-hash-table)\n        - [Iterating over the hash table](#iterating-over-the-hash-table)\n        - [Predicates](#predicates)\n        - [Converting from a hash table](#converting-from-a-hash-table)\n        - [Converting to a hash table](#converting-to-a-hash-table)\n    - [Macros](#macros)\n        - [Returning a hash table](#returning-a-hash-table)\n        - [Iterating over the hash table (anaphoric)](#iterating-over-the-hash-table-anaphoric)\n    - [Examples](#examples)\n    - [Why?](#why)\n        - [Similar libraries](#similar-libraries)\n    - [Installation](#installation)\n    - [Changelog](#changelog)\n    - [Running tests](#running-tests)\n    - [What's an alist/plist?](#whats-an-alistplist)\n\n<!-- markdown-toc end -->\n\n## Functions\n\n### Return a hash table\n\n* `ht-create` `(test?)`\n* `ht-merge` `(&rest tables)`\n* `ht-copy` `(table)`\n* `ht-select` `(function table)`\n* `ht-reject` `(function table)`\n* `ht-select-keys` `(table keys)`\n\n### Accessing the hash table\n\n* `ht-get` `(table key default?)`\n* `ht-get*` `(table &rest keys)`\n* `ht-keys` `(table)`\n* `ht-values` `(table)`\n* `ht-items` `(table)`\n* `ht-find` `(function table)`\n* `ht-size` `(table)`\n\n### Mutating the hash table\n\n* `ht-set!` `(table key value)`\n* `ht-update!` `(table table)`\n* `ht-update-with!` `(table key updater default?)`\n* `ht-remove!` `(table key)`\n* `ht-clear!` `(table)`\n* `ht-reject!` `(function table)`\n\n### Iterating over the hash table\n\n* `ht-map` `(function table)`\n* `ht-each` `(function table)`\n\n### Predicates\n\n* `ht?` `(table-or-object)`\n* `ht-contains?` `(table key)`\n* `ht-equal?` `(table1 table2)`\n* `ht-empty?` `(table)`\n\n### Converting from a hash table\n\n* `ht->alist` `(table)`\n* `ht->plist` `(table)`\n\n### Converting to a hash table\n\n* `ht<-alist` `(alist test?)`\n* `ht<-plist` `(plist test?)`\n\n## Macros\n\n### Returning a hash table\n\n* `ht` `(&rest pairs)`\n\n### Iterating over the hash table (anaphoric)\n\n* `ht-amap` `(form table)`\n* `ht-aeach` `(form table)`\n\n## Examples\n\nCreating a hash table and accessing it:\n\n``` emacs-lisp\n(require 'ht)\n\n(defun say-hello (name)\n  (let ((greetings (ht (\"Bob\" \"Hey bob!\")\n                       (\"Chris\" \"Hi Chris!\"))))\n    (ht-get greetings name \"Hello stranger!\")))\n```\n\nThis could be alternatively written as:\n\n``` emacs-lisp\n(require 'ht)\n\n(defun say-hello (name)\n  (let ((greetings (ht-create)))\n    (ht-set! greetings \"Bob\" \"Hey Bob!\")\n    (ht-set! greetings \"Chris\" \"Hi Chris!\")\n    (ht-get greetings name \"Hello stranger!\")))\n```\n\nAccessing nested hash tables:\n\n``` emacs-lisp\n(let ((alphabets (ht (\"Greek\" (ht (1 (ht ('letter \"α\")\n                                         ('name \"alpha\")))\n                                  (2 (ht ('letter \"β\")\n                                         ('name \"beta\")))))\n                     (\"English\" (ht (1 (ht ('letter \"a\")\n                                           ('name \"A\")))\n                                    (2 (ht ('letter \"b\")\n                                           ('name \"B\"))))))))\n  (ht-get* alphabets \"Greek\" 1 'letter))  ; => \"α\"\n```\n\n`ht-get` and `ht-get*` have gv-setters and so will work with `setf`:\n\n``` emacs-lisp\n(let ((table (ht-create)))\n  (ht-set! table 1 \"A\"))\n```\n\nis equivalent to\n\n``` emacs-lisp\n(let ((table (ht-create)))\n  (setf (ht-get table 1) \"A\"))\n```\n\nand\n\n``` emacs-lisp\n(let ((table (ht (1 (ht (2 (ht (3 \"three\"))))))))\n  (ht-set! (ht-get (ht-get table 1) 2) 3 :three))\n```\n\nis equivalent to\n\n``` emacs-lisp\n(let ((table (ht (1 (ht (2 (ht (3 \"three\"))))))))\n  (setf (ht-get* table 1 2 3) :three))\n```\n\nUpdating values with a function using `ht-update-with!`:\n\n``` emacs-lisp\n(let ((table (ht (\"a\" (list \"a\" \"b\")))))\n  (ht-update-with!\n   table \"a\"\n   (lambda (list)\n     (cons \"c\" list)))\n  (ht-get table \"a\")) ; '(\"c\" \"a\" \"b\"))\n```\n\nis equivalent to\n\n``` emacs-lisp\n(let ((table (ht (\"a\" (list \"a\" \"b\")))))\n  (setf (ht-get table \"a\")\n        (cons \"c\" (ht-get table \"a\")))\n  (ht-get table \"a\")) ; '(\"c\" \"a\" \"b\"))\n```\n\n## Why?\n\nLibraries like [s.el](https://github.com/magnars/s.el) (strings) and\n[dash.el](https://github.com/magnars/dash.el) (lists) have shown how\nmuch nicer Emacs lisp programming can be with good libraries. ht.el\naims to similarly simplify working with hash tables.\n\nCommon operations with hash tables (e.g. enumerate the keys) are too\ndifficult in Emacs lisp.\n\nht.el offers:\n\n* A consistent naming scheme (contrast `make-hash-table` with `puthash`)\n* A more natural argument ordering\n* Mutation functions always return `nil`\n* A more comprehensive range of hash table operations, including a\n  conventional map (`ht-map` returns a list, elisp's `maphash` returns\n  nil).\n\n### Similar libraries\n\n* [kv.el](https://github.com/nicferrier/emacs-kv) (focuses more on\n  alists)\n* [mon-hash-utils](http://www.emacswiki.org/emacs/mon-hash-utils.el)\n\n## Installation\n\nht.el is available on [MELPA](https://melpa.org/) (recommended) and\n[Marmalade](http://marmalade-repo.org/).\n\nAdd MELPA to your .emacs.d/init.el:\n\n``` emacs-lisp\n(require 'package)\n(add-to-list 'package-archives '(\"melpa\" . \"https://melpa.org/packages/\") t)\n```\n\nthen run `M-x package-install <RET> ht <RET>`.\n\n## Changelog\n\nht.el uses semantic versioning, so an incompatible API change will\nresult in the major version increasing. See\n[CHANGELOG.md](CHANGELOG.md) for a history of all changes.\n\n## Running tests\n\n`M-x ht-run-tests`\n\n## What's an alist/plist?\n\nAn alist is an association list, which is a list of pairs. It looks like this:\n\n    ((key1 . value1)\n     (key2 . value2)\n     (key3 . value3))\n\nAn alist can also look like this:\n\n    ((key1 . value1)\n     (key2 . value2)\n     (key1 . oldvalue))\n\nA plist is a property list, which is a flat list with an even number\nof items. It looks like this:\n\n    (key1 value1\n     key2 value2\n     key3 value3)\n\nBoth of these are slow. ht.el provides `ht<-alist` and\n`ht<-plist` to help you convert to hash tables. If you need to\nwork with an alist or plist, use the functions `ht->alist` and\n`ht->plist` to convert an hash table to those formats.\n"
  },
  {
    "path": "ht.el",
    "content": ";;; ht.el --- The missing hash table library for Emacs  -*- lexical-binding: t; -*-\n\n;; Copyright (C) 2013 Wilfred Hughes\n\n;; Author: Wilfred Hughes <me@wilfred.me.uk>\n;; Version: 2.4\n;; Keywords: hash table, hash map, hash\n;; Package-Requires: ((dash \"2.12.0\"))\n\n;; This program is free software; you can redistribute it and/or modify\n;; it under the terms of the GNU General Public License as published by\n;; the Free Software Foundation, either version 3 of the License, or\n;; (at your option) any later version.\n\n;; This program is distributed in the hope that it will be useful,\n;; but WITHOUT ANY WARRANTY; without even the implied warranty of\n;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n;; GNU General Public License for more details.\n\n;; You should have received a copy of the GNU General Public License\n;; along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n;;; Commentary:\n\n;; The missing hash table library for Emacs.\n;;\n;; See documentation at https://github.com/Wilfred/ht.el\n\n;;; Code:\n\n(require 'dash)\n(require 'gv)\n(eval-when-compile\n  (require 'inline))\n\n(defmacro ht (&rest pairs)\n  \"Create a hash table with the key-value pairs given.\nKeys are compared with `equal'.\n\n\\(fn (KEY-1 VALUE-1) (KEY-2 VALUE-2) ...)\"\n  (let* ((table-symbol (make-symbol \"ht-temp\"))\n         (assignments\n          (mapcar\n           (lambda (pair) `(ht-set! ,table-symbol ,@pair))\n           pairs)))\n    `(let ((,table-symbol (ht-create)))\n       ,@assignments\n       ,table-symbol)))\n\n(define-inline ht-set! (table key value)\n  \"Associate KEY in TABLE with VALUE.\"\n  (inline-quote\n   (prog1 nil\n     (puthash ,key ,value ,table))))\n\n(defalias 'ht-set 'ht-set!)\n\n(define-inline ht-create (&optional test)\n  \"Create an empty hash table.\n\nTEST indicates the function used to compare the hash\nkeys.  Default is `equal'.  It can be `eq', `eql', `equal' or a\nuser-supplied test created via `define-hash-table-test'.\"\n  (declare (side-effect-free t))\n  (inline-quote (make-hash-table :test (or ,test 'equal))))\n\n(defun ht<-alist (alist &optional test)\n  \"Create a hash table with initial values according to ALIST.\n\nTEST indicates the function used to compare the hash\nkeys.  Default is `equal'.  It can be `eq', `eql', `equal' or a\nuser-supplied test created via `define-hash-table-test'.\"\n  (declare (side-effect-free t))\n  (let ((h (ht-create test)))\n    ;; the first key-value pair in an alist gets precedence, so we\n    ;; start from the end of the list:\n    (dolist (pair (reverse alist) h)\n      (let ((key (car pair))\n            (value (cdr pair)))\n        (ht-set! h key value)))))\n\n(defalias 'ht-from-alist 'ht<-alist)\n\n(defun ht<-plist (plist &optional test)\n  \"Create a hash table with initial values according to PLIST.\n\nTEST indicates the function used to compare the hash\nkeys.  Default is `equal'.  It can be `eq', `eql', `equal' or a\nuser-supplied test created via `define-hash-table-test'.\"\n  (declare (side-effect-free t))\n  (let ((h (ht-create test)))\n    (dolist (pair (nreverse (-partition 2 plist)) h)\n      (let ((key (car pair))\n            (value (cadr pair)))\n        (ht-set! h key value)))))\n\n(defalias 'ht-from-plist 'ht<-plist)\n\n(define-inline ht-get (table key &optional default)\n  \"Look up KEY in TABLE, and return the matching value.\nIf KEY isn't present, return DEFAULT (nil if not specified).\"\n  (declare (side-effect-free t))\n  (inline-quote\n   (gethash ,key ,table ,default)))\n\n;; Don't use `ht-set!' here, gv setter was assumed to return the value\n;; to be set.\n(gv-define-setter ht-get (value table key) `(puthash ,key ,value ,table))\n\n(define-inline ht-get* (table &rest keys)\n  \"Look up KEYS in nested hash tables, starting with TABLE.\nThe lookup for each key should return another hash table, except\nfor the final key, which may return any value.\"\n  (declare (side-effect-free t))\n  (inline-letevals (table keys)\n    (inline-quote\n     (progn\n       (while ,keys\n         (setf ,table (ht-get ,table (pop ,keys))))\n       ,table))))\n\n(put 'ht-get* 'compiler-macro\n     (lambda (_ table &rest keys)\n       (--reduce-from `(ht-get ,acc ,it) table keys)))\n\n(defun ht-update! (table from-table)\n  \"Update TABLE according to every key-value pair in FROM-TABLE.\"\n  (maphash\n   (lambda (key value) (puthash key value table))\n   from-table)\n  nil)\n\n(defalias 'ht-update 'ht-update!)\n\n(define-inline ht-update-with! (table key updater &optional default)\n  \"Update the value of KEY in TABLE with UPDATER.\nIf the value does not exist, do nothing, unless DEFAULT is\nnon-nil, in which case act as if the value is DEFAULT.\n\nUPDATER receives one argument, the value, and its return value\nbecomes the new value of KEY.\"\n  (inline-quote\n   (let* ((not-found-symbol (make-symbol \"ht--not-found\"))\n          (v (gethash ,key ,table\n                      (or ,default not-found-symbol))))\n     (unless (eq v not-found-symbol)\n       (prog1 nil\n         (puthash ,key (funcall ,updater v) ,table))))))\n\n(defun ht-merge (&rest tables)\n  \"Crete a new table that includes all the key-value pairs from TABLES.\nIf multiple tables have the same key, the value in the last\ntable is used.\"\n  (let ((merged (ht-create)))\n    (mapc (lambda (table) (ht-update! merged table)) tables)\n    merged))\n\n(define-inline ht-remove! (table key)\n  \"Remove KEY from TABLE.\"\n  (inline-quote (remhash ,key ,table)))\n\n(defalias 'ht-remove 'ht-remove!)\n\n(define-inline ht-clear! (table)\n  \"Remove all keys from TABLE.\"\n  (inline-quote\n   (prog1 nil\n     (clrhash ,table))))\n\n(defalias 'ht-clear 'ht-clear!)\n\n(defun ht-map (function table)\n  \"Apply FUNCTION to each key-value pair of TABLE, and make a list of the results.\nFUNCTION is called with two arguments, KEY and VALUE.\"\n  (let (results)\n    (maphash\n     (lambda (key value)\n       (push (funcall function key value) results))\n     table)\n    results))\n\n(defmacro ht-amap (form table)\n  \"Anaphoric version of `ht-map'.\nFor every key-value pair in TABLE, evaluate FORM with the\nvariables KEY and VALUE bound.  If you don't use both of\nthese variables, then use `ht-map' to avoid warnings.\"\n  `(ht-map (lambda (key value) ,form) ,table))\n\n(defun ht-keys (table)\n  \"Return a list of all the keys in TABLE.\"\n  (declare (side-effect-free t))\n  (ht-map (lambda (key _value) key) table))\n\n(defun ht-values (table)\n  \"Return a list of all the values in TABLE.\"\n  (declare (side-effect-free t))\n  (ht-map (lambda (_key value) value) table))\n\n(defun ht-items (table)\n  \"Return a list of two-element lists \\\\='(key value) from TABLE.\"\n  (declare (side-effect-free t))\n  (ht-amap (list key value) table))\n\n(defalias 'ht-each 'maphash\n  \"Apply FUNCTION to each key-value pair of TABLE.\nReturns nil, used for side-effects only.\")\n\n(defmacro ht-aeach (form table)\n  \"Anaphoric version of `ht-each'.\nFor every key-value pair in TABLE, evaluate FORM with the\nvariables key and value bound.\"\n  `(ht-each (lambda (key value) ,form) ,table))\n\n(defun ht-select-keys (table keys)\n  \"Return a copy of TABLE with only the specified KEYS.\"\n  (declare (side-effect-free t))\n  (let ((not-found-symbol (make-symbol \"ht--not-found\"))\n        result)\n    (setq result (make-hash-table :test (hash-table-test table)))\n    (dolist (key keys result)\n      (if (not (equal (gethash key table not-found-symbol) not-found-symbol))\n          (puthash key (gethash key table) result)))))\n\n(defun ht->plist (table)\n  \"Return a flat list \\\\='(key1 value1 key2 value2...) from TABLE.\n\nNote that hash tables are unordered, so this cannot be an exact\ninverse of `ht<-plist'.  The following is not guaranteed:\n\n\\(let ((data \\\\='(a b c d)))\n  (equalp data\n          (ht->plist (ht<-plist data))))\"\n  (declare (side-effect-free t))\n  (apply 'append (ht-items table)))\n\n(defalias 'ht-to-plist 'ht->plist)\n\n(define-inline ht-copy (table)\n  \"Return a shallow copy of TABLE (keys and values are shared).\"\n  (declare (side-effect-free t))\n  (inline-quote (copy-hash-table ,table)))\n\n(defun ht->alist (table)\n  \"Return a list of two-element lists \\\\='(key . value) from TABLE.\n\nNote that hash tables are unordered, so this cannot be an exact\ninverse of `ht<-alist'.  The following is not guaranteed:\n\n\\(let ((data \\\\='((a . b) (c . d))))\n  (equalp data\n          (ht->alist (ht<-alist data))))\"\n  (declare (side-effect-free t))\n  (ht-amap (cons key value) table))\n\n(defalias 'ht-to-alist 'ht->alist)\n\n(defalias 'ht? 'hash-table-p)\n\n(defalias 'ht-p 'hash-table-p)\n\n(define-inline ht-contains? (table key)\n  \"Return \\\\='t if TABLE contains KEY.\"\n  (declare (side-effect-free t))\n  (inline-quote\n   (let ((not-found-symbol (make-symbol \"ht--not-found\")))\n     (not (eq (ht-get ,table ,key not-found-symbol) not-found-symbol)))))\n\n(defalias 'ht-contains-p 'ht-contains?)\n\n(define-inline ht-size (table)\n  \"Return the actual number of entries in TABLE.\"\n  (declare (side-effect-free t))\n  (inline-quote\n   (hash-table-count ,table)))\n\n(define-inline ht-empty? (table)\n  \"Return true if the actual number of entries in TABLE is zero.\"\n  (declare (side-effect-free t))\n  (inline-quote\n   (zerop (ht-size ,table))))\n\n(defalias 'ht-empty-p 'ht-empty?)\n\n(defun ht-select (function table)\n  \"Return a hash table containing all entries in TABLE for which\nFUNCTION returns a truthy value.\n\nFUNCTION is called with two arguments, KEY and VALUE.\"\n  (let ((results (ht-create)))\n    (ht-each\n     (lambda (key value)\n       (when (funcall function key value)\n         (ht-set! results key value)))\n     table)\n    results))\n\n(defun ht-reject (function table)\n  \"Return a hash table containing all entries in TABLE for which\nFUNCTION returns a falsy value.\n\nFUNCTION is called with two arguments, KEY and VALUE.\"\n  (let ((results (ht-create)))\n    (ht-each\n     (lambda (key value)\n       (unless (funcall function key value)\n         (ht-set! results key value)))\n     table)\n    results))\n\n(defun ht-reject! (function table)\n  \"Delete entries from TABLE for which FUNCTION returns non-nil.\n\nFUNCTION is called with two arguments, KEY and VALUE.\"\n  (ht-each\n   (lambda (key value)\n     (when (funcall function key value)\n       (remhash key table)))\n   table)\n  nil)\n\n(defalias 'ht-delete-if 'ht-reject!)\n\n(defun ht-find (function table)\n  \"Return (key, value) from TABLE for which FUNCTION returns a truthy value.\nReturn nil otherwise.\n\nFUNCTION is called with two arguments, KEY and VALUE.\"\n  (catch 'break\n    (ht-each\n     (lambda (key value)\n       (when (funcall function key value)\n         (throw 'break (list key value))))\n     table)))\n\n(defun ht-equal? (table1 table2)\n  \"Return t if TABLE1 and TABLE2 have the same keys and values.\nDoes not compare equality predicates.\"\n  (declare (side-effect-free t))\n  (let ((keys1 (ht-keys table1))\n        (keys2 (ht-keys table2))\n        (sentinel (make-symbol \"ht-sentinel\")))\n    (and (equal (length keys1) (length keys2))\n         (--all?\n          (if (ht-p (ht-get table1 it))\n              (ht-equal-p (ht-get table1 it)\n                          (ht-get table2 it))\n            (equal (ht-get table1 it)\n                 (ht-get table2 it sentinel)))\n          keys1))))\n\n(defalias 'ht-equal-p 'ht-equal?)\n\n(provide 'ht)\n;;; ht.el ends here\n"
  },
  {
    "path": "test/ht-test.el",
    "content": "(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-keys test-table))\n                 (member \"foo\" (ht-keys test-table))\n                 (member 2 (ht-values test-table))\n                 (member 3 (ht-values test-table))))))\n\n(ert-deftest ht-test-create ()\n  (should (hash-table-p (ht-create))))\n\n(ert-deftest ht-test-set-then-get ()\n  (let ((test-table (ht-create)))\n    (ht-set test-table \"foo\" \"bar\")\n    (should (equal (ht-get test-table \"foo\") \"bar\"))))\n\n(ert-deftest ht-test-get-default ()\n  (let ((test-table (ht-create)))\n    (should (equal (ht-get test-table \"foo\" \"default\") \"default\"))))\n\n(ert-deftest ht-test-get* ()\n  (let ((alphabets (ht (\"Greek\" (ht (1 (ht ('letter \"α\")\n                                           ('name \"alpha\")))\n                                    (2 (ht ('letter \"β\")\n                                           ('name \"beta\")))))\n                       (\"English\" (ht (1 (ht ('letter \"a\")\n                                             ('name \"A\")))\n                                      (2 (ht ('letter \"b\")\n                                             ('name \"B\"))))))))\n    ;; Nested\n    (should (equal (ht-get* alphabets \"English\" 1 'letter)\n                   \"a\"))\n    ;; Non-nested\n    (should (equal (ht-get* (ht (1 \"one\")) 1)\n                   \"one\"))\n    ;; Base case (no keys)\n    (should (equal (ht-get* alphabets)\n                   alphabets))\n    ;; Works with apply, see #38\n    (should (equal (apply #'ht-get* (list (ht) 1))\n                   nil))))\n\n(ert-deftest ht-test-setf-ht-get ()\n  (let ((test-table (ht (1 \"one\"))))\n    (should (equal (setf (ht-get test-table 1) \"alpha\") \"alpha\")))\n\n  (let ((test-table (ht (1 \"one\") (2 \"two\"))))\n    (setf (ht-get test-table 1)  \"alpha\")\n    (should (equal (ht-get test-table 1)\n                   \"alpha\"))))\n\n(ert-deftest ht-test-setf-ht-get* ()\n  (let ((test-table (ht (1 (ht (2 (ht (3 \"three\"))))))))\n    (should (equal (setf (ht-get* test-table 1 2 3) \"gamma\")\n                   \"gamma\")))\n\n  ;; nested tables { 1 : { 2 : { 3 : three } } }\n  (let ((test-table (ht (1 (ht (2 (ht (3 \"three\"))))))))\n    (setf (ht-get* test-table 1 2 3) \"gamma\")\n    (should (equal (ht-get* test-table 1 2 3)\n                   \"gamma\")))\n  ;; nested tables { 1 : { 2 : two } }\n  (let ((test-table (ht (1 (ht (2 \"two\"))))))\n    (setf (ht-get* test-table 1 2) \"beta\")\n    (should (equal (ht-get* test-table 1 2)\n                   \"beta\")))\n  ;; Non-nested table { 1 : one }\n  (let ((test-table (ht (1 \"one\"))))\n    (setf (ht-get* test-table 1) \"alpha\")\n    (should (equal (ht-get* test-table 1)\n                   \"alpha\"))))\n\n(ert-deftest ht-test-update-with! ()\n  (let ((test-table (ht (\"foo\" 1))))\n    ;; Do it\n    (ht-update-with! test-table \"foo\" #'1+)\n    (should\n     (equal (ht-get test-table \"foo\") 2))\n\n    ;; Keys stay unset\n    (ht-update-with! test-table \"bar\" #'1+)\n    (should-not\n     (ht-contains? test-table \"bar\"))\n\n    ;; Default value\n    (ht-update-with! test-table \"bar\" #'1+ 0)\n    (should\n     (equal (ht-get test-table \"bar\") 1))))\n\n(ert-deftest ht-test-update ()\n  (let ((test-table (ht (\"foo\" 1))))\n    (ht-update test-table (ht (\"bar\" 2)))\n    (should\n     (equal\n      (list \"bar\" \"foo\")\n      (sort (ht-keys test-table) 'string<)))))\n\n(ert-deftest ht-test-merge ()\n  (let ((table1 (ht (\"foo\" 1)))\n        (table2 (ht (\"bar\" 2))))\n    (should\n     (equal\n      (list \"bar\" \"foo\")\n      (sort (ht-keys (ht-merge table1 table2)) 'string<)))))\n\n(ert-deftest ht-test-create-non-default-test ()\n  (let ((test-table (ht-create 'eq)))\n    (should (equal (hash-table-test test-table) 'eq))))\n\n(ert-deftest ht-test-remove ()\n  (let ((test-table (ht-create)))\n    (ht-set test-table \"foo\" \"bar\")\n    (ht-remove test-table \"foo\")\n    (should (equal (ht-get test-table \"foo\") nil))))\n\n(ert-deftest ht-test-clear ()\n  (let ((test-table (ht-create)))\n    (ht-set test-table \"foo\" \"bar\")\n    (ht-set test-table \"biz\" \"baz\")\n    (ht-clear test-table)\n    (should (equal (ht-items test-table) nil))))\n\n(ert-deftest ht-test-keys ()\n  (let ((test-table (ht-create)))\n    (ht-set test-table \"foo\" \"bar\")\n    (should (equal (ht-keys test-table) (list \"foo\")))))\n\n(ert-deftest ht-test-values ()\n  (let ((test-table (ht-create)))\n    (ht-set test-table \"foo\" \"bar\")\n    (should (equal (ht-values test-table) (list \"bar\")))))\n\n(ert-deftest ht-test-items ()\n  (let ((test-table (ht-create)))\n    (ht-set test-table \"key1\" \"value1\")\n    (should (equal (ht-items test-table) '((\"key1\" \"value1\"))))))\n\n(ert-deftest ht-test-map ()\n  (let ((total 0))\n    (ht-map\n     (lambda (key value) (setq total (+ total value)))\n     (ht (\"foo\" 1) (\"bar\" 2)))\n    (should\n     (equal total 3))))\n\n(ert-deftest ht-test-map-returns-list ()\n  (should\n   (equal\n    (sort\n     (ht-map\n      (lambda (key value) (+ 1 value))\n      (ht (\"foo\" 1) (\"bar\" 2)))\n     '<)\n    (list 2 3))))\n\n(ert-deftest ht-test-amap ()\n  (let ((total 0))\n    (ht-amap\n     (setq total (+ total value))\n     (ht (\"foo\" 1) (\"bar\" 2)))\n    (should\n     (equal total 3))))\n\n(ert-deftest ht-test-each ()\n  (let ((total 0))\n    (ht-each\n     (lambda (key value) (setq total (+ total value)))\n     (ht (\"foo\" 1) (\"bar\" 2)))\n    (should\n     (equal total 3))))\n\n(ert-deftest ht-test-aeach ()\n  (let ((total 0))\n    (ht-aeach\n     (setq total (+ total value))\n     (ht (\"foo\" 1) (\"bar\" 2)))\n    (should\n     (equal total 3))))\n\n(ert-deftest ht-test-aeach-nil ()\n  \"ht-aeach should return nil\"\n  (let ((total 0))\n    (should\n     (equal\n      (ht-aeach\n       (setq total (+ total value))\n       (ht (\"foo\" 1) (\"bar\" 2)))\n      nil))))\n\n(ert-deftest ht-test-select-keys-empty ()\n  \"ht-select-keys should return an empty table if the keys list is empty\"\n  (let ((table (ht (:foo 1) (:bar 3))))\n    (should (ht-empty? (ht-select-keys table '())))))\n\n(ert-deftest ht-test-select-keys ()\n  \"size of returned table should be the same as the keys list\"\n  (let ((table (ht (:foo 1) (:bar 3))))\n    (should (equal (ht-size (ht-select-keys table '(:foo :bar))) 2))))\n\n(ert-deftest ht-test-select-keys-not-found ()\n  \"if the key is not found, it doesn't occur in the returned table\"\n  (let ((table (ht (:foo 1) (:bar 3))))\n    (should (equal (ht-size (ht-select-keys table '(:foo :baz))) 1))))\n\n(ert-deftest ht-test-from-alist ()\n  (let* ((alist '((\"key1\" . \"value1\")))\n         (test-table (ht-from-alist alist)))\n    (should (equal (ht-items test-table) '((\"key1\" \"value1\"))))))\n\n(ert-deftest ht-test-from-alist-masked-values ()\n  (let* ((alist '((\"key1\" . \"value1\") (\"key1\" . \"value2\")))\n         (test-table (ht-from-alist alist)))\n    (should (equal (ht-items test-table) '((\"key1\" \"value1\"))))))\n\n(ert-deftest ht-test-from-plist ()\n  (let* ((plist '(\"key1\" \"value1\"))\n         (test-table (ht-from-plist plist)))\n    (should (equal (ht-items test-table) '((\"key1\" \"value1\"))))))\n\n(ert-deftest ht-test-from-plist-masked-values ()\n  (let* ((plist '(\"key1\" \"value1\" \"key2\" \"value2\" \"key1\" \"value3\"))\n         (test-table (ht-from-plist plist)))\n    (should (equal (ht-get test-table \"key1\") \"value1\"))))\n\n(ert-deftest ht-test-to-alist ()\n  (let* ((alist '((\"key1\" . \"value1\") (\"key2\" . \"value2\")))\n         (test-table (ht-from-alist alist)))\n    (should (or (equal (ht-to-alist test-table) alist)\n                (equal (ht-to-alist test-table) (reverse alist))))))\n\n(ert-deftest ht-test-to-plist ()\n  (let* ((test-table (ht-create)))\n    (ht-set test-table \"foo\" \"bar\")\n    (should (equal (ht-to-plist test-table) '(\"foo\" \"bar\")))))\n\n(ert-deftest ht-test-p ()\n  \"Ensure `ht-p' only returns t for hash-tables.\"\n  (should (ht-p (ht)))\n  (should-not (ht-p nil)))\n\n(ert-deftest ht-test-contains-p ()\n  (should (ht-contains-p (ht (\"key\" nil)) \"key\"))\n  (should-not (ht-contains-p (ht) \"key\")))\n\n(ert-deftest ht-test-size ()\n  (should (= (ht-size (ht)) 0))\n  (should (= (ht-size (ht (\"foo\" \"bar\"))) 1))\n  (should (= (ht-size (ht (\"foo\" \"bar\")\n                          (\"baz\" \"qux\"))) 2)))\n\n(ert-deftest ht-test-empty ()\n  (should (ht-empty? (ht)))\n  (should-not (ht-empty? (ht (\"foo\" \"bar\"))))\n  (should-not (ht-empty? (ht (\"foo\" \"bar\")\n                              (\"baz\" \"qux\")))))\n\n(ert-deftest ht-test-select ()\n  (let ((results\n         (ht-select\n          (lambda (key value)\n            (= (% value 2) 0))\n          (ht\n           (\"foo\" 1)\n           (\"bar\" 2)\n           (\"baz\" 3)\n           (\"qux\" 4)))))\n    (should (= (ht-size results) 2))\n    (should (= (ht-get results \"bar\") 2))\n    (should (= (ht-get results \"qux\") 4))))\n\n(ert-deftest ht-test-reject ()\n  (let ((results\n         (ht-reject\n          (lambda (key value)\n            (= (% value 2) 0))\n          (ht\n           (\"foo\" 1)\n           (\"bar\" 2)\n           (\"baz\" 3)\n           (\"qux\" 4)))))\n    (should (= (ht-size results) 2))\n    (should (= (ht-get results \"foo\") 1))\n    (should (= (ht-get results \"baz\") 3))))\n\n(ert-deftest ht-test-delete-if ()\n  (let* ((table (ht\n                 (\"foo\" 1)\n                 (\"bar\" 2)\n                 (\"baz\" 3)\n                 (\"qux\" 4)))\n         (results\n          (ht-delete-if\n           (lambda (key value)\n             (= (% value 2) 0))\n           table)))\n    (should-not results)\n    (should (= (ht-size table) 2))\n    (should (= (ht-get table \"foo\") 1))\n    (should (= (ht-get table \"baz\") 3))\n    (should-not (ht-get table \"bar\"))\n    (should-not (ht-get table \"qux\"))))\n\n(ert-deftest ht-test-find ()\n  (let* ((table (ht\n                 (\"baz\" 3)\n                 (\"qux\" 4)))\n         (result\n          (ht-find\n           (lambda (key value)\n             (= (% value 2) 0))\n           table)))\n    (should (equal result '(\"qux\" 4)))))\n\n(ert-deftest ht-test-find-nil ()\n  (let* ((table (ht\n                 (\"baz\" 3)\n                 (\"qux\" 4)))\n         (result\n          (ht-find\n           (lambda (key value)\n             nil)\n           table)))\n    (should (equal result nil))))\n\n(ert-deftest ht-test-equal ()\n  ;; Same keys and values.\n  (should (ht-equal-p (ht) (ht)))\n  (should (ht-equal-p (ht (1 2)) (ht (1 2))))\n  ;; Different values.\n  (should (not (ht-equal-p (ht (1 2)) (ht (1 3)))))\n  ;; Different keys.\n  (should (not (ht-equal-p (ht (1 2)) (ht (2 2)))))\n  ;; Different amount of keys.\n  (should (not (ht-equal-p (ht (1 2)) (ht (1 2) (3 4)))))\n  (should (not (ht-equal-p (ht (1 2) (3 4)) (ht (1 2)))))\n  ;; Nested\n  (should (ht-equal-p (ht (\"foo\" (ht (\"bar\" \"baz\"))))\n                      (ht (\"foo\" (ht (\"bar\" \"baz\"))))))\n  (should (ht-equal-p (ht (\"foo\" (ht (\"bar\" (ht (\"baz\" \"qux\"))))))\n                      (ht (\"foo\" (ht (\"bar\" (ht (\"baz\" \"qux\")))))))))\n\n(ert-deftest ht-test-two-name-style-predicator ()\n  (let ((real-definition (lambda (sym)\n                           (let ((def sym))\n                             (while (and def (symbolp def))\n                               (setq def (symbol-function def)))\n                             def))))\n    (mapatoms\n     (lambda (sym)\n       (let ((sym-name (symbol-name sym)))\n         (cond\n          ;; Ignore tests\n          ((string-match-p \"\\\\`ht-test\" sym-name)\n           nil)\n          ((string-match \"\\\\`\\\\(ht.*\\\\)\\\\(?:-p\\\\|\\\\?\\\\)\\\\'\" sym-name)\n           (let* ((name-body (match-string 1 sym-name))\n                  (scheme-style-sym (intern (format \"%s?\" name-body)))\n                  (cl-style-sym (intern (format \"%s-p\" name-body))))\n             (should (eq (funcall real-definition scheme-style-sym)\n                         (funcall real-definition cl-style-sym)))))))))))\n\n(defun ht-run-tests ()\n  (interactive)\n  (ert-run-tests-interactively \"ht-test-\"))\n"
  },
  {
    "path": "test/test-helper.el",
    "content": "(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-path))\n\n(require 'ert)\n(require 'ht (f-expand \"ht\" ht-test/root-path))\n"
  }
]