123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- // 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"
- "io/ioutil"
- "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"},
- }
- // 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)
- }
- ioutil.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 := ioutil.ReadAll(r)
- if err != nil {
- log.Fatalf("Could not download file: %v", err)
- }
- os.MkdirAll(filepath.Dir(file), permissions)
- if err := ioutil.WriteFile(file, b, permissions); err != nil {
- log.Fatalf("Could not create file: %v", err)
- }
- return ioutil.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)
- }
- func updateBuildTags(pattern string) {
- for _, t := range tags {
- oldFile := fmt.Sprintf(pattern, t.version)
- b, err := ioutil.ReadFile(oldFile)
- if err != nil {
- continue
- }
- build := fmt.Sprintf("// +build %s", t.buildTags)
- b = regexp.MustCompile(`// \+build .*`).ReplaceAll(b, []byte(build))
- err = ioutil.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, fmt.Sprintf("// +build %s\n\n", tags)...)
- }
- 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 := ioutil.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())
- }
|