Skip to content

HTTPS clone URL

Subversion checkout URL

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