Compare commits

...

10 Commits

8 changed files with 430 additions and 10 deletions

View File

@ -5,14 +5,14 @@ i.e., lines that end with a "\r\n".
## Documention
Online documentation, which includes examples, can be found at: http://godoc.org/github.com/reiver/go-netln
Online documentation, which includes examples, can be found at: http://godoc.org/sourcecode.social/reiver/go-netln
[![GoDoc](https://godoc.org/github.com/reiver/go-netln?status.svg)](https://godoc.org/github.com/reiver/go-netln)
[![GoDoc](https://godoc.org/sourcecode.social/reiver/go-netln?status.svg)](https://godoc.org/sourcecode.social/reiver/go-netln)
## Examples
```go
import "github.com/reiver/go-netln"
import "sourcecode.social/reiver/go-netln"
// ...

121
copyline.go 100644
View File

@ -0,0 +1,121 @@
package netln
import (
"errors"
"io"
)
// CopyLine reads in a line from reader and writes that line to writer (without the line terminator).
//
// For CopyLine, a line is terminated by the "\r\n" characters.
//
// So, for example, if from reader CopyLine read in:
//
// "Hello world!\r\n"
//
// Then what CopyLine would write to writer is:
//
// "Hello world!"
//
// (Notice that the "\r\n" at the end is missing.)
//
// Note that n64 represents how many bytes were written, not read.
func CopyLine(writer io.Writer, reader io.Reader) (n64 int64, err error) {
var eof bool
for {
var r0 rune
var size0 int
{
r0, size0, err = readRune(reader)
eof = errors.Is(err, io.EOF)
if nil != err && !eof {
/////////////////////////////// RETURN
return
}
}
if size0 <= 0 {
/////////////// BREAK
break
}
if '\r' != r0 || eof {
err = writeRune(writer, r0, size0)
if nil != err {
/////////////////////////////// RETURN
return
}
n64 += int64(size0)
if eof {
/////////////////////// BREAK
break
} else {
/////////////////////// CONTINUE
continue
}
}
var r1 rune
var size1 int
{
r1, size1, err = readRune(reader)
eof = errors.Is(err, io.EOF)
if nil != err && !eof {
/////////////////////////////// RETURN
return
}
}
if size1 <= 0 {
err = writeRune(writer, r0, size0)
if nil != err {
/////////////////////////////// RETURN
return
}
n64 += int64(size0)
/////////////// BREAK
break
}
if '\n' != r1 {
{
err = writeRune(writer, r0, size0)
if nil != err {
/////////////////////////////////////// RETURN
return
}
n64 += int64(size0)
}
{
err = writeRune(writer, r1, size1)
if nil != err {
/////////////////////////////////////// RETURN
return
}
n64 += int64(size1)
}
if eof {
/////////////////////////////// BREAK
break
} else {
/////////////////////////////// CONTINUE
continue
}
}
/////// BREAK
break
}
if eof && n64 <= 0 {
return n64, io.EOF
}
return n64, nil
}

231
copyline_test.go 100644
View File

@ -0,0 +1,231 @@
package netln_test
import (
"sourcecode.social/reiver/go-netln"
"io"
"strings"
"testing"
)
func TestCopyLine(t *testing.T) {
tests := []struct{
Src string
Expected string
}{
{
Src: "apple banana cherry",
Expected: "apple banana cherry",
},
{
Src: "apple banana cherry"+"\r",
Expected: "apple banana cherry"+"\r",
},
{
Src: "apple banana cherry"+"\n",
Expected: "apple banana cherry"+"\n",
},
{
Src: "apple banana cherry"+"\r\n",
Expected: "apple banana cherry",
},
{
Src: "apple banana cherry"+"\r"+"hello world",
Expected: "apple banana cherry"+"\r"+"hello world",
},
{
Src: "apple banana cherry"+"\n"+"hello world",
Expected: "apple banana cherry"+"\n"+"hello world",
},
{
Src: "apple banana cherry"+"\r\n"+"hello world",
Expected: "apple banana cherry",
},
{
Src: "apple banana cherry"+"\r"+"hello world"+"\r",
Expected: "apple banana cherry"+"\r"+"hello world"+"\r",
},
{
Src: "apple banana cherry"+"\r"+"hello world"+"\n",
Expected: "apple banana cherry"+"\r"+"hello world"+"\n",
},
{
Src: "apple banana cherry"+"\r"+"hello world"+"\r\n",
Expected: "apple banana cherry"+"\r"+"hello world",
},
{
Src: "apple banana cherry"+"\n"+"hello world"+"\r",
Expected: "apple banana cherry"+"\n"+"hello world"+"\r",
},
{
Src: "apple banana cherry"+"\n"+"hello world"+"\n",
Expected: "apple banana cherry"+"\n"+"hello world"+"\n",
},
{
Src: "apple banana cherry"+"\n"+"hello world"+"\r\n",
Expected: "apple banana cherry"+"\n"+"hello world",
},
{
Src: "apple banana cherry"+"\r\n"+"hello world"+"\r\n",
Expected: "apple banana cherry",
},
{
Src: "r۵≡🙂",
Expected: "r۵≡🙂",
},
{
Src: "r۵≡🙂"+"\r",
Expected: "r۵≡🙂"+"\r",
},
{
Src: "r۵≡🙂"+"\n",
Expected: "r۵≡🙂"+"\n",
},
{
Src: "r۵≡🙂"+"\r\n",
Expected: "r۵≡🙂",
},
{
Src: "r۵≡🙂"+"\r"+"once twice thrice fource",
Expected: "r۵≡🙂"+"\r"+"once twice thrice fource",
},
{
Src: "r۵≡🙂"+"\r"+"once twice thrice fource"+"\r",
Expected: "r۵≡🙂"+"\r"+"once twice thrice fource"+"\r",
},
{
Src: "r۵≡🙂"+"\r"+"once twice thrice fource"+"\n",
Expected: "r۵≡🙂"+"\r"+"once twice thrice fource"+"\n",
},
{
Src: "r۵≡🙂"+"\r"+"once twice thrice fource"+"\r\n",
Expected: "r۵≡🙂"+"\r"+"once twice thrice fource",
},
{
Src: "r۵≡🙂"+"\n"+"once twice thrice fource",
Expected: "r۵≡🙂"+"\n"+"once twice thrice fource",
},
{
Src: "r۵≡🙂"+"\n"+"once twice thrice fource"+"\r",
Expected: "r۵≡🙂"+"\n"+"once twice thrice fource"+"\r",
},
{
Src: "r۵≡🙂"+"\n"+"once twice thrice fource"+"\n",
Expected: "r۵≡🙂"+"\n"+"once twice thrice fource"+"\n",
},
{
Src: "r۵≡🙂"+"\n"+"once twice thrice fource"+"\r\n",
Expected: "r۵≡🙂"+"\n"+"once twice thrice fource",
},
{
Src: "r۵≡🙂"+"\r\n"+"once twice thrice fource",
Expected: "r۵≡🙂",
},
{
Src: "r۵≡🙂"+"\r\n"+"once twice thrice fource"+"\r",
Expected: "r۵≡🙂",
},
{
Src: "r۵≡🙂"+"\r\n"+"once twice thrice fource"+"\n",
Expected: "r۵≡🙂",
},
{
Src: "r۵≡🙂"+"\r\n"+"once twice thrice fource"+"\r\n",
Expected: "r۵≡🙂",
},
{
Src: "once"+"\r\n"+"twice"+"\r\n"+"thrice"+"\r\n"+"fource"+"\r\n",
Expected: "once",
},
}
for testNumber, test := range tests {
var actualStorage strings.Builder
var reader io.Reader = strings.NewReader(test.Src)
actualN, err := netln.CopyLine(&actualStorage, reader)
if nil != err {
t.Errorf("For test #%d, did not expect an error but actually got one.", testNumber)
t.Logf("ERROR: (%T) %q", err, err)
t.Logf("SRC: %q", test.Src)
t.Logf("ACTUAL-N: %d", actualN)
continue
}
{
var expected int64 = int64(len(test.Expected))
var actual int64 = actualN
if expected != actual {
t.Errorf("For test #%d, the actual number of bytes written is not what was expected.", testNumber)
t.Logf("EXPECTED: %d bytes", expected)
t.Logf("ACTUAL: %d bytes", actual)
t.Logf("SRC: %q", test.Src)
t.Logf("EXPECTED-DST: %q", test.Expected)
t.Logf("ACTUAL-DST: %q", actualStorage.String())
continue
}
}
{
var expected string = test.Expected
var actual string = actualStorage.String()
if expected != actual {
t.Errorf("For test #%d, the actual value of what was written is not what was expected", testNumber)
t.Logf("EXPECTED: %q", expected)
t.Logf("ACTUAL: %q", actual)
t.Logf("SRC: %q", test.Src)
continue
}
}
}
}
func TestCopyLine_empty(t *testing.T) {
var reader io.Reader = strings.NewReader("")
var actualStorage strings.Builder
actualN, err := netln.CopyLine(&actualStorage, reader)
if nil == err {
t.Error("Expected an error but did not actually get one.")
t.Logf("ERROR: (%T) %q", err, err)
return
}
{
var expected error = io.EOF
var actual error = err
if expected != actual {
t.Errorf("The actual error was not what was expeceted.")
t.Logf("EXPECTED-ERROR: (%T) %q", expected, expected)
t.Logf("ACTUAL-ERROR: (%T) %q", actual, actual)
return
}
}
{
var expected int64 = 0
var actual int64 = actualN
if expected != actual {
t.Error("The actual number of bytes written is not what was expected.")
t.Logf("EXPECTED: %d bytes", expected)
t.Logf("ACTUAL: %d bytes", actual)
return
}
}
}

9
errors.go 100644
View File

@ -0,0 +1,9 @@
package netln
import (
"sourcecode.social/reiver/go-erorr"
)
const (
errRuneError = erorr.Error("rune error")
)

6
go.mod
View File

@ -1,8 +1,8 @@
module github.com/reiver/go-netln
module sourcecode.social/reiver/go-netln
go 1.18
require (
github.com/reiver/go-fck v0.0.1 // indirect
github.com/reiver/go-utf8 v2.0.1+incompatible // indirect
sourcecode.social/reiver/go-erorr v0.0.0-20230922202459-231149d185a1 // indirect
sourcecode.social/reiver/go-utf8 v0.0.0-20230818133704-d38de8eb477f // indirect
)

8
go.sum
View File

@ -1,4 +1,4 @@
github.com/reiver/go-fck v0.0.1 h1:GhOiIp/4Au3iPUVC1YRfJJS5CdvLDNF9qt/n1lwgWnM=
github.com/reiver/go-fck v0.0.1/go.mod h1:i77J0nD9GkSF0osPcURZbv9u19F0keF/mrhkgIu9wvM=
github.com/reiver/go-utf8 v2.0.1+incompatible h1:f1rRbwTIcUaX+1wnLM1+FWelKegAxTURE9MkZCRVOPM=
github.com/reiver/go-utf8 v2.0.1+incompatible/go.mod h1:tfGntNDUeEZIAsz0mTu0lF7/IlErc+BpQgO6yxYmJig=
sourcecode.social/reiver/go-erorr v0.0.0-20230922202459-231149d185a1 h1:wpnz4JicQBLWrgGphYBls7DysIFCcnWgDz/vce/sY8E=
sourcecode.social/reiver/go-erorr v0.0.0-20230922202459-231149d185a1/go.mod h1:NFtd7fzEf0r6A6R7JXYZfayRhPaJy0zt/18VWoLzrxA=
sourcecode.social/reiver/go-utf8 v0.0.0-20230818133704-d38de8eb477f h1:fVoIT3wG97EMKMDpR5KU4b5uwFKLl5e9AOV72o45M0k=
sourcecode.social/reiver/go-utf8 v0.0.0-20230818133704-d38de8eb477f/go.mod h1:xZZIkdX6xIkkzalD1YTiM1F3Os9xPaIY7z9s6EBDmCI=

33
readrune.go 100644
View File

@ -0,0 +1,33 @@
package netln
import (
"sourcecode.social/reiver/go-utf8"
"errors"
"fmt"
"io"
)
// readRune deals with reading a single rune.
//
// It also makes sure that any error condition is represented as a Go error,
// rather than a utf8.RuneError.
//
// It also wraps any errors, and provides a more appropriate error message.
func readRune(reader io.Reader) (r rune, size int, err error) {
r, size, err = utf8.ReadRune(reader)
if nil != err && !errors.Is(err, io.EOF) {
err = fmt.Errorf("problem reading UTF-8 character: %w", err)
/////////////// RETURN
return
}
if utf8.RuneError == r {
err = fmt.Errorf("problem reading UTF-8 character: %w", errRuneError)
/////////////// RETURN
return
}
/////// RETURN
return
}

26
writerune.go 100644
View File

@ -0,0 +1,26 @@
package netln
import (
"sourcecode.social/reiver/go-utf8"
"fmt"
"io"
)
// writeRune deals with writing a single rune.
//
// It also makes sure that any error condition is represented as a Go error.
//
// It also wraps any errors, and provides a more appropriate error message.
func writeRune(writer io.Writer, r rune, expectedWritten int) error {
n, err := utf8.WriteRune(writer, r)
if nil != err {
return fmt.Errorf("problem writing UTF-8 character %U: %w", r, err)
}
if expectedWritten != n {
return fmt.Errorf("problem writing UTF-8 character %U — expected to write %d bytes, but actually wrote %b bytes", r, expectedWritten, n)
}
return nil
}