├── rebar
├── src
├── iconv_app.erl
├── iconv.app.src
└── iconv.erl
├── rebar.config
├── LICENSE
├── README.markdown
├── c_src
└── iconv_drv.c
└── Rakefile
/rebar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vagabond/erlang-iconv/HEAD/rebar
--------------------------------------------------------------------------------
/src/iconv_app.erl:
--------------------------------------------------------------------------------
1 | -module(iconv_app).
2 |
3 | -export([start/2, stop/0]).
4 |
5 |
6 | start(normal, []) ->
7 | iconv:start().
8 |
9 | stop() ->
10 | iconv:stop().
11 |
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | {port_envs, [{"darwin|openbsd", "LDFLAGS", "$LDFLAGS -liconv"},
2 | {"openbsd", "CFLAGS", "$CFLAGS -I/usr/local/include"},
3 | {"openbsd", "LDFLAGS", "$LDFLAGS -L/usr/local/lib"}]}.
4 |
--------------------------------------------------------------------------------
/src/iconv.app.src:
--------------------------------------------------------------------------------
1 | {application,iconv,
2 | [{description,"Interface to the iconv character set convertion library"},
3 | {vsn,"1.0.1"},
4 | {modules,[]},
5 | {registered,[iconv]},
6 | {env, []},
7 | {mod, {iconv_app, []}},
8 | {applications, []},
9 | {dependencies, []}]}.
10 |
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Copyright (c) 2004, Torbjorn Tornkvist
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
22 |
23 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | About
2 | =====
3 |
4 | Erlang-iconv is a fork of the iconv in Jungerl. I pulled it out of jungerl
5 | because I only wanted iconv, not all the other stuff in jungerl and because the
6 | jungerl build system wasn't very helpful on anything but linux.
7 |
8 | erlang-iconv has been built and tested on the following platforms:
9 |
10 | * Linux
11 | * FreeBSD
12 | * OpenBSD
13 | * OSX
14 |
15 | It should also build on NetBSD and most other UNIX-like systems. I haven't
16 | bothered to try on windows yet.
17 |
18 | Usage
19 | =====
20 |
21 | Here's some sample usage converting some shift-jis text to utf-8:
22 |
23 |
24 | 1> iconv:start().
25 | {ok,<0.53.0>}
26 | 2> X = <<83,97,109,112,108,101,58,32,142,132,32,130,205,32,131,67,32,131,147,32,131,79,32,131,71,32,130,197,32,130,183,13,10>>.
27 | <<83,97,109,112,108,101,58,32,142,132,32,130,205,32,131,
28 | 67,32,131,147,32,131,79,32,131,71,32,130,197,32,...>>
29 | 3> io:format("~s~n", [X]).
30 | Sample:
31 | Í C O G Å ·
32 |
33 | ok
34 | 4> io:format("~ts~n", [X]).
35 | ** exception exit: {badarg,[{io,format,
36 | [<0.25.0>,"~ts~n",
37 | [<<83,97,109,112,108,101,58,32,142,132,32,130,205,32,
38 | 131,67,32,131,147,...>>]]},
39 | {erl_eval,do_apply,5},
40 | {shell,exprs,6},
41 | {shell,eval_exprs,6},
42 | {shell,eval_loop,3}]}
43 | in function io:o_request/3
44 | 5> {ok, CD} = iconv:open("utf-8", "shift-jis").
45 | {ok,<<176,86,91,1,0,0,0,0>>}
46 | 6> {ok, Output} = iconv:conv(CD, X).
47 | {ok,<<83,97,109,112,108,101,58,32,231,167,129,32,227,129,
48 | 175,32,227,130,164,32,227,131,179,32,227,130,176,
49 | ...>>}
50 | 7> io:format("~ts~n", [Output]).
51 | Sample: 私 は イ ン グ エ で す
52 | 8> iconv:close(CD).
53 | ok
54 |
55 |
56 | As you can see, before we passed it through iconv it was an unprintable mess.
57 |
58 | Installation
59 | ============
60 |
61 | To install, use rake:
62 |
63 |
64 | rake
65 | sudo rake install
66 |
67 |
68 | If the compilation of the C part fails with errors about iconv, you have to
69 | specify the prefix where iconv is located, for example on FreeBSD:
70 |
71 |
72 | rake iconv=/usr/local
73 |
74 |
75 | And if you don't have iconv or it's development header installed (and iconv
76 | isn't part of your C libary) install it.
77 |
--------------------------------------------------------------------------------
/src/iconv.erl:
--------------------------------------------------------------------------------
1 | -module(iconv).
2 | %%%----------------------------------------------------------------------
3 | %%% File : iconv.erl
4 | %%% Author : Torbjorn Tornkvist
5 | %%% Purpose : iconv support
6 | %%% Created : 23 Mar 2004 by
7 | %%%
8 | %%% $Id$
9 | %%%----------------------------------------------------------------------
10 | -behaviour(gen_server).
11 | -export([start/0, start_link/0, stop/0, open/2, conv/2, close/1]).
12 |
13 | %% gen_server callbacks
14 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
15 | terminate/2, code_change/3]).
16 |
17 | -record(state, {port}).
18 |
19 | -ifdef(TEST).
20 | -include_lib("eunit/include/eunit.hrl").
21 | -endif.
22 |
23 | %%% op codes
24 | -define(IV_OPEN, $o).
25 | -define(IV_CONV, $v).
26 | -define(IV_CLOSE, $c).
27 |
28 | -define(INBUF_SZ, 512).
29 |
30 | -define(DRV_NAME, "iconv_drv").
31 | -define(SERVER, ?MODULE).
32 |
33 | %%%----------------------------------------------------------------------
34 | %%% API
35 | %%%----------------------------------------------------------------------
36 | start() ->
37 | gen_server:start({local, ?SERVER}, ?MODULE, [], []).
38 |
39 | start_link() ->
40 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
41 |
42 | stop() ->
43 | gen_server:call(?SERVER, stop).
44 |
45 | %%open(To, From) -> {ok, ballen};
46 | open(To, From) ->
47 | gen_server:call(?SERVER, {open, l2b(To), l2b(From)}, infinity).
48 |
49 | %%conv(Cd, String) -> {ok, l2b(String)};
50 | conv(Cd, String) when is_binary(Cd) ->
51 | gen_server:call(?SERVER, {conv, Cd, l2b(String)}, infinity).
52 |
53 | %%close(Cd) -> ok;
54 | close(Cd) when is_binary(Cd) ->
55 | gen_server:call(?SERVER, {close, Cd}, infinity).
56 |
57 | %%%----------------------------------------------------------------------
58 | %%% Callback functions from gen_server
59 | %%%----------------------------------------------------------------------
60 |
61 | %%----------------------------------------------------------------------
62 | %% Func: init/1
63 | %% Returns: {ok, State} |
64 | %% {ok, State, Timeout} |
65 | %% ignore |
66 | %% {stop, Reason}
67 | %%----------------------------------------------------------------------
68 | init([]) ->
69 | erl_ddll:start(),
70 | Path = case code:priv_dir(iconv) of
71 | {error, _} ->
72 | case load_path(?DRV_NAME++".so") of
73 | {error, _} ->
74 | error;
75 | {ok, P} ->
76 | P
77 | end;
78 | P ->
79 | P
80 | end,
81 |
82 | case Path of
83 | error ->
84 | {stop, no_driver};
85 | Path ->
86 | case erl_ddll:load_driver(Path, ?DRV_NAME) of
87 | ok ->
88 | Port = open_port({spawn, ?DRV_NAME}, [binary]),
89 | {ok, #state{port = Port}};
90 | {error, Error} ->
91 | error_logger:format("Error loading driver: " ++ erl_ddll:format_error(Error), []),
92 | {stop, bad_driver}
93 | end
94 | end.
95 |
96 | %%----------------------------------------------------------------------
97 | %% Func: handle_call/3
98 | %% Returns: {reply, Reply, State} |
99 | %% {reply, Reply, State, Timeout} |
100 | %% {noreply, State} |
101 | %% {noreply, State, Timeout} |
102 | %% {stop, Reason, Reply, State} | (terminate/2 is called)
103 | %% {stop, Reason, State} (terminate/2 is called)
104 | %%----------------------------------------------------------------------
105 | handle_call({open, To, From}, _, S) ->
106 | ToLen = byte_size(To),
107 | FromLen = byte_size(From),
108 | Msg = <>,
109 | Reply = call_drv(S#state.port, Msg),
110 | {reply, Reply, S};
111 | %%
112 | handle_call({conv, Cd, Buf}, _, S) ->
113 | CdLen = byte_size(Cd),
114 | BufLen = byte_size(Buf),
115 | Msg = <>,
116 | Reply = call_drv(S#state.port, Msg),
117 | {reply, Reply, S};
118 |
119 | %%
120 | handle_call({close, Cd}, _, S) ->
121 | CdLen = byte_size(Cd),
122 | Msg = <>,
123 | Reply = call_drv(S#state.port, Msg),
124 | {reply, Reply, S};
125 |
126 | handle_call(stop, _, S) ->
127 | {stop, normal, ok, S}.
128 |
129 | call_drv(Port, Msg) ->
130 | erlang:port_command(Port, [Msg]),
131 | recv(Port).
132 |
133 | recv(Port) ->
134 | receive
135 | {Port, ok} ->
136 | ok;
137 | {Port, value, Bin} ->
138 | {ok,Bin};
139 | {Port, error, ErrAtom} ->
140 | {error, ErrAtom}
141 | end.
142 |
143 |
144 |
145 | %%----------------------------------------------------------------------
146 | %% Func: handle_cast/2
147 | %% Returns: {noreply, State} |
148 | %% {noreply, State, Timeout} |
149 | %% {stop, Reason, State} (terminate/2 is called)
150 | %%----------------------------------------------------------------------
151 | handle_cast(_Msg, State) ->
152 | {noreply, State}.
153 |
154 | %%----------------------------------------------------------------------
155 | %% Func: handle_info/2
156 | %% Returns: {noreply, State} |
157 | %% {noreply, State, Timeout} |
158 | %% {stop, Reason, State} (terminate/2 is called)
159 | %%----------------------------------------------------------------------
160 | handle_info(_Info, State) ->
161 | {noreply, State}.
162 |
163 | %%----------------------------------------------------------------------
164 | %% Func: terminate/2
165 | %% Purpose: Shutdown the server
166 | %% Returns: any (ignored by gen_server)
167 | %%----------------------------------------------------------------------
168 | terminate(_Reason, _State) ->
169 | ok.
170 |
171 |
172 | code_change(_, _, _) ->
173 | ok.
174 |
175 | %%%----------------------------------------------------------------------
176 | %%% Internal functions
177 | %%%----------------------------------------------------------------------
178 | load_path(File) ->
179 | case lists:zf(fun(Ebin) ->
180 | Priv = Ebin ++ "/../priv/",
181 | case file:read_file_info(Priv ++ File) of
182 | {ok, _} -> {true, Priv};
183 | _ -> false
184 | end
185 | end, code:get_path()) of
186 | [Dir|_] ->
187 | {ok, Dir};
188 | [] ->
189 | error_logger:format("Error: ~s not found in code path\n", [File]),
190 | {error, enoent}
191 | end.
192 |
193 | l2b(L) when is_list(L) -> list_to_binary(L);
194 | l2b(B) when is_binary(B) -> B.
195 |
196 | -ifdef(TEST).
197 |
198 | smtp_session_test_() ->
199 | {setup,
200 | fun() ->
201 | iconv:start()
202 | end,
203 | fun(_) ->
204 | iconv:stop()
205 | end,
206 | [
207 | {"Convert from latin-1 to utf-8",
208 | fun() ->
209 | {ok, CD} = iconv:open("utf-8", "ISO-8859-1"),
210 | ?assertEqual({ok, <<"hello world">>}, iconv:conv(CD, "hello world")),
211 | iconv:close(CD)
212 | end
213 | }
214 | ]
215 | }.
216 |
217 |
218 | -endif.
219 |
--------------------------------------------------------------------------------
/c_src/iconv_drv.c:
--------------------------------------------------------------------------------
1 | /* Created : 23 Mar 2004 by Tobbe
2 | * Description : iconv driver - conversion between character sets
3 | *
4 | * $Id$
5 | */
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | #include "erl_driver.h"
13 | #ifndef ERL_DRV_NIL
14 | #include "erl_driver_compat.h"
15 | #endif
16 |
17 | /* op codes */
18 | #define IV_OPEN 'o'
19 | #define IV_CONV 'v'
20 | #define IV_CLOSE 'c'
21 |
22 | /* convert buffers */
23 | #define INBUF_SZ 512
24 | #define OUTBUF_SZ INBUF_SZ*4
25 | /*static char inbuf[INBUF_SZ];*/
26 | //static char outbuf[OUTBUF_SZ];
27 |
28 |
29 | /* these should really be defined in driver.h */
30 | #define LOAD_ATOM(vec, i, atom) \
31 | (((vec)[(i)] = ERL_DRV_ATOM), \
32 | ((vec)[(i)+1] = (atom)), \
33 | (i+2))
34 |
35 | #define LOAD_INT(vec, i, val) \
36 | (((vec)[(i)] = ERL_DRV_INT), \
37 | ((vec)[(i)+1] = (ErlDrvTermData)(val)), \
38 | (i+2))
39 |
40 | #define LOAD_PORT(vec, i, port) \
41 | (((vec)[(i)] = ERL_DRV_PORT), \
42 | ((vec)[(i)+1] = (port)), \
43 | (i+2))
44 |
45 | #define LOAD_PID(vec, i, pid) \
46 | (((vec)[(i)] = ERL_DRV_PID), \
47 | ((vec)[(i)+1] = (pid)), \
48 | (i+2))
49 |
50 | #define LOAD_BINARY(vec, i, bin, offs, len) \
51 | (((vec)[(i)] = ERL_DRV_BINARY), \
52 | ((vec)[(i)+1] = (ErlDrvTermData)(bin)), \
53 | ((vec)[(i)+2] = (len)), \
54 | ((vec)[(i)+3] = (offs)), \
55 | (i+4))
56 |
57 | #define LOAD_STRING(vec, i, str, len) \
58 | (((vec)[(i)] = ERL_DRV_STRING), \
59 | ((vec)[(i)+1] = (ErlDrvTermData)(str)), \
60 | ((vec)[(i)+2] = (len)), \
61 | (i+3))
62 |
63 | #define LOAD_STRING_CONS(vec, i, str, len) \
64 | (((vec)[(i)] = ERL_DRV_STRING_CONS), \
65 | ((vec)[(i)+1] = (ErlDrvTermData)(str)), \
66 | ((vec)[(i)+2] = (len)), \
67 | (i+3))
68 |
69 | #define LOAD_TUPLE(vec, i, size) \
70 | (((vec)[(i)] = ERL_DRV_TUPLE), \
71 | ((vec)[(i)+1] = (size)), \
72 | (i+2))
73 |
74 | #define LOAD_LIST(vec, i, size) \
75 | (((vec)[(i)] = ERL_DRV_LIST), \
76 | ((vec)[(i)+1] = (size)), \
77 | (i+2))
78 |
79 | static int driver_send_bin();
80 |
81 | /* atoms which are sent to erlang */
82 | static ErlDrvTermData am_ok;
83 | static ErlDrvTermData am_value;
84 | static ErlDrvTermData am_error;
85 | static ErlDrvTermData am_enomem;
86 | static ErlDrvTermData am_einval;
87 | static ErlDrvTermData am_eilseq;
88 | static ErlDrvTermData am_e2big;
89 | static ErlDrvTermData am_unknown;
90 |
91 | static ErlDrvEntry iconvdrv_driver_entry;
92 |
93 | typedef struct t_iconvdrv {
94 | ErlDrvPort port;
95 | ErlDrvTermData dport; /* the port identifier as ErlDrvTermData */
96 | unsigned char digest[16];
97 | } t_iconvdrv;
98 |
99 |
100 | static ErlDrvData iconvdrv_start(ErlDrvPort port, char *buf)
101 | {
102 | t_iconvdrv *iconv = (t_iconvdrv*) driver_alloc(sizeof(t_iconvdrv));
103 |
104 | if (iconv == NULL) return (ErlDrvData) -1;
105 | iconv->port = port;
106 | iconv->dport = driver_mk_port(port);
107 | return (ErlDrvData) iconv;
108 | }
109 |
110 | static void iconvdrv_stop(ErlDrvData drv_data)
111 | {
112 | t_iconvdrv *iv = (t_iconvdrv*) drv_data;
113 | driver_free(iv);
114 | }
115 |
116 |
117 | /* send {P, value, Bin} to caller */
118 | static int driver_send_bin(t_iconvdrv *iv, ErlDrvBinary *bin, int len)
119 | {
120 | int i = 0;
121 | ErlDrvTermData to, spec[10];
122 |
123 | to = driver_caller(iv->port);
124 |
125 | i = LOAD_PORT(spec, i, iv->dport);
126 | i = LOAD_ATOM(spec, i, am_value);
127 | i = LOAD_BINARY(spec, i, bin, 0, len);
128 | i = LOAD_TUPLE(spec, i, 3);
129 |
130 | return driver_send_term(iv->port, to, spec, i);
131 | }
132 |
133 | /* send {P, ok} to caller */
134 | static int driver_send_ok(t_iconvdrv *iv)
135 | {
136 | int i = 0;
137 | ErlDrvTermData to, spec[10];
138 |
139 | to = driver_caller(iv->port);
140 |
141 | i = LOAD_PORT(spec, i, iv->dport);
142 | i = LOAD_ATOM(spec, i, am_ok);
143 | i = LOAD_TUPLE(spec, i, 2);
144 |
145 | return driver_send_term(iv->port, to, spec, i);
146 | }
147 |
148 | /* send {P, error, Error} to caller */
149 | static int driver_send_error(t_iconvdrv *iv, ErlDrvTermData *am)
150 | {
151 | int i = 0;
152 | ErlDrvTermData to, spec[8];
153 |
154 | to = driver_caller(iv->port);
155 |
156 | i = LOAD_PORT(spec, i, iv->dport);
157 | i = LOAD_ATOM(spec, i, am_error);
158 | i = LOAD_ATOM(spec, i, *am);
159 | i = LOAD_TUPLE(spec, i, 3);
160 |
161 | return driver_send_term(iv->port, to, spec, i);
162 | }
163 |
164 | #define CODE_STR_SZ 64
165 |
166 | #define get_int16(s) ((((unsigned char*) (s))[0] << 8) | \
167 | (((unsigned char*) (s))[1]))
168 |
169 |
170 | #define put_int16(i, s) {((unsigned char*)(s))[0] = ((i) >> 8) & 0xff; \
171 | ((unsigned char*)(s))[1] = (i) & 0xff;}
172 |
173 | static void iv_open(t_iconvdrv *iv, char *tocode, char *fromcode)
174 | {
175 | int len;
176 | iconv_t cd;
177 | ErlDrvBinary *bin;
178 |
179 | if ((cd = iconv_open(tocode, fromcode)) == (iconv_t) -1) {
180 | driver_send_error(iv, &am_einval);
181 | }
182 | else {
183 | len = sizeof(iconv_t);
184 | if (!(bin = driver_alloc_binary(len+1))) {
185 | iconv_close(cd);
186 | driver_send_error(iv, &am_enomem);
187 | }
188 | else {
189 | memcpy(bin->orig_bytes, &cd, len);
190 | if (!strcasecmp(tocode + strlen(tocode) -6, "IGNORE")) {
191 | /* GLIBC's iconv is annoying and will throw the failure code for
192 | * invalid sequences even though we specify //IGNORE so we have to
193 | * keep track if we initalized this conversion handle with //IGNORE
194 | * or not so we can disregard the error. */
195 | bin->orig_bytes[len] = 1;
196 | } else {
197 | bin->orig_bytes[len] = 0;
198 | }
199 | driver_send_bin(iv, bin, len+1);
200 | driver_free_binary(bin);
201 | }
202 | }
203 |
204 | return;
205 | }
206 |
207 | static void iv_conv(t_iconvdrv *iv, iconv_t cd, char *ip, size_t ileft, char ignore)
208 | {
209 | size_t oleft=ileft;
210 | char *op, *buf;
211 | int olen = ileft + 1;
212 | ErlDrvBinary *bin;
213 |
214 | /* malloc enough for the input size +1 (null terminated),
215 | * with the assumption that the output length will be close to the input
216 | * length. This isn't always the case, but we realloc on E2BIG below. */
217 | buf = malloc(olen);
218 |
219 | if (!buf) {
220 | driver_send_error(iv, &am_enomem);
221 | return;
222 | }
223 |
224 | op = buf;
225 |
226 | /* Reset cd to initial state */
227 | iconv(cd, NULL, NULL, NULL, NULL);
228 |
229 | while (iconv(cd, &ip, &ileft, &op, &oleft) == (size_t) -1 &&
230 | !(ignore && errno == EILSEQ)) {
231 | if (errno == EILSEQ) {
232 | driver_send_error(iv, &am_eilseq);
233 | } else if (errno == EINVAL) {
234 | driver_send_error(iv, &am_einval);
235 | } else if (errno == E2BIG) {
236 | char *newbuf;
237 | /* allocate as much additional space as iconv says we need */
238 | newbuf = realloc(buf, olen + ileft + oleft);
239 | if (!newbuf) {
240 | free(buf); /* realloc failed, make sure we free the old buffer*/
241 | driver_send_error(iv, &am_enomem);
242 | return;
243 | }
244 | buf = newbuf;
245 | op = buf + (olen - oleft - 1);
246 | olen += ileft + oleft;
247 | oleft += ileft;
248 | /* keep going */
249 | continue;
250 | } else {
251 | driver_send_error(iv, &am_unknown);
252 | }
253 | return;
254 | }
255 | *(op++) = 0; /* ensure we null terminate */
256 |
257 | if (ileft == 0) {
258 | /* find the length of the result, minus the terminating NULL */
259 | olen = strlen(buf);
260 | if (!(bin = driver_alloc_binary(olen))) {
261 | driver_send_error(iv, &am_enomem);
262 | } else {
263 | memcpy(bin->orig_bytes, buf, olen);
264 | driver_send_bin(iv, bin, olen);
265 | driver_free_binary(bin);
266 | }
267 | }
268 |
269 | free(buf);
270 |
271 | return;
272 | }
273 |
274 | static void iv_close(t_iconvdrv *iv, iconv_t cd)
275 | {
276 | iconv_close(cd);
277 | driver_send_ok(iv);
278 | return;
279 | }
280 |
281 | static void iconvdrv_from_erlang(ErlDrvData drv_data, char *buf, int len)
282 | {
283 | t_iconvdrv *iv = (t_iconvdrv *) drv_data;
284 | char ignore = 0;
285 | char tocode[CODE_STR_SZ], fromcode[CODE_STR_SZ];
286 | char *bp=buf;
287 | unsigned int i=0;
288 | iconv_t cd;
289 |
290 | i = bp[0];
291 | bp++;
292 | switch (i) {
293 |
294 | case IV_OPEN: {
295 | /*
296 | * Format:
297 | */
298 | i = get_int16(bp);
299 | bp += 2;
300 | memcpy(tocode, bp, i);
301 | tocode[i] = '\0';
302 | bp += i;
303 | i = get_int16(bp);
304 | bp += 2;
305 | memcpy(fromcode, bp, i);
306 | fromcode[i] = '\0';
307 |
308 | iv_open(iv, tocode, fromcode);
309 | break;
310 | }
311 |
312 | case IV_CONV: {
313 | /*
314 | * Format:
315 | */
316 | i = get_int16(bp);
317 | bp += 2;
318 | memcpy(&cd, bp, i-1);
319 | memcpy(&ignore, bp + i -1, 1);
320 | bp += i;
321 | i = get_int16(bp);
322 | bp += 2;
323 |
324 | iv_conv(iv, cd, bp, i, ignore);
325 | break;
326 | }
327 |
328 | case IV_CLOSE: {
329 | /*
330 | * Format:
331 | */
332 | i = get_int16(bp);
333 | bp += 2;
334 | memcpy(&cd, bp, i - 1);
335 |
336 | iv_close(iv, cd);
337 | break;
338 | }
339 |
340 | } /* switch */
341 |
342 | return;
343 | }
344 |
345 |
346 | /*
347 | * Initialize and return a driver entry struct
348 | */
349 |
350 | DRIVER_INIT(iconvdrv)
351 | {
352 | am_ok = driver_mk_atom("ok");
353 | am_value = driver_mk_atom("value");
354 | am_error = driver_mk_atom("error");
355 | am_enomem = driver_mk_atom("enomem");
356 | am_einval = driver_mk_atom("einval");
357 | am_eilseq = driver_mk_atom("eilseq");
358 | am_e2big = driver_mk_atom("e2big");
359 | am_unknown = driver_mk_atom("unknown");
360 |
361 | iconvdrv_driver_entry.init = NULL; /* Not used */
362 | iconvdrv_driver_entry.start = iconvdrv_start;
363 | iconvdrv_driver_entry.stop = iconvdrv_stop;
364 | iconvdrv_driver_entry.output = iconvdrv_from_erlang;
365 | iconvdrv_driver_entry.ready_input = NULL;
366 | iconvdrv_driver_entry.ready_output = NULL;
367 | iconvdrv_driver_entry.driver_name = "iconv_drv";
368 | iconvdrv_driver_entry.finish = NULL;
369 | iconvdrv_driver_entry.outputv = NULL;
370 | return &iconvdrv_driver_entry;
371 | }
372 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rake/clean'
2 |
3 | Dir['tasks/**/*.rake'].each { |rake| load rake }
4 |
5 | def percent_to_color(per)
6 | if ENV['COLORTERM'].to_s.downcase == 'yes' or ENV['TERM'] =~ /-color$/
7 | if per >= 90.0
8 | colorstart = "\e[1;32m"
9 | elsif per >= 75.0
10 | colorstart = "\e[0;32m"
11 | elsif per >= 50.0
12 | colorstart = "\e[0;33m"
13 | elsif per >= 25.0
14 | colorstart = "\e[0;31m"
15 | else
16 | colorstart = "\e[1;31m"
17 | end
18 | return [colorstart, "\e[0m"]
19 | else
20 | return ["", ""]
21 | end
22 | end
23 |
24 |
25 | ROOTDIR = `erl -noshell -eval 'io:format("~n~s~n", [code:root_dir()]).' -s erlang halt | tail -n 1`.chomp
26 |
27 | if RUBY_PLATFORM =~ /darwin/i
28 | $C_FLAGS= "-fno-common -liconv -Wall -Wl,-bundle,-undefined,dynamic_lookup -fPIC -I#{ROOTDIR}/usr/include"
29 | elsif RUBY_PLATFORM =~ /linux/i or RUBY_PLATFORM =~ /netbsd/i
30 | $C_FLAGS = "-shared -Wall -I#{ROOTDIR}/usr/include -fPIC"
31 | else
32 | $C_FLAGS = "-shared -Wall -I#{ROOTDIR}/usr/include -fPIC -liconv"
33 | end
34 |
35 | if ENV['iconv']
36 | $C_FLAGS += " -I#{ENV['iconv']}/include -L#{ENV['iconv']}/lib"
37 | end
38 |
39 | INCLUDE = "include"
40 |
41 | vertest = `erl -noshell -eval 'io:format("~n~s~n", [erlang:system_info(otp_release)]).' -s erlang halt | tail -n 1`.chomp
42 | if vertest =~ /(R\d\d[AB])/
43 | OTPVERSION = $1
44 | else
45 | STDERR.puts "unable to determine OTP version! (I got #{vertest})"
46 | exit -1
47 | end
48 | ERLC_FLAGS = "-I#{INCLUDE} -D #{OTPVERSION} +warn_unused_vars +warn_unused_import +warn_exported_vars +warn_untyped_record"
49 |
50 | C_SRC = FileList['c_src/*.c']
51 | SRC = FileList['src/*.erl']
52 | HEADERS = FileList['include/*.hrl']
53 | C_HEADERS = FileList['c_src/*.h']
54 | OBJ = SRC.pathmap("%{src,ebin}X.beam")
55 | #C_OBJ = C_SRC.pathmap("%{c_src,priv}X.o")
56 | CONTRIB = FileList['contrib/*']
57 | DEBUGOBJ = SRC.pathmap("%{src,debug_ebin}X.beam")
58 | COVERAGE = SRC.pathmap("%{src,coverage}X.txt")
59 | RELEASE = FileList['src/*.rel.src'].pathmap("%{src,ebin}X")
60 |
61 | # check to see if gmake is available, if not fall back on the system make
62 | if res = `which gmake` and $?.exitstatus.zero? and not res =~ /no gmake in/
63 | MAKE = File.basename(res.chomp)
64 | else
65 | MAKE = 'make'
66 | end
67 |
68 | @maxwidth = SRC.map{|x| File.basename(x, 'erl').length}.max
69 |
70 | CLEAN.include("ebin/*.beam")
71 | CLEAN.include("ebin/*.app")
72 | CLEAN.include("ebin/*.script")
73 | CLEAN.include("ebin/*.boot")
74 | CLEAN.include("ebin/*.rel")
75 | CLEAN.include("debug_ebin/*.beam")
76 | CLEAN.include("debug_ebin/*.app")
77 | CLEAN.include("debug_ebin/*.script")
78 | CLEAN.include("debug_ebin/*.boot")
79 | CLEAN.include("coverage/*.txt")
80 | CLEAN.include("coverage/*.txt.failed")
81 | CLEAN.include("coverage/*.html")
82 | CLEAN.include("doc/*.html")
83 | CLEAN.include("c_src/*.o")
84 | CLEAN.include("priv/*")
85 |
86 | verbose(true) unless ENV['quiet']
87 |
88 | directory 'ebin'
89 | directory 'priv'
90 | directory 'debug_ebin'
91 | directory 'coverage'
92 | #directory 'doc'
93 |
94 | rule ".beam" => ["%{ebin,src}X.erl"] + HEADERS do |t|
95 | sh "erlc -pa ebin -W #{ERLC_FLAGS} +warn_missing_spec -o ebin #{t.source} "
96 | end
97 |
98 | rule ".beam" => ["%{debug_ebin,src}X.erl"] + HEADERS do |t|
99 | sh "erlc +debug_info -D TEST -pa debug_ebin -W #{ERLC_FLAGS} -o debug_ebin #{t.source} "
100 | end
101 |
102 | rule ".rel" => ["%{ebin,src}X.rel.src"] do |t|
103 | contents = File.read(t.source)
104 | #p contents
105 | while contents =~ /^[\s\t]*([-a-zA-Z0-9_]+),[\s\t]*$/
106 | app = $1
107 | if app == "erts"
108 | version = `erl -noshell -eval 'io:format("~n~s~n", [erlang:system_info(version)]).' -s erlang halt | tail -n 1`.chomp
109 | else
110 | version = `erl -noshell -eval 'application:load(#{app}), io:format("~n~s~n", [proplists:get_value(#{app}, lists:map(fun({Name, Desc, Vsn}) -> {Name, Vsn} end, application:loaded_applications()))]).' -s erlang halt | tail -n 1`.chomp
111 | end
112 | if md = /(\d+\.\d+(\.\d+(\.\d+|)|))/.match(version)
113 | contents.sub!(app, "{#{app}, \"#{md[1]}\"}")
114 | else
115 | STDERR.puts "Cannot find application #{app} mentioned in release file!"
116 | exit 1
117 | end
118 | end
119 | File.open(t.name, 'w') do |f|
120 | f.puts contents
121 | end
122 | end
123 |
124 | rule ".so" => ["%{priv,c_src}X.c"] + C_HEADERS do |t|
125 | sh "gcc #{$C_FLAGS} #{t.source} -o #{t.to_s}"
126 | end
127 |
128 | rule ".txt" => ["%{coverage,debug_ebin}X.beam"] do |t|
129 | mod = File.basename(t.source, '.beam')
130 | if ENV['modules'] and not ENV['modules'].split(',').include? mod
131 | puts "skipping tests for #{mod}"
132 | next
133 | end
134 |
135 | print " #{mod.ljust(@maxwidth - 1)} : "
136 | STDOUT.flush
137 | test_output = `erl -noshell -pa debug_ebin -pa contrib/mochiweb/ebin -sname testpx -eval ' cover:start(), cover:compile_beam("#{t.source}"), try eunit:test(#{mod}, [verbose]) of _Any -> cover:analyse_to_file(#{mod}, "coverage/#{mod}.txt"), cover:analyse_to_file(#{mod}, "coverage/#{mod}.html", [html]) catch _:_ -> io:format("This module does not provide a test() function~n"), ok end.' -s erlang halt`
138 | if /(All \d+ tests (successful|passed)|There were no tests to run|This module does not provide a test\(\) function|Test (successful|passed))/ =~ test_output
139 | File.delete(t.to_s+'.failed') if File.exists?(t.to_s+'.failed')
140 | if ENV['verbose']
141 | puts test_output.split("\n")[1..-1].map{|x| x.include?('1>') ? x.gsub(/\([a-zA-Z0-9\-@]+\)1>/, '') : x}.join("\n")
142 | else
143 | out = $1
144 | if /(All \d+ tests (successful|passed)|Test (successful|passed))/ =~ test_output
145 | colorstart, colorend = percent_to_color(80)
146 | #elsif /This module does not provide a test\(\) function/ =~ test_output
147 | #colorstart, colorend = percent_to_color(50)
148 | else
149 | colorstart, colorend = percent_to_color(50)
150 | #colorstart, colorend = ["", ""]
151 | end
152 | puts "#{colorstart}#{out}#{colorend}"
153 | #puts " #{mod.ljust(@maxwidth - 1)} : #{out}"
154 | end
155 | else
156 | puts "\e[1;35mFAILED\e[0m"
157 | puts test_output.split("\n")[1..-1].map{|x| x.include?('1>') ? x.gsub(/\([a-zA-Z0-9\-@]+\)1>/, '') : x}.join("\n")
158 | puts " #{mod.ljust(@maxwidth - 1)} : \e[1;35mFAILED\e[0m"
159 | File.delete(t.to_s) if File.exists?(t.to_s)
160 | File.new(t.to_s+'.failed', 'w').close
161 | end
162 | end
163 |
164 | task :compile => [:contrib, 'ebin', 'priv'] + HEADERS + OBJ + ['priv/iconv_drv.so'] + RELEASE do
165 | Dir["ebin/*.rel"].each do |rel|
166 | rel = File.basename(rel, '.rel')
167 | sh "erl -noshell -eval 'systools:make_script(\"ebin/#{rel}\", [{outdir, \"ebin\"}]).' -s erlang halt -pa ebin"
168 | end
169 | end
170 |
171 | task :install => [:compile] do
172 | sh "mkdir #{ROOTDIR}/lib/iconv-1.0.1" unless File.directory? "#{ROOTDIR}/lib/iconv-1.0.1"
173 | sh "cp -r src ebin c_src priv #{ROOTDIR}/lib/iconv-1.0.1"
174 | end
175 |
176 | task :contrib do
177 | CONTRIB.each do |cont|
178 | if File.exists? File.join(cont, 'Makefile')
179 | sh "#{MAKE} -C #{cont}"
180 | elsif File.exists? File.join(cont, 'Rakefile')
181 | pwd = Dir.pwd
182 | Dir.chdir(cont)
183 | sh "#{$0}"
184 | Dir.chdir(pwd)
185 | end
186 | end
187 | unless Dir["src/*.app"].length.zero?
188 | sh "cp src/*.app ebin/"
189 | end
190 | end
191 |
192 | task :default => :compile
193 |
194 | task :release => :compile
195 |
196 | desc "Alias for test:all"
197 | task :test => "test:all"
198 |
199 | desc "Generate Documentation"
200 | task :doc do
201 | sh('mkdir doc') unless File.directory? 'doc'
202 | sh("rm -rf doc/*.html && cd doc && erl -noshell -run edoc files ../#{SRC.join(" ../")} -run init stop")
203 | end
204 |
205 | namespace :test do
206 | desc "Compile .beam files with -DTEST and +debug_info => debug_ebin"
207 | task :compile => [:contrib, 'debug_ebin'] + HEADERS + DEBUGOBJ + ['priv/iconv_drv.so']
208 |
209 | task :contrib do
210 | CONTRIB.each do |cont|
211 | if File.exists? File.join(cont, 'Makefile')
212 | sh "#{MAKE} -C #{cont}"
213 | elsif File.exists? File.join(cont, 'Rakefile')
214 | pwd = Dir.pwd
215 | Dir.chdir(cont)
216 | sh "#{$0} debug=yes"
217 | Dir.chdir(pwd)
218 | end
219 | end
220 | unless Dir["src/*.app"].length.zero?
221 | sh "cp src/*.app debug_ebin/"
222 | end
223 | end
224 |
225 | desc "run eunit tests and output coverage reports"
226 | task :all => [:compile, :eunit, :report_coverage]
227 |
228 | desc "run only the eunit tests"
229 | task :eunit => [:compile, 'coverage'] + COVERAGE
230 |
231 | desc "rerun any outstanding tests and report the coverage"
232 | task :report_coverage => [:eunit, :report_current_coverage]
233 |
234 | desc "report the percentage code coverage of the last test run"
235 | task :report_current_coverage do
236 | global_total = 0
237 | files = (Dir['coverage/*.txt'] + Dir['coverage/*.txt.failed']).sort
238 | maxwidth = files.map{|x| x = File.basename(x, '.failed'); File.basename(x, ".txt").length}.max
239 | puts "Code coverage:"
240 | files.each do |file|
241 | if file =~ /\.txt\.failed$/
242 | if ENV['COLORTERM'].to_s.downcase == 'yes' or ENV['TERM'] =~ /-color$/
243 | puts " #{File.basename(file, ".txt.failed").ljust(maxwidth)} : \e[1;35mFAILED\e[0m"
244 | else
245 | puts " #{File.basename(file, ".txt.failed").ljust(maxwidth)} : FAILED"
246 | end
247 | else
248 | total = 0
249 | tally = 0
250 | File.read(file).each do |line|
251 | if line =~ /^\s+[1-9][0-9]*\.\./
252 | total += 1
253 | tally += 1
254 | elsif line =~ /^\s+0\.\./ and not line =~ /^-module/
255 | total += 1
256 | end
257 | end
258 | per = tally/total.to_f * 100
259 | colorstart, colorend = percent_to_color(per)
260 | puts " #{File.basename(file, ".txt").ljust(maxwidth)} : #{colorstart}#{sprintf("%.2f%%", (tally/(total.to_f)) * 100)}#{colorend}"
261 | global_total += (tally/(total.to_f)) * 100
262 | end
263 | end
264 | colorstart, colorend = percent_to_color(global_total/files.length)
265 | puts "Overall coverage: #{colorstart}#{sprintf("%.2f%%", global_total/files.length)}#{colorend}"
266 | end
267 |
268 | task :report_missing_specs do
269 | unspecced = []
270 | ignored = %w{handle_info handle_cast handle_call code_change terminate init}
271 | puts "Functions missing specs:"
272 | SRC.each do |src|
273 | contents = File.read(src)
274 | contents.each do |line|
275 | if md = /^([a-z_]+)\(.*?\) ->/.match(line) and not ignored.include?(md[1]) and not md[1][-5..-1] == '_test' and not md[1][-6..-1] == '_test_'
276 | unless /^-spec\(#{md[1]}\//.match(contents)
277 | unspecced << File.basename(src, '.erl') + ':'+ md[1]
278 | end
279 | end
280 | end
281 | end
282 | puts " "+unspecced.uniq.join("\n ")
283 | end
284 |
285 | desc "run the dialyzer"
286 | task :dialyzer do
287 | print "running dialyzer..."
288 | `dialyzer --check_plt`
289 | if $?.exitstatus != 0
290 | puts 'no PLT'
291 | puts "The dialyzer can't find the initial PLT, you can try building one using `rake test:build_plt`. This can take quite some time."
292 | exit(1)
293 | end
294 | STDOUT.flush
295 | # Add -DEUNIT=1 here to make dialyzer evaluate the code in the test cases. This generates some spurious warnings so
296 | # it's not set normally but it can be very helpful occasionally.
297 | dialyzer_flags = ""
298 | dialyzer_flags += " -DTEST=1" if ENV['dialyzer_debug']
299 | dialyzer_flags += " -Wunderspecs" if ENV['dialyzer_underspecced']
300 | contribfiles = Dir['contrib**/*.erl'].join(' ')
301 | dialyzer_output = `dialyzer -D#{OTPVERSION}=1 #{dialyzer_flags} --src -I include -c #{SRC.join(' ')} #{contribfiles}`
302 | #puts dialyzer_output
303 | if $?.exitstatus.zero?
304 | puts 'ok'
305 | else
306 | puts 'not ok'
307 | puts dialyzer_output
308 | end
309 | end
310 |
311 | desc "try to create the dialyzer's initial PLT"
312 | task :build_plt do
313 | out = `which erlc`
314 | foo = out.split('/')[0..-3].join('/')+'/lib/erlang/lib'
315 | sh "dialyzer --build_plt -r #{foo}/kernel*/ebin #{foo}/stdlib*/ebin #{foo}/mnesia*/ebin #{foo}/crypto*/ebin #{foo}/eunit*/ebin"
316 | end
317 | end
318 |
--------------------------------------------------------------------------------