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