diff --git a/iconv_test.go b/iconv_test.go new file mode 100644 index 0000000..f976ded --- /dev/null +++ b/iconv_test.go @@ -0,0 +1,116 @@ +package iconv + +import ( + "testing" +) + +type iconvTest struct { + description string + input string + inputEncoding string + output string + outputEncoding string + bytesRead int + bytesWritten int + err Error +} + +var iconvTests = []iconvTest { + iconvTest{ + "simple utf-8 to latin1 conversion success", + "Hello World!", "utf-8", + "Hello World!", "latin1", + 12, 12, nil, + }, + iconvTest{ + "invalid source encoding causes EINVAL", + "", "doesnotexist", + "", "utf-8", + 0, 0, EINVAL, + }, + iconvTest{ + "invalid destination encoding causes EINVAL", + "", "utf-8", + "", "doesnotexist", + 0, 0, EINVAL, + }, + iconvTest{ + "invalid input sequence causes EILSEQ", + "\xFF", "utf-8", + "", "latin1", + 0, 0, EILSEQ, + }, + iconvTest{ + "invalid input causes partial output and EILSEQ", + "Hello\xFF", "utf-8", + "Hello", "latin1", + 5, 5, EILSEQ, + }, +} + +func TestConvertString(t *testing.T) { + for _, test := range iconvTests { + // perform the conversion + output, err := ConvertString(test.input, test.inputEncoding, test.outputEncoding) + + // check that output and err match + if output != test.output { + t.Errorf("test \"%s\" failed, output did not match expected", test.description) + } + + // check that err is same as expected + if err != test.err { + if test.err != nil { + if err != nil { + t.Errorf("test \"%s\" failed, got %s when expecting %s", test.description, err, test.err) + } else { + t.Errorf("test \"%s\" failed, got nil when expecting %s", test.description, test.err) + } + } else { + t.Errorf("test \"%s\" failed, got unexpected error: %s", test.description, err) + } + } + } +} + +func TestConvert(t *testing.T) { + for _, test := range iconvTests { + // setup input buffer + input := []byte(test.input) + + // setup a buffer as large as the expected bytesWritten + output := make([]byte, 50) + + // peform the conversion + bytesRead, bytesWritten, err := Convert(input, output, test.inputEncoding, test.outputEncoding) + + // check that bytesRead is same as expected + if bytesRead != test.bytesRead { + t.Errorf("test \"%s\" failed, bytesRead did not match expected", test.description) + } + + // check that bytesWritten is same as expected + if bytesWritten != test.bytesWritten { + t.Errorf("test \"%s\" failed, bytesWritten did not match expected", test.description) + } + + // check output bytes against expected - simplest to convert output to + // string and then do an equality check which is actually a byte wise operation + if string(output[:bytesWritten]) != test.output { + t.Errorf("test \"%s\" failed, output did not match expected", test.description) + } + + // check that err is same as expected + if err != test.err { + if test.err != nil { + if err != nil { + t.Errorf("test \"%s\" failed, got %s when expecting %s", test.description, err, test.err) + } else { + t.Errorf("test \"%s\" failed, got nil when expecting %s", test.description, test.err) + } + } else { + t.Errorf("test \"%s\" failed, got unexpected error: %s", test.description, err) + } + } + } +} \ No newline at end of file diff --git a/writer.go b/writer.go new file mode 100644 index 0000000..a8f8370 --- /dev/null +++ b/writer.go @@ -0,0 +1,85 @@ +package iconv + +import ( + "io" + "os" +) + +type Writer struct { + destination io.Writer + converter *Converter + buffer []byte + readPos, writePos int + err os.Error +} + +func NewWriter(destination io.Writer, fromEncoding string, toEncoding string) (*Writer, os.Error) { + // create a converter + converter, err := NewConverter(fromEncoding, toEncoding) + + if err == nil { + return NewWriterFromConverter(destination, converter), err + } + + // return the error + return nil, err +} + +func NewWriterFromConverter(destination io.Writer, converter *Converter) (writer *Writer) { + writer = new(Writer) + + // copy elements + writer.destination = destination + writer.converter = converter + + // create 8K buffers + writer.buffer = make([]byte, 8 * 1024) + + return writer +} + +func (this *Writer) emptyBuffer() { + // write new data out of buffer + bytesWritten, err := this.destination.Write(this.buffer[this.readPos:this.writePos]) + + // update read position + this.readPos += bytesWritten + + // slide existing data to beginning + if this.readPos > 0 { + // copy current bytes - is this guaranteed safe? + copy(this.buffer, this.buffer[this.readPos:this.writePos]) + + // adjust positions + this.writePos -= this.readPos + this.readPos = 0 + } + + // track any reader error / EOF + if err != nil { + this.err = err + } +} + +// implement the io.Writer interface +func (this *Writer) Write(p []byte) (n int, err os.Error) { + // write data into our internal buffer + bytesRead, bytesWritten, err := this.converter.Convert(p, this.buffer[this.writePos:]) + + // update bytes written for return + n += bytesRead + this.writePos += bytesWritten + + // checks for when we have a full buffer + for this.writePos > 0 { + // if we have an error, just return it + if this.err != nil { + return + } + + // else empty the buffer + this.emptyBuffer() + } + + return n, err +}