emacs/lisp/fadr.el

163 lines
5.5 KiB
EmacsLisp
Raw Normal View History

2009-06-22 06:39:46 +00:00
;;; fadr.el --- convenient access to recursive list structures
;; 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
;;
;; (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."
`(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."
(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
;;; fadr.el ends here