1 # Simple $5 Iconv implementation using FFI
6 set_ffi_lib FFI::USE_THIS_PROCESS_AS_LIBRARY, "libiconv"
8 attach_function "iconv_open", :create, [:string, :string], :pointer
9 attach_function "iconv_close", :close, [:pointer], :int
10 attach_function "iconv", :convert, [:pointer, :pointer, :pointer, :pointer, :pointer], :long
16 def initialize(mesg, success, failed)
23 "<##{self.class}: #{success.inspect}, #{failed.inspect}>"
27 class BrokenLibrary < RuntimeError
31 class IllegalSequence < ArgumentError
35 class InvalidCharacter < ArgumentError
39 class InvalidEncoding < ArgumentError
43 class OutOfRange < RuntimeError
48 if obj.instance_of? String
51 if obj.respond_to? :to_str
54 raise TypeError.new "can't convert #{obj.class} into String"
59 def initialize(to, from)
60 @to, @from = string_value(to), string_value(from)
61 @handle = Iconv.create @to, @from
63 Errno.handle if @handle.address == -1
65 raise InvalidEncoding.new("invalid encoding (#{@to.inspect}, #{@from.inspect})", nil, [@to, @from])
77 Errno.handle if Iconv.close(@handle) != 0
81 def self.open(to, from)
83 return cd unless block_given?
92 def self.iconv(to, from, *rest)
95 open(to, from) do |cd|
96 rest.each_with_index do |x, i|
98 converted << cd.iconv(x)
100 converted << e.success
101 raise e.class.new(e.message, converted, [e.failed] + rest[i + 1..-1])
109 def self.conv(to, from, str)
111 iconv(to, from, str).join
113 # e.success is nil if open throws InvalidEncoding
114 # e.success is an Array if #iconv throws any Failure
115 raise e.class.new(e.message, (e.success.instance_of? Array) ? e.success.join : e.success, e.failed)
119 def get_success(os, l2)
120 os.read_string(l2.read_int - os.address)
123 def get_failed(is, ic, l1)
124 (is + (l1.read_int - is.address)).read_string(ic.read_long)
130 def iconv(str, start = 0, length = -1)
132 start = 0 if not start
133 length = -1 if not length
135 raise ArgumentError.new("closed iconv") if @closed
137 l1 = MemoryPointer.new(:pointer)
138 l2 = MemoryPointer.new(:pointer)
140 ic = MemoryPointer.new(:long)
143 if not str.instance_of? String then
144 if str.respond_to? :to_str then
147 raise TypeError.new "can't convert #{str.class} into String"
151 is = MemoryPointer.new(str.size + 10)
152 is.write_string str, str.size
154 l1.write_long is.address
156 start += str.size if start < 0
157 if start < 0 || start >= str.size
160 length += str.size + 1 if length < 0
168 l1.write_long(is.address + start)
169 length = str.size if length > str.size
176 else # if str is nil, reset the shift state
182 # Totally made up metric
184 os = MemoryPointer.new(output)
185 oc = MemoryPointer.new(:long)
191 l2.write_long os.address
193 count = Iconv.convert @handle, l1, ic, l2, oc
195 if oc.read_long < 0 || oc.read_long > output then
196 raise OutOfRange.new("bug?(output length = #{output - oc.read_long})", get_success(os, l2), get_failed(is, ic, l1))
201 Errno.handle if count == -1
202 rescue Errno::EILSEQ => e
203 raise IllegalSequence.new(nil, get_success(os, l2), get_failed(is, ic, l1))
204 rescue Errno::E2BIG => e
205 result += get_success(os, l2)
207 rescue Errno::EINVAL => e
208 failed = get_failed(is, ic, l1)
209 raise InvalidCharacter.new(failed.inspect, get_success(os, l2), failed)
210 rescue RuntimeError => e
211 raise BrokenLibrary.new(nil, get_success(os, l2), get_failed(is, ic, l1))
213 elsif ic.read_long > 0 then
214 raise IllegalSequence.new(nil, get_success(os, l2), get_failed(is, ic, l1))
217 result += get_success(os, l2)
224 [l1, l2, is, ic, os, oc].each do |mp|