* wesnoth-update.el (wesnoth-handle-file): Ignore file if doesn't exist.
[wesnoth-mode.git] / wesnoth-update.el
blob3cce907f1ec87390bc05db16364d86bbd554f938
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 ;; Although WML data is provided along with wesnoth-mode, you can generate
25 ;; update-to-date, version-specific WML reference data for `wesnoth-mode'
26 ;; using `wesnoth-update'. This requires Wesnoth to be install and its
27 ;; pathname set for this to behave correctly. for example:
28 ;; (setq wesnoth-root-directory "/usr/share/wesnoth/")
29 ;; Ensuring the pathname ends in a slash.
31 ;; Then set the output directory for `wesnoth-update's results:
32 ;; (setq wesnoth-update-output-directory "/path/to/wesnoth-mode/")
33 ;; This should be in the same directory as `wesnoth-mode' and included in
34 ;; `load-path'.
36 ;; Once set, `wesnoth-update' will
37 ;; produce 'wesnoth-wml-data.el' in `wesnoth-update-output-directory' and the
38 ;; information will automatically be available in the future sessions.
40 ;; Although much data is retreived, it is unlikely to be completely
41 ;; comprehensive. wesnoth-mode can be taught about additional tags,
42 ;; attributes and macros using the current project, or a single file, using
43 ;; `wesnoth-update-wml-additions'.
45 ;; To teach wesnoth-mode about elements it may have missed, you can extend the
46 ;; sample additions included with wesnoth-mode; namely
47 ;; wesnoth-wml-additions.cfg (although any source of WML can be used). To
48 ;; enable this, do the following:
49 ;; Set `wesnoth-addition-file' appropriately, for example:
50 ;; (setq wesnoth-addition-file "/path/to/wesnoth-wml-additions.cfg")
51 ;; and run:
52 ;; M-x wesnoth-update-wml-additions.
53 ;; Once `wesnoth-addition-file' is set, `wesnoth-update' can be used to store
54 ;; the information retrieved, so that it may be used in future sessions.
56 ;;; History:
57 ;; 0.1
58 ;; * Initial version
60 ;;; Code:
61 (require 'cl)
63 (defvar wesnoth-update-version "0.1"
64 "Version of `wesnoth-update'.")
66 (defcustom wesnoth-root-directory nil
67 "Path to the root directory of wesnoth.
68 Path must end in trailing slash."
69 :type 'directory
70 :group 'wesnoth-mode)
72 (defcustom wesnoth-addition-file nil
73 "Filename to the file containing additional WML information."
74 :type 'file
75 :group 'wesnoth-mode)
77 (defcustom wesnoth-update-output-directory nil
78 "Directory to write discovered WML syntax information.
79 Ensure this directory is in your `load-path'."
80 :type 'directory
81 :group 'wesnoth-mode)
83 (defvar wesnoth-macro-directory (concat wesnoth-root-directory "data/core/macros")
84 "Directory which built-in macros are stored.
85 This is relative to `wesnoth-root-directory'.")
87 (defvar wesnoth-found-cfgs '()
88 "Temporary list of all .cfg files found.")
90 (defvar wesnoth-tag-data '()
91 "All information regarding the relation of tags and attributes.")
93 (defvar wesnoth-macro-data '()
94 "Information regarding built-in macros.")
96 (defvar wesnoth-local-macro-data '()
97 "All macro definitions available in the current project.")
99 (defun wesnoth-file-cfg-p (file)
100 "Return non-nil if FILE has a '.cfg' extension."
101 (and (not (file-directory-p file)) (string-match "\\.cfg$" file)))
103 (defun wesnoth-fetch-all-dirs (dir)
104 "Retrieve a list of subdirectories to scan.
105 DIR is the directory to check."
106 (let ((dirs-to-scan (wesnoth-files-in-dir dir)))
107 (while dirs-to-scan
108 (setq dirs-to-scan (append (wesnoth-files-in-dir (pop dirs-to-scan))
109 dirs-to-scan)))))
111 (defun wesnoth-files-in-dir (dir)
112 "Add cfgs to `wesnoth-files-in-dir'.
113 Returns a list of sub-directories in DIR."
114 (let ((cfgs (wesnoth-cfg-files-in-dir dir)))
115 (when cfgs
116 (setq wesnoth-found-cfgs (append cfgs wesnoth-found-cfgs))))
117 (remove-if
118 (lambda (file)
119 (or (not (file-directory-p file))
120 (string-match "^\\..+" (file-name-nondirectory file))))
121 (file-expand-wildcards (concat dir "/*") t)))
123 (defun wesnoth-cfg-files-in-dir (dir)
124 "Return all cfg files in DIR."
125 (remove-if-not 'wesnoth-file-cfg-p
126 (file-expand-wildcards
127 (concat dir "/*") t)))
129 (defun wesnoth-determine-details (dir-or-file function)
130 "Process .cfg files in DIR-OR-FILE using FUNCTION.
131 DIR-OR-FILE can be a file, a directory, or a list of files."
132 (cond ((listp dir-or-file)
133 (dolist (file dir-or-file)
134 (wesnoth-handle-file function file)))
135 ((and (file-exists-p dir-or-file)
136 (not (file-directory-p dir-or-file)))
137 (wesnoth-handle-file function dir-or-file))
139 (wesnoth-fetch-all-dirs dir-or-file)
140 (while wesnoth-found-cfgs
141 (unless (string-match "^\\..+" (file-name-nondirectory
142 (car wesnoth-found-cfgs)))
143 (wesnoth-handle-file function (car wesnoth-found-cfgs))
144 (setq wesnoth-found-cfgs (cdr wesnoth-found-cfgs)))))))
146 (defun wesnoth-handle-file (function file)
147 "Perform FUNCTION on FILE."
148 (with-temp-buffer
149 (when (and (string-match "^\\.#.*" (file-name-nondirectory file))
150 (file-symlink-p file))
151 (setq file (concat (file-name-directory file)
152 (subseq (file-name-nondirectory file) 1) "#")))
153 (when (file-exists-p file)
154 (insert-file-contents file)
155 (funcall function))))
157 (defun wesnoth-extract-tag-information ()
158 "Retrieve relevant tag and attribute information."
159 (let ((unmatched-tag-list '()))
160 (goto-char (point-min))
161 (while (search-forward-regexp
162 "^[\t ]*\\(\\[[+/]?\\(\\(\\w\\|_\\)+\\)\\]\\|\\(\\w\\|_\\)+=\\)"
163 (point-max) t)
164 (beginning-of-line)
165 (cond
166 ((and (save-excursion
167 (search-backward-regexp
168 "^[\t ]*\\(\\[[^/]]?\\|#define \\|#enddef \\)"
169 (point-min) t))
170 (string-match "#define " (match-string 1))
171 (looking-at "^[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]"))
172 (wesnoth-append-tag-information (match-string-no-properties 1) 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) nil nil)
180 (setq unmatched-tag-list (cons (match-string-no-properties 1)
181 unmatched-tag-list)))
182 ((looking-at "^[\t ]*\\(\\(\\w\\|_\\)+\\)=")
183 (wesnoth-append-tag-information (car unmatched-tag-list)
184 nil (match-string-no-properties 1)))
185 ((looking-at "^[\t ]*\\[/\\(\\(\\w\\|_\\)+\\)\\]\\|")
186 (when (string= (match-string-no-properties 1)
187 (car unmatched-tag-list))
188 (setq unmatched-tag-list (cdr unmatched-tag-list)))))
189 (end-of-line))))
191 (defun wesnoth-append-tag-information (tag subtag attribute)
192 "Add the information regarding TAG to the list.
193 SUBTAG and ATTRIBUTE are a children of TAG to be added."
194 (let ((match (find tag wesnoth-tag-data :key 'car :test 'string=)))
195 (if (not match)
196 (add-to-list 'wesnoth-tag-data (list tag (and subtag (list subtag))
197 (and attribute (list attribute))))
198 (if subtag
199 (let ((tmp (nth 1 match)))
200 (when (not (member subtag tmp))
201 (add-to-list 'tmp subtag)
202 (setq match (list tag tmp (car (last match))))))
203 (when attribute (let ((tmp (nth 2 match)))
204 (when (not (member attribute tmp))
205 (add-to-list 'tmp attribute)
206 (setq match (list tag (nth 1 match) tmp))))))
207 (setq wesnoth-tag-data
208 (remove (find tag wesnoth-tag-data :key 'car :test 'string=)
209 wesnoth-tag-data))
210 (add-to-list 'wesnoth-tag-data match))))
212 (defmacro wesnoth-determine-macro-information (macro-list)
213 "Process the buffer, retrieving macro definition information.
214 MACRO-LIST is the variable to append macro information."
215 `(progn
216 (goto-char (point-min))
217 (while (search-forward-regexp
218 "#define \\(\\(\\w\\|_\\)+\\)\\([\t ]+\\(\\w\\|_\\)+\\)?"
219 (point-max) t)
220 (beginning-of-line)
221 (add-to-list ,macro-list (list (match-string 1)
222 (when (match-string 3) t)))
223 (end-of-line))))
225 (defun wesnoth-determine-macro-builtins ()
226 "Retrieve built-in macro definition information."
227 (wesnoth-determine-macro-information 'wesnoth-macro-data))
229 (defun wesnoth-output-path ()
230 "Determine the path to output wml information via `wesnoth-update'."
231 (or wesnoth-update-output-directory
232 (and (boundp user-emacs-directory)
233 user-emacs-directory)
234 "~/.emacs.d/"))
236 (defun wesnoth-update-wml-additions ()
237 "Update WML information contained in `wesnoth-addition-file'."
238 (interactive)
239 (when (interactive-p)
240 (unless (and (stringp wesnoth-addition-file)
241 (file-exists-p wesnoth-addition-file))
242 (error "%s: file does not exist"
243 wesnoth-addition-file)))
244 (wesnoth-determine-details wesnoth-addition-file
245 'wesnoth-extract-tag-information)
246 (wesnoth-determine-details wesnoth-addition-file
247 'wesnoth-determine-macro-builtins))
249 (defun wesnoth-update ()
250 "Update WML information.
251 Path to WML information included in wesnoth is set by
252 `wesnoth-root-directory'."
253 (interactive)
254 (setq wesnoth-tag-data nil
255 wesnoth-macro-data nil)
256 (unless (and (stringp wesnoth-root-directory)
257 (file-exists-p wesnoth-root-directory))
258 (error "%s: directory does not exist"
259 wesnoth-root-directory))
260 (wesnoth-determine-details wesnoth-root-directory
261 'wesnoth-extract-tag-information)
262 (wesnoth-update-wml-additions)
263 (wesnoth-determine-details wesnoth-macro-directory
264 'wesnoth-determine-macro-builtins)
265 (with-temp-buffer
266 (insert (format "(defvar wesnoth-tag-data '%S)\n\n" wesnoth-tag-data))
267 (insert (format "(defvar wesnoth-macro-data '%S)\n\n" wesnoth-macro-data))
268 (insert (format "(provide 'wesnoth-wml-data)\n"))
269 (write-file (expand-file-name "wesnoth-wml-data.el"
270 (wesnoth-output-path)))))
272 (defun wesnoth-update-project-information ()
273 "Update WML macro information for the current project."
274 (interactive)
275 (wesnoth-determine-details (wesnoth-cfg-files-in-dir default-directory)
276 (lambda ()
277 (wesnoth-determine-macro-information 'wesnoth-local-macro-data))))
279 (defun wesnoth-update-teach-wesnoth-mode (file-or-dir)
280 "Update WML tag and attribute information for the current project.
281 If FILE-OR-DIR is provided, perform the update using only that location."
282 (interactive)
283 (wesnoth-determine-details
284 file-or-dir
285 (lambda ()
286 (wesnoth-determine-macro-information 'wesnoth-macro-data)))
287 (wesnoth-determine-details file-or-dir
288 'wesnoth-extract-tag-information))
290 (provide 'wesnoth-update)
292 ;;; wesnoth-update.el ends here