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,
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
29 ;; "/path/to/wesnoth-mode/wesnoth-wml-additions.cfg")
30 ;; Specifying the appropriate path in each case.
32 ;; Although WML data is provided along with wesnoth-mode, you can generate
33 ;; update-to-date, version-specific WML reference data for `wesnoth-mode'
34 ;; using `wesnoth-update'. This requires Wesnoth to be install and its
35 ;; pathname set for this to behave correctly. for example:
36 ;; (setq wesnoth-root-directory "/usr/share/wesnoth/")
38 ;; Then set the output directory for `wesnoth-update's results:
39 ;; (setq wesnoth-update-output-directory "/path/to/wesnoth-mode/")
40 ;; This is recommended to be in the same directory as `wesnoth-mode' and
41 ;; must be in `load-path'.
43 ;; Once set, `wesnoth-update' will produce 'wesnoth-wml-data.el' in
44 ;; `wesnoth-update-output-directory' and the information will automatically
45 ;; be available in the future sessions.
47 ;; Although much data is retreived, it is unlikely to be completely
48 ;; comprehensive. wesnoth-mode can be taught about additional tags,
49 ;; attributes and macros using the current project, or a single file, using
50 ;; `wesnoth-update-wml-additions'.
52 ;; To teach wesnoth-mode about elements it may have missed, you can extend the
53 ;; sample additions included with wesnoth-mode; namely
54 ;; wesnoth-wml-additions.cfg (although any source of WML can be used). To
55 ;; enable this, do the following:
56 ;; Set `wesnoth-addition-file' appropriately, for example:
57 ;; (setq wesnoth-addition-file "/path/to/wesnoth-wml-additions.cfg")
59 ;; Once set correctly, running M-x wesnoth-update will update the WML data
60 ;; available to `wesnoth-mode'.
64 ;; * Fixed inaccuracies when updating project information.
65 ;; * WML data from the addition file can now read when as it is required.
67 ;; * Any arguments are now stored for each macro.
69 ;; * Allow forced updating of the hash table.
70 ;; * Allow clearing of local macro data via a prefix argument.
72 ;; * Provide means for increased performance when referencing attributes and
74 ;; * Gather project macro information for the local buffer only, instead of
75 ;; from files in the directory.
80 (defvar wesnoth-update-version
"0.1.4"
81 "Version of `wesnoth-update'.")
83 (defcustom wesnoth-root-directory nil
84 "Root directory of wesnoth."
88 (defcustom wesnoth-addition-file nil
89 "Filename to the file containing additional WML information."
93 (defcustom wesnoth-update-output-directory nil
94 "Directory to write discovered WML syntax information.
95 Ensure this directory is in your `load-path'."
99 (defconst wesnoth-macro-directory
"data/core/macros"
100 "Directory which built-in macros are stored.
101 This is relative to the wesnoth directory in `wesnoth-root-directory.'.")
103 (defvar wesnoth-found-cfgs
'()
104 "Temporary list of all .cfg files found.")
106 (defvar wesnoth-tmp-tag-data
'()
107 "Temporary list of tag data.")
109 (defvar wesnoth-tmp-macro-data
'()
110 "Temporary list of macro data.")
112 (defvar wesnoth-tag-data
'()
113 "All information regarding the relation of tags and attributes.")
115 (defvar wesnoth-macro-data
'()
116 "Information regarding built-in macros.")
118 (defvar wesnoth-local-macro-data
'()
119 "All macro definitions available in the current project.")
121 (defvar wesnoth-tag-hash-table
(make-hash-table :test
'equal
123 "Hash table of known WML tag data.")
125 (defun wesnoth-create-wml-hash-table (tag-data &optional force
)
126 "Handle generation of `wesnoth-tag-hash-table'.
127 TAG-DATA is the data to add to the hash-table. If FORCE is
128 non-nil, update the hash-table regardless of whether it replacing
130 (when (or (= (hash-table-count wesnoth-tag-hash-table
) 0)
132 (clrhash wesnoth-tag-hash-table
)
133 (dolist (tag tag-data
)
134 (puthash (car tag
) (cdr tag
) wesnoth-tag-hash-table
))))
136 (defun wesnoth-file-cfg-p (file)
137 "Return non-nil if FILE has a '.cfg' extension."
138 (and (not (file-directory-p file
)) (string-match "\\.cfg$" file
)))
140 (defun wesnoth-fetch-all-dirs (dir)
141 "Retrieve a list of subdirectories to scan.
142 DIR is the directory to check."
143 (let ((dirs-to-scan (wesnoth-files-in-dir dir
)))
145 (setq dirs-to-scan
(append (wesnoth-files-in-dir (pop dirs-to-scan
))
148 (defun wesnoth-files-in-dir (dir)
149 "Add cfgs to `wesnoth-files-in-dir'.
150 Returns a list of sub-directories in DIR."
151 (let ((cfgs (wesnoth-cfg-files-in-dir dir
)))
153 (setq wesnoth-found-cfgs
(append cfgs wesnoth-found-cfgs
))))
155 (dolist (file (directory-files dir t
))
156 (unless (string-match "^\\..*" (file-name-nondirectory file
))
157 (cond ((file-directory-p file
)
158 (add-to-list 'dirs file
))
159 ((wesnoth-file-cfg-p file
)
160 (add-to-list 'wesnoth-found-cfgs file
)))))
163 (defun wesnoth-cfg-files-in-dir (dir)
164 "Return all cfg files in DIR."
166 (dolist (file (directory-files dir t
))
167 (and (wesnoth-file-cfg-p file
)
168 (add-to-list 'result file
)))
171 (defun wesnoth-determine-details (dir-or-file function
)
172 "Process .cfg files in DIR-OR-FILE using FUNCTION.
173 DIR-OR-FILE can be a file, a directory, or a list of files."
174 (cond ((listp dir-or-file
)
175 (dolist (file dir-or-file
)
176 (wesnoth-handle-file function file
)))
177 ((and (file-exists-p dir-or-file
)
178 (not (file-directory-p dir-or-file
)))
179 (wesnoth-handle-file function dir-or-file
))
181 (wesnoth-fetch-all-dirs dir-or-file
)
182 (while wesnoth-found-cfgs
183 (unless (string-match "^\\..+" (file-name-nondirectory
184 (car wesnoth-found-cfgs
)))
185 (wesnoth-handle-file function
(car wesnoth-found-cfgs
))
186 (setq wesnoth-found-cfgs
(cdr wesnoth-found-cfgs
)))))))
188 (defun wesnoth-handle-file (function file
)
189 "Perform FUNCTION on FILE."
191 (when (file-exists-p file
)
192 (insert-file-contents file
)
193 (funcall function
))))
195 (defun wesnoth-extract-tag-information ()
196 "Retrieve relevant tag and attribute information."
197 (let ((unmatched-tag-list '()))
198 (goto-char (point-min))
199 (while (search-forward-regexp
200 "^[\t ]*\\(\\[[+/]?\\(\\(\\w\\|_\\)+\\)\\]\\|\\(\\w\\|_\\)+=\\)"
204 ((and (save-excursion
205 (search-backward-regexp
206 "^[\t ]*\\(\\[[^/]]?\\|#define \\|#enddef \\)"
208 (string-match "#define " (match-string 1))
209 (looking-at "^[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]"))
210 (wesnoth-append-tag-information (match-string-no-properties 1)
212 (setq unmatched-tag-list
(cons (match-string-no-properties 1)
213 unmatched-tag-list
)))
214 ((looking-at "^[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]")
215 (wesnoth-append-tag-information (car unmatched-tag-list
)
216 (match-string-no-properties 1)
218 (wesnoth-append-tag-information (match-string-no-properties 1)
220 (setq unmatched-tag-list
(cons (match-string-no-properties 1)
221 unmatched-tag-list
)))
222 ((looking-at "^[\t ]*\\(\\(\\w\\|_\\)+\\)=")
223 (wesnoth-append-tag-information (car unmatched-tag-list
)
224 nil
(match-string-no-properties 1)))
225 ((looking-at "^[\t ]*\\[/\\(\\(\\w\\|_\\)+\\)\\]\\|")
226 (when (string= (match-string-no-properties 1)
227 (car unmatched-tag-list
))
228 (setq unmatched-tag-list
(cdr unmatched-tag-list
)))))
231 (defun wesnoth-append-tag-information (tag subtag attribute
)
232 "Add the information regarding TAG to the list.
233 SUBTAG and ATTRIBUTE are a children of TAG to be added."
234 (let ((match (assoc tag wesnoth-tmp-tag-data
)))
236 (add-to-list 'wesnoth-tmp-tag-data
237 (list tag
(and subtag
(list subtag
))
238 (and attribute
(list attribute
))))
240 (let ((tmp (nth 1 match
)))
241 (when (not (member subtag tmp
))
242 (add-to-list 'tmp subtag
)
243 (setq match
(list tag tmp
(car (last match
))))))
244 (when attribute
(let ((tmp (nth 2 match
)))
245 (when (not (member attribute tmp
))
246 (add-to-list 'tmp attribute
)
247 (setq match
(list tag
(nth 1 match
) tmp
))))))
248 (setq wesnoth-tmp-tag-data
249 (remove (assoc tag wesnoth-tmp-tag-data
)
250 wesnoth-tmp-tag-data
))
251 (add-to-list 'wesnoth-tmp-tag-data match
))))
253 (defun wesnoth-determine-macro-information ()
254 "Process the buffer, retrieving macro definition information.
255 MACRO-LIST is the variable to append macro information."
257 (goto-char (point-min))
258 (while (search-forward-regexp
259 "#define \\(\\(?:\\w\\|_\\)+\\)\\(\\([\t ]+\\(\\w\\|_\\)+\\)*\\)"
262 (add-to-list 'wesnoth-tmp-macro-data
263 (list (match-string-no-properties 1)
264 (and (match-string 2)
266 (match-string-no-properties 2)))))
268 wesnoth-tmp-macro-data
))
270 (defun wesnoth-output-path ()
271 "Determine the path to output wml information via `wesnoth-update'."
272 (or wesnoth-update-output-directory
273 (if (boundp 'user-emacs-directory
)
274 (symbol-value 'user-emacs-directory
)
277 (defun wesnoth-read-tmp-tag-data ()
278 "Read `wesnoth-tmp-tag-data' and reset its value."
279 (let ((results wesnoth-tmp-tag-data
))
280 (setq wesnoth-tmp-tag-data nil
)
283 (defun wesnoth-tag-additions ()
284 "Update WML tag information contained in `wesnoth-addition-file'."
285 (setq wesnoth-tmp-tag-data nil
)
286 (wesnoth-determine-details wesnoth-addition-file
287 'wesnoth-extract-tag-information
)
288 (wesnoth-read-tmp-tag-data))
290 (defun wesnoth-macro-additions ()
291 "Update WML macro information contained in `wesnoth-addition-file'."
292 (setq wesnoth-tmp-macro-data nil
)
293 (wesnoth-determine-details
294 wesnoth-addition-file
296 (wesnoth-determine-macro-information)))
297 (let ((results wesnoth-tmp-macro-data
))
298 (setq wesnoth-tmp-macro-data nil
)
301 (defun wesnoth-update ()
302 "Update WML information.
303 Path to WML information included in wesnoth is set by
304 `wesnoth-root-directory.'."
306 (setq wesnoth-tag-data nil
307 wesnoth-macro-data nil
308 wesnoth-found-cfgs nil
309 wesnoth-tmp-macro-data nil
310 wesnoth-tmp-tag-data nil
)
311 (unless (and (stringp wesnoth-root-directory
)
312 (file-exists-p wesnoth-root-directory
))
313 ;; Update failed; restore data.
314 (load "wesnoth-wml-data")
315 (error "%s: directory does not exist"
316 wesnoth-root-directory
))
317 (message "Updating WML information...")
318 (wesnoth-determine-details wesnoth-root-directory
319 'wesnoth-extract-tag-information
)
320 (wesnoth-determine-details
321 (concat wesnoth-root-directory wesnoth-macro-directory
)
323 (wesnoth-determine-macro-information)))
324 (setq wesnoth-tag-data wesnoth-tmp-tag-data
325 wesnoth-tmp-tag-data nil
326 wesnoth-macro-data wesnoth-tmp-macro-data
327 wesnoth-tmp-macro-data nil
)
329 (insert (format "(setq wesnoth-tag-data '%S)\n\n" wesnoth-tag-data
))
330 (insert (format "(setq wesnoth-macro-data '%S)\n\n" wesnoth-macro-data
))
331 (insert "(provide 'wesnoth-wml-data)\n")
332 (write-file (expand-file-name (format "wesnoth-wml-data.el")
333 (wesnoth-output-path)))
334 (load "wesnoth-wml-data"))
335 (message "Updating WML information...done"))
337 (defun wesnoth-merge-macro-data (&rest macro-data
)
338 "Merge WML macro information and return the result.
339 MACRO-DATA is the macro-data to merge."
341 (macro-base-data (car macro-data
)))
342 (while (setq macro-data
(cdr macro-data
))
343 (setq set-data
(car macro-data
))
345 (setq macro-base-data
346 (append (list (car set-data
))
347 (remove (assoc (car (car set-data
)) macro-base-data
)
349 set-data
(cdr set-data
))))
352 (defun wesnoth-merge-tag-data (&rest tag-data
)
353 "Merge WML tag information and return the result.
354 TAG-DATA is the tag-data to merge."
355 (setq wesnoth-tmp-tag-data
(car tag-data
))
356 (let ((set-data '()))
357 (while (setq tag-data
(cdr tag-data
))
358 (setq set-data
(car tag-data
))
360 (let ((subtags (nth 1 (car set-data
))))
362 (wesnoth-append-tag-information (caar set-data
) (car subtags
)
364 (setq subtags
(cdr subtags
))))
365 (let ((attributes (nth 2 (car set-data
))))
367 (wesnoth-append-tag-information (caar set-data
) nil
369 (setq attributes
(cdr attributes
))))
370 (setq set-data
(cdr set-data
))))
371 (wesnoth-read-tmp-tag-data)))
373 (defun wesnoth-update-project-information (&optional clear
)
374 "Update WML macro information for the current project.
375 If CLEAR is non-nil, reset `wesnoth-local-macro-data'."
377 (setq wesnoth-tmp-macro-data nil
)
379 (setq wesnoth-local-macro-data nil
)
380 (setq wesnoth-local-macro-data
381 (wesnoth-merge-macro-data wesnoth-local-macro-data
382 (wesnoth-determine-macro-information)))
383 (setq wesnoth-tmp-macro-data nil
)))
385 (defun wesnoth-refresh-wml-data ()
386 "Return merged WML tag data and WML data from the addition file."
388 (let ((result (wesnoth-merge-tag-data
389 wesnoth-tag-data
(wesnoth-tag-additions))))
390 (wesnoth-create-wml-hash-table result t
)
393 (provide 'wesnoth-update
)
395 ;;; wesnoth-update.el ends here