github.com/go-xe2/third@v1.0.3/golang.org/x/text/internal/catmsg/catmsg.go (about)

     1  // Copyright 2017 The Go Authors. 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  // Package catmsg contains support types for package x/text/message/catalog.
     6  //
     7  // This package contains the low-level implementations of Message used by the
     8  // catalog package and provides primitives for other packages to implement their
     9  // own. For instance, the plural package provides functionality for selecting
    10  // translation strings based on the plural category of substitution arguments.
    11  //
    12  //
    13  // Encoding and Decoding
    14  //
    15  // Catalogs store Messages encoded as a single string. Compiling a message into
    16  // a string both results in compacter representation and speeds up evaluation.
    17  //
    18  // A Message must implement a Compile method to convert its arbitrary
    19  // representation to a string. The Compile method takes an Encoder which
    20  // facilitates serializing the message. Encoders also provide more context of
    21  // the messages's creation (such as for which language the message is intended),
    22  // which may not be known at the time of the creation of the message.
    23  //
    24  // Each message type must also have an accompanying decoder registered to decode
    25  // the message. This decoder takes a Decoder argument which provides the
    26  // counterparts for the decoding.
    27  //
    28  //
    29  // Renderers
    30  //
    31  // A Decoder must be initialized with a Renderer implementation. These
    32  // implementations must be provided by packages that use Catalogs, typically
    33  // formatting packages such as x/text/message. A typical user will not need to
    34  // worry about this type; it is only relevant to packages that do string
    35  // formatting and want to use the catalog package to handle localized strings.
    36  //
    37  // A package that uses catalogs for selecting strings receives selection results
    38  // as sequence of substrings passed to the Renderer. The following snippet shows
    39  // how to express the above example using the message package.
    40  //
    41  //   message.Set(language.English, "You are %d minute(s) late.",
    42  //       catalog.Var("minutes", plural.Select(1, "one", "minute")),
    43  //       catalog.String("You are %[1]d ${minutes} late."))
    44  //
    45  //   p := message.NewPrinter(language.English)
    46  //   p.Printf("You are %d minute(s) late.", 5) // always 5 minutes late.
    47  //
    48  // To evaluate the Printf, package message wraps the arguments in a Renderer
    49  // that is passed to the catalog for message decoding. The call sequence that
    50  // results from evaluating the above message, assuming the person is rather
    51  // tardy, is:
    52  //
    53  //   Render("You are %[1]d ")
    54  //   Arg(1)
    55  //   Render("minutes")
    56  //   Render(" late.")
    57  //
    58  // The calls to Arg is caused by the plural.Select execution, which evaluates
    59  // the argument to determine whether the singular or plural message form should
    60  // be selected. The calls to Render reports the partial results to the message
    61  // package for further evaluation.
    62  package catmsg
    63  
    64  import (
    65  	"errors"
    66  	"fmt"
    67  	"strconv"
    68  	"strings"
    69  	"sync"
    70  
    71  	"github.com/go-xe2/third/golang.org/x/text/language"
    72  )
    73  
    74  // A Handle refers to a registered message type.
    75  type Handle int
    76  
    77  // First is used as a Handle to EncodeMessageType, followed by a series of calls
    78  // to EncodeMessage, to implement selecting the first matching Message.
    79  //
    80  // TODO: this can be removed once we either can use type aliases or if the
    81  // internals of this package are merged with the catalog package.
    82  var First Handle = msgFirst
    83  
    84  // A Handler decodes and evaluates data compiled by a Message and sends the
    85  // result to the Decoder. The output may depend on the value of the substitution
    86  // arguments, accessible by the Decoder's Arg method. The Handler returns false
    87  // if there is no translation for the given substitution arguments.
    88  type Handler func(d *Decoder) bool
    89  
    90  // Register records the existence of a message type and returns a Handle that
    91  // can be used in the Encoder's EncodeMessageType method to create such
    92  // messages. The prefix of the name should be the package path followed by
    93  // an optional disambiguating string.
    94  // Register will panic if a handle for the same name was already registered.
    95  func Register(name string, handler Handler) Handle {
    96  	mutex.Lock()
    97  	defer mutex.Unlock()
    98  
    99  	if _, ok := names[name]; ok {
   100  		panic(fmt.Errorf("catmsg: handler for %q already exists", name))
   101  	}
   102  	h := Handle(len(handlers))
   103  	names[name] = h
   104  	handlers = append(handlers, handler)
   105  	return h
   106  }
   107  
   108  // These handlers require fixed positions in the handlers slice.
   109  const (
   110  	msgVars Handle = iota
   111  	msgFirst
   112  	msgRaw
   113  	msgString
   114  	numFixed
   115  )
   116  
   117  const prefix = "github.com/go-xe2/third/golang.org/x/text/internal/catmsg."
   118  
   119  var (
   120  	mutex sync.Mutex
   121  	names = map[string]Handle{
   122  		prefix + "Vars":   msgVars,
   123  		prefix + "First":  msgFirst,
   124  		prefix + "Raw":    msgRaw,
   125  		prefix + "String": msgString,
   126  	}
   127  	handlers = make([]Handler, numFixed)
   128  )
   129  
   130  func init() {
   131  	// This handler is a message type wrapper that initializes a decoder
   132  	// with a variable block. This message type, if present, is always at the
   133  	// start of an encoded message.
   134  	handlers[msgVars] = func(d *Decoder) bool {
   135  		blockSize := int(d.DecodeUint())
   136  		d.vars = d.data[:blockSize]
   137  		d.data = d.data[blockSize:]
   138  		return d.executeMessage()
   139  	}
   140  
   141  	// First takes the first message in a sequence that results in a match for
   142  	// the given substitution arguments.
   143  	handlers[msgFirst] = func(d *Decoder) bool {
   144  		for !d.Done() {
   145  			if d.ExecuteMessage() {
   146  				return true
   147  			}
   148  		}
   149  		return false
   150  	}
   151  
   152  	handlers[msgRaw] = func(d *Decoder) bool {
   153  		d.Render(d.data)
   154  		return true
   155  	}
   156  
   157  	// A String message alternates between a string constant and a variable
   158  	// substitution.
   159  	handlers[msgString] = func(d *Decoder) bool {
   160  		for !d.Done() {
   161  			if str := d.DecodeString(); str != "" {
   162  				d.Render(str)
   163  			}
   164  			if d.Done() {
   165  				break
   166  			}
   167  			d.ExecuteSubstitution()
   168  		}
   169  		return true
   170  	}
   171  }
   172  
   173  var (
   174  	// ErrIncomplete indicates a compiled message does not define translations
   175  	// for all possible argument values. If this message is returned, evaluating
   176  	// a message may result in the ErrNoMatch error.
   177  	ErrIncomplete = errors.New("catmsg: incomplete message; may not give result for all inputs")
   178  
   179  	// ErrNoMatch indicates no translation message matched the given input
   180  	// parameters when evaluating a message.
   181  	ErrNoMatch = errors.New("catmsg: no translation for inputs")
   182  )
   183  
   184  // A Message holds a collection of translations for the same phrase that may
   185  // vary based on the values of substitution arguments.
   186  type Message interface {
   187  	// Compile encodes the format string(s) of the message as a string for later
   188  	// evaluation.
   189  	//
   190  	// The first call Compile makes on the encoder must be EncodeMessageType.
   191  	// The handle passed to this call may either be a handle returned by
   192  	// Register to encode a single custom message, or HandleFirst followed by
   193  	// a sequence of calls to EncodeMessage.
   194  	//
   195  	// Compile must return ErrIncomplete if it is possible for evaluation to
   196  	// not match any translation for a given set of formatting parameters.
   197  	// For example, selecting a translation based on plural form may not yield
   198  	// a match if the form "Other" is not one of the selectors.
   199  	//
   200  	// Compile may return any other application-specific error. For backwards
   201  	// compatibility with package like fmt, which often do not do sanity
   202  	// checking of format strings ahead of time, Compile should still make an
   203  	// effort to have some sensible fallback in case of an error.
   204  	Compile(e *Encoder) error
   205  }
   206  
   207  // Compile converts a Message to a data string that can be stored in a Catalog.
   208  // The resulting string can subsequently be decoded by passing to the Execute
   209  // method of a Decoder.
   210  func Compile(tag language.Tag, macros Dictionary, m Message) (data string, err error) {
   211  	// TODO: pass macros so they can be used for validation.
   212  	v := &Encoder{inBody: true} // encoder for variables
   213  	v.root = v
   214  	e := &Encoder{root: v, parent: v, tag: tag} // encoder for messages
   215  	err = m.Compile(e)
   216  	// This package serves te message package, which in turn is meant to be a
   217  	// drop-in replacement for fmt.  With the fmt package, format strings are
   218  	// evaluated lazily and errors are handled by substituting strings in the
   219  	// result, rather then returning an error. Dealing with multiple languages
   220  	// makes it more important to check errors ahead of time. We chose to be
   221  	// consistent and compatible and allow graceful degradation in case of
   222  	// errors.
   223  	buf := e.buf[stripPrefix(e.buf):]
   224  	if len(v.buf) > 0 {
   225  		// Prepend variable block.
   226  		b := make([]byte, 1+maxVarintBytes+len(v.buf)+len(buf))
   227  		b[0] = byte(msgVars)
   228  		b = b[:1+encodeUint(b[1:], uint64(len(v.buf)))]
   229  		b = append(b, v.buf...)
   230  		b = append(b, buf...)
   231  		buf = b
   232  	}
   233  	if err == nil {
   234  		err = v.err
   235  	}
   236  	return string(buf), err
   237  }
   238  
   239  // Var defines a message that can be substituted for a placeholder of the same
   240  // name. If an expression does not result in a string after evaluation, Name is
   241  // used as the substitution. For example:
   242  //    Var{
   243  //      Name:    "minutes",
   244  //      Message: plural.Select(1, "one", "minute"),
   245  //    }
   246  // will resolve to minute for singular and minutes for plural forms.
   247  type Var struct {
   248  	Name    string
   249  	Message Message
   250  }
   251  
   252  var errIsVar = errors.New("catmsg: variable used as message")
   253  
   254  // Compile implements Message.
   255  //
   256  // Note that this method merely registers a variable; it does not create an
   257  // encoded message.
   258  func (v *Var) Compile(e *Encoder) error {
   259  	if err := e.addVar(v.Name, v.Message); err != nil {
   260  		return err
   261  	}
   262  	// Using a Var by itself is an error. If it is in a sequence followed by
   263  	// other messages referring to it, this error will be ignored.
   264  	return errIsVar
   265  }
   266  
   267  // Raw is a message consisting of a single format string that is passed as is
   268  // to the Renderer.
   269  //
   270  // Note that a Renderer may still do its own variable substitution.
   271  type Raw string
   272  
   273  // Compile implements Message.
   274  func (r Raw) Compile(e *Encoder) (err error) {
   275  	e.EncodeMessageType(msgRaw)
   276  	// Special case: raw strings don't have a size encoding and so don't use
   277  	// EncodeString.
   278  	e.buf = append(e.buf, r...)
   279  	return nil
   280  }
   281  
   282  // String is a message consisting of a single format string which contains
   283  // placeholders that may be substituted with variables.
   284  //
   285  // Variable substitutions are marked with placeholders and a variable name of
   286  // the form ${name}. Any other substitutions such as Go templates or
   287  // printf-style substitutions are left to be done by the Renderer.
   288  //
   289  // When evaluation a string interpolation, a Renderer will receive separate
   290  // calls for each placeholder and interstitial string. For example, for the
   291  // message: "%[1]v ${invites} %[2]v to ${their} party." The sequence of calls
   292  // is:
   293  //   d.Render("%[1]v ")
   294  //   d.Arg(1)
   295  //   d.Render(resultOfInvites)
   296  //   d.Render(" %[2]v to ")
   297  //   d.Arg(2)
   298  //   d.Render(resultOfTheir)
   299  //   d.Render(" party.")
   300  // where the messages for "invites" and "their" both use a plural.Select
   301  // referring to the first argument.
   302  //
   303  // Strings may also invoke macros. Macros are essentially variables that can be
   304  // reused. Macros may, for instance, be used to make selections between
   305  // different conjugations of a verb. See the catalog package description for an
   306  // overview of macros.
   307  type String string
   308  
   309  // Compile implements Message. It parses the placeholder formats and returns
   310  // any error.
   311  func (s String) Compile(e *Encoder) (err error) {
   312  	msg := string(s)
   313  	const subStart = "${"
   314  	hasHeader := false
   315  	p := 0
   316  	b := []byte{}
   317  	for {
   318  		i := strings.Index(msg[p:], subStart)
   319  		if i == -1 {
   320  			break
   321  		}
   322  		b = append(b, msg[p:p+i]...)
   323  		p += i + len(subStart)
   324  		if i = strings.IndexByte(msg[p:], '}'); i == -1 {
   325  			b = append(b, "$!(MISSINGBRACE)"...)
   326  			err = fmt.Errorf("catmsg: missing '}'")
   327  			p = len(msg)
   328  			break
   329  		}
   330  		name := strings.TrimSpace(msg[p : p+i])
   331  		if q := strings.IndexByte(name, '('); q == -1 {
   332  			if !hasHeader {
   333  				hasHeader = true
   334  				e.EncodeMessageType(msgString)
   335  			}
   336  			e.EncodeString(string(b))
   337  			e.EncodeSubstitution(name)
   338  			b = b[:0]
   339  		} else if j := strings.IndexByte(name[q:], ')'); j == -1 {
   340  			// TODO: what should the error be?
   341  			b = append(b, "$!(MISSINGPAREN)"...)
   342  			err = fmt.Errorf("catmsg: missing ')'")
   343  		} else if x, sErr := strconv.ParseUint(strings.TrimSpace(name[q+1:q+j]), 10, 32); sErr != nil {
   344  			// TODO: handle more than one argument
   345  			b = append(b, "$!(BADNUM)"...)
   346  			err = fmt.Errorf("catmsg: invalid number %q", strings.TrimSpace(name[q+1:q+j]))
   347  		} else {
   348  			if !hasHeader {
   349  				hasHeader = true
   350  				e.EncodeMessageType(msgString)
   351  			}
   352  			e.EncodeString(string(b))
   353  			e.EncodeSubstitution(name[:q], int(x))
   354  			b = b[:0]
   355  		}
   356  		p += i + 1
   357  	}
   358  	b = append(b, msg[p:]...)
   359  	if !hasHeader {
   360  		// Simplify string to a raw string.
   361  		Raw(string(b)).Compile(e)
   362  	} else if len(b) > 0 {
   363  		e.EncodeString(string(b))
   364  	}
   365  	return err
   366  }