github.com/tcnksm/go@v0.0.0-20141208075154-439b32936367/src/cmd/go/main.go (about)

     1  // Copyright 2011 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 main
     6  
     7  import (
     8  	"bytes"
     9  	"flag"
    10  	"fmt"
    11  	"go/build"
    12  	"io"
    13  	"log"
    14  	"os"
    15  	"os/exec"
    16  	"path"
    17  	"path/filepath"
    18  	"regexp"
    19  	"runtime"
    20  	"strings"
    21  	"sync"
    22  	"text/template"
    23  	"unicode"
    24  	"unicode/utf8"
    25  )
    26  
    27  // A Command is an implementation of a go command
    28  // like go build or go fix.
    29  type Command struct {
    30  	// Run runs the command.
    31  	// The args are the arguments after the command name.
    32  	Run func(cmd *Command, args []string)
    33  
    34  	// UsageLine is the one-line usage message.
    35  	// The first word in the line is taken to be the command name.
    36  	UsageLine string
    37  
    38  	// Short is the short description shown in the 'go help' output.
    39  	Short string
    40  
    41  	// Long is the long message shown in the 'go help <this-command>' output.
    42  	Long string
    43  
    44  	// Flag is a set of flags specific to this command.
    45  	Flag flag.FlagSet
    46  
    47  	// CustomFlags indicates that the command will do its own
    48  	// flag parsing.
    49  	CustomFlags bool
    50  }
    51  
    52  // Name returns the command's name: the first word in the usage line.
    53  func (c *Command) Name() string {
    54  	name := c.UsageLine
    55  	i := strings.Index(name, " ")
    56  	if i >= 0 {
    57  		name = name[:i]
    58  	}
    59  	return name
    60  }
    61  
    62  func (c *Command) Usage() {
    63  	fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine)
    64  	fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long))
    65  	os.Exit(2)
    66  }
    67  
    68  // Runnable reports whether the command can be run; otherwise
    69  // it is a documentation pseudo-command such as importpath.
    70  func (c *Command) Runnable() bool {
    71  	return c.Run != nil
    72  }
    73  
    74  // Commands lists the available commands and help topics.
    75  // The order here is the order in which they are printed by 'go help'.
    76  var commands = []*Command{
    77  	cmdBuild,
    78  	cmdClean,
    79  	cmdEnv,
    80  	cmdFix,
    81  	cmdFmt,
    82  	cmdGenerate,
    83  	cmdGet,
    84  	cmdInstall,
    85  	cmdList,
    86  	cmdRun,
    87  	cmdTest,
    88  	cmdTool,
    89  	cmdVersion,
    90  	cmdVet,
    91  
    92  	helpC,
    93  	helpFileType,
    94  	helpGopath,
    95  	helpImportPath,
    96  	helpPackages,
    97  	helpTestflag,
    98  	helpTestfunc,
    99  }
   100  
   101  var exitStatus = 0
   102  var exitMu sync.Mutex
   103  
   104  func setExitStatus(n int) {
   105  	exitMu.Lock()
   106  	if exitStatus < n {
   107  		exitStatus = n
   108  	}
   109  	exitMu.Unlock()
   110  }
   111  
   112  func main() {
   113  	_ = go11tag
   114  	flag.Usage = usage
   115  	flag.Parse()
   116  	log.SetFlags(0)
   117  
   118  	args := flag.Args()
   119  	if len(args) < 1 {
   120  		usage()
   121  	}
   122  
   123  	if args[0] == "help" {
   124  		help(args[1:])
   125  		return
   126  	}
   127  
   128  	// Diagnose common mistake: GOPATH==GOROOT.
   129  	// This setting is equivalent to not setting GOPATH at all,
   130  	// which is not what most people want when they do it.
   131  	if gopath := os.Getenv("GOPATH"); gopath == runtime.GOROOT() {
   132  		fmt.Fprintf(os.Stderr, "warning: GOPATH set to GOROOT (%s) has no effect\n", gopath)
   133  	} else {
   134  		for _, p := range filepath.SplitList(gopath) {
   135  			// Note: using HasPrefix instead of Contains because a ~ can appear
   136  			// in the middle of directory elements, such as /tmp/git-1.8.2~rc3
   137  			// or C:\PROGRA~1. Only ~ as a path prefix has meaning to the shell.
   138  			if strings.HasPrefix(p, "~") {
   139  				fmt.Fprintf(os.Stderr, "go: GOPATH entry cannot start with shell metacharacter '~': %q\n", p)
   140  				os.Exit(2)
   141  			}
   142  			if build.IsLocalImport(p) {
   143  				fmt.Fprintf(os.Stderr, "go: GOPATH entry is relative; must be absolute path: %q.\nRun 'go help gopath' for usage.\n", p)
   144  				os.Exit(2)
   145  			}
   146  		}
   147  	}
   148  
   149  	if fi, err := os.Stat(goroot); err != nil || !fi.IsDir() {
   150  		fmt.Fprintf(os.Stderr, "go: cannot find GOROOT directory: %v\n", goroot)
   151  		os.Exit(2)
   152  	}
   153  
   154  	for _, cmd := range commands {
   155  		if cmd.Name() == args[0] && cmd.Run != nil {
   156  			cmd.Flag.Usage = func() { cmd.Usage() }
   157  			if cmd.CustomFlags {
   158  				args = args[1:]
   159  			} else {
   160  				cmd.Flag.Parse(args[1:])
   161  				args = cmd.Flag.Args()
   162  			}
   163  			cmd.Run(cmd, args)
   164  			exit()
   165  			return
   166  		}
   167  	}
   168  
   169  	fmt.Fprintf(os.Stderr, "go: unknown subcommand %q\nRun 'go help' for usage.\n", args[0])
   170  	setExitStatus(2)
   171  	exit()
   172  }
   173  
   174  var usageTemplate = `Go is a tool for managing Go source code.
   175  
   176  Usage:
   177  
   178  	go command [arguments]
   179  
   180  The commands are:
   181  {{range .}}{{if .Runnable}}
   182      {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
   183  
   184  Use "go help [command]" for more information about a command.
   185  
   186  Additional help topics:
   187  {{range .}}{{if not .Runnable}}
   188      {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
   189  
   190  Use "go help [topic]" for more information about that topic.
   191  
   192  `
   193  
   194  var helpTemplate = `{{if .Runnable}}usage: go {{.UsageLine}}
   195  
   196  {{end}}{{.Long | trim}}
   197  `
   198  
   199  var documentationTemplate = `// Copyright 2011 The Go Authors.  All rights reserved.
   200  // Use of this source code is governed by a BSD-style
   201  // license that can be found in the LICENSE file.
   202  
   203  // DO NOT EDIT THIS FILE. GENERATED BY mkdoc.sh.
   204  // Edit the documentation in other files and rerun mkdoc.sh to generate this one.
   205  
   206  /*
   207  {{range .}}{{if .Short}}{{.Short | capitalize}}
   208  
   209  {{end}}{{if .Runnable}}Usage:
   210  
   211  	go {{.UsageLine}}
   212  
   213  {{end}}{{.Long | trim}}
   214  
   215  
   216  {{end}}*/
   217  package main
   218  `
   219  
   220  // tmpl executes the given template text on data, writing the result to w.
   221  func tmpl(w io.Writer, text string, data interface{}) {
   222  	t := template.New("top")
   223  	t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
   224  	template.Must(t.Parse(text))
   225  	if err := t.Execute(w, data); err != nil {
   226  		panic(err)
   227  	}
   228  }
   229  
   230  func capitalize(s string) string {
   231  	if s == "" {
   232  		return s
   233  	}
   234  	r, n := utf8.DecodeRuneInString(s)
   235  	return string(unicode.ToTitle(r)) + s[n:]
   236  }
   237  
   238  func printUsage(w io.Writer) {
   239  	tmpl(w, usageTemplate, commands)
   240  }
   241  
   242  func usage() {
   243  	// special case "go test -h"
   244  	if len(os.Args) > 1 && os.Args[1] == "test" {
   245  		help([]string{"testflag"})
   246  		os.Exit(2)
   247  	}
   248  	printUsage(os.Stderr)
   249  	os.Exit(2)
   250  }
   251  
   252  // help implements the 'help' command.
   253  func help(args []string) {
   254  	if len(args) == 0 {
   255  		printUsage(os.Stdout)
   256  		// not exit 2: succeeded at 'go help'.
   257  		return
   258  	}
   259  	if len(args) != 1 {
   260  		fmt.Fprintf(os.Stderr, "usage: go help command\n\nToo many arguments given.\n")
   261  		os.Exit(2) // failed at 'go help'
   262  	}
   263  
   264  	arg := args[0]
   265  
   266  	// 'go help documentation' generates doc.go.
   267  	if arg == "documentation" {
   268  		buf := new(bytes.Buffer)
   269  		printUsage(buf)
   270  		usage := &Command{Long: buf.String()}
   271  		tmpl(os.Stdout, documentationTemplate, append([]*Command{usage}, commands...))
   272  		return
   273  	}
   274  
   275  	for _, cmd := range commands {
   276  		if cmd.Name() == arg {
   277  			tmpl(os.Stdout, helpTemplate, cmd)
   278  			// not exit 2: succeeded at 'go help cmd'.
   279  			return
   280  		}
   281  	}
   282  
   283  	fmt.Fprintf(os.Stderr, "Unknown help topic %#q.  Run 'go help'.\n", arg)
   284  	os.Exit(2) // failed at 'go help cmd'
   285  }
   286  
   287  // importPathsNoDotExpansion returns the import paths to use for the given
   288  // command line, but it does no ... expansion.
   289  func importPathsNoDotExpansion(args []string) []string {
   290  	if len(args) == 0 {
   291  		return []string{"."}
   292  	}
   293  	var out []string
   294  	for _, a := range args {
   295  		// Arguments are supposed to be import paths, but
   296  		// as a courtesy to Windows developers, rewrite \ to /
   297  		// in command-line arguments.  Handles .\... and so on.
   298  		if filepath.Separator == '\\' {
   299  			a = strings.Replace(a, `\`, `/`, -1)
   300  		}
   301  
   302  		// Put argument in canonical form, but preserve leading ./.
   303  		if strings.HasPrefix(a, "./") {
   304  			a = "./" + path.Clean(a)
   305  			if a == "./." {
   306  				a = "."
   307  			}
   308  		} else {
   309  			a = path.Clean(a)
   310  		}
   311  		if a == "all" || a == "std" {
   312  			out = append(out, allPackages(a)...)
   313  			continue
   314  		}
   315  		out = append(out, a)
   316  	}
   317  	return out
   318  }
   319  
   320  // importPaths returns the import paths to use for the given command line.
   321  func importPaths(args []string) []string {
   322  	args = importPathsNoDotExpansion(args)
   323  	var out []string
   324  	for _, a := range args {
   325  		if strings.Contains(a, "...") {
   326  			if build.IsLocalImport(a) {
   327  				out = append(out, allPackagesInFS(a)...)
   328  			} else {
   329  				out = append(out, allPackages(a)...)
   330  			}
   331  			continue
   332  		}
   333  		out = append(out, a)
   334  	}
   335  	return out
   336  }
   337  
   338  var atexitFuncs []func()
   339  
   340  func atexit(f func()) {
   341  	atexitFuncs = append(atexitFuncs, f)
   342  }
   343  
   344  func exit() {
   345  	for _, f := range atexitFuncs {
   346  		f()
   347  	}
   348  	os.Exit(exitStatus)
   349  }
   350  
   351  func fatalf(format string, args ...interface{}) {
   352  	errorf(format, args...)
   353  	exit()
   354  }
   355  
   356  func errorf(format string, args ...interface{}) {
   357  	log.Printf(format, args...)
   358  	setExitStatus(1)
   359  }
   360  
   361  var logf = log.Printf
   362  
   363  func exitIfErrors() {
   364  	if exitStatus != 0 {
   365  		exit()
   366  	}
   367  }
   368  
   369  func run(cmdargs ...interface{}) {
   370  	cmdline := stringList(cmdargs...)
   371  	if buildN || buildX {
   372  		fmt.Printf("%s\n", strings.Join(cmdline, " "))
   373  		if buildN {
   374  			return
   375  		}
   376  	}
   377  
   378  	cmd := exec.Command(cmdline[0], cmdline[1:]...)
   379  	cmd.Stdout = os.Stdout
   380  	cmd.Stderr = os.Stderr
   381  	if err := cmd.Run(); err != nil {
   382  		errorf("%v", err)
   383  	}
   384  }
   385  
   386  func runOut(dir string, cmdargs ...interface{}) []byte {
   387  	cmdline := stringList(cmdargs...)
   388  	cmd := exec.Command(cmdline[0], cmdline[1:]...)
   389  	cmd.Dir = dir
   390  	out, err := cmd.CombinedOutput()
   391  	if err != nil {
   392  		os.Stderr.Write(out)
   393  		errorf("%v", err)
   394  		out = nil
   395  	}
   396  	return out
   397  }
   398  
   399  // envForDir returns a copy of the environment
   400  // suitable for running in the given directory.
   401  // The environment is the current process's environment
   402  // but with an updated $PWD, so that an os.Getwd in the
   403  // child will be faster.
   404  func envForDir(dir string) []string {
   405  	env := os.Environ()
   406  	// Internally we only use rooted paths, so dir is rooted.
   407  	// Even if dir is not rooted, no harm done.
   408  	return mergeEnvLists([]string{"PWD=" + dir}, env)
   409  }
   410  
   411  // mergeEnvLists merges the two environment lists such that
   412  // variables with the same name in "in" replace those in "out".
   413  func mergeEnvLists(in, out []string) []string {
   414  NextVar:
   415  	for _, inkv := range in {
   416  		k := strings.SplitAfterN(inkv, "=", 2)[0]
   417  		for i, outkv := range out {
   418  			if strings.HasPrefix(outkv, k) {
   419  				out[i] = inkv
   420  				continue NextVar
   421  			}
   422  		}
   423  		out = append(out, inkv)
   424  	}
   425  	return out
   426  }
   427  
   428  // matchPattern(pattern)(name) reports whether
   429  // name matches pattern.  Pattern is a limited glob
   430  // pattern in which '...' means 'any string' and there
   431  // is no other special syntax.
   432  func matchPattern(pattern string) func(name string) bool {
   433  	re := regexp.QuoteMeta(pattern)
   434  	re = strings.Replace(re, `\.\.\.`, `.*`, -1)
   435  	// Special case: foo/... matches foo too.
   436  	if strings.HasSuffix(re, `/.*`) {
   437  		re = re[:len(re)-len(`/.*`)] + `(/.*)?`
   438  	}
   439  	reg := regexp.MustCompile(`^` + re + `$`)
   440  	return func(name string) bool {
   441  		return reg.MatchString(name)
   442  	}
   443  }
   444  
   445  // hasPathPrefix reports whether the path s begins with the
   446  // elements in prefix.
   447  func hasPathPrefix(s, prefix string) bool {
   448  	switch {
   449  	default:
   450  		return false
   451  	case len(s) == len(prefix):
   452  		return s == prefix
   453  	case len(s) > len(prefix):
   454  		if prefix != "" && prefix[len(prefix)-1] == '/' {
   455  			return strings.HasPrefix(s, prefix)
   456  		}
   457  		return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
   458  	}
   459  }
   460  
   461  // treeCanMatchPattern(pattern)(name) reports whether
   462  // name or children of name can possibly match pattern.
   463  // Pattern is the same limited glob accepted by matchPattern.
   464  func treeCanMatchPattern(pattern string) func(name string) bool {
   465  	wildCard := false
   466  	if i := strings.Index(pattern, "..."); i >= 0 {
   467  		wildCard = true
   468  		pattern = pattern[:i]
   469  	}
   470  	return func(name string) bool {
   471  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
   472  			wildCard && strings.HasPrefix(name, pattern)
   473  	}
   474  }
   475  
   476  // allPackages returns all the packages that can be found
   477  // under the $GOPATH directories and $GOROOT matching pattern.
   478  // The pattern is either "all" (all packages), "std" (standard packages)
   479  // or a path including "...".
   480  func allPackages(pattern string) []string {
   481  	pkgs := matchPackages(pattern)
   482  	if len(pkgs) == 0 {
   483  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
   484  	}
   485  	return pkgs
   486  }
   487  
   488  func matchPackages(pattern string) []string {
   489  	match := func(string) bool { return true }
   490  	treeCanMatch := func(string) bool { return true }
   491  	if pattern != "all" && pattern != "std" {
   492  		match = matchPattern(pattern)
   493  		treeCanMatch = treeCanMatchPattern(pattern)
   494  	}
   495  
   496  	have := map[string]bool{
   497  		"builtin": true, // ignore pseudo-package that exists only for documentation
   498  	}
   499  	if !buildContext.CgoEnabled {
   500  		have["runtime/cgo"] = true // ignore during walk
   501  	}
   502  	var pkgs []string
   503  
   504  	// Commands
   505  	cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator)
   506  	filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error {
   507  		if err != nil || !fi.IsDir() || path == cmd {
   508  			return nil
   509  		}
   510  		name := path[len(cmd):]
   511  		if !treeCanMatch(name) {
   512  			return filepath.SkipDir
   513  		}
   514  		// Commands are all in cmd/, not in subdirectories.
   515  		if strings.Contains(name, string(filepath.Separator)) {
   516  			return filepath.SkipDir
   517  		}
   518  
   519  		// We use, e.g., cmd/gofmt as the pseudo import path for gofmt.
   520  		name = "cmd/" + name
   521  		if have[name] {
   522  			return nil
   523  		}
   524  		have[name] = true
   525  		if !match(name) {
   526  			return nil
   527  		}
   528  		_, err = buildContext.ImportDir(path, 0)
   529  		if err != nil {
   530  			if _, noGo := err.(*build.NoGoError); !noGo {
   531  				log.Print(err)
   532  			}
   533  			return nil
   534  		}
   535  		pkgs = append(pkgs, name)
   536  		return nil
   537  	})
   538  
   539  	for _, src := range buildContext.SrcDirs() {
   540  		if pattern == "std" && src != gorootSrc {
   541  			continue
   542  		}
   543  		src = filepath.Clean(src) + string(filepath.Separator)
   544  		filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
   545  			if err != nil || !fi.IsDir() || path == src {
   546  				return nil
   547  			}
   548  
   549  			// Avoid .foo, _foo, and testdata directory trees.
   550  			_, elem := filepath.Split(path)
   551  			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
   552  				return filepath.SkipDir
   553  			}
   554  
   555  			name := filepath.ToSlash(path[len(src):])
   556  			if pattern == "std" && strings.Contains(name, ".") {
   557  				return filepath.SkipDir
   558  			}
   559  			if !treeCanMatch(name) {
   560  				return filepath.SkipDir
   561  			}
   562  			if have[name] {
   563  				return nil
   564  			}
   565  			have[name] = true
   566  			if !match(name) {
   567  				return nil
   568  			}
   569  			_, err = buildContext.ImportDir(path, 0)
   570  			if err != nil {
   571  				if _, noGo := err.(*build.NoGoError); noGo {
   572  					return nil
   573  				}
   574  			}
   575  			pkgs = append(pkgs, name)
   576  			return nil
   577  		})
   578  	}
   579  	return pkgs
   580  }
   581  
   582  // allPackagesInFS is like allPackages but is passed a pattern
   583  // beginning ./ or ../, meaning it should scan the tree rooted
   584  // at the given directory.  There are ... in the pattern too.
   585  func allPackagesInFS(pattern string) []string {
   586  	pkgs := matchPackagesInFS(pattern)
   587  	if len(pkgs) == 0 {
   588  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
   589  	}
   590  	return pkgs
   591  }
   592  
   593  func matchPackagesInFS(pattern string) []string {
   594  	// Find directory to begin the scan.
   595  	// Could be smarter but this one optimization
   596  	// is enough for now, since ... is usually at the
   597  	// end of a path.
   598  	i := strings.Index(pattern, "...")
   599  	dir, _ := path.Split(pattern[:i])
   600  
   601  	// pattern begins with ./ or ../.
   602  	// path.Clean will discard the ./ but not the ../.
   603  	// We need to preserve the ./ for pattern matching
   604  	// and in the returned import paths.
   605  	prefix := ""
   606  	if strings.HasPrefix(pattern, "./") {
   607  		prefix = "./"
   608  	}
   609  	match := matchPattern(pattern)
   610  
   611  	var pkgs []string
   612  	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
   613  		if err != nil || !fi.IsDir() {
   614  			return nil
   615  		}
   616  		if path == dir {
   617  			// filepath.Walk starts at dir and recurses. For the recursive case,
   618  			// the path is the result of filepath.Join, which calls filepath.Clean.
   619  			// The initial case is not Cleaned, though, so we do this explicitly.
   620  			//
   621  			// This converts a path like "./io/" to "io". Without this step, running
   622  			// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
   623  			// package, because prepending the prefix "./" to the unclean path would
   624  			// result in "././io", and match("././io") returns false.
   625  			path = filepath.Clean(path)
   626  		}
   627  
   628  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
   629  		_, elem := filepath.Split(path)
   630  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
   631  		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
   632  			return filepath.SkipDir
   633  		}
   634  
   635  		name := prefix + filepath.ToSlash(path)
   636  		if !match(name) {
   637  			return nil
   638  		}
   639  		if _, err = build.ImportDir(path, 0); err != nil {
   640  			if _, noGo := err.(*build.NoGoError); !noGo {
   641  				log.Print(err)
   642  			}
   643  			return nil
   644  		}
   645  		pkgs = append(pkgs, name)
   646  		return nil
   647  	})
   648  	return pkgs
   649  }
   650  
   651  // stringList's arguments should be a sequence of string or []string values.
   652  // stringList flattens them into a single []string.
   653  func stringList(args ...interface{}) []string {
   654  	var x []string
   655  	for _, arg := range args {
   656  		switch arg := arg.(type) {
   657  		case []string:
   658  			x = append(x, arg...)
   659  		case string:
   660  			x = append(x, arg)
   661  		default:
   662  			panic("stringList: invalid argument")
   663  		}
   664  	}
   665  	return x
   666  }
   667  
   668  // toFold returns a string with the property that
   669  //	strings.EqualFold(s, t) iff toFold(s) == toFold(t)
   670  // This lets us test a large set of strings for fold-equivalent
   671  // duplicates without making a quadratic number of calls
   672  // to EqualFold. Note that strings.ToUpper and strings.ToLower
   673  // have the desired property in some corner cases.
   674  func toFold(s string) string {
   675  	// Fast path: all ASCII, no upper case.
   676  	// Most paths look like this already.
   677  	for i := 0; i < len(s); i++ {
   678  		c := s[i]
   679  		if c >= utf8.RuneSelf || 'A' <= c && c <= 'Z' {
   680  			goto Slow
   681  		}
   682  	}
   683  	return s
   684  
   685  Slow:
   686  	var buf bytes.Buffer
   687  	for _, r := range s {
   688  		// SimpleFold(x) cycles to the next equivalent rune > x
   689  		// or wraps around to smaller values. Iterate until it wraps,
   690  		// and we've found the minimum value.
   691  		for {
   692  			r0 := r
   693  			r = unicode.SimpleFold(r0)
   694  			if r <= r0 {
   695  				break
   696  			}
   697  		}
   698  		// Exception to allow fast path above: A-Z => a-z
   699  		if 'A' <= r && r <= 'Z' {
   700  			r += 'a' - 'A'
   701  		}
   702  		buf.WriteRune(r)
   703  	}
   704  	return buf.String()
   705  }
   706  
   707  // foldDup reports a pair of strings from the list that are
   708  // equal according to strings.EqualFold.
   709  // It returns "", "" if there are no such strings.
   710  func foldDup(list []string) (string, string) {
   711  	clash := map[string]string{}
   712  	for _, s := range list {
   713  		fold := toFold(s)
   714  		if t := clash[fold]; t != "" {
   715  			if s > t {
   716  				s, t = t, s
   717  			}
   718  			return s, t
   719  		}
   720  		clash[fold] = s
   721  	}
   722  	return "", ""
   723  }