├── 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 | --------------------------------------------------------------------------------