Source File
catmsg.go
Belonging Package
golang.org/x/text/internal/catmsg
// Copyright 2017 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.// Package catmsg contains support types for package x/text/message/catalog.//// This package contains the low-level implementations of Message used by the// catalog package and provides primitives for other packages to implement their// own. For instance, the plural package provides functionality for selecting// translation strings based on the plural category of substitution arguments.//// # Encoding and Decoding//// Catalogs store Messages encoded as a single string. Compiling a message into// a string both results in compacter representation and speeds up evaluation.//// A Message must implement a Compile method to convert its arbitrary// representation to a string. The Compile method takes an Encoder which// facilitates serializing the message. Encoders also provide more context of// the messages's creation (such as for which language the message is intended),// which may not be known at the time of the creation of the message.//// Each message type must also have an accompanying decoder registered to decode// the message. This decoder takes a Decoder argument which provides the// counterparts for the decoding.//// # Renderers//// A Decoder must be initialized with a Renderer implementation. These// implementations must be provided by packages that use Catalogs, typically// formatting packages such as x/text/message. A typical user will not need to// worry about this type; it is only relevant to packages that do string// formatting and want to use the catalog package to handle localized strings.//// A package that uses catalogs for selecting strings receives selection results// as sequence of substrings passed to the Renderer. The following snippet shows// how to express the above example using the message package.//// message.Set(language.English, "You are %d minute(s) late.",// catalog.Var("minutes", plural.Select(1, "one", "minute")),// catalog.String("You are %[1]d ${minutes} late."))//// p := message.NewPrinter(language.English)// p.Printf("You are %d minute(s) late.", 5) // always 5 minutes late.//// To evaluate the Printf, package message wraps the arguments in a Renderer// that is passed to the catalog for message decoding. The call sequence that// results from evaluating the above message, assuming the person is rather// tardy, is://// Render("You are %[1]d ")// Arg(1)// Render("minutes")// Render(" late.")//// The calls to Arg is caused by the plural.Select execution, which evaluates// the argument to determine whether the singular or plural message form should// be selected. The calls to Render reports the partial results to the message// package for further evaluation.package catmsgimport ()// A Handle refers to a registered message type.type Handle int// A Handler decodes and evaluates data compiled by a Message and sends the// result to the Decoder. The output may depend on the value of the substitution// arguments, accessible by the Decoder's Arg method. The Handler returns false// if there is no translation for the given substitution arguments.type Handler func(d *Decoder) bool// Register records the existence of a message type and returns a Handle that// can be used in the Encoder's EncodeMessageType method to create such// messages. The prefix of the name should be the package path followed by// an optional disambiguating string.// Register will panic if a handle for the same name was already registered.func ( string, Handler) Handle {mutex.Lock()defer mutex.Unlock()if , := names[]; {panic(fmt.Errorf("catmsg: handler for %q already exists", ))}:= Handle(len(handlers))names[] =handlers = append(handlers, )return}// These handlers require fixed positions in the handlers slice.const (msgVars Handle = iotamsgFirstmsgRawmsgStringmsgAffix// Leave some arbitrary room for future expansion: 20 should suffice.numInternal = 20)const prefix = "golang.org/x/text/internal/catmsg."var (// TODO: find a more stable way to link handles to message types.mutex sync.Mutexnames = map[string]Handle{prefix + "Vars": msgVars,prefix + "First": msgFirst,prefix + "Raw": msgRaw,prefix + "String": msgString,prefix + "Affix": msgAffix,}handlers = make([]Handler, numInternal))func init() {// This handler is a message type wrapper that initializes a decoder// with a variable block. This message type, if present, is always at the// start of an encoded message.handlers[msgVars] = func( *Decoder) bool {:= int(.DecodeUint()).vars = .data[:].data = .data[:]return .executeMessage()}// First takes the first message in a sequence that results in a match for// the given substitution arguments.handlers[msgFirst] = func( *Decoder) bool {for !.Done() {if .ExecuteMessage() {return true}}return false}handlers[msgRaw] = func( *Decoder) bool {.Render(.data)return true}// A String message alternates between a string constant and a variable// substitution.handlers[msgString] = func( *Decoder) bool {for !.Done() {if := .DecodeString(); != "" {.Render()}if .Done() {break}.ExecuteSubstitution()}return true}handlers[msgAffix] = func( *Decoder) bool {// TODO: use an alternative method for common cases.:= .DecodeString():= .DecodeString()if != "" {.Render()}:= .ExecuteMessage()if != "" {.Render()}return}}var (// ErrIncomplete indicates a compiled message does not define translations// for all possible argument values. If this message is returned, evaluating// a message may result in the ErrNoMatch error.ErrIncomplete = errors.New("catmsg: incomplete message; may not give result for all inputs")// ErrNoMatch indicates no translation message matched the given input// parameters when evaluating a message.ErrNoMatch = errors.New("catmsg: no translation for inputs"))// A Message holds a collection of translations for the same phrase that may// vary based on the values of substitution arguments.type Message interface {// Compile encodes the format string(s) of the message as a string for later// evaluation.//// The first call Compile makes on the encoder must be EncodeMessageType.// The handle passed to this call may either be a handle returned by// Register to encode a single custom message, or HandleFirst followed by// a sequence of calls to EncodeMessage.//// Compile must return ErrIncomplete if it is possible for evaluation to// not match any translation for a given set of formatting parameters.// For example, selecting a translation based on plural form may not yield// a match if the form "Other" is not one of the selectors.//// Compile may return any other application-specific error. For backwards// compatibility with package like fmt, which often do not do sanity// checking of format strings ahead of time, Compile should still make an// effort to have some sensible fallback in case of an error.Compile(e *Encoder) error}// Compile converts a Message to a data string that can be stored in a Catalog.// The resulting string can subsequently be decoded by passing to the Execute// method of a Decoder.func ( language.Tag, Dictionary, Message) ( string, error) {// TODO: pass macros so they can be used for validation.:= &Encoder{inBody: true} // encoder for variables.root =:= &Encoder{root: , parent: , tag: } // encoder for messages= .Compile()// This package serves te message package, which in turn is meant to be a// drop-in replacement for fmt. With the fmt package, format strings are// evaluated lazily and errors are handled by substituting strings in the// result, rather then returning an error. Dealing with multiple languages// makes it more important to check errors ahead of time. We chose to be// consistent and compatible and allow graceful degradation in case of// errors.:= .buf[stripPrefix(.buf):]if len(.buf) > 0 {// Prepend variable block.:= make([]byte, 1+maxVarintBytes+len(.buf)+len())[0] = byte(msgVars)= [:1+encodeUint([1:], uint64(len(.buf)))]= append(, .buf...)= append(, ...)=}if == nil {= .err}return string(),}// FirstOf is a message type that prints the first message in the sequence that// resolves to a match for the given substitution arguments.type FirstOf []Message// Compile implements Message.func ( FirstOf) ( *Encoder) error {.EncodeMessageType(msgFirst):= ErrIncompletefor , := range {if == nil {return fmt.Errorf("catalog: message argument %d is complete and blocks subsequent messages", -1)}= .EncodeMessage()}return}// Var defines a message that can be substituted for a placeholder of the same// name. If an expression does not result in a string after evaluation, Name is// used as the substitution. For example://// Var{// Name: "minutes",// Message: plural.Select(1, "one", "minute"),// }//// will resolve to minute for singular and minutes for plural forms.type Var struct {Name stringMessage Message}var errIsVar = errors.New("catmsg: variable used as message")// Compile implements Message.//// Note that this method merely registers a variable; it does not create an// encoded message.func ( *Var) ( *Encoder) error {if := .addVar(.Name, .Message); != nil {return}// Using a Var by itself is an error. If it is in a sequence followed by// other messages referring to it, this error will be ignored.return errIsVar}// Raw is a message consisting of a single format string that is passed as is// to the Renderer.//// Note that a Renderer may still do its own variable substitution.type Raw string// Compile implements Message.func ( Raw) ( *Encoder) ( error) {.EncodeMessageType(msgRaw)// Special case: raw strings don't have a size encoding and so don't use// EncodeString..buf = append(.buf, ...)return nil}// String is a message consisting of a single format string which contains// placeholders that may be substituted with variables.//// Variable substitutions are marked with placeholders and a variable name of// the form ${name}. Any other substitutions such as Go templates or// printf-style substitutions are left to be done by the Renderer.//// When evaluation a string interpolation, a Renderer will receive separate// calls for each placeholder and interstitial string. For example, for the// message: "%[1]v ${invites} %[2]v to ${their} party." The sequence of calls// is://// d.Render("%[1]v ")// d.Arg(1)// d.Render(resultOfInvites)// d.Render(" %[2]v to ")// d.Arg(2)// d.Render(resultOfTheir)// d.Render(" party.")//// where the messages for "invites" and "their" both use a plural.Select// referring to the first argument.//// Strings may also invoke macros. Macros are essentially variables that can be// reused. Macros may, for instance, be used to make selections between// different conjugations of a verb. See the catalog package description for an// overview of macros.type String string// Compile implements Message. It parses the placeholder formats and returns// any error.func ( String) ( *Encoder) ( error) {:= string()const = "${":= false:= 0:= []byte{}for {:= strings.Index([:], )if == -1 {break}= append(, [:+]...)+= + len()if = strings.IndexByte([:], '}'); == -1 {= append(, "$!(MISSINGBRACE)"...)= fmt.Errorf("catmsg: missing '}'")= len()break}:= strings.TrimSpace([ : +])if := strings.IndexByte(, '('); == -1 {if ! {= true.EncodeMessageType(msgString)}.EncodeString(string()).EncodeSubstitution()= [:0]} else if := strings.IndexByte([:], ')'); == -1 {// TODO: what should the error be?= append(, "$!(MISSINGPAREN)"...)= fmt.Errorf("catmsg: missing ')'")} else if , := strconv.ParseUint(strings.TrimSpace([+1:+]), 10, 32); != nil {// TODO: handle more than one argument= append(, "$!(BADNUM)"...)= fmt.Errorf("catmsg: invalid number %q", strings.TrimSpace([+1:+]))} else {if ! {= true.EncodeMessageType(msgString)}.EncodeString(string()).EncodeSubstitution([:], int())= [:0]}+= + 1}= append(, [:]...)if ! {// Simplify string to a raw string.Raw(string()).Compile()} else if len() > 0 {.EncodeString(string())}return}// Affix is a message that adds a prefix and suffix to another message.// This is mostly used add back whitespace to a translation that was stripped// before sending it out.type Affix struct {Message MessagePrefix stringSuffix string}// Compile implements Message.func ( Affix) ( *Encoder) ( error) {// TODO: consider adding a special message type that just adds a single// return. This is probably common enough to handle the majority of cases.// Get some stats first, though..EncodeMessageType(msgAffix).EncodeString(.Prefix).EncodeString(.Suffix).EncodeMessage(.Message)return nil}
![]() |
The pages are generated with Golds v0.8.2. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |