1%%-------------------------------------------------------------------- 2%% 3%% %CopyrightBegin% 4%% 5%% Copyright Ericsson AB 2009-2020. All Rights Reserved. 6%% 7%% Licensed under the Apache License, Version 2.0 (the "License"); 8%% you may not use this file except in compliance with the License. 9%% You may obtain a copy of the License at 10%% 11%% http://www.apache.org/licenses/LICENSE-2.0 12%% 13%% Unless required by applicable law or agreed to in writing, software 14%% distributed under the License is distributed on an "AS IS" BASIS, 15%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16%% See the License for the specific language governing permissions and 17%% limitations under the License. 18%% 19%% %CopyrightEnd% 20%% 21%%----------------------------------------------------------------- 22%% File: erl_html_tools.erl 23%% 24%% Description: 25%% This file generates the top index of the documentation. 26%% 27%%----------------------------------------------------------------- 28-module(erl_html_tools). 29 30-export([top_index/0,top_index/1,top_index/4,top_index_silent/3]). 31 32-include_lib("kernel/include/file.hrl"). 33 34group_order() -> 35 [ 36 {basic, "Basic"}, 37 {dat, "Database"}, 38 {oam, "Operation & Maintenance"}, 39 {comm, "Interface and Communication"}, 40 {tools, "Tools"}, 41 {test, "Test"}, 42 {doc, "Documentation"}, 43 {orb, "Object Request Broker & IDL"}, 44 {misc, "Miscellaneous"}, 45 {eric, "Ericsson Internal"} 46 ]. 47 48top_index() -> 49 case os:getenv("ERL_TOP") of 50 false -> 51 io:format("Variable ERL_TOP is required\n",[]); 52 Value -> 53 {_,RelName} = init:script_id(), 54 top_index(src, Value, filename:join(Value, "doc"), RelName) 55 end. 56 57top_index([src, RootDir, DestDir, OtpBaseVsn]) 58 when is_atom(RootDir), is_atom(DestDir), is_atom(OtpBaseVsn) -> 59 top_index(src, atom_to_list(RootDir), atom_to_list(DestDir), atom_to_list(OtpBaseVsn)); 60top_index([rel, RootDir, DestDir, OtpBaseVsn]) 61 when is_atom(RootDir), is_atom(DestDir), is_atom(OtpBaseVsn) -> 62 top_index(rel, atom_to_list(RootDir), atom_to_list(DestDir), atom_to_list(OtpBaseVsn)); 63top_index(RootDir) when is_atom(RootDir) -> 64 {_,RelName} = init:script_id(), 65 top_index(rel, RootDir, filename:join(RootDir, "doc"), RelName). 66 67 68 69top_index(Source, RootDir, DestDir, OtpBaseVsn) -> 70 report("****\nRootDir: ~p", [RootDir]), 71 report("****\nDestDir: ~p", [DestDir]), 72 report("****\nOtpBaseVsn: ~p", [OtpBaseVsn]), 73 74 put(otp_base_vsn, OtpBaseVsn), 75 76 Templates = find_templates(["","templates",DestDir]), 77 report("****\nTemplates: ~p", [Templates]), 78 Bases = [{"../lib/", filename:join(RootDir,"lib")}, 79 {"../", RootDir}], 80 Groups = find_information(Source, Bases), 81 report("****\nGroups: ~p", [Groups]), 82 process_templates(Templates, DestDir, Groups). 83 84top_index_silent(RootDir, DestDir, OtpBaseVsn) -> 85 put(silent,true), 86 Result = top_index(rel, RootDir, DestDir, OtpBaseVsn), 87 erase(silent), 88 Result. 89 90 91 92 93%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 94% Main loop - process templates 95%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 96 97process_templates([], _DestDir, _Groups) -> 98 report("\n", []); 99process_templates([Template | Templates], DestDir, Groups) -> 100 report("****\nIN-FILE: ~s", [Template]), 101 BaseName = filename:basename(Template, ".src"), 102 case lists:reverse(filename:rootname(BaseName)) of 103 "_"++_ -> 104 %% One template expands to several output files. 105 process_multi_template(BaseName, Template, DestDir, Groups); 106 _ -> 107 %% Standard one-to-one template. 108 OutFile = filename:join(DestDir, BaseName), 109 subst_file("", OutFile, Template, Groups) 110 end, 111 process_templates(Templates, DestDir, Groups). 112 113 114process_multi_template(BaseName0, Template, DestDir, Info) -> 115 Ext = filename:extension(BaseName0), 116 BaseName1 = filename:basename(BaseName0, Ext), 117 [_|BaseName2] = lists:reverse(BaseName1), 118 BaseName = lists:reverse(BaseName2), 119 Groups0 = [{[$_|atom_to_list(G)],G} || {G, _} <- group_order()], 120 Groups = [{"",basic}|Groups0], 121 process_multi_template_1(Groups, BaseName, Ext, Template, DestDir, Info). 122 123process_multi_template_1([{Suffix,Group}|Gs], BaseName, Ext, Template, DestDir, Info) -> 124 OutFile = filename:join(DestDir, BaseName++Suffix++Ext), 125 subst_file(Group, OutFile, Template, Info), 126 process_multi_template_1(Gs, BaseName, Ext, Template, DestDir, Info); 127process_multi_template_1([], _, _, _, _, _) -> ok. 128 129subst_file(Group, OutFile, Template, Info) -> 130 report("\nOUTFILE: ~s", [OutFile]), 131 case subst_template(Group, Template, Info) of 132 {ok,Text,_NewInfo} -> 133 case file:open(OutFile, [write]) of 134 {ok, Stream} -> 135 file:write(Stream, Text), 136 file:close(Stream); 137 Error -> 138 local_error("Can't write to file ~s: ~w", [OutFile,Error]) 139 end; 140 Error -> 141 local_error("Can't write to file ~s: ~w", [OutFile,Error]) 142 end. 143 144 145%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 146% Find the templates 147%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 148 149find_templates(SearchPaths) -> 150 find_templates(SearchPaths, SearchPaths). 151 152find_templates([SearchPath | SearchPaths], AllSearchPaths) -> 153 case filelib:wildcard(filename:join(SearchPath, "*.html.src")) of 154 [] -> 155 find_templates(SearchPaths, AllSearchPaths); 156 Result -> 157 Result 158 end; 159find_templates([], AllSearchPaths) -> 160 local_error("No templates found in ~p",[AllSearchPaths]). 161 162 163%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 164% This function read all application names and if present all "info" files. 165%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 166 167find_information(Source, Bases) -> 168 Paths = find_application_paths(Source, Bases), 169% report("****\nPaths: ~p", [Paths]), 170 Apps = find_application_infos(Paths), 171% report("****\nApps: ~p", [Apps]), 172 form_groups(Apps). 173 174% The input is a list of tuples of the form 175% 176% IN: [{BaseURL,SearchDir}, ...] 177% 178% and the output is a list 179% 180% OUT: [{Appname,AppVersion,AppPath,IndexUTL}, ...] 181% 182% We know URL ends in a slash. 183 184find_application_paths(_, []) -> 185 []; 186find_application_paths(Source, [{URL, Dir} | Paths]) -> 187 188 AppDirs = get_app_dirs(Dir), 189 AppPaths = get_app_paths(Source, AppDirs, URL), 190 AppPaths ++ find_application_paths(Source, Paths). 191 192 193get_app_paths(src, AppDirs, URL) -> 194 Sub1 = "doc/html/index.html", 195%% Sub2 = "doc/index.html", 196 lists:map( 197 fun({App, AppPath}) -> 198 VsnFile = filename:join(AppPath, "vsn.mk"), 199 VsnStr = 200 case file:read_file(VsnFile) of 201 {ok, Bin} -> 202 case re:run(Bin, ".*VSN\s*=\s*([0-9\.]+).*",[{capture,[1],list}]) of 203 {match, [V]} -> 204 V; 205 nomatch -> 206 exit(io_lib:format("No VSN variable found in ~s\n", 207 [VsnFile])) 208 end; 209 {error, Reason} -> 210 exit(io_lib:format("~p : ~s\n", [Reason, VsnFile])) 211 end, 212 AppURL = URL ++ App ++ "-" ++ VsnStr, 213 {App, VsnStr, AppPath, AppURL ++ "/" ++ Sub1} 214 end, AppDirs); 215get_app_paths(rel, AppDirs, URL) -> 216 Sub1 = "doc/html/index.html", 217%% Sub2 = "doc/index.html", 218 lists:map( 219 fun({App, AppPath}) -> 220 [AppName, VsnStr] = string:tokens(App, "-"), 221 AppURL = URL ++ App, 222 {AppName, VsnStr, AppPath, AppURL ++ "/" ++ Sub1} 223 end, AppDirs). 224 225 226get_app_dirs(Dir) -> 227 {ok, Files} = file:list_dir(Dir), 228 AFiles = 229 lists:map(fun(File) -> {File, filename:join([Dir, File])} end, Files), 230 lists:zf(fun is_app_with_doc/1, AFiles). 231 232is_app_with_doc({"." ++ _ADir, _APath}) -> 233 false; 234is_app_with_doc({ADir, APath}) -> 235 case file:read_file_info(filename:join([APath, "info"])) of 236 {ok, _FileInfo} -> 237 {true, {ADir, APath}}; 238 _ -> 239 false 240 end. 241 242%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 243% Find info for one application. 244% Read the "info" file for each application. Look at "group" and "short". 245% key words. 246%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 247 248% IN: [{Appname,AppVersion,AppPath,IndexUTL}, ...] 249% OUT: [{Group,Heading,[{AppName,[{AppVersion,Path,URL,Text} | ...]} 250% | ...]}, ...] 251 252find_application_infos([]) -> 253 []; 254find_application_infos([{App, Vsn, AppPath, IndexURL} | Paths]) -> 255 case read_info(filename:join(AppPath,"info")) of 256 {error,_Reason} -> 257 warning("No info for app ~p", [AppPath]), 258 find_application_infos(Paths); 259 Db -> 260 {Group,_Heading} = 261 case lists:keysearch("group", 1, Db) of 262 {value, {_, G0}} -> 263 % This value may be in two parts, 264 % tag and desciption 265 case string:str(G0, " ") of 266 0 -> 267 {list_to_atom(G0), ""}; 268 N -> 269 {list_to_atom(string:substr(G0,1,N-1)), 270 string:substr(G0,N+1)} 271 end; 272 false -> 273 local_error("No group given",[]) 274 end, 275 Text = 276 case lists:keysearch("short", 1, Db) of 277 {value, {_, G1}} -> 278 G1; 279 false -> 280 "" 281 end, 282%% [{Group, Heading, {App, {Vsn, AppPath, IndexURL, Text}}} 283 [{Group, "", {App, {Vsn, AppPath, IndexURL, Text}}} 284 | find_application_infos(Paths)] 285 end. 286 287%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 288% Group into one list element for each group name. 289%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 290 291% IN : {Group,Heading,{AppName,{AppVersion,Path,URL,Text}}} 292% OUT: {Group,Heading,[{AppName,[{AppVersion,Path,URL,Text} | ...]} | ...]} 293 294form_groups(Apps) -> 295 group_apps(lists:sort(Apps)). 296 297group_apps([{Group,Heading,AppInfo} | Info]) -> 298 group_apps(Info, Group, Heading, [AppInfo]); 299group_apps([]) -> 300 []. 301 302% First description 303group_apps([{Group,"",AppInfo} | Info], Group, Heading, AppInfos) -> 304 group_apps(Info, Group, Heading, [AppInfo | AppInfos]); 305group_apps([{Group,Heading,AppInfo} | Info], Group, "", AppInfos) -> 306 group_apps(Info, Group, Heading, [AppInfo | AppInfos]); 307% Exact match 308group_apps([{Group,Heading,AppInfo} | Info], Group, Heading, AppInfos) -> 309 group_apps(Info, Group, Heading, [AppInfo | AppInfos]); 310% Different descriptions 311group_apps([{Group,_OtherHeading,AppInfo} | Info], Group, Heading, AppInfos) -> 312 warning("Group ~w descriptions differ",[Group]), 313 group_apps(Info, Group, Heading, [AppInfo | AppInfos]); 314group_apps(Info, Group, Heading, AppInfos) -> 315 [{Group,Heading,combine_apps(AppInfos)} | group_apps(Info)]. 316 317%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 318% Group into one list element for each application name. 319%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 320 321% IN : {AppName,{AppVersion,Path,URL,Text}} 322% OUT: {AppName,[{AppVersion,Path,URL,Text} | ...]} 323 324combine_apps(Apps) -> 325 combine_apps(Apps,[],[]). 326 327combine_apps([{AppName,{Vsn1,Path1,URL1,Text1}}, 328 {AppName,{Vsn2,Path2,URL2,Text2}} | Apps], AppAcc, Acc) -> 329 combine_apps([{AppName,{Vsn2,Path2,URL2,Text2}} | Apps], 330 [{Vsn1,Path1,URL1,Text1} | AppAcc], 331 Acc); 332combine_apps([{AppName,{Vsn1,Path1,URL1,Text1}}, 333 {NewAppName,{Vsn2,Path2,URL2,Text2}} | Apps], AppAcc, Acc) -> 334 App = lists:sort(fun vsncmp/2,[{Vsn1,Path1,URL1,Text1}|AppAcc]), 335 combine_apps([{NewAppName,{Vsn2,Path2,URL2,Text2}} | Apps], 336 [], 337 [{AppName,App}|Acc]); 338combine_apps([{AppName,{Vsn,Path,URL,Text}}], AppAcc, Acc) -> 339 App = lists:sort(fun vsncmp/2,[{Vsn,Path,URL,Text}|AppAcc]), 340 [{AppName,App}|Acc]. 341 342 343%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 344% Open a template and fill in the missing parts 345%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 346 347% IN : {Group,Heading,[{AppName,[{AppVersion,Path,URL,Text} | ...]} | ...]} 348% OUT: String that is the HTML code 349 350subst_template(Group, File, Info) -> 351 case file:open(File, read) of 352 {ok,Stream} -> 353 Res = subst_template_1(Group, Stream, Info), 354 file:close(Stream), 355 Res; 356 {error,Reason} -> 357 {error, Reason} 358 end. 359 360subst_template_1(Group, Stream, Info) -> 361 case file:read(Stream, 100000) of 362 {ok, Template} -> 363 Fun = fun(Match, _) -> {subst(Match, Info, Group),Info} end, 364 gsub(Template, "#[A-Za-z_0-9]+#", Fun, Info); 365 {error, Reason} -> 366 {error, Reason} 367 end. 368 369get_version(Info) -> 370 case lists:keysearch('runtime', 1, Info) of 371 {value, {_,_,Apps}} -> 372 case lists:keysearch("erts", 1, Apps) of 373 {value, {_,[{Vers,_,_,_} | _]}} -> 374 Vers; 375 _ -> 376 "" 377 end; 378 _ -> 379 "" 380 end. 381 382subst("#otp_base_vsn#", _Info, _Group) -> 383 get(otp_base_vsn); 384subst("#version#", Info, _Group) -> 385 get_version(Info); 386subst("#copyrightyear#", _Info, _Group) -> 387 {Year,_,_} = erlang:date(), 388 integer_to_list(Year); 389subst("#copyright#", _Info, _Group) -> 390 "copyright Copyright © 1991-2004"; 391subst("#groups#", Info, _Group) -> 392 [ 393 subst_groups(Info) 394 ]; 395subst("#applinks#", Info, Group) -> 396 subst_applinks(Info, Group); 397subst(KeyWord, Info, _Group) -> 398 case search_appname(KeyWord -- "##", Info) of 399 {ok,URL} -> 400 URL; 401 _ -> 402 warning("Can't substitute keyword ~s~n",[KeyWord]), 403 "" 404 end. 405 406search_appname(App, [{_Group,_,Apps} | Groups]) -> 407 case lists:keysearch(App, 1, Apps) of 408 {value, {_,[{_Vers,_Path,URL,_Text} | _]}} -> 409 {ok,lists:sublist(URL, length(URL) - length("/index.html"))}; 410 _ -> 411 search_appname(App, Groups) 412 end; 413search_appname(_App, []) -> 414 {error, noapp}. 415 416subst_applinks(Info, Group) -> 417 subst_applinks_1(group_order(), Info, Group). 418 419subst_applinks_1([{G, Heading}|Gs], Info0, Group) -> 420 case lists:keysearch(G, 1, Info0) of 421 {value,{G,_Heading,Apps}} -> 422 Info = lists:keydelete(G, 1, Info0), 423 ["\n<li>",Heading, "\n<ul>\n", 424 html_applinks(Apps), "\n</ul></li>\n"| 425 subst_applinks_1(Gs, Info, Group)]; 426 false -> 427 warning("No applications in group ~w\n", [G]), 428 subst_applinks_1(Gs, Info0, Group) 429 end; 430subst_applinks_1([], [], _) -> []; 431subst_applinks_1([], Info, _) -> 432 local_error("Info left: ~p\n", [Info]), 433 []. 434 435html_applinks([{Name,[{_,_,URL,_}|_]}|AppNames]) -> 436 ["<li><a href=\"",URL,"\">",Name, 437 "</a></li>\n"|html_applinks(AppNames)]; 438html_applinks([]) -> []. 439 440 441% Info: [{Group,Heading,[{AppName,[{AppVersion,Path,URL,Text} | ..]} | ..]} ..] 442 443subst_groups(Info0) -> 444 {Html1,Info1} = subst_known_groups(group_order(), Info0, ""), 445 {Html2,Info} = subst_unknown_groups(Info1, Html1, []), 446 Fun = fun({_Group,_GText,Applist}, Acc) -> Applist ++ Acc end, 447 case lists:foldl(Fun, [], Info) of 448 [] -> 449 Html2; 450 Apps -> 451 [Html2,group_table("Misc Applications",Apps)] 452 end. 453 454 455subst_known_groups([], Info, Text) -> 456 {Text,Info}; 457subst_known_groups([{Group, Heading} | Groups], Info0, Text0) -> 458 case lists:keysearch(Group, 1, Info0) of 459 {value,{_,_Heading,Apps}} -> 460 Text = group_table(Heading,Apps), 461 Info = lists:keydelete(Group, 1, Info0), 462 subst_known_groups(Groups, Info, Text0 ++ Text); 463 false -> 464 warning("No applications in group ~w~n",[Group]), 465 subst_known_groups(Groups, Info0, Text0) 466 end. 467 468 469subst_unknown_groups([], Text0, Left) -> 470 {Text0,Left}; 471subst_unknown_groups([{Group,"",Apps} | Groups], Text0, Left) -> 472 warning("No text describes ~w",[Group]), 473 subst_unknown_groups(Groups, Text0, [{Group,"",Apps} | Left]); 474subst_unknown_groups([{_Group,Heading,Apps} | Groups], Text0, Left) -> 475 Text = group_table(Heading,Apps), 476 subst_unknown_groups(Groups, Text0 ++ Text, Left). 477 478 479group_table(Heading,Apps) -> 480 ["<h2>",Heading,"</h2>", 481 "<table class=\"group-table\">\n", 482 subst_apps(Apps), 483 "</table>\n" 484 ]. 485 486% Count and split the applications in half to get the right sort 487% order in the table. 488 489subst_apps([{App,VersionInfo} | Apps]) -> 490 [subst_app(App, VersionInfo) | subst_apps(Apps)]; 491subst_apps([]) -> 492 []. 493 494 495subst_app(App, [{VSN,_Path,Link,Text}]) -> 496 [ 497 " <tr class=app>\n", 498 " <td>\n", 499 " <a href=\"",Link,"\" target=\"_top\">",uc(App),"</a>\n", 500 " <a href=\"",Link,"\" target=\"_top\">",VSN,"</a>\n", 501 " </td>\n", 502 " <td>\n", 503 Text,"\n", 504 " </td>\n", 505 " </tr>\n" 506 ]; 507subst_app(App, [{VSN,_Path,Link,Text} | VerInfos]) -> 508 [ 509 " <tr class=app>\n", 510 " <td>\n", 511 " <a href=\"",Link,"\" target=\"_top\">",uc(App), 512 "</a>\n", 513 " <a href=\"",Link,"\" target=\"_top\">",VSN,"</a>\n", 514 " <br/>\n", 515 subst_vsn(VerInfos), 516 " </td>\n", 517 " <td>\n", 518 Text,"\n", 519 " </td>\n", 520 " </tr>\n" 521 ]. 522 523 524subst_vsn([{VSN,_Path,Link,_Text} | VSNs]) -> 525 [ 526 " <font size=\"2\"><a class=anum href=\"",Link,"\" target=\"_top\">", 527 VSN, 528 "</a></font><br/>\n", 529 subst_vsn(VSNs) 530 ]; 531subst_vsn([]) -> 532 "". 533 534 535% Yes, this is very inefficient an is done for every comarision 536% in the sort but it doesn't matter in this case. 537 538vsncmp({Vsn1,_,_,_}, {Vsn2,_,_,_}) -> 539 L1 = [list_to_integer(N1) || N1 <- string:tokens(Vsn1, ".")], 540 L2 = [list_to_integer(N2) || N2 <- string:tokens(Vsn2, ".")], 541 L1 > L2. 542 543 544%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 545%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 546% 547% GENERIC FUNCTIONS, NOT SPECIFIC FOR GENERATING INDEX.HTML 548% 549%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 550%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 551 552%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 553% Read the "info" file into a list of Key/Value pairs 554%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 555 556read_info(File) -> 557 case file:open(File, read) of 558 {ok,Stream} -> 559 Res = 560 case file:read(Stream,10000) of 561 {ok, Text} -> 562 Lines = string:tokens(Text, "\n\r"), 563 KeyValues0 = lines_to_key_value(Lines), 564 combine_key_value(KeyValues0); 565 {error, Reason} -> 566 {error, Reason} 567 end, 568 file:close(Stream), 569 Res; 570 {error,Reason} -> 571 {error,Reason} 572 end. 573 574combine_key_value([{Key,Value1},{Key,Value2} | KeyValues]) -> 575 combine_key_value([{Key,Value1 ++ "\n" ++ Value2} | KeyValues]); 576combine_key_value([KeyValue | KeyValues]) -> 577 [KeyValue | combine_key_value(KeyValues)]; 578combine_key_value([]) -> 579 []. 580 581lines_to_key_value([]) -> 582 []; 583lines_to_key_value([Line | Lines]) -> 584 case re:run(Line, "^[a-zA-Z_\\-]+:") of 585 nomatch -> 586 case re:run(Line, "[\041-\377]") of 587 nomatch -> 588 lines_to_key_value(Lines); 589 _ -> 590 warning("skipping line \"~s\"",[Line]), 591 lines_to_key_value(Lines) 592 end; 593 {match, [{0, Length} |_]} -> 594 Value0 = lists:sublist(Line, Length+1, length(Line) - Length), 595 Value1 = re:replace(Value0, "^[ \t]*", "", 596 [{return, list}]), 597 Value = re:replace(Value1, "[ \t]*$", "", 598 [{return, list}]), 599 Key = lists:sublist(Line, Length-1), 600 [{Key, Value} | lines_to_key_value(Lines)] 601 end. 602 603%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 604% Regular expression helpers. 605%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 606 607%% -type gsub(String, RegExp, Fun, Acc) -> subres(). 608%% Substitute every match of the regular expression RegExp with the 609%% string returned from the function Fun(Match, Acc). Accept pre-parsed 610%% regular expressions. Acc is an argument to the Fun. The Fun should return 611%% a tuple {Replacement, NewAcc}. 612 613gsub(String, RegExp, Fun, Acc) when is_list(RegExp) -> 614 case re:compile(RegExp) of 615 {ok, RE} -> 616 gsub(String, RE, Fun, Acc); 617 {error, E} -> 618 {error, E} 619 end; 620gsub(String, RE, Fun, Acc) -> 621 {match, Ss} = re:run(String, RE, [global]), 622 {NewString, NewAcc} = sub_repl(Ss, Fun, Acc, String, 0), 623 {ok, NewString, NewAcc}. 624 625 626% New code that uses fun for finding the replacement. Also uses accumulator 627% to pass argument between the calls to the fun. 628sub_repl([[{St, L}] |Ss], Fun, Acc0, S, Pos) -> 629 Match = string:substr(S, St+1, L), 630 {Rep, Acc} = Fun(Match, Acc0), 631 {Rs, NewAcc} = sub_repl(Ss, Fun, Acc, S, St+L), 632 {string:substr(S, Pos+1, St-Pos) ++ Rep ++ Rs, NewAcc}; 633sub_repl([], _Fun, Acc, S, Pos) -> {string:substr(S, Pos+1), Acc}. 634 635 636 637%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 638% Error and warnings 639%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 640 641local_error(Format, Args) -> 642 io:format("ERROR: " ++ Format ++ "\n", Args), 643 exit(1). 644 645warning(Format, Args) -> 646 case get(silent) of 647 true -> ok; 648 _ -> io:format("WARNING: " ++ Format ++ "\n", Args) 649 end. 650 651report(Format, Args) -> 652 case get(silent) of 653 true -> ok; 654 _ -> io:format(Format ++ "\n", Args) 655 end. 656 657 658%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 659% Extensions to the 'string' module. 660%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 661 662uc(String) -> 663 lists:reverse(uc(String, [])). 664 665uc([], Acc) -> 666 Acc; 667uc([H | T], Acc) when is_integer(H), [97] =< H, H =< $z -> 668 uc(T, [H - 32 | Acc]); 669uc([H | T], Acc) -> 670 uc(T, [H | Acc]). 671 672