Skip to content

Commit

Permalink
Adding fixes from ngerakines and adding some sugar to prevent callers…
Browse files Browse the repository at this point in the history
… from having to know about the underlying Erlang port.
  • Loading branch information
Kevin Smith committed Jul 20, 2009
1 parent fcda297 commit 95b5401
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 62 deletions.
20 changes: 20 additions & 0 deletions README
@@ -1,3 +1,23 @@
Copyright (c) 2009 Electronic Arts, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

This is baberl, an Erlang wrapper for iconv.

32-bit installation is simple: ./configure;make;sudo make install
Expand Down
4 changes: 4 additions & 0 deletions erl/Emakefile
@@ -0,0 +1,4 @@
{'*', [{outdir, '../ebin'},
{i, "../include/"},
{pz, "../ebin"},
debug_info]}.
4 changes: 3 additions & 1 deletion erl/Makefile.in
Expand Up @@ -10,7 +10,9 @@ all: compile

compile:
mkdir -p ../ebin
erlc -I../include -o ../ebin baberl.erl
mkdir -p ../include
escript ../baberl_generator.escript ../include/baberl.hrl
erl -make

clean:
-rm -f $(EBIN_DIR)/*
Expand Down
4 changes: 2 additions & 2 deletions erl/baberl.app
Expand Up @@ -2,6 +2,6 @@
[{description, "iconv driver"},
{vsn, "0.0.1"},
{modules, [baberl]},
{mod, {baberl_app, []}},
{registered, [baberl_sup, baberl]},
{applications, [kernel, stdlib]}
]}.
{applications, [kernel, stdlib]}]}.
102 changes: 49 additions & 53 deletions erl/baberl.erl
Expand Up @@ -23,78 +23,74 @@
-author("Kevin A. Smith <kevin@hypotheticalabs.com").
-include("baberl.hrl").

-export([start/0, convert/3, convert/4, check_encodings/1, is_supported_encoding/1]).
-export([convert/2, convert/3, check_encodings/1, is_supported_encoding/1]).
-export([encodings/0, closest_match/1]).

%% @hidden
%% @private
load_driver() ->
Dir = filename:join([filename:dirname(code:which(baberl)), "..", "priv"]),
erl_ddll:load(Dir, "baberl_drv").

%% @spec start() -> {ok, pid()} | {error, term()}
%% @doc Starts a baberl instance.
start() ->
case load_driver() of
ok ->
P = open_port({spawn, 'baberl_drv'}, [binary]),
{ok, {baberl, P}};
{error, Err} ->
Msg = erl_ddll:format_error(Err),
{error, Msg}
end.

%% @equiv convert(Pid, "", ToEncoding, Text).
convert(Pid, ToEncoding, Text) when is_list(ToEncoding), is_binary(Text) ->
convert(Pid, "", ToEncoding, Text).
convert(ToEncoding, Text) when is_list(ToEncoding), is_binary(Text) ->
convert("", ToEncoding, Text).

%% @spec convert(BaberlPort, FromEncoding, ToEncoding, Text) -> Result
%% BaberlPort = {atom(), pid()}
%% FromEncoding = string()
%% ToEncoding = string()
%% Text = binary()
%% Result = term()
convert(Port, FromEncoding, ToEncoding, Text) when is_list(FromEncoding), is_list(ToEncoding), is_binary(Text) ->
case unicode:characters_to_list(Text, utf8) of
{incomplete, _, _} -> throw(bad_input);
_ -> ok
end,
check_encodings([extract_encoding_name(FromEncoding), extract_encoding_name(ToEncoding)]),
Command = iolist_to_binary(lists:map(fun
("") -> [<<0:32>>, []];
(Term) -> S = size(Term), [<<S:32>>, Term]
end, [list_to_binary(FromEncoding), list_to_binary(ToEncoding), Text])),
port_command(Port, Command),
receive R -> R
after 50 -> {error, timeout}
end.
convert(FromEncoding, ToEncoding, Text) when is_list(FromEncoding), is_list(ToEncoding), is_binary(Text) ->
case unicode:characters_to_list(Text, utf8) of
{incomplete, _, _} -> throw(bad_input);
_ -> ok
end,
check_encodings([extract_encoding_name(FromEncoding), extract_encoding_name(ToEncoding)]),
Command = iolist_to_binary(lists:map(fun
("") -> [<<0:32>>, []];
(Term) -> S = size(Term), [<<S:32>>, Term]
end, [list_to_binary(FromEncoding), list_to_binary(ToEncoding), Text])),
Port = start_port(),
try
begin
port_command(Port, Command),
receive
R ->
R
after 50 ->
{error, timeout}
end
end
after
port_close(Port)
end.

check_encodings(Encodings) ->
lists:foreach(fun(E) ->
case is_supported_encoding(E) =:= true orelse E =:= "" of
true -> ok;
false -> throw({error, {unsupported_encoding, E}})
end
end, Encodings).
lists:foreach(fun(E) ->
case is_supported_encoding(E) =:= true orelse E =:= "" of
true -> ok;
false -> throw({error, {unsupported_encoding, E}})
end
end, Encodings).

is_supported_encoding(Encoding) ->
lists:member(Encoding, ?SUPPORTED_ENCODINGS) or lists:member(Encoding ++ "//", ?SUPPORTED_ENCODINGS).
lists:member(Encoding, ?SUPPORTED_ENCODINGS) or lists:member(Encoding ++ "//", ?SUPPORTED_ENCODINGS).

encodings() ->
?SUPPORTED_ENCODINGS.
?SUPPORTED_ENCODINGS.

closest_match(Encoding) ->
case is_supported_encoding(Encoding) of
true -> Encoding;
false ->
case lists:reverse([E || E <- ?SUPPORTED_ENCODINGS, string:str(E, Encoding) == 1]) of
[] -> [];
[H | _] -> re:replace(H, "//", "", [{return, list}])
end
end.
case is_supported_encoding(Encoding) of
true -> Encoding;
false ->
case lists:reverse([E || E <- ?SUPPORTED_ENCODINGS, string:str(E, Encoding) == 1]) of
[] -> [];
[H | _] -> re:replace(H, "//", "", [{return, list}])
end
end.

%% @private
extract_encoding_name("") -> "";
extract_encoding_name(Encoding) ->
[EncodingName | _] = string:tokens(Encoding, "//"),
EncodingName.
[EncodingName | _] = string:tokens(Encoding, "//"),
EncodingName.

%% @private
start_port() ->
open_port({spawn, 'baberl_drv'}, [binary]).
32 changes: 32 additions & 0 deletions erl/baberl_app.erl
@@ -0,0 +1,32 @@
%% Copyright (c) 2009 Electronic Arts, Inc.

%% Permission is hereby granted, free of charge, to any person obtaining a copy
%% of this software and associated documentation files (the "Software"), to deal
%% in the Software without restriction, including without limitation the rights
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%% copies of the Software, and to permit persons to whom the Software is
%% furnished to do so, subject to the following conditions:
%%
%% The above copyright notice and this permission notice shall be included in
%% all copies or substantial portions of the Software.
%%
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%% THE SOFTWARE.

-module(baberl_app).

-behaviour(application).

%% Application callbacks
-export([start/2, stop/1]).

start(_StartType, _StartArgs) ->
baberl_sup:start_link().

stop(_State) ->
ok.
65 changes: 65 additions & 0 deletions erl/baberl_loader.erl
@@ -0,0 +1,65 @@
%% Copyright (c) 2009 Electronic Arts, Inc.

%% Permission is hereby granted, free of charge, to any person obtaining a copy
%% of this software and associated documentation files (the "Software"), to deal
%% in the Software without restriction, including without limitation the rights
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%% copies of the Software, and to permit persons to whom the Software is
%% furnished to do so, subject to the following conditions:
%%
%% The above copyright notice and this permission notice shall be included in
%% all copies or substantial portions of the Software.
%%
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%% THE SOFTWARE.

-module(baberl_loader).

-behaviour(gen_server).

-export([start_link/0]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).

start_link() ->
gen_server:start_link(?MODULE, [], []).

init([]) ->
case load_driver() of
ok ->
{ok, undefined};
Error ->
{stop, Error}
end.

handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.

handle_cast(_Msg, State) ->
{noreply, State}.

handle_info(_Info, State) ->
{noreply, State}.

terminate(_Reason, _State) ->
erl_ddll:unload(baberl_drv).

code_change(_OldVsn, State, _Extra) ->
{ok, State}.

%% Internal functions
load_driver() ->
Dir = filename:join([filename:dirname(code:which(baberl)), "..", "priv"]),
case erl_ddll:load(Dir, baberl_drv) of
ok ->
ok;
Error ->
erl_ddll:format_error(Error)
end.
50 changes: 50 additions & 0 deletions erl/baberl_sup.erl
@@ -0,0 +1,50 @@
%% Copyright (c) 2009 Electronic Arts, Inc.

%% Permission is hereby granted, free of charge, to any person obtaining a copy
%% of this software and associated documentation files (the "Software"), to deal
%% in the Software without restriction, including without limitation the rights
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%% copies of the Software, and to permit persons to whom the Software is
%% furnished to do so, subject to the following conditions:
%%
%% The above copyright notice and this permission notice shall be included in
%% all copies or substantial portions of the Software.
%%
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%% THE SOFTWARE.

-module(baberl_sup).

-behaviour(supervisor).

%% API
-export([start_link/0]).

%% Supervisor callbacks
-export([init/1]).

-define(SERVER, ?MODULE).

start_link() ->
supervisor:start_link(?MODULE, []).

init([]) ->
RestartStrategy = one_for_one,
MaxRestarts = 10,
MaxSecondsBetweenRestarts = 10,

SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts},

Restart = permanent,
Shutdown = 2000,
Type = worker,

Loader = {baberl_loader, {baberl_loader, start_link, []},
Restart, Shutdown, Type, [baberl_loader]},

{ok, {SupFlags, [Loader]}}.
11 changes: 5 additions & 6 deletions t/baberl_t_002.t
Expand Up @@ -5,12 +5,11 @@
main(_) ->
etap:plan(6),
etap:is(application:start(baberl), ok, "Start baberl app"),
{ok, {baberl, BaberlPid}} = baberl:start(),
etap:is(baberl:convert(BaberlPid, "", "UTF-8", <<"foo">>), {ok, <<"foo">>}, "Convert a default-encoded string"),
etap:is(baberl:convert(BaberlPid, "UTF-8", "ISO-8859-1", <<"foo">>), {ok, <<"foo">>}, "Convert a UTF-8 encoded string"),
etap:is(baberl:convert(BaberlPid, "UTF-8", "ASCII", <<"foo">>), {ok, <<"foo">>}, "Simple conversion works"),
etap:is(baberl:convert(BaberlPid, "UTF-8", "ASCII//translit//IGNORE", <<"foo">>), {ok, <<"foo">>}, "Complex conversion works"),
etap:ok(true =:= check_return(baberl:convert(BaberlPid, "UTF-8", "ASCII//translit//IGNORE", unicode:characters_to_binary("fooÔ"))), "Transliteration works"),
etap:is(baberl:convert("", "UTF-8", <<"foo">>), {ok, <<"foo">>}, "Convert a default-encoded string"),
etap:is(baberl:convert("UTF-8", "ISO-8859-1", <<"foo">>), {ok, <<"foo">>}, "Convert a UTF-8 encoded string"),
etap:is(baberl:convert("UTF-8", "ASCII", <<"foo">>), {ok, <<"foo">>}, "Simple conversion works"),
etap:is(baberl:convert("UTF-8", "ASCII//translit//IGNORE", <<"foo">>), {ok, <<"foo">>}, "Complex conversion works"),
etap:ok(true =:= check_return(baberl:convert("UTF-8", "ASCII//translit//IGNORE", unicode:characters_to_binary("fooÔ"))), "Transliteration works"),
etap:end_tests().

check_return({ok, <<"foo~A">>}) ->
Expand Down

0 comments on commit 95b5401

Please sign in to comment.