wesnoth/data/tools/emacs_mode/wesnoth-update.el
2011-06-04 12:11:11 +00:00

388 lines
14 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; wesnoth-update.el --- Update known WML data via existing valid WML.
;; Author: Chris Mann
;; This file is part of wesnoth-mode.
;;; Commentary:
;; Update WML information using WML built-in to Wesnoth.
;; The following should be added to your .emacs so that `wesnoth-update' can
;; correctly generate WML data:
;; (setq wesnoth-root-directory "/path/to/wesnoth/"
;; wesnoth-update-output-directory "/path/to/wesnoth-mode/"
;; wesnoth-addition-file
;; "/path/to/wesnoth-mode/wesnoth-wml-additions.cfg")
;; Specifying the appropriate path in each case.
;; Although WML data is provided along with wesnoth-mode, you can generate
;; update-to-date, version-specific WML reference data for `wesnoth-mode'
;; using `wesnoth-update'. This requires Wesnoth to be install and its
;; pathname set for this to behave correctly. for example:
;; (setq wesnoth-root-directory "/usr/share/wesnoth/")
;; Then set the output directory for `wesnoth-update's results:
;; (setq wesnoth-update-output-directory "/path/to/wesnoth-mode/")
;; This is recommended to be in the same directory as `wesnoth-mode' and
;; must be in `load-path'.
;; Once set, `wesnoth-update' will produce 'wesnoth-wml-data.el' in
;; `wesnoth-update-output-directory' and the information will automatically
;; be available in the future sessions.
;; Although much data is retreived, it is unlikely to be completely
;; comprehensive. wesnoth-mode can be taught about additional tags,
;; attributes and macros using the current project, or a single file, using
;; `wesnoth-update-wml-additions'.
;; To teach wesnoth-mode about elements it may have missed, you can extend the
;; sample additions included with wesnoth-mode; namely
;; wesnoth-wml-additions.cfg (although any source of WML can be used). To
;; enable this, do the following:
;; Set `wesnoth-addition-file' appropriately, for example:
;; (setq wesnoth-addition-file "/path/to/wesnoth-wml-additions.cfg")
;; Once set correctly, running M-x wesnoth-update will update the WML data
;; available to `wesnoth-mode'.
;;; History:
;; 0.1.6
;; * More verbose output
;; * Fix bug which could cause `wesnoth-update' to hang.
;; 0.1.5
;; * `wesnoth-update' now finds more built-in macros.
;; 0.1.4
;; * Fixed inaccuracies when updating project information.
;; * WML data from the addition file can now read when as it is required.
;; 0.1.3
;; * Any arguments are now stored for each macro.
;; 0.1.2
;; * Allow forced updating of the hash table.
;; * Allow clearing of local macro data via a prefix argument.
;; 0.1.1
;; * Provide means for increased performance when referencing attributes and
;; tags.
;; * Gather project macro information for the local buffer only, instead of
;; from files in the directory.
;; 0.1
;; * Initial version
;;; Code:
(require 'pp)
(defvar wesnoth-update-version "0.1.6"
"Version of `wesnoth-update'.")
(defcustom wesnoth-root-directory nil
"Root directory of wesnoth."
:type 'directory
:group 'wesnoth-mode)
(defcustom wesnoth-addition-file nil
"Filename to the file containing additional WML information."
:type 'file
:group 'wesnoth-mode)
(defcustom wesnoth-update-output-directory nil
"Directory to write discovered WML syntax information.
Ensure this directory is in your `load-path'."
:type 'directory
:group 'wesnoth-mode)
(defvar wesnoth-found-cfgs '()
"Temporary list of all .cfg files found.")
(defvar wesnoth-tmp-tag-data '()
"Temporary list of tag data.")
(defvar wesnoth-tmp-macro-data '()
"Temporary list of macro data.")
(defvar wesnoth-tag-data '()
"All information regarding the relation of tags and attributes.")
(defvar wesnoth-macro-data '()
"Information regarding built-in macros.")
(defvar wesnoth-local-macro-data '()
"All macro definitions available in the current project.")
(defvar wesnoth-tag-hash-table (make-hash-table :test 'equal
:size 350)
"Hash table of known WML tag data.")
(defun wesnoth-create-wml-hash-table (tag-data &optional force)
"Handle generation of `wesnoth-tag-hash-table'.
TAG-DATA is the data to add to the hash-table. If FORCE is
non-nil, update the hash-table regardless of whether it replacing
any existing data."
(when (or (= (hash-table-count wesnoth-tag-hash-table) 0)
force)
(clrhash wesnoth-tag-hash-table)
(dolist (tag tag-data)
(puthash (car tag) (cdr tag) wesnoth-tag-hash-table))))
(defun wesnoth-file-cfg-p (file)
"Return non-nil if FILE has a '.cfg' extension."
(and (not (file-directory-p file)) (string-match "\\.cfg$" file)))
(defun wesnoth-fetch-all-dirs (dir)
"Retrieve a list of subdirectories to scan.
DIR is the directory to check."
(let ((dirs-to-scan (wesnoth-files-in-dir dir)))
(while dirs-to-scan
(setq dirs-to-scan (append (wesnoth-files-in-dir (pop dirs-to-scan))
dirs-to-scan)))))
(defun wesnoth-files-in-dir (dir)
"Add cfgs to `wesnoth-files-in-dir'.
Returns a list of sub-directories in DIR."
(let ((cfgs (wesnoth-cfg-files-in-dir dir)))
(when cfgs
(setq wesnoth-found-cfgs (append cfgs wesnoth-found-cfgs))))
(let ((dirs '()))
(dolist (file (directory-files dir t))
(when (and (not (string-match "^\\..*" (file-name-nondirectory file)))
(not (file-symlink-p file)))
(cond ((file-directory-p file)
(add-to-list 'dirs file))
((wesnoth-file-cfg-p file)
(add-to-list 'wesnoth-found-cfgs file)))))
dirs))
(defun wesnoth-cfg-files-in-dir (dir)
"Return all cfg files in DIR."
(let ((result '()))
(dolist (file (directory-files dir t))
(and (wesnoth-file-cfg-p file)
(add-to-list 'result file)))
result))
(defun wesnoth-determine-details (dir-or-file function)
"Process .cfg files in DIR-OR-FILE using FUNCTION.
DIR-OR-FILE can be a file, a directory, or a list of files."
(cond ((listp dir-or-file)
(dolist (file dir-or-file)
(wesnoth-handle-file function file)))
((and (file-exists-p dir-or-file)
(not (file-directory-p dir-or-file)))
(wesnoth-handle-file function dir-or-file))
(t
(wesnoth-fetch-all-dirs dir-or-file)
(while wesnoth-found-cfgs
(message "Updating WML semantics: %s..."
(substring (car wesnoth-found-cfgs)
(length (expand-file-name
wesnoth-root-directory))))
(unless (string-match "^\\..+" (file-name-nondirectory
(car wesnoth-found-cfgs)))
(wesnoth-handle-file function (car wesnoth-found-cfgs))
(setq wesnoth-found-cfgs (cdr wesnoth-found-cfgs)))))))
(defun wesnoth-handle-file (function file)
"Perform FUNCTION on FILE."
(with-temp-buffer
(when (file-exists-p file)
(insert-file-contents file)
(funcall function))))
(defun wesnoth-extract-tag-information ()
"Retrieve relevant tag and attribute information."
(let ((unmatched-tag-list '()))
(goto-char (point-min))
(wesnoth-determine-macro-information)
(while (search-forward-regexp
"^[\t ]*\\(\\[[+/]?\\(\\(\\w\\|_\\)+\\)\\]\\|\\(\\w\\|_\\)+=\\)"
(point-max) t)
(beginning-of-line)
(cond
((and (save-excursion
(search-backward-regexp
"^[\t ]*\\(\\[[^/]]?\\|#define \\|#enddef \\)"
(point-min) t))
(string-match "#define " (match-string 1))
(looking-at "^[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]"))
(wesnoth-append-tag-information (match-string-no-properties 1)
nil nil)
(setq unmatched-tag-list (cons (match-string-no-properties 1)
unmatched-tag-list)))
((looking-at "^[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]")
(wesnoth-append-tag-information (car unmatched-tag-list)
(match-string-no-properties 1)
nil)
(wesnoth-append-tag-information (match-string-no-properties 1)
nil nil)
(setq unmatched-tag-list (cons (match-string-no-properties 1)
unmatched-tag-list)))
((looking-at "^[\t ]*\\(\\(\\w\\|_\\)+\\)=")
(wesnoth-append-tag-information (car unmatched-tag-list)
nil (match-string-no-properties 1)))
((looking-at "^[\t ]*\\[/\\(\\(\\w\\|_\\)+\\)\\]\\|")
(when (string= (match-string-no-properties 1)
(car unmatched-tag-list))
(setq unmatched-tag-list (cdr unmatched-tag-list)))))
(end-of-line))))
(defun wesnoth-append-tag-information (tag subtag attribute)
"Add the information regarding TAG to the list.
SUBTAG and ATTRIBUTE are a children of TAG to be added."
(let ((match (assoc tag wesnoth-tmp-tag-data)))
(if (not match)
(add-to-list 'wesnoth-tmp-tag-data
(list tag (and subtag (list subtag))
(and attribute (list attribute))))
(if subtag
(let ((tmp (nth 1 match)))
(when (not (member subtag tmp))
(add-to-list 'tmp subtag)
(setq match (list tag tmp (car (last match))))))
(when attribute (let ((tmp (nth 2 match)))
(when (not (member attribute tmp))
(add-to-list 'tmp attribute)
(setq match (list tag (nth 1 match) tmp))))))
(setq wesnoth-tmp-tag-data
(remove (assoc tag wesnoth-tmp-tag-data)
wesnoth-tmp-tag-data))
(add-to-list 'wesnoth-tmp-tag-data match))))
(defun wesnoth-determine-macro-information ()
"Process the buffer, retrieving macro definition information."
(save-excursion
(goto-char (point-min))
(while (search-forward-regexp
"#define \\(\\(?:\\w\\|_\\)+\\)\\(\\([\t ]+\\(\\w\\|_\\)+\\)*\\)"
(point-max) t)
(beginning-of-line)
(add-to-list 'wesnoth-tmp-macro-data
(list (match-string-no-properties 1)
(and (match-string 2)
(split-string
(match-string-no-properties 2)))))
(end-of-line))
wesnoth-tmp-macro-data))
(defun wesnoth-output-path ()
"Determine the path to output wml information via `wesnoth-update'."
(or wesnoth-update-output-directory
(if (boundp 'user-emacs-directory)
(symbol-value 'user-emacs-directory)
"~/.emacs.d/")))
(defun wesnoth-read-tmp-tag-data ()
"Read `wesnoth-tmp-tag-data' and reset its value."
(let ((results wesnoth-tmp-tag-data))
(setq wesnoth-tmp-tag-data nil)
results))
(defun wesnoth-tag-additions ()
"Update WML tag information contained in `wesnoth-addition-file'."
(setq wesnoth-tmp-tag-data nil)
(wesnoth-determine-details wesnoth-addition-file
'wesnoth-extract-tag-information)
(wesnoth-read-tmp-tag-data))
(defun wesnoth-macro-additions ()
"Update WML macro information contained in `wesnoth-addition-file'."
(setq wesnoth-tmp-macro-data nil)
(wesnoth-determine-details
wesnoth-addition-file
(lambda ()
(wesnoth-determine-macro-information)))
(let ((results wesnoth-tmp-macro-data))
(setq wesnoth-tmp-macro-data nil)
results))
(defun wesnoth-update ()
"Update WML information.
Path to WML information included in wesnoth is set by
`wesnoth-root-directory.'."
(interactive)
(setq wesnoth-tag-data nil
wesnoth-macro-data nil
wesnoth-found-cfgs nil
wesnoth-tmp-macro-data nil
wesnoth-tmp-tag-data nil)
(unless (and (stringp wesnoth-root-directory)
(file-exists-p wesnoth-root-directory))
;; Update failed; restore data.
(load "wesnoth-wml-data")
(error "%s: directory does not exist"
wesnoth-root-directory))
(message "Updating WML semantics: Searching for .cfg's...")
(wesnoth-determine-details wesnoth-root-directory
'wesnoth-extract-tag-information)
(setq wesnoth-tag-data wesnoth-tmp-tag-data
wesnoth-tmp-tag-data nil
wesnoth-macro-data wesnoth-tmp-macro-data
wesnoth-tmp-macro-data nil)
(with-temp-buffer
(insert "(setq wesnoth-tag-data '"
(pp wesnoth-tag-data) ")\n\n"
"(setq wesnoth-macro-data '"
(pp wesnoth-macro-data) ")\n\n"
"(provide 'wesnoth-wml-data)\n")
(write-file (expand-file-name "wesnoth-wml-data.el"
(wesnoth-output-path)))
(load "wesnoth-wml-data"))
(message "Updating WML semantics: done"))
(defun wesnoth-merge-macro-data (&rest macro-data)
"Merge WML macro information and return the result.
MACRO-DATA is the macro-data to merge."
(let ((set-data '())
(macro-base-data (car macro-data)))
(while (setq macro-data (cdr macro-data))
(setq set-data (car macro-data))
(while set-data
(setq macro-base-data
(append (list (car set-data))
(remove (assoc (car (car set-data)) macro-base-data)
macro-base-data))
set-data (cdr set-data))))
macro-base-data))
(defun wesnoth-merge-tag-data (&rest tag-data)
"Merge WML tag information and return the result.
TAG-DATA is the tag-data to merge."
(setq wesnoth-tmp-tag-data (car tag-data))
(let ((set-data '()))
(while (setq tag-data (cdr tag-data))
(setq set-data (car tag-data))
(while set-data
(let ((subtags (nth 1 (car set-data))))
(while subtags
(wesnoth-append-tag-information (caar set-data) (car subtags)
nil)
(setq subtags (cdr subtags))))
(let ((attributes (nth 2 (car set-data))))
(while attributes
(wesnoth-append-tag-information (caar set-data) nil
(car attributes))
(setq attributes (cdr attributes))))
(setq set-data (cdr set-data))))
(wesnoth-read-tmp-tag-data)))
(defun wesnoth-update-project-information (&optional clear)
"Update WML macro information for the current project.
If CLEAR is non-nil, reset `wesnoth-local-macro-data'."
(interactive "P")
(setq wesnoth-tmp-macro-data nil)
(if clear
(setq wesnoth-local-macro-data nil)
(setq wesnoth-local-macro-data
(wesnoth-merge-macro-data wesnoth-local-macro-data
(wesnoth-determine-macro-information)))
(setq wesnoth-tmp-macro-data nil)))
(defun wesnoth-refresh-wml-data ()
"Return merged WML tag data and WML data from the addition file."
(save-match-data
(let ((result (wesnoth-merge-tag-data
wesnoth-tag-data (wesnoth-tag-additions))))
(wesnoth-create-wml-hash-table result t)
result)))
(provide 'wesnoth-update)
;;; wesnoth-update.el ends here