Skip to content

Instantly share code, notes, and snippets.

@nerg4l
Last active July 16, 2019 10:39
Show Gist options
  • Save nerg4l/4f28edfbe4fa9e227d4f72c0411a457c to your computer and use it in GitHub Desktop.
Save nerg4l/4f28edfbe4fa9e227d4f72c0411a457c to your computer and use it in GitHub Desktop.
Re: faiface - How to use 'try'
name: Boris Bateman
gender: man
os: Windows
lang: PHP
name: Darin May
gender: woman
os: OSX
lang: JavaScript
name: Shea Wilks
gender: nonbinary
os: Linux
lang: Emacs LISP
name: Robert Griesemer
gender: man
os: Linux
lang: Go
// A solution where error handling is "lifted"
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
var path = flag.String("file", "", "path to file")
type Respondent struct {
Name string
Gender string
OS string
Lang string
}
type LineAwareScanner struct {
*bufio.Scanner
line uint
}
func (s *LineAwareScanner) Scan() bool {
r := s.Scanner.Scan()
if r {
s.line++
}
return r
}
func (s *LineAwareScanner) Line() uint {
return s.line
}
type RespondentScanner struct {
scanner *LineAwareScanner
resp Respondent
err error
done bool
}
func (s *RespondentScanner) parseNextLine(name string) string {
// do nothing if an error already occured
if s.err != nil {
return ""
}
defer func() {
if s.err != nil {
s.err = fmt.Errorf("line %d: %s", s.scanner.Line(), s.err)
}
}()
// Handle when scan stops before expected
if !s.scanner.Scan() {
s.err = io.ErrUnexpectedEOF
return ""
}
// Handle when scanner has an error
if err := s.scanner.Err(); err != nil {
return ""
}
line := s.scanner.Text()
prefix := name + ":"
if !strings.HasPrefix(line, prefix) {
s.err = fmt.Errorf("scan field '%s': expected prefix %q", name, prefix)
return ""
}
return strings.TrimSpace(strings.TrimPrefix(line, prefix))
}
func (s *RespondentScanner) Scan() bool {
// Do not scan if done or an error occurred
if s.done || s.err != nil {
return false
}
resp := Respondent{
Name: s.parseNextLine("name"),
Gender: s.parseNextLine("gender"),
OS: s.parseNextLine("os"),
Lang: s.parseNextLine("lang"),
}
if s.Err() != nil {
return false
}
// Scan should be done in next cycle
s.done = !s.scanner.Scan()
s.resp = resp
return true
}
// Err returns the first non-EOF error that was encountered by the Scanner.
func (s *RespondentScanner) Err() error {
if s.err != nil {
return s.err
}
return s.scanner.Err()
}
// Resp returns the created Respondent after Scan
func (s *RespondentScanner) Resp() Respondent {
return s.resp
}
func main() {
flag.Parse()
if len(*path) == 0 {
_, _ = fmt.Println("file flag is required")
return
}
p, err := filepath.Abs(*path)
if err != nil {
panic(err)
}
f, err := os.Open(p)
if err != nil {
panic(err)
}
defer func() {
_ = f.Close()
}()
s := RespondentScanner{
scanner: &LineAwareScanner{
Scanner: bufio.NewScanner(f),
},
done: false,
}
var resps []Respondent
for s.Scan() {
resps = append(resps, s.Resp())
}
if err := s.Err(); err != nil {
_, _ = fmt.Println(fmt.Errorf("parse %s: %s", f.Name(), err))
return
}
_, _ = fmt.Println(resps)
}
// A solution with on the spot error handling
package main
import (
"bufio"
"errors"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
var path = flag.String("file", "", "path to file")
type Respondent struct {
Name string
Gender string
OS string
Lang string
}
func parseField(line string, name string) (value string, err error) {
prefix := name + ":"
if !strings.HasPrefix(line, prefix) {
return "", fmt.Errorf("scan field '%s': expected prefix %q", name, prefix)
}
return strings.TrimPrefix(line, prefix), nil
}
func parseRespondents(f io.Reader) (resps []Respondent, err error) {
i := 1
defer func() {
if err != nil {
err = fmt.Errorf("line %d: %s", i, err)
}
}()
resp := Respondent{}
s := bufio.NewScanner(f)
resetLine := 5
for s.Scan() {
line := s.Text()
switch i % resetLine {
case 0:
if s.Text() != "" {
err = errors.New("expected empty line")
}
case 1:
resp.Name, err = parseField(line, "name")
case 2:
resp.Gender, err = parseField(line, "gender")
case 3:
resp.OS, err = parseField(line, "os")
case 4:
resp.Lang, err = parseField(line, "lang")
default:
}
if err != nil {
return resps, err
}
if i % resetLine == resetLine - 1 {
resps = append(resps, resp)
resp = Respondent{}
}
i++
}
if i % 5 != 0 {
return nil, io.ErrUnexpectedEOF
}
return resps, nil
}
func main() {
flag.Parse()
if len(*path) == 0 {
_, _ = fmt.Println("file flag is required")
return
}
p, err := filepath.Abs(*path)
if err != nil {
panic(err)
}
f, err := os.Open(p)
if err != nil {
panic(err)
}
defer func() {
_ = f.Close()
}()
resps, err := parseRespondents(f)
if err != nil {
_, _ = fmt.Println(fmt.Errorf("parse %s: %s", f.Name(), err))
return
}
_, _ = fmt.Println(resps)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment