;;; query-tree.el --- query interface to data structures. -*- lexical-binding: t; -*- ;; ;; Copyright © 2023 Howard X. Abrams ;; ;; Author: Howard X. Abrams ;; Maintainer: Howard X. Abrams ;; Created: April 23, 2023 ;; ;; This file is not part of GNU Emacs. ;; ;;; Commentary: ;; ;; A Lisp-focused approach to extracting values from a complex data ;; structure, similar to how jq works with JSON objects. ;; ;; For instance, given a _parsed_ JSON object (a collection of ;; hash-tables and vectors), like (stored in variable data): ;; ;; { "a": 1, ;; "b": { ;; "x": 10, ;; "y": [100, 101, 102, 103], ;; "z": 30 ;; }, ;; "c": 3 ;; } ;; ;; We should be able to call: (query-tree data 'a 'b 2) ;; Should return 102. ;; ;; *NB:* Do not edit this file. Instead, edit the original literate file at: ;; ~/website/Technical/Emacs/query-data-structures.org ;; And tangle the file to recreate this one. ;; ;;; Code: (defun query-tree (data-structure &rest query) "Return value from DATA-STRUCTURE based on elements of QUERY." (query-tree-parse query data-structure)) (defun query-tree-parse (query tree) "Return value of QUERY from TREE data structure." (if (null query) tree (query-tree-parse (cdr query) (query-tree--parse-node (car query) tree)))) (defun query-tree--parse-node (node tree) "Return results of TREE data structure of symbol, NODE. If TREE is a hash-table, then NODE would be the key, either a string or a symbol. Return value. If TREE is a sequence and NODE is number, Return element indexed by NODE. If TREE is a Property List, return value of NODE as a key. This should work with association lists as well." (cond ((null tree) nil) ((hash-table-p tree) (or (gethash node tree) (gethash (symbol-name node) tree))) ((and (numberp node) (seqp tree)) (seq-elt tree node)) ((and (eq node :first) (seqp tree)) (car node)) ((and (eq node :last) (seqp tree)) (last node)) ((assoc node tree) (alist-get node tree)) ((plistp tree) (plist-get tree node)) (t (error "query-tree: Looking for '%s' in %s" node tree)))) (ert-deftest query-tree-walk-tree-test () ;; PLIST: (let ((data '(:a 1 :b 2 :c 3))) (should (eq (query-tree--parse-node :b data) 2))) ;; ALIST: (let ((data '((pine . cones) (oak . acorns) (maple . seeds)))) (should (eq (query-tree--parse-node 'oak data) 'acorns))) ;; LIST, aka a sequence (let ((data '(a b c d e f))) (should (eq (query-tree--parse-node 3 data) 'd))) ;; VECTOR: (let ((data [a b c d e])) (should (eq (query-tree--parse-node 2 data) 'c))) ;; HASH TABLE with strings: (let ((h (make-hash-table :test 'equal))) (puthash "a" 1 h) (puthash "b" 2 h) (puthash "c" 3 h) (should (eq (query-tree--parse-node "b" h) 2)) (should (eq (query-tree--parse-node 'b h) 2))) ;; HASH TABLE with symbols: (let ((h (make-hash-table))) (puthash 'a 1 h) (puthash 'b 2 h) (puthash 'c 3 h) (should (eq (query-tree--parse-node 'b h) 2)))) (ert-deftest query-tree-parse-tree-test () (should (null (query-tree-parse nil nil))) (let ((data :final)) (should (eq (query-tree-parse nil data) :final))) (let ((data [ a b c d e f ])) (should (eq (query-tree-parse '(3) data) 'd))) (let ((data (json-parse-string "{ \"a\": 1, \"b\": { \"x\": 10, \"y\": [100, 101, 102, 103], \"z\": 30 }, \"c\": 3 }"))) (should (hash-table-p data)) (should (= (query-tree-parse '("a") data) 1)) (should (= (query-tree-parse '("b" "x") data) 10)) (should (= (query-tree-parse '("b" "y" 2) data) 102)) (should (= (query-tree-parse '(a) data) 1)) (should (= (query-tree-parse '(b x) data) 10)) (should (= (query-tree-parse '(b y 2) data) 102)))) (ert-deftest query-tree-test () (let ((data (json-parse-string "{ \"a\": 1, \"b\": { \"x\": 10, \"y\": [100, 101, 102, 103], \"z\": 30 }, \"c\": 3 }"))) (should (= (query-tree data 'b 'y 2) 102)))) (provide 'query-tree) ;;; query-tree.el ends here