1// Copyright 2015 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
5package message // import "golang.org/x/text/message"
6
7import (
8	"io"
9	"os"
10
11	// Include features to facilitate generated catalogs.
12	_ "golang.org/x/text/feature/plural"
13
14	"golang.org/x/text/internal/number"
15	"golang.org/x/text/language"
16	"golang.org/x/text/message/catalog"
17)
18
19// A Printer implements language-specific formatted I/O analogous to the fmt
20// package.
21type Printer struct {
22	// the language
23	tag language.Tag
24
25	toDecimal    number.Formatter
26	toScientific number.Formatter
27
28	cat catalog.Catalog
29}
30
31type options struct {
32	cat catalog.Catalog
33	// TODO:
34	// - allow %s to print integers in written form (tables are likely too large
35	//   to enable this by default).
36	// - list behavior
37	//
38}
39
40// An Option defines an option of a Printer.
41type Option func(o *options)
42
43// Catalog defines the catalog to be used.
44func Catalog(c catalog.Catalog) Option {
45	return func(o *options) { o.cat = c }
46}
47
48// NewPrinter returns a Printer that formats messages tailored to language t.
49func NewPrinter(t language.Tag, opts ...Option) *Printer {
50	options := &options{
51		cat: DefaultCatalog,
52	}
53	for _, o := range opts {
54		o(options)
55	}
56	p := &Printer{
57		tag: t,
58		cat: options.cat,
59	}
60	p.toDecimal.InitDecimal(t)
61	p.toScientific.InitScientific(t)
62	return p
63}
64
65// Sprint is like fmt.Sprint, but using language-specific formatting.
66func (p *Printer) Sprint(a ...interface{}) string {
67	pp := newPrinter(p)
68	pp.doPrint(a)
69	s := pp.String()
70	pp.free()
71	return s
72}
73
74// Fprint is like fmt.Fprint, but using language-specific formatting.
75func (p *Printer) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
76	pp := newPrinter(p)
77	pp.doPrint(a)
78	n64, err := io.Copy(w, &pp.Buffer)
79	pp.free()
80	return int(n64), err
81}
82
83// Print is like fmt.Print, but using language-specific formatting.
84func (p *Printer) Print(a ...interface{}) (n int, err error) {
85	return p.Fprint(os.Stdout, a...)
86}
87
88// Sprintln is like fmt.Sprintln, but using language-specific formatting.
89func (p *Printer) Sprintln(a ...interface{}) string {
90	pp := newPrinter(p)
91	pp.doPrintln(a)
92	s := pp.String()
93	pp.free()
94	return s
95}
96
97// Fprintln is like fmt.Fprintln, but using language-specific formatting.
98func (p *Printer) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
99	pp := newPrinter(p)
100	pp.doPrintln(a)
101	n64, err := io.Copy(w, &pp.Buffer)
102	pp.free()
103	return int(n64), err
104}
105
106// Println is like fmt.Println, but using language-specific formatting.
107func (p *Printer) Println(a ...interface{}) (n int, err error) {
108	return p.Fprintln(os.Stdout, a...)
109}
110
111// Sprintf is like fmt.Sprintf, but using language-specific formatting.
112func (p *Printer) Sprintf(key Reference, a ...interface{}) string {
113	pp := newPrinter(p)
114	lookupAndFormat(pp, key, a)
115	s := pp.String()
116	pp.free()
117	return s
118}
119
120// Fprintf is like fmt.Fprintf, but using language-specific formatting.
121func (p *Printer) Fprintf(w io.Writer, key Reference, a ...interface{}) (n int, err error) {
122	pp := newPrinter(p)
123	lookupAndFormat(pp, key, a)
124	n, err = w.Write(pp.Bytes())
125	pp.free()
126	return n, err
127
128}
129
130// Printf is like fmt.Printf, but using language-specific formatting.
131func (p *Printer) Printf(key Reference, a ...interface{}) (n int, err error) {
132	pp := newPrinter(p)
133	lookupAndFormat(pp, key, a)
134	n, err = os.Stdout.Write(pp.Bytes())
135	pp.free()
136	return n, err
137}
138
139func lookupAndFormat(p *printer, r Reference, a []interface{}) {
140	p.fmt.Reset(a)
141	var id, msg string
142	switch v := r.(type) {
143	case string:
144		id, msg = v, v
145	case key:
146		id, msg = v.id, v.fallback
147	default:
148		panic("key argument is not a Reference")
149	}
150
151	if p.catContext.Execute(id) == catalog.ErrNotFound {
152		if p.catContext.Execute(msg) == catalog.ErrNotFound {
153			p.Render(msg)
154			return
155		}
156	}
157}
158
159type rawPrinter struct {
160	p *printer
161}
162
163func (p rawPrinter) Render(msg string)     { p.p.WriteString(msg) }
164func (p rawPrinter) Arg(i int) interface{} { return nil }
165
166// Arg implements catmsg.Renderer.
167func (p *printer) Arg(i int) interface{} { // TODO, also return "ok" bool
168	i--
169	if uint(i) < uint(len(p.fmt.Args)) {
170		return p.fmt.Args[i]
171	}
172	return nil
173}
174
175// Render implements catmsg.Renderer.
176func (p *printer) Render(msg string) {
177	p.doPrintf(msg)
178}
179
180// A Reference is a string or a message reference.
181type Reference interface {
182	// TODO: also allow []string
183}
184
185// Key creates a message Reference for a message where the given id is used for
186// message lookup and the fallback is returned when no matches are found.
187func Key(id string, fallback string) Reference {
188	return key{id, fallback}
189}
190
191type key struct {
192	id, fallback string
193}
194