Packages
gpb
4.3.2
5.0.0
4.21.7
4.21.6
4.21.5
4.21.4
4.21.3
4.21.2
4.21.1
4.21.0
4.20.0
4.19.9
4.19.8
4.19.7
4.19.6
4.19.5
4.19.4
4.19.3
4.19.2
4.19.1
4.19.0
4.18.0
4.17.7
4.17.6
4.17.5
4.17.3
4.17.2
4.17.1
4.17.0
4.16.2
4.16.1
4.16.0
4.15.2
4.15.1
4.14.2
4.14.1
4.14.0
4.13.0
4.12.0
4.11.2
4.11.1
4.11.0
4.10.6
4.10.5
4.10.4
4.10.3
4.10.2
4.10.1
4.10.0
4.9.3
4.9.2
4.9.1
4.9.0
4.8.0
4.7.3
4.7.2
4.7.1
4.7.0
4.6.0
4.5.1
4.5.0
4.4.1
4.4.0
4.3.3
4.3.2
4.3.1
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.9
4.1.8
4.1.7
4.1.6
4.1.5
4.1.4
4.1.3
4.1.2
4.1.1
4.1.0
4.0.2
4.0.1
4.0.0
3.28.1
3.28.0
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.8
3.26.7
3.26.6
3.26.5
3.26.4
3.26.3
3.26.2
3.26.1
3.26.0
3.25.2
3.25.1
3.25.0
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.2
3.23.1
3.23.0
3.22.5
3.22.4
3.22.3
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.3
3.20.2
3.20.0
3.19.0
3.18.10
3.18.9
3.18.8
3.18.7
3.18.6
3.18.5
3.18.4
3.18.3
3.18.2
3.18.1
3.18.0
3.17.13
3.17.12
3.17.11
3.17.10
3.17.9
3.17.8
3.17.5
3.17.4
3.17.3
3.17.2
3.17.1
3.17.0
3.16.0
3.15.0
3.14.0
3.13.0
3.12.2
3.12.1
3.12.0
3.11.0
A compiler for Google protocol buffer definitions files for Erlang.
Current section
Files
Jump to
Current section
Files
src/gpb_names.erl
%%% Copyright (C) 2017 Tomas Abrahamsson
%%%
%%% Author: Tomas Abrahamsson <tab@lysator.liu.se>
%%%
%%% This library is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU Lesser General Public
%%% License as published by the Free Software Foundation; either
%%% version 2.1 of the License, or (at your option) any later version.
%%%
%%% This library is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% Lesser General Public License for more details.
%%%
%%% You should have received a copy of the GNU Lesser General Public
%%% License along with this library; if not, write to the Free Software
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
%%% MA 02110-1301 USA
%%% @doc Transformation of module names and names in parsed
%%% definitions, such as prefixing and lowercasing of message names etc.
-module(gpb_names).
-export([file_name_to_module_name/2]).
-export([rename_module/2]).
-export([rename_defs/2]).
-export([format_error/1]).
-include("../include/gpb.hrl").
-define(f(Fmt, Args), io_lib:format(Fmt, Args)).
%% @doc Given a file name of a proto file, turn it into a module name,
%% possibly with name transformations, such as prefix or suffix
%% according to options.
-spec file_name_to_module_name(string(), gpb_compile:opts()) -> atom().
file_name_to_module_name(ProtoFileName, Opts) ->
Ext = filename:extension(ProtoFileName),
BaseNameNoExt = filename:basename(ProtoFileName, Ext),
rename_module(BaseNameNoExt, Opts).
%% @doc Given a module name, rename it according to opts, for example
%% by prefixing it.
-spec rename_module(atom() | string(), gpb_compile:opts()) -> atom().
rename_module(Mod, Opts) when is_atom(Mod) ->
rename_module(atom_to_list(Mod), Opts);
rename_module(Mod, Opts) when is_list(Mod) ->
list_to_atom(possibly_suffix_mod(
possibly_prefix_mod(
mod_name_from_opts_or_else_filename(Mod, Opts),
Opts),
Opts)).
mod_name_from_opts_or_else_filename(FileBaseName, Opts) ->
proplists:get_value(module_name, Opts, FileBaseName).
possibly_prefix_mod(BaseNameNoExt, Opts) ->
case proplists:get_value(module_name_prefix, Opts) of
undefined ->
BaseNameNoExt;
Prefix ->
lists:concat([Prefix, BaseNameNoExt])
end.
possibly_suffix_mod(BaseNameNoExt, Opts) ->
case proplists:get_value(module_name_suffix, Opts) of
undefined ->
BaseNameNoExt;
Suffix ->
lists:concat([BaseNameNoExt, Suffix])
end.
%% @doc Rename definitions according to options, for example
%% lowercasing message names.
-spec rename_defs(gpb_parse:defs(), gpb_compile:opts()) ->
{ok, gpb_parse:defs()} |
{error, Reason::term()}.
rename_defs(Defs, Opts) ->
Opts1 = convert_legacy_opts(Opts),
case mk_rename_operations(Opts1) of
[] ->
{ok, Defs};
RenameOpFs ->
case mk_renamer(RenameOpFs, Defs, Opts1) of
{ok, RF} ->
{ok, do_rename(RF, Defs)};
{error, Reason} ->
{error, {rename_defs, Reason}}
end
end.
format_error({error, {rename_defs, Reason}}) -> fmt_err(Reason);
format_error({rename_defs, Reason}) -> fmt_err(Reason);
format_error(Reason) -> fmt_err(Reason).
%% Note: do NOT include trailing newline (\n or ~n)
fmt_err({duplicates, Dups}) ->
["Renaming to same name:\n",
gpb_lib:nl_join(
lists:append(
[[case K of
{Service,Rpc} -> ?f(" ~s/~s -> ~s", [Service, Rpc, V]);
K -> ?f(" ~s -> ~s", [K, V])
end
|| K <- Ks]
|| {Ks, V} <- Dups]))];
fmt_err(X) ->
?f("Unexpected error ~p", [X]).
%% -- Converting legacy opts ------------------
convert_legacy_opts([Opt | Opts]) ->
case Opt of
{msg_name_prefix, Prefix} ->
l_msg_prefix_opts(Prefix) ++ convert_legacy_opts(Opts);
{msg_name_suffix, Suffix} ->
l_msg_suffix_opts(Suffix) ++ convert_legacy_opts(Opts);
msg_name_to_snake_case ->
l_msg_snake_case_opts() ++ convert_legacy_opts(Opts);
{msg_name_to_snake_case=OptKey, Bool} ->
if Bool -> l_msg_snake_case_opts() ++ convert_legacy_opts(Opts);
true -> convert_legacy_opts(drop_opt(OptKey, Opts))
end;
msg_name_to_lower ->
l_msg_lowercase_opts() ++ convert_legacy_opts(Opts);
{msg_name_to_lower=OptKey, Bool} ->
if Bool -> l_msg_lowercase_opts() ++ convert_legacy_opts(Opts);
true -> convert_legacy_opts(drop_opt(OptKey, Opts))
end;
_ ->
[Opt | convert_legacy_opts(Opts)]
end;
convert_legacy_opts([]) ->
[].
drop_opt(Opt, [Opt | Rest]) -> drop_opt(Opt, Rest);
drop_opt(Opt, [{Opt, _} | Rest]) -> drop_opt(Opt, Rest);
drop_opt(Opt, [Other | Rest]) -> [Other | drop_opt(Opt, Rest)];
drop_opt(_Opt, []) -> [].
l_msg_prefix_opts({by_proto,_PrefixList}=ByProto) ->
[{rename, {msg_fqname, {prefix, ByProto}}}];
l_msg_prefix_opts(Prefix) ->
l_msg_only_opts({prefix, Prefix}).
l_msg_suffix_opts(Suffix) ->
l_msg_only_opts({suffix, Suffix}).
l_msg_snake_case_opts() ->
l_msg_and_service_and_rpc_opts(snake_case).
l_msg_lowercase_opts() ->
l_msg_and_service_and_rpc_opts(lowercase).
l_msg_only_opts(Value) ->
[{rename, {pkg_name, Value}},
{rename, {msg_fqname, Value}},
{rename, {group_fqname, Value}}].
l_msg_and_service_and_rpc_opts(Value) ->
[{rename, {pkg_name, Value}},
{rename, {service_fqname, Value}},
{rename, {rpc_name, Value}},
{rename, {msg_fqname, Value}},
{rename, {group_fqname, Value}}].
%% -- Renaming opts -> renaming functions ------------------
mk_rename_operations(Opts) ->
[{What, mk_rename_op(What, How)} || {rename, {What, How}} <- Opts].
mk_rename_op(pkg_name, How) -> mk_pkg_rename_op(How);
mk_rename_op(msg_fqname, How) -> mk_msg_rename_op(How);
mk_rename_op(msg_name, How) -> mk_msg_rename_op(How);
mk_rename_op(group_fqname, How) -> mk_msg_rename_op(How);
mk_rename_op(group_name, How) -> mk_group_rename_op(How);
mk_rename_op(service_fqname, How) -> mk_service_rename_op(How);
mk_rename_op(service_name, How) -> mk_service_rename_op(How);
mk_rename_op(rpc_name, How) -> mk_rpc_rename_op(How).
mk_pkg_rename_op(PrimOp) ->
fun(Name, _Proto) -> do_prim_op(PrimOp, Name) end.
mk_msg_rename_op({prefix, {by_proto, PrefixList}}) ->
fun(Name, Proto) ->
ProtoName = list_to_atom(Proto),
Prefix = proplists:get_value(ProtoName, PrefixList, ""),
list_to_atom(lists:concat([Prefix, Name]))
end;
mk_msg_rename_op(PrimOp) ->
fun(Name, _Proto) -> do_prim_op(PrimOp, Name) end.
mk_group_rename_op(PrimOp) ->
fun(Name, _Proto) -> do_prim_op(PrimOp, Name) end.
mk_service_rename_op(PrimOp) ->
fun(Name, _Proto) -> do_prim_op(PrimOp, Name) end.
mk_rpc_rename_op(PrimOp) ->
fun(Name, _Proto) -> do_prim_op(PrimOp, Name) end.
do_prim_op({prefix, Prefix}, Name) ->
list_to_atom(lists:concat([Prefix, Name]));
do_prim_op({suffix, Suffix}, Name) ->
list_to_atom(lists:concat([Name, Suffix]));
do_prim_op(lowercase, Name) ->
list_to_atom(gpb_lib:lowercase(atom_to_list(Name)));
do_prim_op(snake_case, Name) ->
list_to_atom(gpb_lib:snake_case(atom_to_list(Name)));
do_prim_op(dots_to_underscores, Name) ->
list_to_atom(do_dot_uscore(atom_to_list(Name)));
do_prim_op(base_name, Name) ->
list_to_atom(lists:last(gpb_lib:string_lexemes(atom_to_list(Name), "."))).
do_dot_uscore("."++Rest) -> "_" ++ do_dot_uscore(Rest);
do_dot_uscore([C | Rest]) -> [C | do_dot_uscore(Rest)];
do_dot_uscore("") -> "".
%% -- Compute old-name -> new name mappings -----------
%%
%% This stage is chiefly to call the RenameOp function---which could
%% possibly be a user-supplied function---only once or twice for every
%% msg, service or rpc name, but still be able to map all occurrences
%% of such names, which may be many times more (eg for messages: once
%% for the message name, again for each field of that type.)
%%
mk_renamer(RenameOps, Defs, Opts) ->
PkgByProto = calc_package_by_proto(Defs),
PkgRenamings = pkg_renamings(PkgByProto, RenameOps),
MsgRenamings = msg_renamings(PkgByProto, PkgRenamings, Defs, RenameOps),
GroupRenamings = group_renamings(PkgByProto, PkgRenamings, Defs,
RenameOps),
ServiceRenamings = service_renamings(PkgByProto, PkgRenamings, Defs,
RenameOps),
RpcRenamings = rpc_renamings(Defs, RenameOps),
MostRenamings = [PkgRenamings, MsgRenamings, GroupRenamings,
ServiceRenamings],
UsePackages = proplists:get_bool(use_packages, Opts),
case check_no_dups(MostRenamings, RpcRenamings) of
ok ->
RF = fun(package, Name) ->
if UsePackages ->
dict_fetch(Name, PkgRenamings);
not UsePackages ->
%% No pkg_containment items present in Defs
%% when the use_packages option is not set.
Name
end;
(msg, Name) ->
dict_fetch(Name, MsgRenamings);
(group, Name) ->
dict_fetch(Name, GroupRenamings);
(service, Name) ->
dict_fetch(Name, ServiceRenamings);
({rpc, ServiceName}, RpcName) ->
dict_fetch({ServiceName, RpcName}, RpcRenamings)
end,
{ok, RF};
{error, Reason} ->
{error, Reason}
end.
calc_package_by_proto(Defs) ->
dict:from_list(
[{Proto, PkgName}
|| {{pkg_containment, Proto}, PkgName} <- Defs]).
pkg_renamings(PkgByProto, RenameOps) ->
dict:from_list(
lists:map(
fun({Proto, Pkg}) ->
Pkg1 = run_ops(pkg_name, Pkg, Proto, RenameOps),
{Pkg, Pkg1}
end,
dict:to_list(PkgByProto))).
msg_renamings(PkgByProto, PkgRenamings, Defs, RenameOps) ->
dict:from_list(
lists:append(
[begin
Pkg = dict_fetch_or_default(Proto, PkgByProto, ''),
[begin
Name = drop_prefix(Pkg, FqName),
Name1 = run_ops(msg_name, Name, Proto, RenameOps),
Pkg1 = dict_fetch_or_default(Pkg, PkgRenamings, ''),
FqName1 = prefix(Pkg1, Name1),
FqName2 = run_ops(msg_fqname, FqName1, Proto, RenameOps),
{FqName, FqName2}
end
|| FqName <- MsgNames]
end
|| {{msg_containment, Proto}, MsgNames} <- Defs])).
group_renamings(PkgByProto, PkgRenamings, Defs, RenameOps) ->
ProtoByMsg = dict:from_list(
lists:append(
[[{MsgName, Proto} || MsgName <- MsgNames]
|| {{msg_containment, Proto}, MsgNames} <- Defs])),
dict:from_list(
[begin
MsgName = group_name_to_msg_name(GroupFqName),
Proto = dict:fetch(MsgName, ProtoByMsg),
Pkg = dict_fetch_or_default(Proto, PkgByProto, ''),
Name = drop_prefix(Pkg, GroupFqName),
Name1 = run_ops(group_name, Name, Proto, RenameOps),
Pkg1 = dict_fetch_or_default(Pkg, PkgRenamings, ''),
FqName1 = prefix(Pkg1, Name1),
FqName2 = run_ops(group_fqname, FqName1, Proto, RenameOps),
{GroupFqName, FqName2}
end
|| {{group,GroupFqName}, _Fields} <- Defs]).
group_name_to_msg_name(GName) ->
Components = gpb_lib:string_lexemes(atom_to_list(GName), "."),
[_G | ButLast] = lists:reverse(Components),
list_to_atom(gpb_lib:dot_join(lists:reverse(ButLast))).
service_renamings(PkgByProto, PkgRenamings, Defs, RenameOps) ->
dict:from_list(
lists:append(
[begin
Pkg = dict_fetch_or_default(Proto, PkgByProto, ''),
[begin
Name = drop_prefix(Pkg, FqName),
Name1 = run_ops(service_name, Name, Proto, RenameOps),
Pkg1 = dict_fetch_or_default(Pkg, PkgRenamings, ''),
FqName1 = prefix(Pkg1, Name1),
FqName2 = run_ops(service_fqname, FqName1, Proto, RenameOps),
{FqName, FqName2}
end
|| FqName <- ServiceNames]
end
|| {{service_containment, Proto}, ServiceNames} <- Defs])).
rpc_renamings(Defs, RenameOps) ->
dict:from_list(
lists:append(
[begin
[begin
RpcName1 = run_ops(rpc_name, RpcName, Proto, RenameOps),
{{ServiceName, RpcName}, RpcName1}
end
|| {ServiceName, RpcName} <- Rpcs]
end
|| {{rpc_containment, Proto}, Rpcs} <- Defs])).
run_ops(What, Name0, Proto, RenameOps) ->
lists:foldl(fun(F, Name) -> F(Name, Proto) end,
Name0,
[F || {W, F} <- RenameOps,
W =:= What]).
drop_prefix('', Value) when is_atom(Value) ->
Value; % fast path (no package)
drop_prefix(Prefix, Value) when is_atom(Prefix), is_atom(Value) ->
P = atom_to_list(Prefix),
V = atom_to_list(Value),
case lists:sublist(V, length(P) + 1, length(V) - length(P)) of
"." ++ Rest -> list_to_atom(Rest);
Rest -> list_to_atom(Rest)
end.
prefix('', V) ->
V; % fast path (no package)
prefix(P, '') ->
P; % fast path (no remainder)
prefix(P, V) ->
list_to_atom(lists:concat([P, ".", V])).
dict_fetch_or_default(Key, Dict, Default) ->
case dict:find(Key, Dict) of
{ok, Value} ->
Value;
error ->
Default
end.
dict_fetch(Key, Dict) ->
case dict:find(Key, Dict) of
{ok, Value} ->
Value;
error ->
error({not_found_in_dict, Key, dict:to_list(Dict)})
end.
check_no_dups(Renamings, RpcRenamings) ->
Errs1 = lists:foldl(fun renaming_dups/2, [], Renamings),
Errs2 = renaming_rpc_dups(RpcRenamings, Errs1),
if Errs2 == [] ->
ok;
true ->
{error, {duplicates, Errs2}}
end.
renaming_dups(Dict, Errs) ->
RDict = dict:fold(fun(K, V, RDict) -> dict:append(V, K, RDict) end,
dict:new(),
Dict),
DupsDict = dict:filter(fun(_V, [_K1,_K2|_]) -> true; % >= 2 entries
(_V, [_]) -> false
end,
RDict),
[{Keys, V} || {V, Keys} <- dict:to_list(DupsDict)] ++ Errs.
%% check for dups on a per service basis
renaming_rpc_dups(Dict, Errs) ->
%% split into dict of dicts, one per service (service name is used as key)
Ds = dict:fold(
fun({Service,_Rpc}=Entry, NewName, D) ->
ED = case dict:find(Service, D) of
error -> dict:store(Entry, NewName, dict:new());
{ok,ED0} -> dict:store(Entry, NewName, ED0)
end,
dict:store(Service, ED, D)
end,
dict:new(),
Dict),
lists:foldl(fun renaming_dups/2,
Errs,
[D || {_Service,D} <- dict:to_list(Ds)]).
%% -- Traversing defs, doing rename ----------
do_rename(RF, Defs) ->
lists:map(
fun({{msg,Name}, Fields}) ->
{{msg, RF(msg, Name)}, rename_fields(RF, Fields, Defs)};
({{group,Name}, Fields}) ->
{{group, RF(group, Name)}, rename_fields(RF, Fields, Defs)};
({{extensions,Name}, Exts}) ->
{{extensions, RF(msg, Name)}, Exts};
({{service,Name}, Rpcs}) ->
{{service, RF(service, Name)}, rename_rpcs(RF, Name, Rpcs)};
({package,Name}) ->
{package, RF(package, Name)};
({proto3_msgs,Names}) ->
{proto3_msgs, [RF(msg, Name) || Name <- Names]};
({{msg_containment,Proto}, MsgNames}) ->
{{msg_containment,Proto}, [RF(msg, Name) || Name <- MsgNames]};
({{pkg_containment,Proto}, PkgName}) ->
{{pkg_containment,Proto}, RF(package, PkgName)};
({{service_containment,Proto}, ServiceNames}) ->
{{service_containment,Proto},
[RF(service, Name) || Name <- ServiceNames]};
({{rpc_containment,Proto}, Rpcs}) ->
{{rpc_containment,Proto},
[{RF(service, SvcName), RF({rpc, SvcName}, RpcName)}
|| {SvcName, RpcName} <- Rpcs]};
(OtherElem) ->
OtherElem
end,
Defs).
rename_fields(RF, Fields, Defs) ->
lists:map(
fun(#?gpb_field{type={msg,MsgName}}=F) ->
F#?gpb_field{type={msg, RF(msg, MsgName)}};
(#?gpb_field{type={map,KeyType,{msg,MsgName}}}=F) ->
F#?gpb_field{type={map,KeyType,{msg, RF(msg, MsgName)}}};
(#?gpb_field{type={group,MsgName}}=F) ->
F#?gpb_field{type={group, RF(group, MsgName)}};
(#gpb_oneof{fields=Fs}=F) ->
F#gpb_oneof{fields=rename_fields(RF, Fs, Defs)};
(#?gpb_field{}=F) ->
F
end,
Fields).
rename_rpcs(RF, ServiceName, RPCs) ->
lists:map(
fun(#?gpb_rpc{name=RpcName, input=Arg, output=Return}=R) ->
R#?gpb_rpc{name=RF({rpc, ServiceName}, RpcName),
input=RF(msg, Arg),
output=RF(msg, Return)}
end,
RPCs).