You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

334 lines
10 KiB

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