...

Source file src/golang.org/x/text/internal/gen/gen.go

Documentation: golang.org/x/text/internal/gen

     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  
     5  // Package gen contains common code for the various code generation tools in the
     6  // text repository. Its usage ensures consistency between tools.
     7  //
     8  // This package defines command line flags that are common to most generation
     9  // tools. The flags allow for specifying specific Unicode and CLDR versions
    10  // in the public Unicode data repository (https://www.unicode.org/Public).
    11  //
    12  // A local Unicode data mirror can be set through the flag -local or the
    13  // environment variable UNICODE_DIR. The former takes precedence. The local
    14  // directory should follow the same structure as the public repository.
    15  //
    16  // IANA data can also optionally be mirrored by putting it in the iana directory
    17  // rooted at the top of the local mirror. Beware, though, that IANA data is not
    18  // versioned. So it is up to the developer to use the right version.
    19  package gen // import "golang.org/x/text/internal/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  // Init performs common initialization for a gen command. It parses the flags
    64  // and sets up the standard logging parameters.
    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  // UnicodeVersion reports the requested Unicode version.
    76  func UnicodeVersion() string {
    77  	return *unicodeVersion
    78  }
    79  
    80  // CLDRVersion reports the requested CLDR version.
    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  // buildTags reports the build tags used for the current Unicode version.
    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  // IsLocal reports whether data files are available locally.
   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  // OpenUCDFile opens the requested UCD file. The file is specified relative to
   119  // the public Unicode root directory. It will call log.Fatal if there are any
   120  // errors.
   121  func OpenUCDFile(file string) io.ReadCloser {
   122  	return openUnicode(path.Join(*unicodeVersion, "ucd", file))
   123  }
   124  
   125  // OpenCLDRCoreZip opens the CLDR core zip file. It will call log.Fatal if there
   126  // are any errors.
   127  func OpenCLDRCoreZip() io.ReadCloser {
   128  	return OpenUnicodeFile("cldr", *cldrVersion, "core.zip")
   129  }
   130  
   131  // OpenUnicodeFile opens the requested file of the requested category from the
   132  // root of the Unicode data archive. The file is specified relative to the
   133  // public Unicode root directory. If version is "", it will use the default
   134  // Unicode version. It will call log.Fatal if there are any errors.
   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  // OpenIANAFile opens the requested IANA file. The file is specified relative
   143  // to the IANA root, which is typically either http://www.iana.org or the
   144  // iana directory in the local mirror. It will call log.Fatal if there are any
   145  // errors.
   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  // Open opens subdir/path if a local directory is specified and the file exists,
   196  // where subdir is a directory relative to the local root, or fetches it from
   197  // urlRoot/path otherwise. It will call log.Fatal if there are any errors.
   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  // TODO: automatically periodically update non-versioned files.
   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  // TODO: use Write*Version in all applicable packages.
   242  
   243  // WriteUnicodeVersion writes a constant for the Unicode version from which the
   244  // tables are generated.
   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  // WriteCLDRVersion writes a constant for the CLDR version from which the
   251  // tables are generated.
   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  // WriteGoFile prepends a standard file comment and package statement to the
   258  // given bytes, applies gofmt, and writes them to a file with the given name.
   259  // It will call log.Fatal if there are any errors.
   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  // tagLines returns the //go:build lines to add to the file.
   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  // WriteVersionedGoFile prepends a standard file comment, adds build tags to
   301  // version the file for the current Unicode version, and package statement to
   302  // the given bytes, applies gofmt, and writes them to a file with the given
   303  // name. It will call log.Fatal if there are any errors.
   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  // WriteGo prepends a standard file comment and package statement to the given
   320  // bytes, applies gofmt, and writes them to w.
   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  		// Print the generated code even in case of an error so that the
   332  		// returned error can be meaningfully interpreted.
   333  		n, _ = w.Write(src)
   334  		return n, err
   335  	}
   336  	return w.Write(formatted)
   337  }
   338  
   339  // Repackage rewrites a Go file from belonging to package main to belonging to
   340  // the given package.
   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