Defining a reader

Any Go application can define a custom implementation of the io.Reader interface. A good general rule when implementing interfaces is to accept interfaces and return concrete types, avoiding unnecessary abstraction.

Let's look at a practical example. We want to implement a custom reader that takes the content from another reader and transforms it into uppercase; we could call this AngryReader, for instance:

func NewAngryReader(r io.Reader) *AngryReader {
return &AngryReader{r: r}
}

type AngryReader struct {
r io.Reader
}

func (a *AngryReader) Read(b []byte) (int, error) {
n, err := a.r.Read(b)
for r, i, w := rune(0), 0, 0; i < n; i += w {
// read a rune
r, w = utf8.DecodeRune(b[i:])
// skip if not a letter
if !unicode.IsLetter(r) {
continue
}
// uppercase version of the rune
ru := unicode.ToUpper(r)
// encode the rune and expect same length
if wu := utf8.EncodeRune(b[i:], ru); w != wu {
return n, fmt.Errorf("%c->%c, size mismatch %d->%d", r, ru, w, wu)
}
}
return n, err
}

This is a pretty straightforward example that uses unicode and unicode/utf8 to achieve its goal:

  • utf8.DecodeRune is used to obtain the first rune and its width is a portion of the slice read
  • unicode.IsLetter determines whether a rune is a letter
  • unicode.ToUpper converts the text into uppercase
  • ut8.EncodeLetter writes the new letter in the necessary bytes
  • The letter and its uppercase version should be the same width

The full example is available at https://play.golang.org/p/PhdSsbzXcbE.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.27.178