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