Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100755 380 lines (346 sloc) 14.975 kb
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
1 #!/usr/bin/env escript
b831cbd @manopapad Set compilation flags automatically according to OTP version
authored
2
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
3 %%% Copyright 2010-2011 Manolis Papadakis <manopapad@gmail.com>,
4 %%% Eirini Arvaniti <eirinibob@gmail.com>
5 %%% and Kostis Sagonas <kostis@cs.ntua.gr>
6 %%%
7 %%% This file is part of PropEr.
8 %%%
9 %%% PropEr is free software: you can redistribute it and/or modify
10 %%% it under the terms of the GNU General Public License as published by
11 %%% the Free Software Foundation, either version 3 of the License, or
12 %%% (at your option) any later version.
13 %%%
14 %%% PropEr is distributed in the hope that it will be useful,
15 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
16 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 %%% GNU General Public License for more details.
18 %%%
19 %%% You should have received a copy of the GNU General Public License
20 %%% along with PropEr. If not, see <http://www.gnu.org/licenses/>.
21
b36c594 @manopapad Remove e-mail addresses from EDoc output
authored
22 %%% Author: Manolis Papadakis
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
23 %%% Description: Documentation processing script: This script will call EDoc on
24 %%% the application's source files, after inlining all types
25 %%% denoted as aliases, and removing from the exported types lists
26 %%% all types denoted as private.
27 %%% Known Bugs: * This scipt is very hacky, it will probably break easily.
28 %%% * Record declarations with no type information are discarded.
29 %%% * Any text inside the same multi-line comment as an @alias or
30 %%% @private_type tag will be discarded.
31 %%% * Comments inside included files are not processed.
32 %%% * Comments will generally be displaced, especially comments
33 %%% inside type declarations or functions.
34 %%% * File and line information is partially lost.
35
36
37 %%------------------------------------------------------------------------------
38 %% Constants
39 %%------------------------------------------------------------------------------
40
41 -define(SRC_FILES_RE, "^.*\\.erl$").
42 -define(APP_NAME, proper).
43 -define(BASE_DIR, ".").
44 -define(SRC_DIR, (?BASE_DIR ++ "/src")).
45 -define(INCLUDE_DIR, (?BASE_DIR ++ "/include")).
46 -define(TEMP_SRC_DIR, (?BASE_DIR ++ "/temp_src")).
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
47 -define(EDOC_OPTS, [{report_missing_types,true}, {report_type_mismatch,true},
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
48 {pretty_printer,erl_pp}, {preprocess,true},
49 {source_path, [?TEMP_SRC_DIR]}]).
50
51
52 %%------------------------------------------------------------------------------
53 %% Types
54 %%------------------------------------------------------------------------------
55
56 %% TODO: Replace these with the appropriate types from stdlib.
57 -type abs_form() :: term().
58 -type abs_clause() :: term().
59 -type abs_type() :: term().
60 -type abs_rec_field() :: term().
61
62 -type line() :: non_neg_integer().
63 -type var_name() :: atom().
64 -type rec_name() :: atom().
65 -type var_form() :: {'var', line(), var_name()}.
66 -type type_name() :: atom().
67 -type type_ref() :: {'type', type_name(), arity()}.
68 -type type_def() :: {abs_type(), [var_form()]}.
69
70 -type charlist() :: [char() | charlist()].
71 -type charlist_with_lines() :: {[line(),...],charlist()}.
72
73
74 %%------------------------------------------------------------------------------
75 %% File handling
76 %%------------------------------------------------------------------------------
77
78 -spec main([string()]) -> 'ok'.
79 main(_CmdlineArgs) ->
80 ok = file:make_dir(?TEMP_SRC_DIR),
81 Copy =
82 fun(SrcFilename, ok) ->
83 Basename = filename:basename(SrcFilename),
84 DstFilename = ?TEMP_SRC_DIR ++ "/" ++ Basename,
85 {ok,_Bytes} = file:copy(SrcFilename, DstFilename),
86 ok
87 end,
88 ok = filelib:fold_files(?SRC_DIR, ?SRC_FILES_RE, false, Copy, ok),
89 Process = fun(Filename,ok) -> process(Filename) end,
90 ok = filelib:fold_files(?TEMP_SRC_DIR, ?SRC_FILES_RE, false, Process, ok),
91 ok = edoc:application(?APP_NAME, ?BASE_DIR, ?EDOC_OPTS),
92 Delete = fun(Filename,ok) -> file:delete(Filename) end,
93 ok = filelib:fold_files(?TEMP_SRC_DIR, ?SRC_FILES_RE, false, Delete, ok),
94 ok = file:del_dir(?TEMP_SRC_DIR),
95 ok.
96
97 -spec process(file:filename()) -> 'ok'.
98 process(Filename) ->
99 {ok,Forms} = epp:parse_file(Filename, [?INCLUDE_DIR], []),
100 Comments = erl_comment_scan:file(Filename),
101 {NewForms,NewComments} = process_forms(Forms, Comments),
102 Code = pretty_print(NewForms, NewComments),
103 {ok,IODev} = file:open(Filename, [write]),
104 ok = io:put_chars(IODev, Code),
105 ok = file:close(IODev),
106 ok.
107
108 -spec pretty_print([abs_form()], [erl_comment_scan:comment()]) -> charlist().
109 pretty_print(Forms, Comments) ->
110 FormsWithLines = add_lines_to_forms(Forms),
111 CommentsWithLines =
ca96110 @manopapad Complete EDoc docs, fix bug in make_doc, remove pure_check from API
authored
112 [{[Line],[["%",Str,"\n"] || Str <- Text] ++ "%@end\n"}
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
113 || {Line,_Col,_Ind,Text} <- Comments],
114 CodeWithLines = lists:keymerge(1, FormsWithLines, CommentsWithLines),
ca96110 @manopapad Complete EDoc docs, fix bug in make_doc, remove pure_check from API
authored
115 [S || {_L,S} <- CodeWithLines].
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
116
117 -spec add_lines_to_forms([abs_form()]) -> [charlist_with_lines()].
118 add_lines_to_forms(Forms) ->
119 add_lines_to_forms(Forms, [], {"",0}, []).
120
121 -spec add_lines_to_forms([abs_form()], [charlist_with_lines()],
122 {file:filename(),line()},
123 [{file:filename(),line()}]) ->
124 [charlist_with_lines()].
125 add_lines_to_forms([], Acc, _FilePos, _Stack) ->
126 lists:reverse(Acc);
127 add_lines_to_forms([Form|Rest], Acc, {FileName,_FileLine}, Stack) ->
128 case Form of
129 {attribute,Line,file,{NewFileName,_NewFileLine} = NewFilePos} ->
130 case NewFileName of
131 "" ->
132 %% TODO: What is the meaning of an empty file name?
133 %% TODO: Why is it causing problems?
134 add_lines_to_forms(Rest, Acc, {FileName,Line}, Stack);
135 FileName ->
136 %% TODO: Can this happen?
137 add_lines_to_forms(Rest, Acc, NewFilePos, Stack);
138 _ ->
139 NewStack =
140 case Stack of
141 [{NewFileName,_}|Bottom] ->
142 Bottom;
143 _ ->
144 [{FileName,Line}|Stack]
145 end,
146 add_lines_to_forms(Rest, Acc, NewFilePos, NewStack)
147 end;
148 {attribute,Line,record,_Fields} ->
149 add_lines_to_forms(Rest, Acc, {FileName,Line}, Stack);
150 _ ->
151 PrintedForm = print_form(Form),
152 Line = get_line_from_form(Form),
153 Lines = tl(lists:reverse([Line | [L || {_F,L} <- Stack]])),
154 add_lines_to_forms(Rest, [{Lines,PrintedForm}|Acc],
155 {FileName,Line}, Stack)
156 end.
157
158 -spec print_form(abs_form()) -> charlist().
159 print_form({attribute,_,type,{{record,Name},Fields,[]}}) ->
160 print_record_type(Name,Fields);
161 print_form(OtherForm) ->
162 erl_pp:form(OtherForm).
163
164 -spec print_record_type(rec_name(), [abs_rec_field()]) -> charlist().
165 print_record_type(Name, Fields) ->
166 ["-record(", atom_to_list(Name), ",{",
167 case Fields of
168 [] ->
169 [];
170 [Head|Rest] ->
171 [print_record_field(Head),
172 [[",",print_record_field(F)] || F <- Rest]]
173 end,
174 "}).\n"].
175
176 -spec print_record_field(abs_rec_field()) -> charlist().
177 print_record_field({record_field,_,{atom,_,Name}}) ->
178 atom_to_list(Name);
179 print_record_field({record_field,_,{atom,_,Name},Initialization}) ->
180 [atom_to_list(Name), $=, erl_pp:expr(Initialization,-1,none)];
181 print_record_field({typed_record_field,InnerField,FieldType}) ->
182 MyTypeDecl = {attribute,0,type,{mytype,FieldType,[]}},
183 PrintedMyType = lists:flatten(erl_pp:form(MyTypeDecl)),
184 PrintedFieldType =
185 lists:reverse(remove_from_head("\n.",
186 lists:reverse(remove_from_head("-typemytype()::", PrintedMyType)))),
187 [print_record_field(InnerField), "::", PrintedFieldType].
188
189 -spec remove_from_head(string(), string()) -> string().
190 remove_from_head([], Str) ->
191 Str;
192 remove_from_head(ToRemove, [32|StrRest]) ->
193 remove_from_head(ToRemove, StrRest);
194 remove_from_head([C|ToRemoveRest], [C|StrRest]) ->
195 remove_from_head(ToRemoveRest, StrRest).
196
197 -spec get_line_from_form(abs_form()) -> line().
198 get_line_from_form({attribute,Line,_Kind,_Value}) ->
199 Line;
200 get_line_from_form({function,Line,_Name,_Arity,_Clauses}) ->
201 Line;
202 get_line_from_form({eof,Line}) ->
203 Line.
204
205
206 %%------------------------------------------------------------------------------
207 %% Abstract code processing
208 %%------------------------------------------------------------------------------
209
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
210 -spec process_forms([abs_form(),...], [erl_comment_scan:comment()]) ->
211 {[abs_form(),...],[erl_comment_scan:comment()]}.
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
212 process_forms(Forms, Comments) ->
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
213 [FileAttr|Rest] = Forms,
214 {attribute,_Line,file,{TopFileName,_FileLine}} = FileAttr,
215 process_forms([FileAttr], Rest, [], Comments, [], TopFileName).
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
216
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
217 -spec process_forms([abs_form(),...], [abs_form()],
218 [erl_comment_scan:comment()], [erl_comment_scan:comment()],
219 [{type_name(),arity()}], file:filename()) ->
220 {[abs_form(),...],[erl_comment_scan:comment()]}.
221 process_forms(RevForms, Forms, RevComments, [], PrivTypes, _TopFileName) ->
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
222 NewForms = lists:reverse(RevForms) ++ Forms,
223 NewComments = lists:reverse(RevComments),
224 {remove_private_types(NewForms,PrivTypes), NewComments};
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
225 process_forms(RevForms, Forms, RevComments, [Comment|Rest], PrivTypes,
226 TopFileName) ->
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
227 {CommLine,_Column,_Indentation,Text} = Comment,
228 IsPrivate = contains_tag(Text, "@private_type"),
229 IsAlias = contains_tag(Text, "@alias"),
230 case IsPrivate orelse IsAlias of
231 true ->
232 {MaybeType,NewRevForms,NewForms} =
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
233 find_next_type(CommLine, RevForms, Forms, TopFileName),
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
234 case MaybeType of
235 error ->
236 process_forms(NewRevForms, NewForms, RevComments, Rest,
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
237 PrivTypes, TopFileName);
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
238 {TypeRef,TypeDef} ->
239 %% TODO: Also throw away alias type forms?
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
240 {FinalRevForms,FinalForms} =
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
241 case IsAlias of
242 true ->
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
243 {[replace(F,TypeRef,TypeDef)
244 || F <- NewRevForms],
245 [replace(F,TypeRef,TypeDef) || F <- NewForms]};
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
246 false ->
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
247 {NewRevForms,NewForms}
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
248 end,
249 NewPrivTypes =
250 case IsPrivate of
251 true ->
252 {type,Name,Arity} = TypeRef,
253 [{Name,Arity} | PrivTypes];
254 false ->
255 PrivTypes
256 end,
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
257 process_forms(FinalRevForms, FinalForms, RevComments, Rest,
258 NewPrivTypes, TopFileName)
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
259 end;
260 false ->
261 process_forms(RevForms, Forms, [Comment|RevComments], Rest,
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
262 PrivTypes, TopFileName)
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
263 end.
264
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
265 -spec find_next_type(line(), [abs_form()], [abs_form()], file:filename()) ->
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
266 {'error' | {type_ref(),type_def()}, [abs_form()], [abs_form()]}.
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
267 find_next_type(_CommLine, RevForms, [], _TopFileName) ->
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
268 {error, RevForms, []};
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
269 find_next_type(CommLine, RevForms, [Form|Rest] = Forms, TopFileName) ->
270 case Form of
271 {attribute,_AttrLine,file,_FilePos} ->
272 continue_after_header(CommLine, RevForms, Forms, TopFileName);
273 _ ->
274 case get_line_from_form(Form) =< CommLine of
275 true ->
276 find_next_type(CommLine, [Form|RevForms], Rest,
277 TopFileName);
278 false ->
279 case Form of
280 {attribute,_AttrLine,Kind,Value} when Kind =:= type
281 orelse Kind =:= opaque ->
282 {Name,TypeForm,VarForms} = Value,
283 case is_atom(Name) of
284 true ->
285 Arity = length(VarForms),
286 TypeRef = {type,Name,Arity},
287 TypeDef = {TypeForm,VarForms},
288 {{TypeRef,TypeDef}, RevForms, Forms};
289 false ->
290 {error, RevForms, Forms}
291 end;
292 _ ->
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
293 {error, RevForms, Forms}
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
294 end
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
295 end
296 end.
297
cd9fc15 @manopapad Fix some bugs in the make_doc script
authored
298 -spec continue_after_header(line(), [abs_form()], [abs_form(),...],
299 file:filename()) ->
300 {'error' | {type_ref(),type_def()}, [abs_form(),...], [abs_form()]}.
301 continue_after_header(CommLine, RevForms, [Form|Rest], TopFileName) ->
302 case Form of
303 {attribute,_AttrLine,file,{TopFileName,_TopFileLine}} ->
304 find_next_type(CommLine, [Form|RevForms], Rest, TopFileName);
305 _Other ->
306 continue_after_header(CommLine, [Form|RevForms], Rest, TopFileName)
307 end.
308
da5e6b3 @manopapad Hacked support for documentation tags @alias and @private_type
authored
309 -spec contains_tag([string()], string()) -> boolean().
310 contains_tag(Text, Tag) ->
311 StrContainsTag = fun(Str) -> string:str(Str,Tag) =/= 0 end,
312 lists:any(StrContainsTag, Text).
313
314 -spec replace(abs_form() | abs_type() | abs_rec_field() | abs_clause(),
315 var_form() | type_ref(), abs_type() | type_def()) ->
316 abs_form() | abs_type() | abs_rec_field() | abs_clause().
317 %% TODO: Should we update the source lines when inlining?
318 replace({attribute,Line,type,{{record,Name},Fields,[]}}, Alias, Value) ->
319 NewFields = [replace(Field,Alias,Value) || Field <- Fields],
320 {attribute, Line, type, {{record,Name},NewFields,[]}};
321 replace({attribute,Line,Kind,{Name,TypeForm,VarForms}}, Alias, Value)
322 when Kind =:= type orelse Kind =:= opaque ->
323 NewTypeForm = replace(TypeForm, Alias, Value),
324 {attribute, Line, Kind, {Name,NewTypeForm,VarForms}};
325 replace({attribute,Line,spec,{FunRef,Clauses}}, Alias, Value) ->
326 NewClauses = [replace(Clause,Alias,Value) || Clause <- Clauses],
327 {attribute, Line, spec, {FunRef,NewClauses}};
328 replace({typed_record_field,RecField,FieldType}, Alias, Value) ->
329 {typed_record_field, RecField, replace(FieldType,Alias,Value)};
330 replace({type,Line,bounded_fun,[MainClause,Constraints]}, Alias, Value) ->
331 ReplaceInConstraint =
332 fun({type,L,constraint,[ConstrKind,Args]}) ->
333 NewArgs = [replace(Arg,Alias,Value) || Arg <- Args],
334 {type, L, constraint, [ConstrKind,NewArgs]}
335 end,
336 NewConstraints = [ReplaceInConstraint(C) || C <- Constraints],
337 {type, Line, bounded_fun, [MainClause,NewConstraints]};
338 replace({var,_Line1,SameName}, {var,_Line2,SameName}, Value) ->
339 Value;
340 replace({Kind,Line,Args}, Alias, Value) when Kind =:= ann_type
341 orelse Kind =:= paren_type
342 orelse Kind =:= remote_type ->
343 NewArgs = [replace(Arg,Alias,Value) || Arg <- Args],
344 {Kind, Line, NewArgs};
345 replace(Type = {type,_Line,tuple,any}, _Alias, _Value) ->
346 Type;
347 replace({type,_Line,SameName,Args}, Alias = {type,SameName,Arity},
348 Value = {TypeForm,VarForms}) when length(Args) =:= Arity ->
349 FixedArgs = [replace(Arg,Alias,Value) || Arg <- Args],
350 ReplaceVar = fun({Var,Val},T) -> replace(T, Var, Val) end,
351 lists:foldl(ReplaceVar, TypeForm, lists:zip(VarForms,FixedArgs));
352 replace({type,Line,Name,Args}, Alias, Value) ->
353 NewArgs = [replace(Arg,Alias,Value) || Arg <- Args],
354 {type, Line, Name, NewArgs};
355 replace(Other, _Alias, _Value) ->
356 Other.
357
358 -spec remove_private_types([abs_form()], [{type_name(),arity()}]) ->
359 [abs_form()].
360 remove_private_types(Forms, PrivTypesList) ->
361 PrivTypesSet = sets:from_list(PrivTypesList),
362 [remove_from_exported(Form,PrivTypesSet) || Form <- Forms].
363
364 -spec remove_from_exported(abs_form(), set()) -> abs_form().
365 %% set({type_name(),arity()})
366 remove_from_exported({attribute,Line,export_type,TypesList}, PrivTypesSet) ->
367 IsNotPrivate = fun(T) -> not sets:is_element(T,PrivTypesSet) end,
368 {attribute, Line, export_type, lists:filter(IsNotPrivate,TypesList)};
369 remove_from_exported(OtherAttr, _PrivTypesSet) ->
370 OtherAttr.
371
372 %% -spec update_line(abs_type(), line()) -> abs_type().
373 %% update_line(Type, Line) ->
374 %% %% TODO: Is this function necessary?
375 %% %% TODO: Will this work with type declarations?
376 %% UpdateNodeLine = fun(Node) -> set_pos(Node, Line) end,
377 %% %% TODO: Is the 'revert' operation necessary?
378 %% erl_syntax:revert(erl_syntax_lib:map(UpdateNodeLine, Type)).
379 %% kate: syntax erlang;
Something went wrong with that request. Please try again.