* wesnoth-mode.el (wesnoth-syntactic-keywords): Update doc-string.
[wesnoth-mode.git] / wesnoth-update.el
blobd7ee633e749466a4b103c71e0fe0b3505728155b
1 ;;; wesnoth-update.el --- Update known WML data via existing valid WML.
2 ;; Copyright (C) 2008 Chris Mann
4 ;; This file is part of wesnoth-mode.
6 ;; This program is free software; you can redistribute it and/or
7 ;; modify it under the terms of the GNU General Public License as
8 ;; published by the Free Software Foundation; either version 2 of the
9 ;; License, or (at your option) any later version.
11 ;; This program is distributed in the hope that it will be useful, but
12 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 ;; General Public License for more details.
16 ;; You should have received a copy of the GNU General Public License
17 ;; along with this program; see the file COPYING. If not, write to the
18 ;; Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston,
19 ;; MA 02139, USA.
21 ;;; Commentary:
22 ;; Update WML information using WML built-in to Wesnoth.
24 ;; The following should be added to your .emacs so that `wesnoth-update' can
25 ;; correctly generate WML data:
26 ;; (setq wesnoth-root-directory "/path/to/wesnoth/"
27 ;; wesnoth-update-output-directory "/path/to/wesnoth-mode/"
28 ;; wesnoth-addition-file "/path/to/wesnoth-mode/wesnoth-wml-additions.cfg")
29 ;; Specifying the appropriate path in each case.
31 ;; Although WML data is provided along with wesnoth-mode, you can generate
32 ;; update-to-date, version-specific WML reference data for `wesnoth-mode'
33 ;; using `wesnoth-update'. This requires Wesnoth to be install and its
34 ;; pathname set for this to behave correctly. for example:
35 ;; (setq wesnoth-root-directory "/usr/share/wesnoth/")
37 ;; Then set the output directory for `wesnoth-update's results:
38 ;; (setq wesnoth-update-output-directory "/path/to/wesnoth-mode/")
39 ;; This is recommended to be in the same directory as `wesnoth-mode' and
40 ;; must be in `load-path'.
42 ;; Once set, `wesnoth-update' will produce 'wesnoth-wml-data.el' in
43 ;; `wesnoth-update-output-directory' and the information will automatically
44 ;; be available in the future sessions.
46 ;; Although much data is retreived, it is unlikely to be completely
47 ;; comprehensive. wesnoth-mode can be taught about additional tags,
48 ;; attributes and macros using the current project, or a single file, using
49 ;; `wesnoth-update-wml-additions'.
51 ;; To teach wesnoth-mode about elements it may have missed, you can extend the
52 ;; sample additions included with wesnoth-mode; namely
53 ;; wesnoth-wml-additions.cfg (although any source of WML can be used). To
54 ;; enable this, do the following:
55 ;; Set `wesnoth-addition-file' appropriately, for example:
56 ;; (setq wesnoth-addition-file "/path/to/wesnoth-wml-additions.cfg")
58 ;; Once set correctly, running M-x wesnoth-update will update the WML data
59 ;; available to `wesnoth-mode'.
61 ;;; History:
62 ;; 0.1.3
63 ;; * Any arguments are now stored for each macro.
64 ;; 0.1.2
65 ;; * Allow forced updating of the hash table.
66 ;; * Allow clearing of local macro data via a prefix argument.
67 ;; 0.1.1
68 ;; * Provide means for increased performance when referencing attributes and
69 ;; tags.
70 ;; * Gather project macro information for the local buffer only, instead of
71 ;; from files in the directory.
72 ;; 0.1
73 ;; * Initial version
75 ;;; Code:
76 (defvar wesnoth-update-version "0.1.3"
77 "Version of `wesnoth-update'.")
79 (defcustom wesnoth-root-directory nil
80 "Root directory of wesnoth."
81 :type 'directory
82 :group 'wesnoth-mode)
84 (defcustom wesnoth-addition-file nil
85 "Filename to the file containing additional WML information."
86 :type 'file
87 :group 'wesnoth-mode)
89 (defcustom wesnoth-update-output-directory nil
90 "Directory to write discovered WML syntax information.
91 Ensure this directory is in your `load-path'."
92 :type 'directory
93 :group 'wesnoth-mode)
95 (defconst wesnoth-macro-directory "data/core/macros"
96 "Directory which built-in macros are stored.
97 This is relative to the wesnoth directory in `wesnoth-root-directory.'.")
99 (defvar wesnoth-found-cfgs '()
100 "Temporary list of all .cfg files found.")
102 (defvar wesnoth-tag-data '()
103 "All information regarding the relation of tags and attributes.")
105 (defvar wesnoth-macro-data '()
106 "Information regarding built-in macros.")
108 (defvar wesnoth-local-macro-data '()
109 "All macro definitions available in the current project.")
111 (defvar wesnoth-tag-hash-table (make-hash-table :test 'equal
112 :size 350)
113 "Hash table of known WML tag data.")
115 (defun wesnoth-create-wml-hash-table (&optional force)
116 "Handle generation of `wesnoth-tag-hash-table'."
117 (when (or (= (hash-table-count wesnoth-tag-hash-table) 0)
118 force)
119 (clrhash wesnoth-tag-hash-table)
120 (dolist (tag wesnoth-tag-data)
121 (puthash (car tag) (cdr tag) wesnoth-tag-hash-table))))
123 (defun wesnoth-file-cfg-p (file)
124 "Return non-nil if FILE has a '.cfg' extension."
125 (and (not (file-directory-p file)) (string-match "\\.cfg$" file)))
127 (defun wesnoth-fetch-all-dirs (dir)
128 "Retrieve a list of subdirectories to scan.
129 DIR is the directory to check."
130 (let ((dirs-to-scan (wesnoth-files-in-dir dir)))
131 (while dirs-to-scan
132 (setq dirs-to-scan (append (wesnoth-files-in-dir (pop dirs-to-scan))
133 dirs-to-scan)))))
135 (defun wesnoth-files-in-dir (dir)
136 "Add cfgs to `wesnoth-files-in-dir'.
137 Returns a list of sub-directories in DIR."
138 (let ((cfgs (wesnoth-cfg-files-in-dir dir)))
139 (when cfgs
140 (setq wesnoth-found-cfgs (append cfgs wesnoth-found-cfgs))))
141 (let ((dirs '()))
142 (dolist (file (directory-files dir t))
143 (unless (string-match "^\\..*" (file-name-nondirectory file))
144 (cond ((file-directory-p file)
145 (add-to-list 'dirs file))
146 ((wesnoth-file-cfg-p file)
147 (add-to-list 'wesnoth-found-cfgs file)))))
148 dirs))
150 (defun wesnoth-cfg-files-in-dir (dir)
151 "Return all cfg files in DIR."
152 (let ((result '()))
153 (dolist (file (directory-files dir t))
154 (and (wesnoth-file-cfg-p file)
155 (add-to-list 'result file)))
156 result))
158 (defun wesnoth-determine-details (dir-or-file function)
159 "Process .cfg files in DIR-OR-FILE using FUNCTION.
160 DIR-OR-FILE can be a file, a directory, or a list of files."
161 (cond ((listp dir-or-file)
162 (dolist (file dir-or-file)
163 (wesnoth-handle-file function file)))
164 ((and (file-exists-p dir-or-file)
165 (not (file-directory-p dir-or-file)))
166 (wesnoth-handle-file function dir-or-file))
168 (wesnoth-fetch-all-dirs dir-or-file)
169 (while wesnoth-found-cfgs
170 (unless (string-match "^\\..+" (file-name-nondirectory
171 (car wesnoth-found-cfgs)))
172 (wesnoth-handle-file function (car wesnoth-found-cfgs))
173 (setq wesnoth-found-cfgs (cdr wesnoth-found-cfgs)))))))
175 (defun wesnoth-handle-file (function file)
176 "Perform FUNCTION on FILE."
177 (with-temp-buffer
178 (when (file-exists-p file)
179 (insert-file-contents file)
180 (funcall function))))
182 (defun wesnoth-extract-tag-information ()
183 "Retrieve relevant tag and attribute information."
184 (let ((unmatched-tag-list '()))
185 (goto-char (point-min))
186 (while (search-forward-regexp
187 "^[\t ]*\\(\\[[+/]?\\(\\(\\w\\|_\\)+\\)\\]\\|\\(\\w\\|_\\)+=\\)"
188 (point-max) t)
189 (beginning-of-line)
190 (cond
191 ((and (save-excursion
192 (search-backward-regexp
193 "^[\t ]*\\(\\[[^/]]?\\|#define \\|#enddef \\)"
194 (point-min) t))
195 (string-match "#define " (match-string 1))
196 (looking-at "^[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]"))
197 (wesnoth-append-tag-information (match-string-no-properties 1) nil nil)
198 (setq unmatched-tag-list (cons (match-string-no-properties 1)
199 unmatched-tag-list)))
200 ((looking-at "^[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]")
201 (wesnoth-append-tag-information (car unmatched-tag-list)
202 (match-string-no-properties 1)
203 nil)
204 (wesnoth-append-tag-information (match-string-no-properties 1) nil nil)
205 (setq unmatched-tag-list (cons (match-string-no-properties 1)
206 unmatched-tag-list)))
207 ((looking-at "^[\t ]*\\(\\(\\w\\|_\\)+\\)=")
208 (wesnoth-append-tag-information (car unmatched-tag-list)
209 nil (match-string-no-properties 1)))
210 ((looking-at "^[\t ]*\\[/\\(\\(\\w\\|_\\)+\\)\\]\\|")
211 (when (string= (match-string-no-properties 1)
212 (car unmatched-tag-list))
213 (setq unmatched-tag-list (cdr unmatched-tag-list)))))
214 (end-of-line))))
216 (defun wesnoth-append-tag-information (tag subtag attribute)
217 "Add the information regarding TAG to the list.
218 SUBTAG and ATTRIBUTE are a children of TAG to be added."
219 (let ((match (assoc tag wesnoth-tag-data)))
220 (if (not match)
221 (add-to-list 'wesnoth-tag-data (list tag (and subtag (list subtag))
222 (and attribute (list attribute))))
223 (if subtag
224 (let ((tmp (nth 1 match)))
225 (when (not (member subtag tmp))
226 (add-to-list 'tmp subtag)
227 (setq match (list tag tmp (car (last match))))))
228 (when attribute (let ((tmp (nth 2 match)))
229 (when (not (member attribute tmp))
230 (add-to-list 'tmp attribute)
231 (setq match (list tag (nth 1 match) tmp))))))
232 (setq wesnoth-tag-data
233 (remove (assoc tag wesnoth-tag-data)
234 wesnoth-tag-data))
235 (add-to-list 'wesnoth-tag-data match))))
237 (defmacro wesnoth-determine-macro-information (macro-list)
238 "Process the buffer, retrieving macro definition information.
239 MACRO-LIST is the variable to append macro information."
240 `(save-excursion
241 (goto-char (point-min))
242 (while (search-forward-regexp
243 "#define \\(\\(?:\\w\\|_\\)+\\)\\(\\([\t ]+\\(\\w\\|_\\)+\\)*\\)"
244 (point-max) t)
245 (beginning-of-line)
246 (add-to-list ,macro-list (list (match-string-no-properties 1)
247 (and (match-string 2)
248 (split-string
249 (match-string-no-properties 2)))))
250 (end-of-line))))
252 (defun wesnoth-determine-macro-builtins ()
253 "Retrieve built-in macro definition information."
254 (wesnoth-determine-macro-information 'wesnoth-macro-data))
256 (defun wesnoth-output-path ()
257 "Determine the path to output wml information via `wesnoth-update'."
258 (or wesnoth-update-output-directory
259 (if (boundp 'user-emacs-directory)
260 (symbol-value 'user-emacs-directory)
261 "~/.emacs.d/")))
263 (defun wesnoth-update-wml-additions ()
264 "Update WML information contained in `wesnoth-addition-file'."
265 (wesnoth-determine-details wesnoth-addition-file
266 'wesnoth-extract-tag-information)
267 (wesnoth-determine-details wesnoth-addition-file
268 'wesnoth-determine-macro-builtins))
270 (defun wesnoth-update ()
271 "Update WML information.
272 Path to WML information included in wesnoth is set by
273 `wesnoth-root-directory.'."
274 (interactive)
275 (setq wesnoth-tag-data nil
276 wesnoth-macro-data nil
277 wesnoth-found-cfgs nil)
278 (unless (and (stringp wesnoth-root-directory)
279 (file-exists-p wesnoth-root-directory))
280 ;; Update failed; restore data.
281 (load "wesnoth-wml-data")
282 (error "%s: directory does not exist"
283 wesnoth-root-directory))
284 (message "Updating WML information...")
285 (wesnoth-determine-details wesnoth-root-directory
286 'wesnoth-extract-tag-information)
287 (wesnoth-update-wml-additions)
288 (wesnoth-determine-details (concat wesnoth-root-directory
289 wesnoth-macro-directory)
290 'wesnoth-determine-macro-builtins)
291 (with-temp-buffer
292 (insert (format "(setq wesnoth-tag-data '%S)\n\n" wesnoth-tag-data))
293 (insert (format "(setq wesnoth-macro-data '%S)\n\n" wesnoth-macro-data))
294 (insert "(provide 'wesnoth-wml-data)\n")
295 (write-file (expand-file-name (format "wesnoth-wml-data.el")
296 (wesnoth-output-path)))
297 (load "wesnoth-wml-data"))
298 (wesnoth-create-wml-hash-table t)
299 (message "Updating WML information...done"))
301 (defun wesnoth-update-project-information (&optional clear)
302 "Update WML macro information for the current project."
303 (interactive "P")
304 (if clear
305 (setq wesnoth-local-macro-data nil)
306 (wesnoth-determine-macro-information 'wesnoth-local-macro-data)))
308 (defun wesnoth-update-teach-wesnoth-mode (file-or-dir)
309 "Update WML tag and attribute information for the current project.
310 If FILE-OR-DIR is provided, perform the update using only that location."
311 (interactive)
312 (wesnoth-determine-details
313 file-or-dir
314 (lambda ()
315 (wesnoth-determine-macro-information 'wesnoth-macro-data)))
316 (wesnoth-determine-details file-or-dir
317 'wesnoth-extract-tag-information))
319 (provide 'wesnoth-update)
321 ;;; wesnoth-update.el ends here