Black Lives Matter. Support the Equal Justice Initiative.

Source file src/cmd/internal/objabi/flag.go

Documentation: cmd/internal/objabi

     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 objabi
     6  
     7  import (
     8  	"bytes"
     9  	"flag"
    10  	"fmt"
    11  	"internal/buildcfg"
    12  	"io"
    13  	"io/ioutil"
    14  	"log"
    15  	"os"
    16  	"strconv"
    17  	"strings"
    18  )
    19  
    20  func Flagcount(name, usage string, val *int) {
    21  	flag.Var((*count)(val), name, usage)
    22  }
    23  
    24  func Flagfn1(name, usage string, f func(string)) {
    25  	flag.Var(fn1(f), name, usage)
    26  }
    27  
    28  func Flagprint(w io.Writer) {
    29  	flag.CommandLine.SetOutput(w)
    30  	flag.PrintDefaults()
    31  }
    32  
    33  func Flagparse(usage func()) {
    34  	flag.Usage = usage
    35  	os.Args = expandArgs(os.Args)
    36  	flag.Parse()
    37  }
    38  
    39  // expandArgs expands "response files" arguments in the provided slice.
    40  //
    41  // A "response file" argument starts with '@' and the rest of that
    42  // argument is a filename with CR-or-CRLF-separated arguments. Each
    43  // argument in the named files can also contain response file
    44  // arguments. See Issue 18468.
    45  //
    46  // The returned slice 'out' aliases 'in' iff the input did not contain
    47  // any response file arguments.
    48  //
    49  // TODO: handle relative paths of recursive expansions in different directories?
    50  // Is there a spec for this? Are relative paths allowed?
    51  func expandArgs(in []string) (out []string) {
    52  	// out is nil until we see a "@" argument.
    53  	for i, s := range in {
    54  		if strings.HasPrefix(s, "@") {
    55  			if out == nil {
    56  				out = make([]string, 0, len(in)*2)
    57  				out = append(out, in[:i]...)
    58  			}
    59  			slurp, err := ioutil.ReadFile(s[1:])
    60  			if err != nil {
    61  				log.Fatal(err)
    62  			}
    63  			args := strings.Split(strings.TrimSpace(strings.Replace(string(slurp), "\r", "", -1)), "\n")
    64  			for i, arg := range args {
    65  				args[i] = DecodeArg(arg)
    66  			}
    67  			out = append(out, expandArgs(args)...)
    68  		} else if out != nil {
    69  			out = append(out, s)
    70  		}
    71  	}
    72  	if out == nil {
    73  		return in
    74  	}
    75  	return
    76  }
    77  
    78  func AddVersionFlag() {
    79  	flag.Var(versionFlag{}, "V", "print version and exit")
    80  }
    81  
    82  var buildID string // filled in by linker
    83  
    84  type versionFlag struct{}
    85  
    86  func (versionFlag) IsBoolFlag() bool { return true }
    87  func (versionFlag) Get() interface{} { return nil }
    88  func (versionFlag) String() string   { return "" }
    89  func (versionFlag) Set(s string) error {
    90  	name := os.Args[0]
    91  	name = name[strings.LastIndex(name, `/`)+1:]
    92  	name = name[strings.LastIndex(name, `\`)+1:]
    93  	name = strings.TrimSuffix(name, ".exe")
    94  
    95  	p := ""
    96  
    97  	if s == "goexperiment" {
    98  		// test/run.go uses this to discover the full set of
    99  		// experiment tags. Report everything.
   100  		p = " X:" + strings.Join(buildcfg.AllExperiments(), ",")
   101  	} else {
   102  		// If the enabled experiments differ from the defaults,
   103  		// include that difference.
   104  		if goexperiment := buildcfg.GOEXPERIMENT(); goexperiment != "" {
   105  			p = " X:" + goexperiment
   106  		}
   107  	}
   108  
   109  	// The go command invokes -V=full to get a unique identifier
   110  	// for this tool. It is assumed that the release version is sufficient
   111  	// for releases, but during development we include the full
   112  	// build ID of the binary, so that if the compiler is changed and
   113  	// rebuilt, we notice and rebuild all packages.
   114  	if s == "full" {
   115  		if strings.HasPrefix(buildcfg.Version, "devel") {
   116  			p += " buildID=" + buildID
   117  		}
   118  	}
   119  
   120  	fmt.Printf("%s version %s%s\n", name, buildcfg.Version, p)
   121  	os.Exit(0)
   122  	return nil
   123  }
   124  
   125  // count is a flag.Value that is like a flag.Bool and a flag.Int.
   126  // If used as -name, it increments the count, but -name=x sets the count.
   127  // Used for verbose flag -v.
   128  type count int
   129  
   130  func (c *count) String() string {
   131  	return fmt.Sprint(int(*c))
   132  }
   133  
   134  func (c *count) Set(s string) error {
   135  	switch s {
   136  	case "true":
   137  		*c++
   138  	case "false":
   139  		*c = 0
   140  	default:
   141  		n, err := strconv.Atoi(s)
   142  		if err != nil {
   143  			return fmt.Errorf("invalid count %q", s)
   144  		}
   145  		*c = count(n)
   146  	}
   147  	return nil
   148  }
   149  
   150  func (c *count) Get() interface{} {
   151  	return int(*c)
   152  }
   153  
   154  func (c *count) IsBoolFlag() bool {
   155  	return true
   156  }
   157  
   158  func (c *count) IsCountFlag() bool {
   159  	return true
   160  }
   161  
   162  type fn1 func(string)
   163  
   164  func (f fn1) Set(s string) error {
   165  	f(s)
   166  	return nil
   167  }
   168  
   169  func (f fn1) String() string { return "" }
   170  
   171  // DecodeArg decodes an argument.
   172  //
   173  // This function is public for testing with the parallel encoder.
   174  func DecodeArg(arg string) string {
   175  	// If no encoding, fastpath out.
   176  	if !strings.ContainsAny(arg, "\\\n") {
   177  		return arg
   178  	}
   179  
   180  	// We can't use strings.Builder as this must work at bootstrap.
   181  	var b bytes.Buffer
   182  	var wasBS bool
   183  	for _, r := range arg {
   184  		if wasBS {
   185  			switch r {
   186  			case '\\':
   187  				b.WriteByte('\\')
   188  			case 'n':
   189  				b.WriteByte('\n')
   190  			default:
   191  				// This shouldn't happen. The only backslashes that reach here
   192  				// should encode '\n' and '\\' exclusively.
   193  				panic("badly formatted input")
   194  			}
   195  		} else if r == '\\' {
   196  			wasBS = true
   197  			continue
   198  		} else {
   199  			b.WriteRune(r)
   200  		}
   201  		wasBS = false
   202  	}
   203  	return b.String()
   204  }
   205  

View as plain text