summaryrefslogtreecommitdiffhomepage
path: root/vendor/golang.org/x/text/internal/gen/gen.go
blob: 78bfef6216a9c8403bf1f7f7af4a3d89e259c51e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
// Copyright 2015 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 gen contains common code for the various code generation tools in the
// text repository. Its usage ensures consistency between tools.
//
// This package defines command line flags that are common to most generation
// tools. The flags allow for specifying specific Unicode and CLDR versions
// in the public Unicode data repository (https://www.unicode.org/Public).
//
// A local Unicode data mirror can be set through the flag -local or the
// environment variable UNICODE_DIR. The former takes precedence. The local
// directory should follow the same structure as the public repository.
//
// IANA data can also optionally be mirrored by putting it in the iana directory
// rooted at the top of the local mirror. Beware, though, that IANA data is not
// versioned. So it is up to the developer to use the right version.
package gen // import "golang.org/x/text/internal/gen"

import (
	"bytes"
	"flag"
	"fmt"
	"go/build"
	"go/format"
	"io"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"unicode"

	"golang.org/x/text/unicode/cldr"
)

var (
	url = flag.String("url",
		"https://www.unicode.org/Public",
		"URL of Unicode database directory")
	iana = flag.String("iana",
		"http://www.iana.org",
		"URL of the IANA repository")
	unicodeVersion = flag.String("unicode",
		getEnv("UNICODE_VERSION", unicode.Version),
		"unicode version to use")
	cldrVersion = flag.String("cldr",
		getEnv("CLDR_VERSION", cldr.Version),
		"cldr version to use")
)

func getEnv(name, def string) string {
	if v := os.Getenv(name); v != "" {
		return v
	}
	return def
}

// Init performs common initialization for a gen command. It parses the flags
// and sets up the standard logging parameters.
func Init() {
	log.SetPrefix("")
	log.SetFlags(log.Lshortfile)
	flag.Parse()
}

const header = `// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.

`

// UnicodeVersion reports the requested Unicode version.
func UnicodeVersion() string {
	return *unicodeVersion
}

// CLDRVersion reports the requested CLDR version.
func CLDRVersion() string {
	return *cldrVersion
}

var tags = []struct{ version, buildTags string }{
	{"9.0.0", "!go1.10"},
	{"10.0.0", "go1.10,!go1.13"},
	{"11.0.0", "go1.13,!go1.14"},
	{"12.0.0", "go1.14,!go1.16"},
	{"13.0.0", "go1.16,!go1.21"},
	{"15.0.0", "go1.21"},
}

// buildTags reports the build tags used for the current Unicode version.
func buildTags() string {
	v := UnicodeVersion()
	for _, e := range tags {
		if e.version == v {
			return e.buildTags
		}
	}
	log.Fatalf("Unknown build tags for Unicode version %q.", v)
	return ""
}

// IsLocal reports whether data files are available locally.
func IsLocal() bool {
	dir, err := localReadmeFile()
	if err != nil {
		return false
	}
	if _, err = os.Stat(dir); err != nil {
		return false
	}
	return true
}

// OpenUCDFile opens the requested UCD file. The file is specified relative to
// the public Unicode root directory. It will call log.Fatal if there are any
// errors.
func OpenUCDFile(file string) io.ReadCloser {
	return openUnicode(path.Join(*unicodeVersion, "ucd", file))
}

// OpenCLDRCoreZip opens the CLDR core zip file. It will call log.Fatal if there
// are any errors.
func OpenCLDRCoreZip() io.ReadCloser {
	return OpenUnicodeFile("cldr", *cldrVersion, "core.zip")
}

// OpenUnicodeFile opens the requested file of the requested category from the
// root of the Unicode data archive. The file is specified relative to the
// public Unicode root directory. If version is "", it will use the default
// Unicode version. It will call log.Fatal if there are any errors.
func OpenUnicodeFile(category, version, file string) io.ReadCloser {
	if version == "" {
		version = UnicodeVersion()
	}
	return openUnicode(path.Join(category, version, file))
}

// OpenIANAFile opens the requested IANA file. The file is specified relative
// to the IANA root, which is typically either http://www.iana.org or the
// iana directory in the local mirror. It will call log.Fatal if there are any
// errors.
func OpenIANAFile(path string) io.ReadCloser {
	return Open(*iana, "iana", path)
}

var (
	dirMutex sync.Mutex
	localDir string
)

const permissions = 0755

func localReadmeFile() (string, error) {
	p, err := build.Import("golang.org/x/text", "", build.FindOnly)
	if err != nil {
		return "", fmt.Errorf("Could not locate package: %v", err)
	}
	return filepath.Join(p.Dir, "DATA", "README"), nil
}

func getLocalDir() string {
	dirMutex.Lock()
	defer dirMutex.Unlock()

	readme, err := localReadmeFile()
	if err != nil {
		log.Fatal(err)
	}
	dir := filepath.Dir(readme)
	if _, err := os.Stat(readme); err != nil {
		if err := os.MkdirAll(dir, permissions); err != nil {
			log.Fatalf("Could not create directory: %v", err)
		}
		os.WriteFile(readme, []byte(readmeTxt), permissions)
	}
	return dir
}

const readmeTxt = `Generated by golang.org/x/text/internal/gen. DO NOT EDIT.

This directory contains downloaded files used to generate the various tables
in the golang.org/x/text subrepo.

Note that the language subtag repo (iana/assignments/language-subtag-registry)
and all other times in the iana subdirectory are not versioned and will need
to be periodically manually updated. The easiest way to do this is to remove
the entire iana directory. This is mostly of concern when updating the language
package.
`

// Open opens subdir/path if a local directory is specified and the file exists,
// where subdir is a directory relative to the local root, or fetches it from
// urlRoot/path otherwise. It will call log.Fatal if there are any errors.
func Open(urlRoot, subdir, path string) io.ReadCloser {
	file := filepath.Join(getLocalDir(), subdir, filepath.FromSlash(path))
	return open(file, urlRoot, path)
}

func openUnicode(path string) io.ReadCloser {
	file := filepath.Join(getLocalDir(), filepath.FromSlash(path))
	return open(file, *url, path)
}

// TODO: automatically periodically update non-versioned files.

func open(file, urlRoot, path string) io.ReadCloser {
	if f, err := os.Open(file); err == nil {
		return f
	}
	r := get(urlRoot, path)
	defer r.Close()
	b, err := io.ReadAll(r)
	if err != nil {
		log.Fatalf("Could not download file: %v", err)
	}
	os.MkdirAll(filepath.Dir(file), permissions)
	if err := os.WriteFile(file, b, permissions); err != nil {
		log.Fatalf("Could not create file: %v", err)
	}
	return io.NopCloser(bytes.NewReader(b))
}

func get(root, path string) io.ReadCloser {
	url := root + "/" + path
	fmt.Printf("Fetching %s...", url)
	defer fmt.Println(" done.")
	resp, err := http.Get(url)
	if err != nil {
		log.Fatalf("HTTP GET: %v", err)
	}
	if resp.StatusCode != 200 {
		log.Fatalf("Bad GET status for %q: %q", url, resp.Status)
	}
	return resp.Body
}

// TODO: use Write*Version in all applicable packages.

// WriteUnicodeVersion writes a constant for the Unicode version from which the
// tables are generated.
func WriteUnicodeVersion(w io.Writer) {
	fmt.Fprintf(w, "// UnicodeVersion is the Unicode version from which the tables in this package are derived.\n")
	fmt.Fprintf(w, "const UnicodeVersion = %q\n\n", UnicodeVersion())
}

// WriteCLDRVersion writes a constant for the CLDR version from which the
// tables are generated.
func WriteCLDRVersion(w io.Writer) {
	fmt.Fprintf(w, "// CLDRVersion is the CLDR version from which the tables in this package are derived.\n")
	fmt.Fprintf(w, "const CLDRVersion = %q\n\n", CLDRVersion())
}

// WriteGoFile prepends a standard file comment and package statement to the
// given bytes, applies gofmt, and writes them to a file with the given name.
// It will call log.Fatal if there are any errors.
func WriteGoFile(filename, pkg string, b []byte) {
	w, err := os.Create(filename)
	if err != nil {
		log.Fatalf("Could not create file %s: %v", filename, err)
	}
	defer w.Close()
	if _, err = WriteGo(w, pkg, "", b); err != nil {
		log.Fatalf("Error writing file %s: %v", filename, err)
	}
}

func fileToPattern(filename string) string {
	suffix := ".go"
	if strings.HasSuffix(filename, "_test.go") {
		suffix = "_test.go"
	}
	prefix := filename[:len(filename)-len(suffix)]
	return fmt.Sprint(prefix, "%s", suffix)
}

// tagLines returns the //go:build lines to add to the file.
func tagLines(tags string) string {
	return "//go:build " + strings.ReplaceAll(tags, ",", " && ") + "\n"
}

func updateBuildTags(pattern string) {
	for _, t := range tags {
		oldFile := fmt.Sprintf(pattern, t.version)
		b, err := os.ReadFile(oldFile)
		if err != nil {
			continue
		}
		b = regexp.MustCompile(`//go:build.*\n`).ReplaceAll(b, []byte(tagLines(t.buildTags)))
		err = os.WriteFile(oldFile, b, 0644)
		if err != nil {
			log.Fatal(err)
		}
	}
}

// WriteVersionedGoFile prepends a standard file comment, adds build tags to
// version the file for the current Unicode version, and package statement to
// the given bytes, applies gofmt, and writes them to a file with the given
// name. It will call log.Fatal if there are any errors.
func WriteVersionedGoFile(filename, pkg string, b []byte) {
	pattern := fileToPattern(filename)
	updateBuildTags(pattern)
	filename = fmt.Sprintf(pattern, UnicodeVersion())

	w, err := os.Create(filename)
	if err != nil {
		log.Fatalf("Could not create file %s: %v", filename, err)
	}
	defer w.Close()
	if _, err = WriteGo(w, pkg, buildTags(), b); err != nil {
		log.Fatalf("Error writing file %s: %v", filename, err)
	}
}

// WriteGo prepends a standard file comment and package statement to the given
// bytes, applies gofmt, and writes them to w.
func WriteGo(w io.Writer, pkg, tags string, b []byte) (n int, err error) {
	src := []byte(header)
	if tags != "" {
		src = append(src, tagLines(tags)...)
		src = append(src, '\n')
	}
	src = append(src, fmt.Sprintf("package %s\n\n", pkg)...)
	src = append(src, b...)
	formatted, err := format.Source(src)
	if err != nil {
		// Print the generated code even in case of an error so that the
		// returned error can be meaningfully interpreted.
		n, _ = w.Write(src)
		return n, err
	}
	return w.Write(formatted)
}

// Repackage rewrites a Go file from belonging to package main to belonging to
// the given package.
func Repackage(inFile, outFile, pkg string) {
	src, err := os.ReadFile(inFile)
	if err != nil {
		log.Fatalf("reading %s: %v", inFile, err)
	}
	const toDelete = "package main\n\n"
	i := bytes.Index(src, []byte(toDelete))
	if i < 0 {
		log.Fatalf("Could not find %q in %s.", toDelete, inFile)
	}
	w := &bytes.Buffer{}
	w.Write(src[i+len(toDelete):])
	WriteGoFile(outFile, pkg, w.Bytes())
}