├── LICENSE ├── README.md ├── go.mod ├── lines.go ├── lines_test.go ├── string_test.go └── test.txt /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2015, Daisuke (yet another) Maki 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-lines 2 | 3 | import "github.com/Maki-Daisuke/go-lines" 4 | 5 | Package lines makes it a bit easier to read lines from text files in Go. 6 | 7 | 8 | ### Description 9 | 10 | There are so many ways to read lines in Go! But, I wanted to write less lines of 11 | code as possible. I think life is too short to write lines of code to read 12 | lines.) 13 | 14 | For example, you need to write code like the following to read line from STDIN: 15 | 16 | ```go 17 | import ( 18 | "bufio" 19 | "io" 20 | "os" 21 | ) 22 | 23 | func main(){ 24 | r := bufio.NewReader(os.Stdin) 25 | line := "" 26 | for { 27 | l, isPrefix, err := r.ReadLine() 28 | if err == io.EOF { 29 | break 30 | } else if err != nil { 31 | panic(err) 32 | } 33 | line += l 34 | if !isPrefix { 35 | do_something_with(line) // this is what really I want to do. 36 | line = "" 37 | } 38 | } 39 | } 40 | ``` 41 | 42 | With go-lines package and Go 1.23's range over func feature, you can write like this: 43 | 44 | ```go 45 | import ( 46 | "os" 47 | "github.com/Maki-Daisuke/go-lines" 48 | ) 49 | 50 | func main(){ 51 | for line := range lines.Reader(os.Stdin) { 52 | do_something_with(line) 53 | } 54 | } 55 | ``` 56 | 57 | Yay! It's much less lines of code and much cleaner! 58 | 59 | If an error occurs during reading, the Reader function will panic. 60 | If you want to handle errors, you can use a recover: 61 | 62 | ```go 63 | import ( 64 | "fmt" 65 | "os" 66 | "github.com/Maki-Daisuke/go-lines" 67 | ) 68 | 69 | func main(){ 70 | defer func() { 71 | if err := recover(); err != nil { 72 | // Handle the error 73 | fmt.Println("Error:", err) 74 | } 75 | }() 76 | 77 | for line := range lines.Reader(os.Stdin) { 78 | do_something_with(line) 79 | } 80 | } 81 | ``` 82 | 83 | It's still less lines of code, isn't it? 84 | 85 | 86 | ## Usage 87 | 88 | #### func Reader 89 | 90 | ```go 91 | func Reader(r io.Reader) func(yield func(string) bool) 92 | ``` 93 | `Reader` converts a `io.Reader` to a func that can be used with range to iterate over lines. 94 | If an error occurs during reading, the function will panic. With Go 1.23's range over func 95 | feature, you can use it like: 96 | 97 | ```go 98 | for line := range Reader(reader) { 99 | do_something_with(line) 100 | } 101 | ``` 102 | 103 | #### func String 104 | 105 | ```go 106 | func String(s string) func(yield func(string) bool) 107 | ``` 108 | `String` takes a string and returns a func that can be used with range to iterate over 109 | lines in the string. It internally uses a strings.Reader. With Go 1.23's range over func 110 | feature, you can use it like: 111 | 112 | ```go 113 | s := "line1\nline2\nline3" 114 | for line := range String(s) { 115 | do_something_with(line) 116 | } 117 | ``` 118 | 119 | 120 | ## License 121 | 122 | The Simplified BSD License (2-clause). 123 | See [LICENSE](LICENSE) file also. 124 | 125 | 126 | ## Author 127 | 128 | Daisuke (yet another) Maki 129 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Maki-Daisuke/go-lines 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /lines.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Daisuke (yet another) Maki. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package lines makes it a bit easier to read lines from text files in Go. 7 | 8 | # Description 9 | 10 | There are so many ways to read lines in Go! But, I wanted to write less lines of 11 | code as possible. I think life is too short to write lines of code to read lines.) 12 | 13 | For example, you need to write code like the following to read line from STDIN: 14 | 15 | import ( 16 | "bufio" 17 | "io" 18 | "os" 19 | ) 20 | 21 | func main(){ 22 | r := bufio.NewReader(os.Stdin) 23 | line := "" 24 | for { 25 | l, isPrefix, err := r.ReadLine() 26 | if err == io.EOF { 27 | break 28 | } else if err != nil { 29 | panic(err) 30 | } 31 | line += l 32 | if !isPrefix { 33 | do_something_with(line) // this is what really I want to do. 34 | line = "" 35 | } 36 | } 37 | } 38 | 39 | With this package and Go 1.23's range over func feature, you can write like this: 40 | 41 | import ( 42 | "os" 43 | . "github.com/Maki-Daisuke/go-lines" 44 | ) 45 | 46 | func main(){ 47 | for line := range lines.Reader(os.Stdin) { 48 | do_something_with(line) 49 | } 50 | } 51 | 52 | Yay! It's much less lines of code and much cleaner! 53 | 54 | If an error occurs during reading, the Reader function will panic. 55 | If you want to handle errors, you can use a recover: 56 | 57 | import ( 58 | "fmt" 59 | "os" 60 | . "github.com/Maki-Daisuke/go-lines" 61 | ) 62 | 63 | func main(){ 64 | defer func() { 65 | if err := recover(); err != nil { 66 | // Handle the error 67 | } 68 | }() 69 | 70 | for line := range lines.Reader(os.Stdin) { 71 | do_something_with(line) 72 | } 73 | } 74 | 75 | It's still less lines of code, isn't it? 76 | */ 77 | package lines 78 | 79 | import ( 80 | "bufio" 81 | "io" 82 | "strings" 83 | ) 84 | 85 | // `Reader` converts a `io.Reader` to a func that can be used with range 86 | // to iterate over lines. If an error occurs during reading, the function will panic. 87 | // With Go 1.23's range over func feature, you can use it like: 88 | // 89 | // for line := range lines.Reader(reader) { 90 | // do_something_with(line) 91 | // } 92 | func Reader(r io.Reader) func(yield func(string) bool) { 93 | br := bufio.NewReader(r) 94 | return func(yield func(string) bool) { 95 | linebuf := "" 96 | for { 97 | line, isPrefix, err := br.ReadLine() 98 | if err == io.EOF { 99 | return 100 | } else if err != nil { 101 | panic(err) 102 | } 103 | linebuf += string(line) 104 | if !isPrefix { 105 | if !yield(linebuf) { 106 | return 107 | } 108 | linebuf = "" 109 | } 110 | } 111 | } 112 | } 113 | 114 | // `String` takes a string and returns a func that can be used with range 115 | // to iterate over lines in the string. It internally uses a strings.Reader. 116 | // With Go 1.23's range over func feature, you can use it like: 117 | // 118 | // for line := range lines.String(s) { 119 | // do_something_with(line) 120 | // } 121 | func String(s string) func(yield func(string) bool) { 122 | return Reader(strings.NewReader(s)) 123 | } 124 | -------------------------------------------------------------------------------- /lines_test.go: -------------------------------------------------------------------------------- 1 | package lines 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestReader(t *testing.T) { 9 | file, err := os.Open("test.txt") 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | defer file.Close() 14 | ans := []string{ 15 | "hoge", 16 | "fuga", 17 | "piyo", 18 | "", 19 | "foo bar baz", 20 | } 21 | i := 0 22 | var readErr error 23 | func() { 24 | defer func() { 25 | if r := recover(); r != nil { 26 | if err, ok := r.(error); ok { 27 | readErr = err 28 | } else { 29 | panic(r) 30 | } 31 | } 32 | }() 33 | for line := range Reader(file) { 34 | if line != ans[i] { 35 | t.Fatalf("Expected %v, but got %v", ans[i], line) 36 | } 37 | i++ 38 | } 39 | }() 40 | if readErr != nil { 41 | t.Fatal(readErr) 42 | } 43 | if i != 5 { 44 | t.Fatalf("Expected 5 lines, but there are only %d lines", i) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /string_test.go: -------------------------------------------------------------------------------- 1 | package lines 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestString(t *testing.T) { 9 | ans := []string{ 10 | "hoge", 11 | "fuga", 12 | "piyo", 13 | "", 14 | "foo bar baz", 15 | } 16 | s := strings.Join(ans, "\n") 17 | 18 | i := 0 19 | for line := range String(s) { 20 | if line != ans[i] { 21 | t.Fatalf("Expected %v, but got %v", ans[i], line) 22 | } 23 | i++ 24 | } 25 | if i != 5 { 26 | t.Fatalf("Expected 5 lines, but there are only %d lines", i) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test.txt: -------------------------------------------------------------------------------- 1 | hoge 2 | fuga 3 | piyo 4 | 5 | foo bar baz 6 | --------------------------------------------------------------------------------