123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- // Package interpol provides utility functions for doing format-string like
- // string interpolation using named parameters.
- // Currently, a template only accepts variable placeholders delimited by brace
- // characters (eg. "Hello {foo} {bar}").
- package interpol
- import (
- "bytes"
- "errors"
- "io"
- "strings"
- )
- // Errors returned when formatting templates.
- var (
- ErrUnexpectedClose = errors.New("interpol: unexpected close in template")
- ErrExpectingClose = errors.New("interpol: expecting close in template")
- ErrKeyNotFound = errors.New("interpol: key not found")
- ErrReadByteFailed = errors.New("interpol: read byte failed")
- )
- // Func receives the placeholder key and writes to the io.Writer. If an error
- // happens, the function can return an error, in which case the interpolation
- // will be aborted.
- type Func func(key string, w io.Writer) error
- // New creates a new interpolator with the given list of options.
- // You can use options such as the ones returned by WithTemplate, WithFormat
- // and WithOutput.
- func New(opts ...Option) *Interpolator {
- opts2 := &Options{}
- setOptions(opts, newOptionSetter(opts2))
- return NewWithOptions(opts2)
- }
- // NewWithOptions creates a new interpolator with the given options.
- func NewWithOptions(opts *Options) *Interpolator {
- return &Interpolator{
- template: templateReader(opts),
- output: outputWriter(opts),
- format: opts.Format,
- rb: make([]rune, 0, 64),
- start: -1,
- closing: false,
- }
- }
- // Interpolator interpolates Template to Output, according to Format.
- type Interpolator struct {
- template io.RuneReader
- output runeWriter
- format Func
- rb []rune
- start int
- closing bool
- }
- // Interpolate reads runes from Template and writes them to Output, with the
- // exception of placeholders which are passed to Format.
- func (i *Interpolator) Interpolate() error {
- for pos := 0; ; pos++ {
- r, _, err := i.template.ReadRune()
- if err != nil {
- if err == io.EOF {
- break
- }
- return err
- }
- if err := i.parse(r, pos); err != nil {
- return err
- }
- }
- return i.finish()
- }
- func (i *Interpolator) parse(r rune, pos int) error {
- switch r {
- case '{':
- return i.open(pos)
- case '}':
- return i.close()
- default:
- return i.append(r)
- }
- }
- func (i *Interpolator) open(pos int) error {
- if i.closing {
- return ErrUnexpectedClose
- }
- if i.start >= 0 {
- if _, err := i.output.WriteRune('{'); err != nil {
- return err
- }
- i.start = -1
- } else {
- i.start = pos + 1
- }
- return nil
- }
- func (i *Interpolator) close() error {
- if i.start >= 0 {
- if err := i.format(string(i.rb), i.output); err != nil {
- return err
- }
- i.rb = i.rb[:0]
- i.start = -1
- } else if i.closing {
- i.closing = false
- if _, err := i.output.WriteRune('}'); err != nil {
- return err
- }
- } else {
- i.closing = true
- }
- return nil
- }
- func (i *Interpolator) append(r rune) error {
- if i.closing {
- return ErrUnexpectedClose
- }
- if i.start < 0 {
- _, err := i.output.WriteRune(r)
- return err
- }
- i.rb = append(i.rb, r)
- return nil
- }
- func (i *Interpolator) finish() error {
- if i.start >= 0 {
- return ErrExpectingClose
- }
- if i.closing {
- return ErrUnexpectedClose
- }
- return nil
- }
- // WithFunc interpolates the specified template with replacements using the
- // given function.
- func WithFunc(template string, format Func) (string, error) {
- buffer := bytes.NewBuffer(make([]byte, 0, len(template)))
- opts := &Options{
- Template: strings.NewReader(template),
- Output: buffer,
- Format: format,
- }
- i := NewWithOptions(opts)
- if err := i.Interpolate(); err != nil {
- return "", err
- }
- return buffer.String(), nil
- }
- // WithMap interpolates the specified template with replacements using the
- // given map. If a placeholder is used for which a value is not found, an error
- // is returned.
- func WithMap(template string, m map[string]string) (string, error) {
- format := func(key string, w io.Writer) error {
- value, ok := m[key]
- if !ok {
- return ErrKeyNotFound
- }
- _, err := w.Write([]byte(value))
- return err
- }
- return WithFunc(template, format)
- }
|