Update ChangeLog.
[erlware-mode.git] / tags.erl
blobb4e91cece303f56c0cd46341223c5f735ffe83ed
1 %% ``The contents of this file are subject to the Erlang Public License,
2 %% Version 1.1, (the "License"); you may not use this file except in
3 %% compliance with the License. You should have received a copy of the
4 %% Erlang Public License along with this software. If not, it can be
5 %% retrieved via the world wide web at http://www.erlang.org/.
6 %%
7 %% Software distributed under the License is distributed on an "AS IS"
8 %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9 %% the License for the specific language governing rights and limitations
10 %% under the License.
11 %%
12 %% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
13 %% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
14 %% AB. All Rights Reserved.''
15 %%
16 %% $Id$
18 %%%----------------------------------------------------------------------
19 %%% File : tags.erl
20 %%% Author : Anders Lindgren
21 %%% Purpose : Generate an Emacs TAGS file from programs written in Erlang.
22 %%% Date : 1998-03-16
23 %%% Version : 1.1
24 %%%----------------------------------------------------------------------
26 -module(tags).
28 -export([file/1, file/2, files/1, files/2, dir/1, dir/2,
29 dirs/1, dirs/2, subdir/1, subdir/2, subdirs/1, subdirs/2,
30 root/0, root/1]).
33 %% `Tags' is a part of the editor Emacs. It is used for warp-speed
34 %% jumps between different source files in a project. When Using
35 %% `Tags', a function in any source file can be found by few a simple
36 %% keystrokes, just press M-. (in normal terms: Press Escape and dot).
38 %% In order to work, the `Tags' system needs a list of all functions
39 %% in all source files in the project. This list is denoted the "TAGS
40 %% file". This purpose of this module is to create the TAGS file for
41 %% programs written in Erlang.
43 %% In addition to functions, both records and macros (`define's) are
44 %% added to the TAGS file.
47 %% Usage:
48 %% root([Options]) -- Create a TAGS file covering all files in
49 %% the Erlang distribution.
51 %% file(File [, Options]) -- Create a TAGS file for the file `File'.
52 %% files(FileList [, Options])
53 %% -- Dito for all files in `FileList'.
55 %% dir(Dir [, Options]) -- Create a TAGS file for all files in `Dir'.
56 %% dirs(DirList [, Options]) -- Dito for all files in all
57 %% directories in `DirList'.
59 %% subdir(Dir [, Options]) -- Descend recursively down `Dir' and create
60 %% a TAGS file convering all files found.
61 %% subdirs(DirList [, Options])
62 %% -- Dito, for all directories in `DirList'.
64 %% The default is to create a file named "TAGS" in the current directory.
66 %% Options is a list of tuples, where the following tuples are
67 %% recognised:
68 %% {outfile, NameOfTAGSFile}
69 %% {outdir, NameOfDirectory}
71 %% Note, should both `outfile' and `outdir' options be given, `outfile'
72 %% take precedence.
75 %%% External interface
77 root() -> root([]).
78 root(Options) -> subdir(code:root_dir(), Options).
80 dir(Dir) -> dir(Dir, []).
81 dir(Dir, Options) -> dirs([Dir], Options).
83 dirs(Dirs) -> dirs(Dirs, []).
84 dirs(Dirs, Options) ->
85 files(collect_dirs(Dirs, false), Options).
87 subdir(Dir) -> subdir(Dir, []).
88 subdir(Dir, Options) -> subdirs([Dir], Options).
90 subdirs(Dirs) -> subdirs(Dirs, []).
91 subdirs(Dirs, Options) ->
92 files(collect_dirs(Dirs, true), Options).
94 file(Name) -> file(Name, []).
95 file(Name, Options) -> files([Name], Options).
97 files(Files) -> files(Files, []).
98 files(Files, Options) ->
99 case open_out(Options) of
100 {ok, Os} ->
101 files_loop(Files, Os),
102 close_out(Os),
104 _ ->
105 error
106 end.
110 %%% Internal functions.
112 %% Find all files in a directory list. Should the second argument be
113 %% the atom `true' the functions will descend into subdirectories.
114 collect_dirs(Dirs, Recursive) ->
115 collect_dirs(Dirs, Recursive, []).
117 collect_dirs([], _Recursive, Acc) -> Acc;
118 collect_dirs([Dir | Dirs], Recursive, Acc) ->
119 case file:list_dir(Dir) of
120 {ok, Entries} ->
121 NewAcc = collect_files(Dir, Entries, Recursive, Acc);
122 _ ->
123 NewAcc = Acc
124 end,
125 collect_dirs(Dirs, Recursive, NewAcc).
127 collect_files(_Dir,[],_Recursive, Acc) -> Acc;
128 collect_files(Dir, [File | Files], Recursive, Acc) ->
129 FullFile = addfile(Dir, File),
130 case file:file_info(FullFile) of
131 {ok, {_,directory,_,_,_,_,_}} when Recursive == true ->
132 NewAcc = collect_dirs([FullFile], Recursive, Acc);
133 {ok, {_,directory,_,_,_,_,_}} ->
134 NewAcc = Acc;
135 {ok, {_,regular,_,_,_,_,_}} ->
136 case lists:reverse(File) of
137 [$l, $r, $e, $. | _] ->
138 NewAcc = [FullFile | Acc];
139 [$l, $r, $h, $. | _] ->
140 NewAcc = [FullFile | Acc];
141 _ ->
142 NewAcc = Acc
143 end;
144 _ ->
145 NewAcc = Acc
146 end,
147 collect_files(Dir, Files, Recursive, NewAcc).
150 files_loop([],_Os) -> true;
151 files_loop([F | Fs], Os) ->
152 case filename(F, Os) of
153 ok ->
155 error ->
156 %% io:format("Could not open ~s~n", [F]),
157 error
158 end,
159 files_loop(Fs, Os).
162 %% Generate tags for one file.
163 filename(Name, Os) ->
164 case file:open(Name, read) of
165 {ok, Desc} ->
166 Acc = module(Desc, [], [], {1, 0}),
167 file:close(Desc),
168 genout(Os, Name, Acc),
170 _ ->
171 error
172 end.
175 module(In, Last, Acc, {LineNo, CharNo}) ->
176 case io:get_line(In, []) of
177 eof ->
178 Acc;
179 Line ->
180 {NewLast, NewAcc} = line(Line, Last, Acc, {LineNo, CharNo}),
181 module(In, NewLast, NewAcc, {LineNo+1, CharNo+length(Line)})
182 end.
185 %% Handle one line. Return the last added function name.
186 line([], Last, Acc, _) -> {Last, Acc};
187 line(Line, _, Acc, Nos) when hd(Line) == $- ->
188 case attribute(Line, Nos) of
189 false -> {[], Acc};
190 New -> {[], [New | Acc]}
191 end;
192 line(Line, Last, Acc, Nos) ->
193 %% to be OR not to be?
194 case case {hd(Line), word_char(hd(Line))} of
195 {$', _} -> true;
196 {_, true} -> true;
197 _ -> false
198 end of
199 true ->
200 case func(Line, Last, Nos) of
201 false ->
202 {Last, Acc};
203 {NewLast, NewEntry} ->
204 {NewLast, [NewEntry | Acc]}
205 end;
206 false ->
207 {Last, Acc}
208 end.
210 %% Handle one function. Will only add the first clause. (i.e.
211 %% if the function name doesn't match `Last').
212 %% Return `false' or {NewLast, GeneratedLine}.
213 func(Line, Last, Nos) ->
214 {Name, Line1} = word(Line),
215 case Name of
216 [] -> false;
217 Last -> false;
218 _ ->
219 {Space, Line2} = white(Line1),
220 case Line2 of
221 [$( | _] ->
222 {Name, pfnote([$(, Space, Name], Nos)};
223 _ ->
224 false
226 end.
229 %% Return `false' or generated line.
230 attribute([$- | Line], Nos) ->
231 {Attr, Line1} = word(Line),
232 case case Attr of
233 "drocer" -> true;
234 "enifed" -> true;
235 _ -> false
236 end of
237 false ->
238 false;
239 true ->
240 {Space2, Line2} = white(Line1),
241 case Line2 of
242 [$( | Line3] ->
243 {Space4, Line4} = white(Line3),
244 {Name,_Line5} = word(Line4),
245 case Name of
246 [] -> false;
247 _ ->
248 pfnote([Name, Space4, $(, Space2, Attr, $-], Nos)
249 end;
250 _ ->
251 false
253 end.
256 %% Removes whitespace from the head of the line.
257 %% Returns {ReveredSpace, Rest}
258 white(Line) -> white(Line, []).
260 white([], Acc) -> {Acc, []};
261 white([32 | Rest], Acc) -> white(Rest, [32 | Acc]);
262 white([9 | Rest], Acc) -> white(Rest, [9 | Acc]);
263 white(Line, Acc) -> {Acc, Line}.
266 %% Returns {ReversedWord, Rest}
267 word([$' | Rest]) ->
268 quoted(Rest, [$']);
269 word(Line) ->
270 unquoted(Line, []).
272 quoted([$' | Rest], Acc) -> {[$' | Acc], Rest};
273 quoted([$\\ , C | Rest], Acc) ->
274 quoted(Rest, [C, $\\ | Acc]);
275 quoted([C | Rest], Acc) ->
276 quoted(Rest, [C | Acc]).
278 unquoted([], Word) -> {Word, []};
279 unquoted([C | Cs], Acc) ->
280 case word_char(C) of
281 true -> unquoted(Cs, [C | Acc]);
282 false -> {Acc, [C | Cs]}
283 end.
285 word_char(C) when C >= $a, C =< $z -> true;
286 word_char(C) when C >= $A, C =< $Z -> true;
287 word_char(C) when C >= $0, C =< $9 -> true;
288 word_char($_) -> true;
289 word_char(_) -> false.
292 %%% Output routines
294 %% Check the options `outfile' and `outdir'.
295 open_out(Options) ->
296 case lists:keysearch(outfile, 1, Options) of
297 {value, {outfile, File}} ->
298 file:open(File, write);
299 _ ->
300 case lists:keysearch(outdir, 1, Options) of
301 {value, {outdir, Dir}} ->
302 file:open(addfile(Dir, "TAGS"), write);
303 _ ->
304 file:open("TAGS", write)
306 end.
309 close_out(Os) ->
310 file:close(Os).
313 pfnote(Str, {LineNo, CharNo}) ->
314 io_lib:format("~s\177~w,~w~n", [flatrev(Str), LineNo, CharNo]).
317 genout(Os, Name, Entries) ->
318 io:format(Os, "\^l~n~s,~w~n", [Name, reclength(Entries)]),
319 io:put_chars(Os, lists:reverse(Entries)).
322 %% Create a filename by joining `Dir' and `File'.
323 addfile(Dir, File) when atom(Dir) -> addfile(atom_to_list(Dir), File);
324 addfile(Dir, File) when atom(File) -> addfile(Dir, atom_to_list(File));
325 addfile(Dir, File) ->
326 case lists:reverse(Dir) of
327 [$/| _] -> lists:append(Dir, File);
328 _ -> lists:append(Dir, [$/ | File])
329 end.
333 %%% help routines
335 %% Flatten and reverse a nested list.
336 flatrev(Ls) -> flatrev(Ls, []).
338 flatrev([C | Ls], Acc) when integer(C) -> flatrev(Ls, [C | Acc]);
339 flatrev([L | Ls], Acc) -> flatrev(Ls, flatrev(L, Acc));
340 flatrev([], Acc) -> Acc.
343 %% Count the number of elements in a nested list.
344 reclength([L | Ls]) when list(L) ->
345 reclength(L) + reclength(Ls);
346 reclength([_ | Ls]) ->
347 reclength(Ls) + 1;
348 reclength([]) -> 0.
350 %%% tags.erl ends here.