diff options
Diffstat (limited to 'system/doc/top/src/erl_html_tools.erl')
-rw-r--r--[l---------] | system/doc/top/src/erl_html_tools.erl | 685 |
1 files changed, 684 insertions, 1 deletions
diff --git a/system/doc/top/src/erl_html_tools.erl b/system/doc/top/src/erl_html_tools.erl index 35a199b08d..8a5c744128 120000..100644 --- a/system/doc/top/src/erl_html_tools.erl +++ b/system/doc/top/src/erl_html_tools.erl @@ -1 +1,684 @@ -../../../../internal_tools/integration/scripts/make_index/erl_html_tools.erl
\ No newline at end of file +%%-------------------------------------------------------------------- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%----------------------------------------------------------------- +%% File: erl_html_tools.erl +%% +%% Description: +%% This file generates the top index of the documentation. +%% +%%----------------------------------------------------------------- +-module(erl_html_tools). + +-export([top_index/0,top_index/1,top_index/3,top_index_silent/3]). + +-include_lib("kernel/include/file.hrl"). + +group_order() -> + [ + {basic, "Basic"}, + {dat, "Database"}, + {oam, "Operation & Maintenance"}, + {comm, "Interface and Communication"}, + {tools, "Tools"}, + {test, "Test"}, + {doc, "Documentation"}, + {orb, "Object Request Broker & IDL"}, + {misc, "Miscellaneous"} + ]. + +top_index() -> + case os:getenv("ERL_TOP") of + false -> + io:format("Variable ERL_TOP is required\n",[]); + Value -> + {_,RelName} = init:script_id(), + top_index(Value, filename:join(Value, "doc"), RelName) + end. + +top_index([RootDir, DestDir, OtpRel]) + when is_atom(RootDir), is_atom(DestDir), is_atom(OtpRel) -> + top_index(atom_to_list(RootDir), atom_to_list(DestDir), atom_to_list(OtpRel)); +top_index(RootDir) when is_atom(RootDir) -> + {_,RelName} = init:script_id(), + top_index(RootDir, filename:join(RootDir, "doc"), RelName). + + + +top_index(RootDir, DestDir, OtpRel) -> + report("****\nRootDir: ~p", [RootDir]), + report("****\nDestDir: ~p", [DestDir]), + report("****\nOtpRel: ~p", [OtpRel]), + + put(otp_release, OtpRel), + + Templates = find_templates(["","templates",DestDir]), + report("****\nTemplates: ~p", [Templates]), + Bases = [{"../lib/", filename:join(RootDir,"lib")}, + {"../", RootDir}], + Groups = find_information(Bases), + report("****\nGroups: ~p", [Groups]), + process_templates(Templates, DestDir, Groups). + +top_index_silent(RootDir, DestDir, OtpRel) -> + put(silent,true), + Result = top_index(RootDir, DestDir, OtpRel), + erase(silent), + Result. + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Main loop - process templates +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +process_templates([], _DestDir, _Groups) -> + report("\n", []); +process_templates([Template | Templates], DestDir, Groups) -> + report("****\nIN-FILE: ~s", [Template]), + BaseName = filename:basename(Template, ".src"), + case lists:reverse(filename:rootname(BaseName)) of + "_"++_ -> + %% One template expands to several output files. + process_multi_template(BaseName, Template, DestDir, Groups); + _ -> + %% Standard one-to-one template. + OutFile = filename:join(DestDir, BaseName), + subst_file("", OutFile, Template, Groups) + end, + process_templates(Templates, DestDir, Groups). + + +process_multi_template(BaseName0, Template, DestDir, Info) -> + Ext = filename:extension(BaseName0), + BaseName1 = filename:basename(BaseName0, Ext), + [_|BaseName2] = lists:reverse(BaseName1), + BaseName = lists:reverse(BaseName2), + Groups0 = [{[$_|atom_to_list(G)],G} || {G, _} <- group_order()], + Groups = [{"",basic}|Groups0], + process_multi_template_1(Groups, BaseName, Ext, Template, DestDir, Info). + +process_multi_template_1([{Suffix,Group}|Gs], BaseName, Ext, Template, DestDir, Info) -> + OutFile = filename:join(DestDir, BaseName++Suffix++Ext), + subst_file(Group, OutFile, Template, Info), + process_multi_template_1(Gs, BaseName, Ext, Template, DestDir, Info); +process_multi_template_1([], _, _, _, _, _) -> ok. + +subst_file(Group, OutFile, Template, Info) -> + report("\nOUTFILE: ~s", [OutFile]), + case subst_template(Group, Template, Info) of + {ok,Text,_NewInfo} -> + case file:open(OutFile, [write]) of + {ok, Stream} -> + file:write(Stream, Text), + file:close(Stream); + Error -> + error("Can't write to file ~s: ~w", [OutFile,Error]) + end; + Error -> + error("Can't write to file ~s: ~w", [OutFile,Error]) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Find the templates +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +find_templates(SearchPaths) -> + find_templates(SearchPaths, SearchPaths). + +find_templates([SearchPath | SearchPaths], AllSearchPaths) -> + case filelib:wildcard(filename:join(SearchPath, "*.html.src")) of + [] -> + find_templates(SearchPaths, AllSearchPaths); + Result -> + Result + end; +find_templates([], AllSearchPaths) -> + error("No templates found in ~p",[AllSearchPaths]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% This function read all application names and if present all "info" files. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +find_information(Bases) -> + Paths = find_application_paths(Bases), +% report("****\nPaths: ~p", [Paths]), + Apps = find_application_infos(Paths), +% report("****\nApps: ~p", [Apps]), + form_groups(Apps). + +% The input is a list of tuples of the form +% +% IN: [{BaseURL,SearchDir}, ...] +% +% and the output is a list +% +% OUT: [{Appname,AppVersion,AppPath,IndexUTL}, ...] +% +% We know URL ends in a slash. + +find_application_paths([]) -> + []; +find_application_paths([{URL, Dir} | Paths]) -> + Sub1 = "doc/html/index.html", +%% Sub2 = "doc/index.html", + + AppDirs = get_app_dirs(Dir), + + AppPaths = + lists:map( + fun({App, AppPath}) -> + VsnFile = filename:join(AppPath, "vsn.mk"), + VsnStr = + case file:read_file(VsnFile) of + {ok, Bin} -> + case re:run(Bin, ".*VSN\s*=\s*([0-9\.]+).*",[{capture,[1],list}]) of + {match, [V]} -> + V; + nomatch -> + exit(io_lib:format("No VSN variable found in ~s\n", + [VsnFile])) + end; + {error, Reason} -> + exit(io_lib:format("~p : ~s\n", [Reason, VsnFile])) + end, + AppURL = URL ++ App ++ "-" ++ VsnStr, + {App, VsnStr, AppPath, AppURL ++ "/" ++ Sub1} + end, AppDirs), + AppPaths ++ find_application_paths(Paths). + +get_app_dirs(Dir) -> + {ok, Files} = file:list_dir(Dir), + AFiles = + lists:map(fun(File) -> {File, filename:join([Dir, File])} end, Files), + lists:zf(fun is_app_with_doc/1, AFiles). + +is_app_with_doc({ADir, APath}) -> + case file:read_file_info(filename:join([APath, "info"])) of + {ok, _FileInfo} -> + {true, {ADir, APath}}; + _ -> + false + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Find info for one application. +% Read the "info" file for each application. Look at "group" and "short". +% key words. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% IN: [{Appname,AppVersion,AppPath,IndexUTL}, ...] +% OUT: [{Group,Heading,[{AppName,[{AppVersion,Path,URL,Text} | ...]} +% | ...]}, ...] + +find_application_infos([]) -> + []; +find_application_infos([{App, Vsn, AppPath, IndexURL} | Paths]) -> + case read_info(filename:join(AppPath,"info")) of + {error,_Reason} -> + warning("No info for app ~p", [AppPath]), + find_application_infos(Paths); + Db -> + {Group,_Heading} = + case lists:keysearch("group", 1, Db) of + {value, {_, G0}} -> + % This value may be in two parts, + % tag and desciption + case string:str(G0, " ") of + 0 -> + {list_to_atom(G0), ""}; + N -> + {list_to_atom(string:substr(G0,1,N-1)), + string:substr(G0,N+1)} + end; + false -> + error("No group given",[]) + end, + Text = + case lists:keysearch("short", 1, Db) of + {value, {_, G1}} -> + G1; + false -> + "" + end, +%% [{Group, Heading, {App, {Vsn, AppPath, IndexURL, Text}}} + [{Group, "", {App, {Vsn, AppPath, IndexURL, Text}}} + | find_application_infos(Paths)] + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Group into one list element for each group name. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% IN : {Group,Heading,{AppName,{AppVersion,Path,URL,Text}}} +% OUT: {Group,Heading,[{AppName,[{AppVersion,Path,URL,Text} | ...]} | ...]} + +form_groups(Apps) -> + group_apps(lists:sort(Apps)). + +group_apps([{Group,Heading,AppInfo} | Info]) -> + group_apps(Info, Group, Heading, [AppInfo]); +group_apps([]) -> + []. + +% First description +group_apps([{Group,"",AppInfo} | Info], Group, Heading, AppInfos) -> + group_apps(Info, Group, Heading, [AppInfo | AppInfos]); +group_apps([{Group,Heading,AppInfo} | Info], Group, "", AppInfos) -> + group_apps(Info, Group, Heading, [AppInfo | AppInfos]); +% Exact match +group_apps([{Group,Heading,AppInfo} | Info], Group, Heading, AppInfos) -> + group_apps(Info, Group, Heading, [AppInfo | AppInfos]); +% Different descriptions +group_apps([{Group,_OtherHeading,AppInfo} | Info], Group, Heading, AppInfos) -> + warning("Group ~w descriptions differ",[Group]), + group_apps(Info, Group, Heading, [AppInfo | AppInfos]); +group_apps(Info, Group, Heading, AppInfos) -> + [{Group,Heading,combine_apps(AppInfos)} | group_apps(Info)]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Group into one list element for each application name. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% IN : {AppName,{AppVersion,Path,URL,Text}} +% OUT: {AppName,[{AppVersion,Path,URL,Text} | ...]} + +combine_apps(Apps) -> + combine_apps(Apps,[],[]). + +combine_apps([{AppName,{Vsn1,Path1,URL1,Text1}}, + {AppName,{Vsn2,Path2,URL2,Text2}} | Apps], AppAcc, Acc) -> + combine_apps([{AppName,{Vsn2,Path2,URL2,Text2}} | Apps], + [{Vsn1,Path1,URL1,Text1} | AppAcc], + Acc); +combine_apps([{AppName,{Vsn1,Path1,URL1,Text1}}, + {NewAppName,{Vsn2,Path2,URL2,Text2}} | Apps], AppAcc, Acc) -> + App = lists:sort(fun vsncmp/2,[{Vsn1,Path1,URL1,Text1}|AppAcc]), + combine_apps([{NewAppName,{Vsn2,Path2,URL2,Text2}} | Apps], + [], + [{AppName,App}|Acc]); +combine_apps([{AppName,{Vsn,Path,URL,Text}}], AppAcc, Acc) -> + App = lists:sort(fun vsncmp/2,[{Vsn,Path,URL,Text}|AppAcc]), + [{AppName,App}|Acc]. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Open a template and fill in the missing parts +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% IN : {Group,Heading,[{AppName,[{AppVersion,Path,URL,Text} | ...]} | ...]} +% OUT: String that is the HTML code + +subst_template(Group, File, Info) -> + case file:open(File, read) of + {ok,Stream} -> + Res = subst_template_1(Group, Stream, Info), + file:close(Stream), + Res; + {error,Reason} -> + {error, Reason} + end. + +subst_template_1(Group, Stream, Info) -> + case file:read(Stream, 100000) of + {ok, Template} -> + Fun = fun(Match, _) -> {subst(Match, Info, Group),Info} end, + gsub(Template, "#[A-Za-z0-9]+#", Fun, Info); + {error, Reason} -> + {error, Reason} + end. + +get_version(Info) -> + case lists:keysearch('runtime', 1, Info) of + {value, {_,_,Apps}} -> + case lists:keysearch("erts", 1, Apps) of + {value, {_,[{Vers,_,_,_} | _]}} -> + Vers; + _ -> + "" + end; + _ -> + "" + end. + +subst("#release#", _Info, _Group) -> + get(otp_release); +subst("#version#", Info, _Group) -> + get_version(Info); +subst("#copyright#", _Info, _Group) -> + "copyright Copyright © 1991-2004"; +subst("#groups#", Info, _Group) -> + [ + "<table border=0 width=\"90%\" cellspacing=3 cellpadding=5>\n", + subst_groups(Info), + "</table>\n" + ]; +subst("#applinks#", Info, Group) -> + subst_applinks(Info, Group); +subst(KeyWord, Info, _Group) -> + case search_appname(KeyWord -- "##", Info) of + {ok,URL} -> + URL; + _ -> + warning("Can't substitute keyword ~s~n",[KeyWord]), + "" + end. + +search_appname(App, [{_Group,_,Apps} | Groups]) -> + case lists:keysearch(App, 1, Apps) of + {value, {_,[{_Vers,_Path,URL,_Text} | _]}} -> + {ok,lists:sublist(URL, length(URL) - length("/index.html"))}; + _ -> + search_appname(App, Groups) + end; +search_appname(_App, []) -> + {error, noapp}. + +subst_applinks(Info, Group) -> + subst_applinks_1(group_order(), Info, Group). + +subst_applinks_1([{G, Heading}|Gs], Info0, Group) -> + case lists:keysearch(G, 1, Info0) of + {value,{G,_Heading,Apps}} -> + Info = lists:keydelete(G, 1, Info0), + ["\n<li>",Heading, "\n<ul>\n", + html_applinks(Apps), "\n</ul></li>\n"| + subst_applinks_1(Gs, Info, Group)]; + false -> + warning("No applications in group ~w\n", [G]), + subst_applinks_1(Gs, Info0, Group) + end; +subst_applinks_1([], [], _) -> []; +subst_applinks_1([], Info, _) -> + error("Info left: ~p\n", [Info]), + []. + +html_applinks([{Name,[{_,_,URL,_}|_]}|AppNames]) -> + ["<li><a href=\"",URL,"\">",Name, + "</a></li>\n"|html_applinks(AppNames)]; +html_applinks([]) -> []. + + +% Info: [{Group,Heading,[{AppName,[{AppVersion,Path,URL,Text} | ..]} | ..]} ..] + +subst_groups(Info0) -> + {Html1,Info1} = subst_known_groups(group_order(), Info0, ""), + {Html2,Info} = subst_unknown_groups(Info1, Html1, []), + Fun = fun({_Group,_GText,Applist}, Acc) -> Applist ++ Acc end, + case lists:foldl(Fun, [], Info) of + [] -> + Html2; + Apps -> + [Html2,group_table("Misc Applications",Apps)] + end. + + +subst_known_groups([], Info, Text) -> + {Text,Info}; +subst_known_groups([{Group, Heading} | Groups], Info0, Text0) -> + case lists:keysearch(Group, 1, Info0) of + {value,{_,_Heading,Apps}} -> + Text = group_table(Heading,Apps), + Info = lists:keydelete(Group, 1, Info0), + subst_known_groups(Groups, Info, Text0 ++ Text); + false -> + warning("No applications in group ~w~n",[Group]), + subst_known_groups(Groups, Info0, Text0) + end. + + +subst_unknown_groups([], Text0, Left) -> + {Text0,Left}; +subst_unknown_groups([{Group,"",Apps} | Groups], Text0, Left) -> + warning("No text describes ~w",[Group]), + subst_unknown_groups(Groups, Text0, [{Group,"",Apps} | Left]); +subst_unknown_groups([{_Group,Heading,Apps} | Groups], Text0, Left) -> + Text = group_table(Heading,Apps), + subst_unknown_groups(Groups, Text0 ++ Text, Left). + + +group_table(Heading,Apps) -> + [ + " <tr>\n", + " <td colspan=2 class=header>\n", + " <font size=\"+1\"><b>",Heading,"</b></font>\n", + " </td>\n", + " </tr>\n", + subst_apps(Apps), + " <tr>\n", + " <td colspan=2><font size=1> </font></td>\n", + " </tr>\n" + ]. + +% Count and split the applications in half to get the right sort +% order in the table. + +subst_apps([{App,VersionInfo} | Apps]) -> + [subst_app(App, VersionInfo) | subst_apps(Apps)]; +subst_apps([]) -> + []. + + +subst_app(App, [{VSN,_Path,Link,Text}]) -> + [ + " <tr class=app>\n", + " <td align=left valign=top>\n", + " <table border=0 width=\"100%\" cellspacing=0 cellpadding=0>\n", + " <tr class=app>\n", + " <td align=left valign=top>\n", + " <a href=\"",Link,"\" target=\"_top\">",uc(App),"</a>\n", + " <a href=\"",Link,"\" target=\"_top\">",VSN,"</a>\n", + " </td>\n", + " </tr>\n", + " </table>\n" + " </td>\n", + " <td align=left valign=top>\n", + Text,"\n", + " </td>\n", + " </tr>\n" + ]; +subst_app(App, [{VSN,_Path,Link,Text} | VerInfos]) -> + [ + " <tr class=app>\n", + " <td align=left valign=top>\n", + " <table border=0 width=\"100%\" cellspacing=0 cellpadding=0>\n", + " <tr class=app>\n", + " <td align=left valign=top>\n", + " <a href=\"",Link,"\" target=\"_top\">",uc(App), + "</a> <br>\n", + " <a href=\"",Link,"\" target=\"_top\">",VSN,"</a>\n", + " </td>\n", + " <td align=right valign=top width=50>\n", + " <table border=0 width=40 cellspacing=0 cellpadding=0>\n", + " <tr class=app>\n", + " <td align=left valign=top class=appnums>\n", + subst_vsn(VerInfos), + " </td>\n", + " </tr>\n", + " </table>\n" + " </td>\n", + " </tr>\n", + " </table>\n" + " </td>\n", + " <td align=left valign=top>\n", + Text,"\n", + " </td>\n", + " </tr>\n" + ]. + + +subst_vsn([{VSN,_Path,Link,_Text} | VSNs]) -> + [ + " <font size=\"2\"><a class=anum href=\"",Link,"\" target=\"_top\">", + VSN, + "</a></font><br>\n", + subst_vsn(VSNs) + ]; +subst_vsn([]) -> + "". + + +% Yes, this is very inefficient an is done for every comarision +% in the sort but it doesn't matter in this case. + +vsncmp({Vsn1,_,_,_}, {Vsn2,_,_,_}) -> + L1 = [list_to_integer(N1) || N1 <- string:tokens(Vsn1, ".")], + L2 = [list_to_integer(N2) || N2 <- string:tokens(Vsn2, ".")], + L1 > L2. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% GENERIC FUNCTIONS, NOT SPECIFIC FOR GENERATING INDEX.HTML +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Read the "info" file into a list of Key/Value pairs +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +read_info(File) -> + case file:open(File, read) of + {ok,Stream} -> + Res = + case file:read(Stream,10000) of + {ok, Text} -> + Lines = string:tokens(Text, "\n\r"), + KeyValues0 = lines_to_key_value(Lines), + combine_key_value(KeyValues0); + {error, Reason} -> + {error, Reason} + end, + file:close(Stream), + Res; + {error,Reason} -> + {error,Reason} + end. + +combine_key_value([{Key,Value1},{Key,Value2} | KeyValues]) -> + combine_key_value([{Key,Value1 ++ "\n" ++ Value2} | KeyValues]); +combine_key_value([KeyValue | KeyValues]) -> + [KeyValue | combine_key_value(KeyValues)]; +combine_key_value([]) -> + []. + +lines_to_key_value([]) -> + []; +lines_to_key_value([Line | Lines]) -> + case re:run(Line, "^[a-zA-Z_\\-]+:") of + nomatch -> + case re:run(Line, "[\041-\377]") of + nomatch -> + lines_to_key_value(Lines); + _ -> + warning("skipping line \"~s\"",[Line]), + lines_to_key_value(Lines) + end; + {match, [{0, Length} |_]} -> + Value0 = lists:sublist(Line, Length+1, length(Line) - Length), + Value1 = re:replace(Value0, "^[ \t]*", "", + [{return, list}]), + Value = re:replace(Value1, "[ \t]*$", "", + [{return, list}]), + Key = lists:sublist(Line, Length-1), + [{Key, Value} | lines_to_key_value(Lines)] + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Extensions to the 'regexp' module. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% is_match(Ex, Re) -> +%% case regexp:first_match(Ex, Re) of +%% {match, _, _} -> +%% true; +%% nomatch -> +%% false +%% end. + +%% -type gsub(String, RegExp, Fun, Acc) -> subres(). +%% Substitute every match of the regular expression RegExp with the +%% string returned from the function Fun(Match, Acc). Accept pre-parsed +%% regular expressions. Acc is an argument to the Fun. The Fun should return +%% a tuple {Replacement, NewAcc}. + +gsub(String, RegExp, Fun, Acc) when is_list(RegExp) -> + case re:compile(RegExp) of + {ok, RE} -> + gsub(String, RE, Fun, Acc); + {error, E} -> + {error, E} + end; +gsub(String, RE, Fun, Acc) -> + {match, Ss} = re:run(String, RE, [global]), + {NewString, NewAcc} = sub_repl(Ss, Fun, Acc, String, 0), + {ok, NewString, NewAcc}. + + +% New code that uses fun for finding the replacement. Also uses accumulator +% to pass argument between the calls to the fun. +sub_repl([[{St, L}] |Ss], Fun, Acc0, S, Pos) -> + Match = string:substr(S, St+1, L), + {Rep, Acc} = Fun(Match, Acc0), + {Rs, NewAcc} = sub_repl(Ss, Fun, Acc, S, St+L), + {string:substr(S, Pos+1, St-Pos) ++ Rep ++ Rs, NewAcc}; +sub_repl([], _Fun, Acc, S, Pos) -> {string:substr(S, Pos+1), Acc}. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Error and warnings +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +error(Format, Args) -> + io:format("ERROR: " ++ Format ++ "\n", Args), + exit(1). + +warning(Format, Args) -> + case get(silent) of + true -> ok; + _ -> io:format("WARNING: " ++ Format ++ "\n", Args) + end. + +report(Format, Args) -> + case get(silent) of + true -> ok; + _ -> io:format(Format ++ "\n", Args) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Extensions to the 'string' module. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +uc(String) -> + lists:reverse(uc(String, [])). + +uc([], Acc) -> + Acc; +uc([H | T], Acc) when is_integer(H), [97] =< H, H =< $z -> + uc(T, [H - 32 | Acc]); +uc([H | T], Acc) -> + uc(T, [H | Acc]). + |