gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/text/internal/gen/gen.go (about)

     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 (http://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  	"io/ioutil"
    29  	"log"
    30  	"net/http"
    31  	"os"
    32  	"path"
    33  	"path/filepath"
    34  	"sync"
    35  	"unicode"
    36  
    37  	"golang.org/x/text/unicode/cldr"
    38  )
    39  
    40  var (
    41  	url = flag.String("url",
    42  		"http://www.unicode.org/Public",
    43  		"URL of Unicode database directory")
    44  	iana = flag.String("iana",
    45  		"http://www.iana.org",
    46  		"URL of the IANA repository")
    47  	unicodeVersion = flag.String("unicode",
    48  		getEnv("UNICODE_VERSION", unicode.Version),
    49  		"unicode version to use")
    50  	cldrVersion = flag.String("cldr",
    51  		getEnv("CLDR_VERSION", cldr.Version),
    52  		"cldr version to use")
    53  )
    54  
    55  func getEnv(name, def string) string {
    56  	if v := os.Getenv(name); v != "" {
    57  		return v
    58  	}
    59  	return def
    60  }
    61  
    62  // Init performs common initialization for a gen command. It parses the flags
    63  // and sets up the standard logging parameters.
    64  func Init() {
    65  	log.SetPrefix("")
    66  	log.SetFlags(log.Lshortfile)
    67  	flag.Parse()
    68  }
    69  
    70  const header = `// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
    71  
    72  package %s
    73  
    74  `
    75  
    76  // UnicodeVersion reports the requested Unicode version.
    77  func UnicodeVersion() string {
    78  	return *unicodeVersion
    79  }
    80  
    81  // UnicodeVersion reports the requested CLDR version.
    82  func CLDRVersion() string {
    83  	return *cldrVersion
    84  }
    85  
    86  // IsLocal reports whether data files are available locally.
    87  func IsLocal() bool {
    88  	dir, err := localReadmeFile()
    89  	if err != nil {
    90  		return false
    91  	}
    92  	if _, err = os.Stat(dir); err != nil {
    93  		return false
    94  	}
    95  	return true
    96  }
    97  
    98  // OpenUCDFile opens the requested UCD file. The file is specified relative to
    99  // the public Unicode root directory. It will call log.Fatal if there are any
   100  // errors.
   101  func OpenUCDFile(file string) io.ReadCloser {
   102  	return openUnicode(path.Join(*unicodeVersion, "ucd", file))
   103  }
   104  
   105  // OpenCLDRCoreZip opens the CLDR core zip file. It will call log.Fatal if there
   106  // are any errors.
   107  func OpenCLDRCoreZip() io.ReadCloser {
   108  	return OpenUnicodeFile("cldr", *cldrVersion, "core.zip")
   109  }
   110  
   111  // OpenUnicodeFile opens the requested file of the requested category from the
   112  // root of the Unicode data archive. The file is specified relative to the
   113  // public Unicode root directory. If version is "", it will use the default
   114  // Unicode version. It will call log.Fatal if there are any errors.
   115  func OpenUnicodeFile(category, version, file string) io.ReadCloser {
   116  	if version == "" {
   117  		version = UnicodeVersion()
   118  	}
   119  	return openUnicode(path.Join(category, version, file))
   120  }
   121  
   122  // OpenIANAFile opens the requested IANA file. The file is specified relative
   123  // to the IANA root, which is typically either http://www.iana.org or the
   124  // iana directory in the local mirror. It will call log.Fatal if there are any
   125  // errors.
   126  func OpenIANAFile(path string) io.ReadCloser {
   127  	return Open(*iana, "iana", path)
   128  }
   129  
   130  var (
   131  	dirMutex sync.Mutex
   132  	localDir string
   133  )
   134  
   135  const permissions = 0755
   136  
   137  func localReadmeFile() (string, error) {
   138  	p, err := build.Import("golang.org/x/text", "", build.FindOnly)
   139  	if err != nil {
   140  		return "", fmt.Errorf("Could not locate package: %v", err)
   141  	}
   142  	return filepath.Join(p.Dir, "DATA", "README"), nil
   143  }
   144  
   145  func getLocalDir() string {
   146  	dirMutex.Lock()
   147  	defer dirMutex.Unlock()
   148  
   149  	readme, err := localReadmeFile()
   150  	if err != nil {
   151  		log.Fatal(err)
   152  	}
   153  	dir := filepath.Dir(readme)
   154  	if _, err := os.Stat(readme); err != nil {
   155  		if err := os.MkdirAll(dir, permissions); err != nil {
   156  			log.Fatalf("Could not create directory: %v", err)
   157  		}
   158  		ioutil.WriteFile(readme, []byte(readmeTxt), permissions)
   159  	}
   160  	return dir
   161  }
   162  
   163  const readmeTxt = `Generated by golang.org/x/text/internal/gen. DO NOT EDIT.
   164  
   165  This directory contains downloaded files used to generate the various tables
   166  in the golang.org/x/text subrepo.
   167  
   168  Note that the language subtag repo (iana/assignments/language-subtag-registry)
   169  and all other times in the iana subdirectory are not versioned and will need
   170  to be periodically manually updated. The easiest way to do this is to remove
   171  the entire iana directory. This is mostly of concern when updating the language
   172  package.
   173  `
   174  
   175  // Open opens subdir/path if a local directory is specified and the file exists,
   176  // where subdir is a directory relative to the local root, or fetches it from
   177  // urlRoot/path otherwise. It will call log.Fatal if there are any errors.
   178  func Open(urlRoot, subdir, path string) io.ReadCloser {
   179  	file := filepath.Join(getLocalDir(), subdir, filepath.FromSlash(path))
   180  	return open(file, urlRoot, path)
   181  }
   182  
   183  func openUnicode(path string) io.ReadCloser {
   184  	file := filepath.Join(getLocalDir(), filepath.FromSlash(path))
   185  	return open(file, *url, path)
   186  }
   187  
   188  // TODO: automatically periodically update non-versioned files.
   189  
   190  func open(file, urlRoot, path string) io.ReadCloser {
   191  	if f, err := os.Open(file); err == nil {
   192  		return f
   193  	}
   194  	r := get(urlRoot, path)
   195  	defer r.Close()
   196  	b, err := ioutil.ReadAll(r)
   197  	if err != nil {
   198  		log.Fatalf("Could not download file: %v", err)
   199  	}
   200  	os.MkdirAll(filepath.Dir(file), permissions)
   201  	if err := ioutil.WriteFile(file, b, permissions); err != nil {
   202  		log.Fatalf("Could not create file: %v", err)
   203  	}
   204  	return ioutil.NopCloser(bytes.NewReader(b))
   205  }
   206  
   207  func get(root, path string) io.ReadCloser {
   208  	url := root + "/" + path
   209  	fmt.Printf("Fetching %s...", url)
   210  	defer fmt.Println(" done.")
   211  	resp, err := http.Get(url)
   212  	if err != nil {
   213  		log.Fatalf("HTTP GET: %v", err)
   214  	}
   215  	if resp.StatusCode != 200 {
   216  		log.Fatalf("Bad GET status for %q: %q", url, resp.Status)
   217  	}
   218  	return resp.Body
   219  }
   220  
   221  // TODO: use Write*Version in all applicable packages.
   222  
   223  // WriteUnicodeVersion writes a constant for the Unicode version from which the
   224  // tables are generated.
   225  func WriteUnicodeVersion(w io.Writer) {
   226  	fmt.Fprintf(w, "// UnicodeVersion is the Unicode version from which the tables in this package are derived.\n")
   227  	fmt.Fprintf(w, "const UnicodeVersion = %q\n\n", UnicodeVersion())
   228  }
   229  
   230  // WriteCLDRVersion writes a constant for the CLDR version from which the
   231  // tables are generated.
   232  func WriteCLDRVersion(w io.Writer) {
   233  	fmt.Fprintf(w, "// CLDRVersion is the CLDR version from which the tables in this package are derived.\n")
   234  	fmt.Fprintf(w, "const CLDRVersion = %q\n\n", CLDRVersion())
   235  }
   236  
   237  // WriteGoFile prepends a standard file comment and package statement to the
   238  // given bytes, applies gofmt, and writes them to a file with the given name.
   239  // It will call log.Fatal if there are any errors.
   240  func WriteGoFile(filename, pkg string, b []byte) {
   241  	w, err := os.Create(filename)
   242  	if err != nil {
   243  		log.Fatalf("Could not create file %s: %v", filename, err)
   244  	}
   245  	defer w.Close()
   246  	if _, err = WriteGo(w, pkg, b); err != nil {
   247  		log.Fatalf("Error writing file %s: %v", filename, err)
   248  	}
   249  }
   250  
   251  // WriteGo prepends a standard file comment and package statement to the given
   252  // bytes, applies gofmt, and writes them to w.
   253  func WriteGo(w io.Writer, pkg string, b []byte) (n int, err error) {
   254  	src := []byte(fmt.Sprintf(header, pkg))
   255  	src = append(src, b...)
   256  	formatted, err := format.Source(src)
   257  	if err != nil {
   258  		// Print the generated code even in case of an error so that the
   259  		// returned error can be meaningfully interpreted.
   260  		n, _ = w.Write(src)
   261  		return n, err
   262  	}
   263  	return w.Write(formatted)
   264  }
   265  
   266  // Repackage rewrites a Go file from belonging to package main to belonging to
   267  // the given package.
   268  func Repackage(inFile, outFile, pkg string) {
   269  	src, err := ioutil.ReadFile(inFile)
   270  	if err != nil {
   271  		log.Fatalf("reading %s: %v", inFile, err)
   272  	}
   273  	const toDelete = "package main\n\n"
   274  	i := bytes.Index(src, []byte(toDelete))
   275  	if i < 0 {
   276  		log.Fatalf("Could not find %q in %s.", toDelete, inFile)
   277  	}
   278  	w := &bytes.Buffer{}
   279  	w.Write(src[i+len(toDelete):])
   280  	WriteGoFile(outFile, pkg, w.Bytes())
   281  }