2009-06-22 06:39:46 +00:00
|
|
|
;;; fadr.el --- convenient access to recursive list structures
|
2009-06-21 12:08:53 +00:00
|
|
|
|
|
|
|
;; Copyright (C) 2009 Free Software Foundation, Inc.
|
|
|
|
|
|
|
|
;; Author: Dmitry Dzhus <dima@sphinx.net.ru>
|
|
|
|
;; Keywords: lisp, internal
|
|
|
|
|
|
|
|
;; 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:
|
|
|
|
|
|
|
|
;; This code allows accessing data stored in recursive association and
|
|
|
|
;; plain lists using a compact notation.
|
|
|
|
;;
|
|
|
|
;; Consider the following list:
|
2009-06-22 06:39:46 +00:00
|
|
|
;;
|
2009-06-21 12:08:53 +00:00
|
|
|
;; (setq basket '((apples . (((color . green) (taste . delicious)) ((color . red) (taste . disgusting))))))
|
|
|
|
;;
|
|
|
|
;; Its contents may be accessed using `fadr-member':
|
|
|
|
;;
|
|
|
|
;; (fadr-member basket ".apples[1].color")
|
|
|
|
;; red
|
|
|
|
;;
|
|
|
|
;; Associated values are selected using a dot followed by a key, while
|
|
|
|
;; lists accept an index (0-based) in square brackets.
|
|
|
|
;;
|
|
|
|
;; `fadr-q' is a one-argument shortcut fro `fadr-member', where
|
|
|
|
;; (fadr-q "res.path") results to (fadr-member res ".path"):
|
|
|
|
;;
|
|
|
|
;; (fadr-q "basket.apples[0].taste")
|
|
|
|
;; delicious
|
|
|
|
;;
|
|
|
|
;; `fadr-expand' substitutes ~PATH with results of `fadr-member' calls
|
|
|
|
;; with respective arguments:
|
|
|
|
;;
|
|
|
|
;; (fadr-expand "~.color apple is ~.taste" (fadr-member basket ".apples[0]"))
|
|
|
|
;; "green apple is delicious"
|
|
|
|
;;
|
|
|
|
;; `fadr-format' is like `fadr-expand', but it performs %-substitutions first:
|
|
|
|
;;
|
|
|
|
;; (fadr-format "%s #%d is ~.color and ~.taste" (fadr-member basket ".apples[1]") "Apple" 1)
|
|
|
|
;; "Apple #1 is red and disgusting"
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
(defun fadr-get-field-value (field object)
|
|
|
|
"Get value of FIELD from OBJECT.
|
|
|
|
|
|
|
|
FIELD is a symbol."
|
|
|
|
(cdr (assoc field object)))
|
|
|
|
|
|
|
|
(defsubst bol-regexp (regexp)
|
|
|
|
(concat "^" regexp))
|
|
|
|
(defconst fadr-field-name-regexp
|
|
|
|
"[[:alpha:]_-]+")
|
|
|
|
(defconst fadr-field-selector-regexp
|
|
|
|
(concat "\\.\\(" fadr-field-name-regexp "\\)"))
|
|
|
|
(defconst fadr-index-selector-regexp
|
|
|
|
"\\[\\([[:digit:]]+\\)\\]")
|
|
|
|
(defconst fadr-path-regexp
|
|
|
|
(concat "\\(" fadr-field-selector-regexp "\\|"
|
|
|
|
fadr-index-selector-regexp
|
|
|
|
"\\)+"))
|
|
|
|
|
|
|
|
(defmacro fadr-define-select (name regexp &optional doc filter)
|
|
|
|
"Define a function NAME of one string argument which will
|
2009-06-22 06:41:50 +00:00
|
|
|
extract data from it using the first subgroup in REGEXP. If
|
|
|
|
FILTER is specified, it will be called with the resulting string."
|
2009-06-21 12:08:53 +00:00
|
|
|
`(defun ,name (path)
|
|
|
|
,doc
|
|
|
|
(let ((string (if (string-match ,regexp path)
|
|
|
|
(match-string-no-properties 1 path)
|
|
|
|
nil)))
|
|
|
|
(if string
|
|
|
|
,(if filter
|
|
|
|
`(funcall ,filter string)
|
|
|
|
'string)
|
|
|
|
nil))))
|
|
|
|
|
|
|
|
(fadr-define-select fadr-index-select
|
|
|
|
(bol-regexp fadr-index-selector-regexp)
|
|
|
|
"Extract name of the next field selected in PATH as a symbol."
|
|
|
|
'string-to-number)
|
|
|
|
|
|
|
|
;; Bad case: (fadr-field-select ".nil")
|
|
|
|
(fadr-define-select fadr-field-select
|
|
|
|
(bol-regexp fadr-field-selector-regexp)
|
|
|
|
"Extract value of the next list index selected in PATH as a
|
|
|
|
number."
|
|
|
|
'intern)
|
|
|
|
|
|
|
|
;; TODO: define this function using macros to ease the adding of new
|
|
|
|
;; selector types
|
|
|
|
(defun fadr-member (object path)
|
|
|
|
"Access data in OBJECT using PATH.
|
|
|
|
|
|
|
|
This function is not match-safe, meaning that you may need to
|
|
|
|
wrap a call to it with `save-match-data'."
|
|
|
|
(if (string= path "")
|
|
|
|
object
|
|
|
|
(let ((index (fadr-index-select path))
|
|
|
|
(field (fadr-field-select path)))
|
|
|
|
(cond (index
|
|
|
|
(fadr-member (elt object index)
|
|
|
|
(fadr-peel-path path)))
|
|
|
|
(field
|
|
|
|
(fadr-member (fadr-get-field-value field object)
|
|
|
|
(fadr-peel-path path)))
|
|
|
|
(t (error "Bad path"))))))
|
|
|
|
|
|
|
|
(defun fadr-q (full-path)
|
|
|
|
(catch 'bad-path
|
|
|
|
(if (string-match fadr-path-regexp full-path)
|
|
|
|
(if (not (= (match-beginning 0) 0))
|
|
|
|
(let ((object (eval (intern (substring full-path 0 (match-beginning 0)))))
|
|
|
|
(path (substring full-path (match-beginning 0))))
|
|
|
|
(fadr-member object path))
|
|
|
|
(throw 'bad-path (error "No object specified")))
|
|
|
|
(throw 'bad-path (error "Incorrect path")))))
|
|
|
|
|
|
|
|
(defun fadr-peel-path (path)
|
|
|
|
"Return PATH without first selector."
|
|
|
|
(cond ((fadr-field-select path)
|
|
|
|
(string-match (bol-regexp fadr-field-selector-regexp) path))
|
|
|
|
((fadr-index-select path)
|
|
|
|
(string-match (bol-regexp fadr-index-selector-regexp) path))
|
|
|
|
(t (error "Could not peel path")))
|
|
|
|
(substring path (match-end 0)))
|
|
|
|
|
|
|
|
(defun fadr-expand (string object)
|
|
|
|
"Format STRING using OBJECT members.
|
|
|
|
|
|
|
|
All ~.<path> substrings within STRING are replaced with
|
|
|
|
respective values of OBJECT members."
|
|
|
|
(replace-regexp-in-string
|
|
|
|
(concat "~\\(" fadr-path-regexp "\\)")
|
|
|
|
#'(lambda (text)
|
|
|
|
(save-match-data
|
|
|
|
(format "%s"
|
|
|
|
(fadr-member object (substring text 1)))))
|
|
|
|
string))
|
|
|
|
|
|
|
|
(defun fadr-format (string object &rest objects)
|
2009-06-22 06:41:50 +00:00
|
|
|
"Format STRING with OBJECTS, then `fadr-expand' the result with OBJECT."
|
2009-06-21 12:08:53 +00:00
|
|
|
(let ((new-string (apply 'format (append (list string) objects))))
|
|
|
|
(fadr-expand new-string object)))
|
|
|
|
|
|
|
|
(provide 'fadr)
|
2009-06-22 06:39:46 +00:00
|
|
|
|
|
|
|
;; arch-tag: 4edced02-a5c3-4516-b278-3f85a12146ea
|
2009-06-21 12:08:53 +00:00
|
|
|
;;; fadr.el ends here
|