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/.
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
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.''
18 %%%----------------------------------------------------------------------
20 %%% Author : Anders Lindgren
21 %%% Purpose : Generate an Emacs TAGS file from programs written in Erlang.
24 %%%----------------------------------------------------------------------
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,
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.
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
68 %% {outfile, NameOfTAGSFile}
69 %% {outdir, NameOfDirectory}
71 %% Note, should both `outfile' and `outdir' options be given, `outfile'
75 %%% External interface
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
101 files_loop(Files
, Os
),
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
121 NewAcc
= collect_files(Dir
, Entries
, Recursive
, Acc
);
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
,_
,_
,_
,_
,_
}} ->
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
];
147 collect_files(Dir
, Files
, Recursive
, NewAcc
).
150 files_loop([],_Os
) -> true
;
151 files_loop([F
| Fs
], Os
) ->
152 case filename(F
, Os
) of
156 %% io:format("Could not open ~s~n", [F]),
162 %% Generate tags for one file.
163 filename(Name
, Os
) ->
164 case file:open(Name
, read
) of
166 Acc
= module(Desc
, [], [], {1, 0}),
168 genout(Os
, Name
, Acc
),
175 module(In
, Last
, Acc
, {LineNo
, CharNo
}) ->
176 case io:get_line(In
, []) of
180 {NewLast
, NewAcc
} = line(Line
, Last
, Acc
, {LineNo
, CharNo
}),
181 module(In
, NewLast
, NewAcc
, {LineNo
+1, CharNo
+length(Line
)})
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
190 New
-> {[], [New
| Acc
]}
192 line(Line
, Last
, Acc
, Nos
) ->
193 %% to be OR not to be?
194 case case {hd(Line
), word_char(hd(Line
))} of
200 case func(Line, Last, Nos) of
203 {NewLast, NewEntry} ->
204 {NewLast, [NewEntry | Acc]}
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
),
219 {Space
, Line2
} = white(Line1
),
222 {Name
, pfnote([$
(, Space
, Name
], Nos
)};
229 %% Return `false' or generated line.
230 attribute([$
- | Line
], Nos
) ->
231 {Attr
, Line1
} = word(Line
),
240 {Space2
, Line2
} = white(Line1
),
243 {Space4
, Line4
} = white(Line3
),
244 {Name
,_Line5
} = word(Line4
),
248 pfnote([Name
, Space4
, $
(, Space2
, Attr
, $
-], Nos
)
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}
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
) ->
281 true
-> unquoted(Cs
, [C
| Acc
]);
282 false
-> {Acc
, [C
| Cs
]}
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
.
294 %% Check the options `outfile' and `outdir'.
296 case lists:keysearch(outfile
, 1, Options
) of
297 {value
, {outfile
, File
}} ->
298 file:open(File
, write
);
300 case lists:keysearch(outdir
, 1, Options
) of
301 {value
, {outdir
, Dir
}} ->
302 file:open(addfile(Dir
, "TAGS"), write
);
304 file:open("TAGS", write
)
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
])
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
]) ->
350 %%% tags.erl ends here.