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