Compare commits
No commits in common. "ed00812ac3953c521a14e5abea700b62559cf156" and "ad3a61b064b4c3c1bf9e8d5a3bc790f37548367a" have entirely different histories.
ed00812ac3
...
ad3a61b064
|
@ -5,14 +5,14 @@ i.e., lines that end with a "\r\n".
|
||||||
|
|
||||||
## Documention
|
## Documention
|
||||||
|
|
||||||
Online documentation, which includes examples, can be found at: http://godoc.org/sourcecode.social/reiver/go-netln
|
Online documentation, which includes examples, can be found at: http://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)
|
[![GoDoc](https://godoc.org/github.com/reiver/go-netln?status.svg)](https://godoc.org/github.com/reiver/go-netln)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "sourcecode.social/reiver/go-netln"
|
import "github.com/reiver/go-netln"
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
|
|
121
copyline.go
121
copyline.go
|
@ -1,121 +0,0 @@
|
||||||
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
231
copyline_test.go
|
@ -1,231 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package netln
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sourcecode.social/reiver/go-erorr"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
errRuneError = erorr.Error("rune error")
|
|
||||||
)
|
|
6
go.mod
6
go.mod
|
@ -1,8 +1,8 @@
|
||||||
module sourcecode.social/reiver/go-netln
|
module github.com/reiver/go-netln
|
||||||
|
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
sourcecode.social/reiver/go-erorr v0.0.0-20230922202459-231149d185a1 // indirect
|
github.com/reiver/go-fck v0.0.1 // indirect
|
||||||
sourcecode.social/reiver/go-utf8 v0.0.0-20230818133704-d38de8eb477f // indirect
|
github.com/reiver/go-utf8 v2.0.1+incompatible // indirect
|
||||||
)
|
)
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -1,4 +1,4 @@
|
||||||
sourcecode.social/reiver/go-erorr v0.0.0-20230922202459-231149d185a1 h1:wpnz4JicQBLWrgGphYBls7DysIFCcnWgDz/vce/sY8E=
|
github.com/reiver/go-fck v0.0.1 h1:GhOiIp/4Au3iPUVC1YRfJJS5CdvLDNF9qt/n1lwgWnM=
|
||||||
sourcecode.social/reiver/go-erorr v0.0.0-20230922202459-231149d185a1/go.mod h1:NFtd7fzEf0r6A6R7JXYZfayRhPaJy0zt/18VWoLzrxA=
|
github.com/reiver/go-fck v0.0.1/go.mod h1:i77J0nD9GkSF0osPcURZbv9u19F0keF/mrhkgIu9wvM=
|
||||||
sourcecode.social/reiver/go-utf8 v0.0.0-20230818133704-d38de8eb477f h1:fVoIT3wG97EMKMDpR5KU4b5uwFKLl5e9AOV72o45M0k=
|
github.com/reiver/go-utf8 v2.0.1+incompatible h1:f1rRbwTIcUaX+1wnLM1+FWelKegAxTURE9MkZCRVOPM=
|
||||||
sourcecode.social/reiver/go-utf8 v0.0.0-20230818133704-d38de8eb477f/go.mod h1:xZZIkdX6xIkkzalD1YTiM1F3Os9xPaIY7z9s6EBDmCI=
|
github.com/reiver/go-utf8 v2.0.1+incompatible/go.mod h1:tfGntNDUeEZIAsz0mTu0lF7/IlErc+BpQgO6yxYmJig=
|
||||||
|
|
33
readrune.go
33
readrune.go
|
@ -1,33 +0,0 @@
|
||||||
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
26
writerune.go
|
@ -1,26 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
Loading…
Reference in New Issue