* wesnoth-mode.el: Fix over-long lines.
[wesnoth-mode.git] / wesnoth-update.el
bloba84c84bfe94cf9ade014f402fdefc627cc3d2c80
1 ;;; wesnoth-update.el --- Update known WML data via existing valid WML.
2 ;; Copyright (C) 2008, 2009 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.4
63 ;; * Fixed inaccuracies when updating project information.
64 ;; * WML data from the addition file can now read when as it is required.
65 ;; 0.1.3
66 ;; * Any arguments are now stored for each macro.
67 ;; 0.1.2
68 ;; * Allow forced updating of the hash table.
69 ;; * Allow clearing of local macro data via a prefix argument.
70 ;; 0.1.1
71 ;; * Provide means for increased performance when referencing attributes and
72 ;; tags.
73 ;; * Gather project macro information for the local buffer only, instead of
74 ;; from files in the directory.
75 ;; 0.1
76 ;; * Initial version
78 ;;; Code:
79 (defvar wesnoth-update-version "0.1.4"
80 "Version of `wesnoth-update'.")
82 (defcustom wesnoth-root-directory nil
83 "Root directory of wesnoth."
84 :type 'directory
85 :group 'wesnoth-mode)
87 (defcustom wesnoth-addition-file nil
88 "Filename to the file containing additional WML information."
89 :type 'file
90 :group 'wesnoth-mode)
92 (defcustom wesnoth-update-output-directory nil
93 "Directory to write discovered WML syntax information.
94 Ensure this directory is in your `load-path'."
95 :type 'directory
96 :group 'wesnoth-mode)
98 (defconst wesnoth-macro-directory "data/core/macros"
99 "Directory which built-in macros are stored.
100 This is relative to the wesnoth directory in `wesnoth-root-directory.'.")
102 (defvar wesnoth-found-cfgs '()
103 "Temporary list of all .cfg files found.")
105 (defvar wesnoth-tmp-tag-data '()
106 "Temporary list of tag data.")
108 (defvar wesnoth-tmp-macro-data '()
109 "Temporary list of macro data.")
111 (defvar wesnoth-tag-data '()
112 "All information regarding the relation of tags and attributes.")
114 (defvar wesnoth-macro-data '()
115 "Information regarding built-in macros.")
117 (defvar wesnoth-local-macro-data '()
118 "All macro definitions available in the current project.")
120 (defvar wesnoth-tag-hash-table (make-hash-table :test 'equal
121 :size 350)
122 "Hash table of known WML tag data.")
124 (defun wesnoth-create-wml-hash-table (tag-data &optional force)
125 "Handle generation of `wesnoth-tag-hash-table'.
126 TAG-DATA is the data to add to the hash-table. If FORCE is
127 non-nil, update the hash-table regardless of whether it replacing
128 any existing data."
129 (when (or (= (hash-table-count wesnoth-tag-hash-table) 0)
130 force)
131 (clrhash wesnoth-tag-hash-table)
132 (dolist (tag tag-data)
133 (puthash (car tag) (cdr tag) wesnoth-tag-hash-table))))
135 (defun wesnoth-file-cfg-p (file)
136 "Return non-nil if FILE has a '.cfg' extension."
137 (and (not (file-directory-p file)) (string-match "\\.cfg$" file)))
139 (defun wesnoth-fetch-all-dirs (dir)
140 "Retrieve a list of subdirectories to scan.
141 DIR is the directory to check."
142 (let ((dirs-to-scan (wesnoth-files-in-dir dir)))
143 (while dirs-to-scan
144 (setq dirs-to-scan (append (wesnoth-files-in-dir (pop dirs-to-scan))
145 dirs-to-scan)))))
147 (defun wesnoth-files-in-dir (dir)
148 "Add cfgs to `wesnoth-files-in-dir'.
149 Returns a list of sub-directories in DIR."
150 (let ((cfgs (wesnoth-cfg-files-in-dir dir)))
151 (when cfgs
152 (setq wesnoth-found-cfgs (append cfgs wesnoth-found-cfgs))))
153 (let ((dirs '()))
154 (dolist (file (directory-files dir t))
155 (unless (string-match "^\\..*" (file-name-nondirectory file))
156 (cond ((file-directory-p file)
157 (add-to-list 'dirs file))
158 ((wesnoth-file-cfg-p file)
159 (add-to-list 'wesnoth-found-cfgs file)))))
160 dirs))
162 (defun wesnoth-cfg-files-in-dir (dir)
163 "Return all cfg files in DIR."
164 (let ((result '()))
165 (dolist (file (directory-files dir t))
166 (and (wesnoth-file-cfg-p file)
167 (add-to-list 'result file)))
168 result))
170 (defun wesnoth-determine-details (dir-or-file function)
171 "Process .cfg files in DIR-OR-FILE using FUNCTION.
172 DIR-OR-FILE can be a file, a directory, or a list of files."
173 (cond ((listp dir-or-file)
174 (dolist (file dir-or-file)
175 (wesnoth-handle-file function file)))
176 ((and (file-exists-p dir-or-file)
177 (not (file-directory-p dir-or-file)))
178 (wesnoth-handle-file function dir-or-file))
180 (wesnoth-fetch-all-dirs dir-or-file)
181 (while wesnoth-found-cfgs
182 (unless (string-match "^\\..+" (file-name-nondirectory
183 (car wesnoth-found-cfgs)))
184 (wesnoth-handle-file function (car wesnoth-found-cfgs))
185 (setq wesnoth-found-cfgs (cdr wesnoth-found-cfgs)))))))
187 (defun wesnoth-handle-file (function file)
188 "Perform FUNCTION on FILE."
189 (with-temp-buffer
190 (when (file-exists-p file)
191 (insert-file-contents file)
192 (funcall function))))
194 (defun wesnoth-extract-tag-information ()
195 "Retrieve relevant tag and attribute information."
196 (let ((unmatched-tag-list '()))
197 (goto-char (point-min))
198 (while (search-forward-regexp
199 "^[\t ]*\\(\\[[+/]?\\(\\(\\w\\|_\\)+\\)\\]\\|\\(\\w\\|_\\)+=\\)"
200 (point-max) t)
201 (beginning-of-line)
202 (cond
203 ((and (save-excursion
204 (search-backward-regexp
205 "^[\t ]*\\(\\[[^/]]?\\|#define \\|#enddef \\)"
206 (point-min) t))
207 (string-match "#define " (match-string 1))
208 (looking-at "^[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]"))
209 (wesnoth-append-tag-information (match-string-no-properties 1) nil nil)
210 (setq unmatched-tag-list (cons (match-string-no-properties 1)
211 unmatched-tag-list)))
212 ((looking-at "^[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]")
213 (wesnoth-append-tag-information (car unmatched-tag-list)
214 (match-string-no-properties 1)
215 nil)
216 (wesnoth-append-tag-information (match-string-no-properties 1) nil nil)
217 (setq unmatched-tag-list (cons (match-string-no-properties 1)
218 unmatched-tag-list)))
219 ((looking-at "^[\t ]*\\(\\(\\w\\|_\\)+\\)=")
220 (wesnoth-append-tag-information (car unmatched-tag-list)
221 nil (match-string-no-properties 1)))
222 ((looking-at "^[\t ]*\\[/\\(\\(\\w\\|_\\)+\\)\\]\\|")
223 (when (string= (match-string-no-properties 1)
224 (car unmatched-tag-list))
225 (setq unmatched-tag-list (cdr unmatched-tag-list)))))
226 (end-of-line))))
228 (defun wesnoth-append-tag-information (tag subtag attribute)
229 "Add the information regarding TAG to the list.
230 SUBTAG and ATTRIBUTE are a children of TAG to be added."
231 (let ((match (assoc tag wesnoth-tmp-tag-data)))
232 (if (not match)
233 (add-to-list 'wesnoth-tmp-tag-data (list tag (and subtag (list subtag))
234 (and attribute (list attribute))))
235 (if subtag
236 (let ((tmp (nth 1 match)))
237 (when (not (member subtag tmp))
238 (add-to-list 'tmp subtag)
239 (setq match (list tag tmp (car (last match))))))
240 (when attribute (let ((tmp (nth 2 match)))
241 (when (not (member attribute tmp))
242 (add-to-list 'tmp attribute)
243 (setq match (list tag (nth 1 match) tmp))))))
244 (setq wesnoth-tmp-tag-data
245 (remove (assoc tag wesnoth-tmp-tag-data)
246 wesnoth-tmp-tag-data))
247 (add-to-list 'wesnoth-tmp-tag-data match))))
249 (defun wesnoth-determine-macro-information ()
250 "Process the buffer, retrieving macro definition information.
251 MACRO-LIST is the variable to append macro information."
252 (save-excursion
253 (goto-char (point-min))
254 (while (search-forward-regexp
255 "#define \\(\\(?:\\w\\|_\\)+\\)\\(\\([\t ]+\\(\\w\\|_\\)+\\)*\\)"
256 (point-max) t)
257 (beginning-of-line)
258 (add-to-list 'wesnoth-tmp-macro-data
259 (list (match-string-no-properties 1)
260 (and (match-string 2)
261 (split-string
262 (match-string-no-properties 2)))))
263 (end-of-line))
264 wesnoth-tmp-macro-data))
266 (defun wesnoth-output-path ()
267 "Determine the path to output wml information via `wesnoth-update'."
268 (or wesnoth-update-output-directory
269 (if (boundp 'user-emacs-directory)
270 (symbol-value 'user-emacs-directory)
271 "~/.emacs.d/")))
273 (defun wesnoth-read-tmp-tag-data ()
274 "Read `wesnoth-tmp-tag-data' and reset its value."
275 (let ((results wesnoth-tmp-tag-data))
276 (setq wesnoth-tmp-tag-data nil)
277 results))
279 (defun wesnoth-tag-additions ()
280 "Update WML tag information contained in `wesnoth-addition-file'."
281 (setq wesnoth-tmp-tag-data nil)
282 (wesnoth-determine-details wesnoth-addition-file
283 'wesnoth-extract-tag-information)
284 (wesnoth-read-tmp-tag-data))
286 (defun wesnoth-macro-additions ()
287 "Update WML macro information contained in `wesnoth-addition-file'."
288 (setq wesnoth-tmp-macro-data nil)
289 (wesnoth-determine-details
290 wesnoth-addition-file
291 (lambda ()
292 (wesnoth-determine-macro-information)))
293 (let ((results wesnoth-tmp-macro-data))
294 (setq wesnoth-tmp-macro-data nil)
295 results))
297 (defun wesnoth-update ()
298 "Update WML information.
299 Path to WML information included in wesnoth is set by
300 `wesnoth-root-directory.'."
301 (interactive)
302 (setq wesnoth-tag-data nil
303 wesnoth-macro-data nil
304 wesnoth-found-cfgs nil
305 wesnoth-tmp-macro-data nil
306 wesnoth-tmp-tag-data nil)
307 (unless (and (stringp wesnoth-root-directory)
308 (file-exists-p wesnoth-root-directory))
309 ;; Update failed; restore data.
310 (load "wesnoth-wml-data")
311 (error "%s: directory does not exist"
312 wesnoth-root-directory))
313 (message "Updating WML information...")
314 (wesnoth-determine-details wesnoth-root-directory
315 'wesnoth-extract-tag-information)
316 (wesnoth-determine-details
317 (concat wesnoth-root-directory wesnoth-macro-directory)
318 (lambda ()
319 (wesnoth-determine-macro-information)))
320 (setq wesnoth-tag-data wesnoth-tmp-tag-data
321 wesnoth-tmp-tag-data nil
322 wesnoth-macro-data wesnoth-tmp-macro-data
323 wesnoth-tmp-macro-data nil)
324 (with-temp-buffer
325 (insert (format "(setq wesnoth-tag-data '%S)\n\n" wesnoth-tag-data))
326 (insert (format "(setq wesnoth-macro-data '%S)\n\n" wesnoth-macro-data))
327 (insert "(provide 'wesnoth-wml-data)\n")
328 (write-file (expand-file-name (format "wesnoth-wml-data.el")
329 (wesnoth-output-path)))
330 (load "wesnoth-wml-data"))
331 (message "Updating WML information...done"))
333 (defun wesnoth-merge-macro-data (&rest macro-data)
334 "Merge WML macro information and return the result.
335 MACRO-DATA is the macro-data to merge."
336 (let ((set-data '())
337 (macro-base-data (car macro-data)))
338 (while (setq macro-data (cdr macro-data))
339 (setq set-data (car macro-data))
340 (while set-data
341 (setq macro-base-data
342 (append (list (car set-data))
343 (remove (assoc (car (car set-data)) macro-base-data)
344 macro-base-data))
345 set-data (cdr set-data))))
346 macro-base-data))
348 (defun wesnoth-merge-tag-data (&rest tag-data)
349 "Merge WML tag information and return the result.
350 TAG-DATA is the tag-data to merge."
351 (setq wesnoth-tmp-tag-data (car tag-data))
352 (let ((set-data '()))
353 (while (setq tag-data (cdr tag-data))
354 (setq set-data (car tag-data))
355 (while set-data
356 (let ((subtags (nth 1 (car set-data))))
357 (while subtags
358 (wesnoth-append-tag-information (caar set-data) (car subtags)
359 nil)
360 (setq subtags (cdr subtags))))
361 (let ((attributes (nth 2 (car set-data))))
362 (while attributes
363 (wesnoth-append-tag-information (caar set-data) nil
364 (car attributes))
365 (setq attributes (cdr attributes))))
366 (setq set-data (cdr set-data))))
367 (wesnoth-read-tmp-tag-data)))
369 (defun wesnoth-update-project-information (&optional clear)
370 "Update WML macro information for the current project.
371 If CLEAR is non-nil, reset `wesnoth-local-macro-data'."
372 (interactive "P")
373 (setq wesnoth-tmp-macro-data nil)
374 (if clear
375 (setq wesnoth-local-macro-data nil)
376 (setq wesnoth-local-macro-data
377 (wesnoth-merge-macro-data wesnoth-local-macro-data
378 (wesnoth-determine-macro-information)))
379 (setq wesnoth-tmp-macro-data nil)))
381 (defun wesnoth-refresh-wml-data ()
382 "Return merged WML tag data and WML data from the addition file."
383 (save-match-data
384 (let ((result (wesnoth-merge-tag-data
385 wesnoth-tag-data (wesnoth-tag-additions))))
386 (wesnoth-create-wml-hash-table result t)
387 result)))
389 (provide 'wesnoth-update)
391 ;;; wesnoth-update.el ends here