Compare commits
10 Commits
ad3a61b064
...
ed00812ac3
Author | SHA1 | Date |
---|---|---|
Charles Iliya Krempeaux | ed00812ac3 | |
Charles Iliya Krempeaux | fcb820cf6f | |
Charles Iliya Krempeaux | fef92ac5cc | |
Charles Iliya Krempeaux | 08353d00b9 | |
Charles Iliya Krempeaux | 94e427931f | |
Charles Iliya Krempeaux | 18a8a568a2 | |
Charles Iliya Krempeaux | 857efdf601 | |
Charles Iliya Krempeaux | 5a9076dd18 | |
Charles Iliya Krempeaux | 9dcc5386e5 | |
Charles Iliya Krempeaux | 1b41d2f679 |
|
@ -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"
|
||||
|
||||
// ...
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
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 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
8
go.sum
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue