1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package gen
20
21 import (
22 "bytes"
23 "flag"
24 "fmt"
25 "go/build"
26 "go/format"
27 "io"
28 "log"
29 "net/http"
30 "os"
31 "path"
32 "path/filepath"
33 "regexp"
34 "strings"
35 "sync"
36 "unicode"
37
38 "golang.org/x/text/unicode/cldr"
39 )
40
41 var (
42 url = flag.String("url",
43 "https://www.unicode.org/Public",
44 "URL of Unicode database directory")
45 iana = flag.String("iana",
46 "http://www.iana.org",
47 "URL of the IANA repository")
48 unicodeVersion = flag.String("unicode",
49 getEnv("UNICODE_VERSION", unicode.Version),
50 "unicode version to use")
51 cldrVersion = flag.String("cldr",
52 getEnv("CLDR_VERSION", cldr.Version),
53 "cldr version to use")
54 )
55
56 func getEnv(name, def string) string {
57 if v := os.Getenv(name); v != "" {
58 return v
59 }
60 return def
61 }
62
63
64
65 func Init() {
66 log.SetPrefix("")
67 log.SetFlags(log.Lshortfile)
68 flag.Parse()
69 }
70
71 const header = `// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
72
73 `
74
75
76 func UnicodeVersion() string {
77 return *unicodeVersion
78 }
79
80
81 func CLDRVersion() string {
82 return *cldrVersion
83 }
84
85 var tags = []struct{ version, buildTags string }{
86 {"9.0.0", "!go1.10"},
87 {"10.0.0", "go1.10,!go1.13"},
88 {"11.0.0", "go1.13,!go1.14"},
89 {"12.0.0", "go1.14,!go1.16"},
90 {"13.0.0", "go1.16,!go1.21"},
91 {"15.0.0", "go1.21"},
92 }
93
94
95 func buildTags() string {
96 v := UnicodeVersion()
97 for _, e := range tags {
98 if e.version == v {
99 return e.buildTags
100 }
101 }
102 log.Fatalf("Unknown build tags for Unicode version %q.", v)
103 return ""
104 }
105
106
107 func IsLocal() bool {
108 dir, err := localReadmeFile()
109 if err != nil {
110 return false
111 }
112 if _, err = os.Stat(dir); err != nil {
113 return false
114 }
115 return true
116 }
117
118
119
120
121 func OpenUCDFile(file string) io.ReadCloser {
122 return openUnicode(path.Join(*unicodeVersion, "ucd", file))
123 }
124
125
126
127 func OpenCLDRCoreZip() io.ReadCloser {
128 return OpenUnicodeFile("cldr", *cldrVersion, "core.zip")
129 }
130
131
132
133
134
135 func OpenUnicodeFile(category, version, file string) io.ReadCloser {
136 if version == "" {
137 version = UnicodeVersion()
138 }
139 return openUnicode(path.Join(category, version, file))
140 }
141
142
143
144
145
146 func OpenIANAFile(path string) io.ReadCloser {
147 return Open(*iana, "iana", path)
148 }
149
150 var (
151 dirMutex sync.Mutex
152 localDir string
153 )
154
155 const permissions = 0755
156
157 func localReadmeFile() (string, error) {
158 p, err := build.Import("golang.org/x/text", "", build.FindOnly)
159 if err != nil {
160 return "", fmt.Errorf("Could not locate package: %v", err)
161 }
162 return filepath.Join(p.Dir, "DATA", "README"), nil
163 }
164
165 func getLocalDir() string {
166 dirMutex.Lock()
167 defer dirMutex.Unlock()
168
169 readme, err := localReadmeFile()
170 if err != nil {
171 log.Fatal(err)
172 }
173 dir := filepath.Dir(readme)
174 if _, err := os.Stat(readme); err != nil {
175 if err := os.MkdirAll(dir, permissions); err != nil {
176 log.Fatalf("Could not create directory: %v", err)
177 }
178 os.WriteFile(readme, []byte(readmeTxt), permissions)
179 }
180 return dir
181 }
182
183 const readmeTxt = `Generated by golang.org/x/text/internal/gen. DO NOT EDIT.
184
185 This directory contains downloaded files used to generate the various tables
186 in the golang.org/x/text subrepo.
187
188 Note that the language subtag repo (iana/assignments/language-subtag-registry)
189 and all other times in the iana subdirectory are not versioned and will need
190 to be periodically manually updated. The easiest way to do this is to remove
191 the entire iana directory. This is mostly of concern when updating the language
192 package.
193 `
194
195
196
197
198 func Open(urlRoot, subdir, path string) io.ReadCloser {
199 file := filepath.Join(getLocalDir(), subdir, filepath.FromSlash(path))
200 return open(file, urlRoot, path)
201 }
202
203 func openUnicode(path string) io.ReadCloser {
204 file := filepath.Join(getLocalDir(), filepath.FromSlash(path))
205 return open(file, *url, path)
206 }
207
208
209
210 func open(file, urlRoot, path string) io.ReadCloser {
211 if f, err := os.Open(file); err == nil {
212 return f
213 }
214 r := get(urlRoot, path)
215 defer r.Close()
216 b, err := io.ReadAll(r)
217 if err != nil {
218 log.Fatalf("Could not download file: %v", err)
219 }
220 os.MkdirAll(filepath.Dir(file), permissions)
221 if err := os.WriteFile(file, b, permissions); err != nil {
222 log.Fatalf("Could not create file: %v", err)
223 }
224 return io.NopCloser(bytes.NewReader(b))
225 }
226
227 func get(root, path string) io.ReadCloser {
228 url := root + "/" + path
229 fmt.Printf("Fetching %s...", url)
230 defer fmt.Println(" done.")
231 resp, err := http.Get(url)
232 if err != nil {
233 log.Fatalf("HTTP GET: %v", err)
234 }
235 if resp.StatusCode != 200 {
236 log.Fatalf("Bad GET status for %q: %q", url, resp.Status)
237 }
238 return resp.Body
239 }
240
241
242
243
244
245 func WriteUnicodeVersion(w io.Writer) {
246 fmt.Fprintf(w, "// UnicodeVersion is the Unicode version from which the tables in this package are derived.\n")
247 fmt.Fprintf(w, "const UnicodeVersion = %q\n\n", UnicodeVersion())
248 }
249
250
251
252 func WriteCLDRVersion(w io.Writer) {
253 fmt.Fprintf(w, "// CLDRVersion is the CLDR version from which the tables in this package are derived.\n")
254 fmt.Fprintf(w, "const CLDRVersion = %q\n\n", CLDRVersion())
255 }
256
257
258
259
260 func WriteGoFile(filename, pkg string, b []byte) {
261 w, err := os.Create(filename)
262 if err != nil {
263 log.Fatalf("Could not create file %s: %v", filename, err)
264 }
265 defer w.Close()
266 if _, err = WriteGo(w, pkg, "", b); err != nil {
267 log.Fatalf("Error writing file %s: %v", filename, err)
268 }
269 }
270
271 func fileToPattern(filename string) string {
272 suffix := ".go"
273 if strings.HasSuffix(filename, "_test.go") {
274 suffix = "_test.go"
275 }
276 prefix := filename[:len(filename)-len(suffix)]
277 return fmt.Sprint(prefix, "%s", suffix)
278 }
279
280
281 func tagLines(tags string) string {
282 return "//go:build " + strings.ReplaceAll(tags, ",", " && ") + "\n"
283 }
284
285 func updateBuildTags(pattern string) {
286 for _, t := range tags {
287 oldFile := fmt.Sprintf(pattern, t.version)
288 b, err := os.ReadFile(oldFile)
289 if err != nil {
290 continue
291 }
292 b = regexp.MustCompile(`//go:build.*\n`).ReplaceAll(b, []byte(tagLines(t.buildTags)))
293 err = os.WriteFile(oldFile, b, 0644)
294 if err != nil {
295 log.Fatal(err)
296 }
297 }
298 }
299
300
301
302
303
304 func WriteVersionedGoFile(filename, pkg string, b []byte) {
305 pattern := fileToPattern(filename)
306 updateBuildTags(pattern)
307 filename = fmt.Sprintf(pattern, UnicodeVersion())
308
309 w, err := os.Create(filename)
310 if err != nil {
311 log.Fatalf("Could not create file %s: %v", filename, err)
312 }
313 defer w.Close()
314 if _, err = WriteGo(w, pkg, buildTags(), b); err != nil {
315 log.Fatalf("Error writing file %s: %v", filename, err)
316 }
317 }
318
319
320
321 func WriteGo(w io.Writer, pkg, tags string, b []byte) (n int, err error) {
322 src := []byte(header)
323 if tags != "" {
324 src = append(src, tagLines(tags)...)
325 src = append(src, '\n')
326 }
327 src = append(src, fmt.Sprintf("package %s\n\n", pkg)...)
328 src = append(src, b...)
329 formatted, err := format.Source(src)
330 if err != nil {
331
332
333 n, _ = w.Write(src)
334 return n, err
335 }
336 return w.Write(formatted)
337 }
338
339
340
341 func Repackage(inFile, outFile, pkg string) {
342 src, err := os.ReadFile(inFile)
343 if err != nil {
344 log.Fatalf("reading %s: %v", inFile, err)
345 }
346 const toDelete = "package main\n\n"
347 i := bytes.Index(src, []byte(toDelete))
348 if i < 0 {
349 log.Fatalf("Could not find %q in %s.", toDelete, inFile)
350 }
351 w := &bytes.Buffer{}
352 w.Write(src[i+len(toDelete):])
353 WriteGoFile(outFile, pkg, w.Bytes())
354 }
355
View as plain text