├── README.md ├── LICENSE ├── vendor └── rsc.io │ └── getopt │ ├── LICENSE │ ├── README.md │ ├── getopt_test.go │ └── getopt.go └── cmd ├── mailgun-sendmail └── main.go ├── mailgun-mail └── mail.go └── internal └── mg └── mg.go /README.md: -------------------------------------------------------------------------------- 1 | # rsc.io/mailgun 2 | 3 | This repo holds basic utilities for interacting with the 4 | [Mailgun email service](https://www.mailgun.com). 5 | 6 | `rsc.io/mailgun/cmd/mailgun-mail` is a drop-in replacement for the mail-sending mode of BSD mailx. 7 | 8 | `rsc.io/mailgun/cmd/mailgun-sendmail` is a drop-in replacement for the mail-sending mode of the sendmail daemon. 9 | 10 | The idea behind both these programs is that you can install them in 11 | place of the usual mail and sendmail programs, and then programs can 12 | still send mail from your local system, without having to configure and 13 | run a full-blown mail system. 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/rsc.io/getopt/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/rsc.io/getopt/README.md: -------------------------------------------------------------------------------- 1 | # rsc.io/getopt 2 | 3 | [For full package documentation, see [https://godoc.org/rsc.io/getopt](https://godoc.org/rsc.io/getopt).] 4 | 5 | package getopt // import "rsc.io/getopt" 6 | 7 | Package getopt parses command lines using [_getopt_(3)](http://man7.org/linux/man-pages/man3/getopt.3.html) syntax. It is a 8 | replacement for `flag.Parse` but still expects flags themselves to be defined 9 | in package flag. 10 | 11 | Flags defined with one-letter names are available as short flags (invoked 12 | using one dash, as in `-x`) and all flags are available as long flags (invoked 13 | using two dashes, as in `--x` or `--xylophone`). 14 | 15 | To use, define flags as usual with [package flag](https://godoc.org/flag). Then introduce any aliases 16 | by calling `getopt.Alias`: 17 | 18 | getopt.Alias("v", "verbose") 19 | 20 | Or call `getopt.Aliases` to define a list of aliases: 21 | 22 | getopt.Aliases( 23 | "v", "verbose", 24 | "x", "xylophone", 25 | ) 26 | 27 | One name in each pair must already be defined in package flag (so either 28 | "v" or "verbose", and also either "x" or "xylophone"). 29 | 30 | Then parse the command-line: 31 | 32 | getopt.Parse() 33 | 34 | If it encounters an error, `Parse` calls `flag.Usage` and then exits the 35 | program. 36 | 37 | When writing a custom `flag.Usage` function, call `getopt.PrintDefaults` instead 38 | of `flag.PrintDefaults` to get a usage message that includes the 39 | names of aliases in flag descriptions. 40 | 41 | At initialization time, package getopt installs a new `flag.Usage` that is the same 42 | as the default `flag.Usage` except that it calls `getopt.PrintDefaults` instead 43 | of `flag.PrintDefaults`. 44 | 45 | This package also defines a `FlagSet` wrapping the standard `flag.FlagSet`. 46 | 47 | ## Caveat 48 | 49 | In general Go flag parsing is preferred for new programs, because it is not 50 | as pedantic about the number of dashes used to invoke a flag (you can write 51 | `-verbose` or `--verbose` and the program does not care). This package is meant 52 | to be used in situations where, for legacy reasons, it is important to use 53 | exactly _getopt_(3) syntax, such as when rewriting in Go an existing tool that 54 | already uses _getopt_(3). 55 | -------------------------------------------------------------------------------- /vendor/rsc.io/getopt/getopt_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 getopt 6 | 7 | import ( 8 | "bytes" 9 | "flag" 10 | "fmt" 11 | "strings" 12 | "testing" 13 | ) 14 | 15 | // TODO: Real tests. 16 | 17 | type testFlagSet struct { 18 | A *bool 19 | B *bool 20 | C *bool 21 | D *bool 22 | I *int 23 | Long *int 24 | S *string 25 | Args []string 26 | 27 | t *testing.T 28 | buf bytes.Buffer 29 | flag *FlagSet 30 | } 31 | 32 | func (tf *testFlagSet) str() string { 33 | out := "" 34 | if *tf.A { 35 | out += " -a" 36 | } 37 | if *tf.B { 38 | out += " -b" 39 | } 40 | if *tf.C { 41 | out += " -c" 42 | } 43 | if *tf.D { 44 | out += " -d" 45 | } 46 | if *tf.I != 0 { 47 | out += fmt.Sprintf(" -i %d", *tf.I) 48 | } 49 | if *tf.Long != 0 { 50 | out += fmt.Sprintf(" --long %d", *tf.Long) 51 | } 52 | if *tf.S != "" { 53 | out += " -s " + *tf.S 54 | } 55 | if len(tf.Args) > 0 { 56 | out += " " + strings.Join(tf.Args, " ") 57 | } 58 | if out == "" { 59 | return out 60 | } 61 | return out[1:] 62 | } 63 | 64 | func newTestFlagSet(t *testing.T) *testFlagSet { 65 | tf := &testFlagSet{t: t, flag: NewFlagSet("x", flag.ContinueOnError)} 66 | f := tf.flag 67 | f.SetOutput(&tf.buf) 68 | tf.A = f.Bool("a", false, "desc of a") 69 | tf.B = f.Bool("b", false, "desc of b") 70 | tf.C = f.Bool("c", false, "desc of c") 71 | tf.D = f.Bool("d", false, "desc of d") 72 | tf.Long = f.Int("long", 0, "long only") 73 | f.Alias("a", "aah") 74 | f.Aliases("b", "beeta", "c", "charlie") 75 | tf.I = f.Int("i", 0, "i") 76 | f.Alias("i", "india") 77 | tf.S = f.String("sierra", "", "string") 78 | f.Alias("s", "sierra") 79 | 80 | return tf 81 | } 82 | 83 | var tests = []struct { 84 | cmd string 85 | out string 86 | }{ 87 | {"-i 1", "-i 1"}, 88 | {"--india 1", "-i 1"}, 89 | {"--india=1", "-i 1"}, 90 | {"-i=1", `ERR: invalid value "=1" for flag -i: strconv.ParseInt: parsing "=1": invalid syntax`}, 91 | {"--i=1", "-i 1"}, 92 | {"-abc", "-a -b -c"}, 93 | {"--abc", `ERR: flag provided but not defined: --abc`}, 94 | {"-sfoo", "-s foo"}, 95 | {"-s foo", "-s foo"}, 96 | {"--s=foo", "-s foo"}, 97 | {"-s=foo", "-s =foo"}, 98 | {"-s", `ERR: missing argument for -s`}, 99 | {"--s", `ERR: missing argument for --s`}, 100 | {"--s=", ``}, 101 | {"-sfooi1 -i2", "-i 2 -s fooi1"}, 102 | {"-absfoo", "-a -b -s foo"}, 103 | {"-i1 -- arg", "-i 1 arg"}, 104 | {"-i1 - arg", "-i 1 - arg"}, 105 | {"-i1 --- arg", `ERR: flag provided but not defined: ---`}, 106 | {"-i1 arg", "-i 1 arg"}, 107 | {"--aah --charlie --beeta --sierra=123", "-a -b -c -s 123"}, 108 | {"-i1 --long=2", "-i 1 --long 2"}, 109 | } 110 | 111 | func TestBasic(t *testing.T) { 112 | for _, tt := range tests { 113 | tf := newTestFlagSet(t) 114 | err := tf.flag.Parse(strings.Fields(tt.cmd)) 115 | var out string 116 | if err != nil { 117 | out = "ERR: " + err.Error() 118 | } else { 119 | tf.Args = tf.flag.Args() 120 | out = tf.str() 121 | } 122 | if out != tt.out { 123 | t.Errorf("%s:\nhave %s\nwant %s", tt.cmd, out, tt.out) 124 | } 125 | } 126 | } 127 | 128 | var wantHelpText = ` -a, --aah 129 | desc of a 130 | -b, --beeta 131 | desc of b 132 | -c, --charlie 133 | desc of c 134 | -d desc of d 135 | -i, --india int 136 | i 137 | --long int 138 | long only 139 | -s, --sierra string 140 | string 141 | ` 142 | 143 | func TestHelpText(t *testing.T) { 144 | tf := newTestFlagSet(t) 145 | tf.flag.PrintDefaults() 146 | out := tf.buf.String() 147 | if out != wantHelpText { 148 | t.Errorf("have<\n%s>\nwant<\n%s>", out, wantHelpText) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /cmd/mailgun-sendmail/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | // Mailgun-sendmail, a drop-in replacement for the standard 6 | // Unix sendmail program, sends mail using Mailgun. 7 | // 8 | // Usage: 9 | // 10 | // mailgun-sendmail [-itv] [-B type] [-b m] [-d val] [-F name] [-f addr] [-r addr] [addr ...] 11 | // 12 | // Mailgun-sendmail sends mail to the given addresses. 13 | // 14 | // The options are a subset of the standard sendmail options: 15 | // 16 | // -i ignore single dot lines on incoming message (default unless stdin is TTY) 17 | // -t use To:, Cc:, Bcc: lines from input 18 | // -v verbose mode 19 | // 20 | // -B type 21 | // set body type 22 | // -b code 23 | // set mode code (must be "m", the default, meaning deliver a message from standard input) 24 | // -d val 25 | // set debugging value 26 | // -F name 27 | // set full name of sender 28 | // -f addr 29 | // set address of sender 30 | // -r addr 31 | // archaic equivalent of -f 32 | // 33 | // Configuration 34 | // 35 | // Mailgun-mail expects to find an mailgun API domain and authorization key 36 | // of the form " api:key-" in the environment variable 37 | // $MAILGUNKEY, or else in the file $HOME/.mailgun.key, 38 | // or else in the file /etc/mailgun.key. 39 | // 40 | // Diagnostics 41 | // 42 | // If the file /var/log/mailgun.log can be opened for writing, mailgun 43 | // logs its actions, successes, and failures there. 44 | // 45 | package main 46 | 47 | import ( 48 | "bufio" 49 | "bytes" 50 | "flag" 51 | "fmt" 52 | "io" 53 | "net/mail" 54 | "os" 55 | "sort" 56 | 57 | "rsc.io/getopt" 58 | "rsc.io/mailgun/cmd/internal/mg" 59 | ) 60 | 61 | func usage() { 62 | mg.Logf("invalid command line") 63 | fmt.Fprintf(os.Stderr, "usage: mailgun-sendmail [options] [addr...]\n") 64 | getopt.PrintDefaults() 65 | os.Exit(2) 66 | } 67 | 68 | var ( 69 | Bflag string 70 | bflag string 71 | dflag mg.StringListFlag 72 | Fflag string 73 | fflag string 74 | iflag bool 75 | tflag bool 76 | vflag bool 77 | 78 | to mg.AddrListFlag 79 | ) 80 | 81 | func main() { 82 | mg.Init() 83 | 84 | flag.StringVar(&Bflag, "B", "", "set body `type` (ignored)") 85 | flag.StringVar(&bflag, "b", "m", "run operation named by `code` (must be m)") 86 | /* 87 | codes: 88 | ba ARPANET mode 89 | bd background daemon 90 | bD foreground daemon 91 | bh print persistent host status database 92 | bH purge persistent host status database 93 | bi initialize the alias database 94 | bm deliver mail in the usual way 95 | bp print a listing of the mail queue 96 | bs SMTP mode (SMTP on stdin/stdout) 97 | bt address test mode (for debugging config tables) 98 | bv verify names only (validating users or mailing lists) 99 | */ 100 | // flag.String("C", "", "use alternate config `file`") 101 | flag.Var(&dflag, "d", "set debugging `value` (http, nosend)") 102 | flag.StringVar(&Fflag, "F", "", "set the full `name` of the sender") 103 | flag.StringVar(&fflag, "f", "", "set the `from` address of the mail") 104 | flag.BoolVar(&iflag, "i", false, "ignore single dot lines on incoming message") 105 | flag.StringVar(&fflag, "r", "", "archaic alias for -f") 106 | flag.BoolVar(&tflag, "t", false, "read From:, To:, Cc:, Bcc: lines from message") 107 | // flag.Bool("U", false, "ignored (initial user submission)") 108 | // flag.String("V", "", "set the envelope `id`") 109 | flag.BoolVar(&vflag, "v", false, "verbose mode") 110 | flag.StringVar(new(string), "o", "", "set option (ignored)") 111 | // flag.Var(&Oflag, "O", "", "set `option=value`") 112 | 113 | flag.Usage = usage 114 | getopt.Parse() 115 | for _, v := range dflag { 116 | switch v { 117 | default: 118 | mg.Die(fmt.Errorf("unknown debug value -d %s", v)) 119 | case "http": 120 | mg.DebugHTTP = true 121 | case "nosend": 122 | mg.DisableMail = true 123 | } 124 | } 125 | mg.Verbose = vflag 126 | 127 | if bflag != "m" { 128 | mg.Die(fmt.Errorf("only sendmail -bm is supported")) 129 | } 130 | 131 | if flag.NArg() == 0 && !tflag { 132 | mg.Die(fmt.Errorf("no delivery addresses given")) 133 | } 134 | 135 | for _, arg := range flag.Args() { 136 | if err := to.Set(arg); err != nil { 137 | mg.Die(fmt.Errorf("cannot parse To: address: %v", err)) 138 | } 139 | } 140 | 141 | // From address. 142 | from := new(mail.Address) 143 | from.Name = Fflag 144 | if fflag != "" { 145 | from.Address = fflag 146 | } else { 147 | from.Address = os.Getenv("USER") 148 | if from.Address == "" && !tflag { 149 | mg.Die(fmt.Errorf("cannot determine From address: -f/-r not used, and $USER not set")) 150 | } 151 | } 152 | mg.FixLocalAddr(from) 153 | 154 | // Read message header from stdin. 155 | // At the least we need to delete the BCC line (apparently). 156 | // Note Header keys are as per textproto.CanonicalMIMEHeaderKey, so "Bcc" not "BCC". 157 | msg, err := mail.ReadMessage(stdinReader()) 158 | if err != nil { 159 | mg.Die(fmt.Errorf("reading message header: %v", err)) 160 | } 161 | if tflag { 162 | for _, key := range []string{"From", "To", "Cc", "Bcc"} { 163 | if len(msg.Header[key]) == 0 { 164 | continue 165 | } 166 | addrs, err := msg.Header.AddressList(key) 167 | if err != nil { 168 | mg.Die(fmt.Errorf("cannot parse %s: list: %v [%q]", key, err, msg.Header[key])) 169 | } 170 | if key == "From" && len(addrs) > 0 { 171 | *from = *addrs[0] 172 | continue 173 | } 174 | to = append(to, addrs...) 175 | } 176 | if len(to) == 0 { 177 | mg.Die(fmt.Errorf("no recipients found in message")) 178 | } 179 | if from.Address == "" { 180 | mg.Die(fmt.Errorf("cannot determine From address: -f/-r not used, $USER not set, and no From: line in -t message")) 181 | } 182 | } 183 | if len(msg.Header["From"]) == 0 { 184 | msg.Header["From"] = []string{from.String()} 185 | } 186 | delete(msg.Header, "Bcc") 187 | 188 | var hdr bytes.Buffer 189 | var keys []string 190 | for k := range msg.Header { 191 | keys = append(keys, k) 192 | } 193 | sort.Strings(keys) 194 | for _, k := range keys { 195 | for _, v := range msg.Header[k] { 196 | fmt.Fprintf(&hdr, "%s: %s\n", k, v) 197 | } 198 | } 199 | fmt.Fprintf(&hdr, "\n") 200 | 201 | mg.MailMIME(from, to, io.MultiReader(&hdr, msg.Body)) 202 | } 203 | 204 | func stdinReader() io.Reader { 205 | pr, pw := io.Pipe() 206 | go func() { 207 | msgCopy(pw, os.Stdin) 208 | pw.Close() 209 | }() 210 | return pr 211 | } 212 | 213 | var nl = []byte("\n") 214 | 215 | func msgCopy(w io.Writer, r io.Reader) { 216 | b := bufio.NewReaderSize(r, 64*1024) 217 | hdr := true 218 | for { 219 | line, err := b.ReadBytes('\n') 220 | if len(line) == 0 && err == io.EOF { 221 | break 222 | } 223 | if len(line) == 0 { 224 | mg.Die(fmt.Errorf("reading message: %v", err)) 225 | } 226 | // Stop reading tty stdin at line containing only ".\n", except in -i mode. 227 | if mg.IsTTY && !iflag && len(line) == 2 && line[0] == '.' && line[1] == '\n' { 228 | break 229 | } 230 | if hdr && line[0] != ' ' && line[0] != '\t' && bytes.IndexByte(line, ':') < 0 { 231 | hdr = false 232 | if line[0] != '\n' { 233 | // sendmail accepts a non-header line as first line of body. 234 | // mail.ReadMessage wants a blank line. Give it one. 235 | w.Write(nl) 236 | } 237 | } 238 | w.Write(line) 239 | if line[len(line)-1] != '\n' { 240 | w.Write(nl) 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /cmd/mailgun-mail/mail.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | // Mailgun-mail, a drop-in replacement for the sending half 6 | // of the standard Unix mail program, sends mail using Mailgun. 7 | // 8 | // Usage: 9 | // 10 | // mailgun-mail [-Edntv] [-a file] [-b bcc] [-c cc] [-r from] [-s subject] to... 11 | // 12 | // Mailgun-mail sends mail to the given "to" addresses. 13 | // 14 | // The options are a subset of the standard mail program's options: 15 | // 16 | // -E discard (do not send) empty messages 17 | // -d print debugging information 18 | // -n do not send any mail 19 | // -t use From:, Subject:, To:, Cc:, Bcc: lines from input 20 | // -v verbose mode 21 | // 22 | // -a file 23 | // attach the file to the message (can repeat) 24 | // -b addr 25 | // bcc the address (can repeat) 26 | // -c addr 27 | // cc the address (can repeat) 28 | // -r from 29 | // set the from address 30 | // -s subject 31 | // set mail subject 32 | // 33 | // Configuration 34 | // 35 | // Mailgun-mail expects to find an mailgun API domain and authorization key 36 | // of the form " api:key-" in the environment variable 37 | // $MAILGUNKEY, or else in the file $HOME/.mailgun.key, 38 | // or else in the file /etc/mailgun.key. 39 | // 40 | // Diagnostics 41 | // 42 | // If the file /var/log/mailgun.log can be opened for writing, mailgun 43 | // logs its actions, successes, and failures there. 44 | // 45 | package main 46 | 47 | import ( 48 | "bufio" 49 | "bytes" 50 | "flag" 51 | "fmt" 52 | "io" 53 | "net/mail" 54 | "os" 55 | "strings" 56 | 57 | "rsc.io/getopt" 58 | "rsc.io/mailgun/cmd/internal/mg" 59 | ) 60 | 61 | func usage() { 62 | mg.Logf("invalid command line") 63 | fmt.Fprintf(os.Stderr, "usage: mailgun-mail [options] [addr...]\n") 64 | getopt.PrintDefaults() 65 | os.Exit(2) 66 | } 67 | 68 | var ( 69 | Eflag bool 70 | dflag bool 71 | nflag bool 72 | tflag bool 73 | vflag bool 74 | ) 75 | 76 | func main() { 77 | mg.Init() 78 | 79 | var to, cc, bcc, froms mg.AddrListFlag 80 | var aflag, rflag, sflag mg.StringListFlag 81 | var body bytes.Buffer 82 | 83 | flag.BoolVar(&Eflag, "E", false, "discard (do not send) empty messages") 84 | flag.BoolVar(&dflag, "d", false, "print debugging information") 85 | flag.BoolVar(&nflag, "n", false, "do not send actual mail") 86 | flag.BoolVar(&tflag, "t", false, "use From:, Subject:, To:, Cc:, and Bcc: lines from message header") 87 | flag.BoolVar(&vflag, "v", false, "verbose mode") 88 | 89 | flag.Var(&aflag, "a", "attach `file` to message") 90 | flag.Var(&bcc, "b", "BCC `address") 91 | flag.Var(&cc, "c", "CC `address") 92 | flag.Var(&rflag, "r", "send mail from `address`") // list so we can tell empty from missing 93 | flag.Var(&sflag, "s", "set message `subject`") // list so we can tell empty from missing 94 | 95 | flag.Usage = usage 96 | getopt.Parse() 97 | mg.DisableMail = nflag 98 | mg.DebugHTTP = dflag 99 | mg.Verbose = vflag 100 | 101 | // To addresses from command line. 102 | if flag.NArg() == 0 && !tflag { 103 | mg.Die(fmt.Errorf("mail reading is not supported")) 104 | } 105 | for _, arg := range flag.Args() { 106 | if err := to.Set(arg); err != nil { 107 | mg.Die(fmt.Errorf("cannot parse To: address: %v", err)) 108 | } 109 | } 110 | 111 | // From address. 112 | from := new(mail.Address) 113 | if len(rflag) > 0 { 114 | a, err := mg.ParseAddress(rflag[len(rflag)-1]) 115 | if err != nil { 116 | mg.Die(fmt.Errorf("cannot parse From: address: %v", err)) 117 | } 118 | from = a 119 | } else { 120 | from.Address = os.Getenv("USER") 121 | if from.Address == "" && !tflag { 122 | mg.Die(fmt.Errorf("cannot determine From address: -r not used, and $USER not set")) 123 | } 124 | } 125 | 126 | // Subject from command line or TTY. 127 | b := bufio.NewReader(os.Stdin) 128 | subject := "" 129 | if len(sflag) > 0 { 130 | subject = sflag[len(sflag)-1] 131 | } else if mg.IsTTY { 132 | fmt.Fprintf(os.Stderr, "Subject: ") 133 | line, err := b.ReadBytes('\n') 134 | if len(line) == 0 && err == io.EOF { 135 | mg.Logf("no subject, no text, not sending") 136 | return 137 | } 138 | if len(line) == 0 && err != nil { 139 | mg.Die(fmt.Errorf("reading subject: %v", err)) 140 | } 141 | subject = strings.TrimSuffix(string(line), "\n") 142 | } 143 | 144 | // From, Subject, To, CC, BCC from input using -t. 145 | if tflag { 146 | for { 147 | line, err := b.ReadBytes('\n') 148 | if len(line) == 0 && err == io.EOF { 149 | break 150 | } 151 | if len(line) == 0 && err != nil { 152 | mg.Die(fmt.Errorf("reading message: %v", err)) 153 | } 154 | i := bytes.IndexByte(line, ':') 155 | if i < 0 { 156 | if len(line) > 0 && line[0] != '\n' { 157 | if line[0] == '.' && (len(line) == 1 || len(line) == 2 && line[1] == '\n') && mg.IsTTY { 158 | goto Send 159 | } 160 | body.Write(line) 161 | } 162 | break 163 | } 164 | key := string(line[:i]) 165 | val := strings.TrimSpace(string(line[i+1:])) 166 | var list *mg.AddrListFlag 167 | switch strings.ToLower(key) { 168 | default: 169 | fmt.Fprintf(os.Stderr, "mailgun-mail: ignoring header field %q\n", strings.TrimSuffix(string(line), "\n")) 170 | case "from": 171 | list = &froms 172 | case "subject": 173 | subject = val 174 | continue 175 | case "to": 176 | list = &to 177 | key = "To" 178 | case "cc": 179 | list = &cc 180 | key = "CC" 181 | case "bcc": 182 | list = &bcc 183 | key = "BCC" 184 | } 185 | 186 | addrs, err := mail.ParseAddressList(val) 187 | if err != nil { 188 | mg.Die(fmt.Errorf("cannot parse %s: list: %v [%q]", key, err, val)) 189 | } 190 | *list = append(*list, addrs...) 191 | } 192 | 193 | if from.Address == "" { 194 | mg.Die(fmt.Errorf("cannot determine From address: -r not used, $USER not set, and no From: line in -t message")) 195 | } 196 | } 197 | 198 | if mg.IsTTY { 199 | // Message from TTY ends with . on line by itself or EOF. 200 | for { 201 | line, err := b.ReadBytes('\n') 202 | if len(line) == 0 && err == io.EOF { 203 | break 204 | } 205 | if len(line) == 0 && err != nil { 206 | mg.Die(fmt.Errorf("reading message: %v", err)) 207 | } 208 | if len(line) == 2 && line[0] == '.' && line[1] == '\n' { 209 | break 210 | } 211 | body.Write(line) 212 | } 213 | fmt.Fprintf(os.Stderr, "EOT\n") // dumb but mail does it 214 | } else { 215 | // Message from stdin is until EOF. 216 | _, err := io.Copy(&body, b) 217 | if err != nil { 218 | mg.Die(fmt.Errorf("reading message: %v", err)) 219 | } 220 | } 221 | 222 | Send: 223 | msg := &mg.Message{ 224 | From: from, 225 | To: to, 226 | CC: cc, 227 | BCC: bcc, 228 | Subject: subject, 229 | Body: body.String(), 230 | Attachments: aflag, 231 | } 232 | if vflag { 233 | printList := func(x []*mail.Address) string { 234 | var s []string 235 | for _, a := range x { 236 | s = append(s, a.String()) 237 | } 238 | return strings.Join(s, ", ") 239 | } 240 | fmt.Fprintf(os.Stderr, "from: %v\n", msg.From) 241 | fmt.Fprintf(os.Stderr, "to: %v\n", printList(msg.To)) 242 | fmt.Fprintf(os.Stderr, "cc: %v\n", printList(msg.CC)) 243 | fmt.Fprintf(os.Stderr, "bcc: %v\n", printList(msg.BCC)) 244 | fmt.Fprintf(os.Stderr, "subject: %v\n", msg.Subject) 245 | fmt.Fprintf(os.Stderr, "body: %d bytes\n", len(msg.Body)) 246 | if len(msg.Attachments) > 0 { 247 | fmt.Fprintf(os.Stderr, "attachments:\n") 248 | for _, a := range msg.Attachments { 249 | fmt.Fprintf(os.Stderr, "\t%s\n", a) 250 | } 251 | } 252 | } 253 | mg.Mail(msg) 254 | } 255 | -------------------------------------------------------------------------------- /cmd/internal/mg/mg.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 mg holds common code shared between 6 | // the various mailgun commands. 7 | package mg 8 | 9 | import ( 10 | "bytes" 11 | "encoding/json" 12 | "fmt" 13 | "io" 14 | "io/ioutil" 15 | "log" 16 | "mime/multipart" 17 | "net/http" 18 | "net/http/httputil" 19 | "net/mail" 20 | "os" 21 | "path/filepath" 22 | "strings" 23 | ) 24 | 25 | var ( 26 | IsTTY bool = isTTY() 27 | Domain string 28 | APIKey string 29 | User string = os.Getenv("USER") 30 | Verbose bool 31 | ) 32 | 33 | func isTTY() bool { 34 | stdin, err := os.Stdin.Stat() 35 | return err == nil && stdin.Mode()&(os.ModeDevice|os.ModeCharDevice) == os.ModeDevice|os.ModeCharDevice 36 | } 37 | 38 | func Init() { 39 | f, err := os.OpenFile("/var/log/mailgun.log", os.O_WRONLY|os.O_APPEND, 0) 40 | if err == nil { 41 | log.SetOutput(f) 42 | } else { 43 | log.SetOutput(ioutil.Discard) 44 | } 45 | 46 | readConfig() 47 | } 48 | 49 | func readConfig() { 50 | key := os.Getenv("MAILGUNKEY") 51 | if key != "" { 52 | parseKey("$MAILGUNKEY", key) 53 | return 54 | } 55 | file := os.Getenv("HOME") + "/.mailgun.key" 56 | data, err := ioutil.ReadFile(file) 57 | if err == nil { 58 | parseKey(file, string(data)) 59 | return 60 | } 61 | file = "/etc/mailgun.key" 62 | data, err = ioutil.ReadFile(file) 63 | if err == nil { 64 | parseKey(file, string(data)) 65 | return 66 | } 67 | Die(err) 68 | } 69 | 70 | func Die(err error) { 71 | log.Printf("[%s]%q %v", User, os.Args, err) 72 | fmt.Fprintf(os.Stderr, "%s: %s\n", filepath.Base(os.Args[0]), err) 73 | os.Exit(2) 74 | } 75 | 76 | func parseKey(src, key string) { 77 | f := strings.Fields(key) 78 | if len(f) != 2 || !strings.Contains(f[0], ".") || !strings.HasPrefix(f[1], "api:key-") { 79 | Die(fmt.Errorf("malformed mailgun API key in %s", src)) 80 | } 81 | Domain = f[0] 82 | APIKey = strings.TrimPrefix(f[1], "api:") 83 | } 84 | 85 | func ParseAddress(addr string) (*mail.Address, error) { 86 | if !strings.ContainsAny(addr, "<>()\" \t\r\n") { 87 | return &mail.Address{Address: addr}, nil 88 | } 89 | if strings.HasSuffix(addr, ">") && !strings.HasPrefix(addr, `"`) { 90 | // Foo (Bar) 91 | // not handled "correctly" by ParseAddress. 92 | if i := strings.LastIndex(addr, "<"); i >= 0 { 93 | return &mail.Address{Name: strings.TrimSpace(addr[:i]), Address: addr[i:len(addr)-1]}, nil 94 | } 95 | } 96 | return mail.ParseAddress(addr) 97 | 98 | } 99 | 100 | type AddrListFlag []*mail.Address 101 | 102 | func (x *AddrListFlag) String() string { 103 | if len(*x) == 0 { 104 | return "" 105 | } 106 | return "[addrs]" 107 | } 108 | 109 | func (x *AddrListFlag) Set(addr string) error { 110 | a, err := ParseAddress(addr) 111 | if err != nil { 112 | return err 113 | } 114 | *x = append(*x, a) 115 | return nil 116 | } 117 | 118 | type StringListFlag []string 119 | 120 | func (x *StringListFlag) String() string { 121 | if len(*x) == 0 { 122 | return "" 123 | } 124 | return "[strings]" 125 | } 126 | 127 | func (x *StringListFlag) Set(s string) error { 128 | *x = append(*x, s) 129 | return nil 130 | } 131 | 132 | var ( 133 | DebugHTTP bool 134 | DisableMail bool 135 | ) 136 | 137 | // A Message is a structured mail message to be sent. 138 | type Message struct { 139 | From *mail.Address 140 | To []*mail.Address 141 | CC []*mail.Address 142 | BCC []*mail.Address 143 | Subject string 144 | Body string `json:"-"` 145 | Attachments []string // file names 146 | } 147 | 148 | // Allow implicit local domain in addresses. 149 | func FixLocalAddr(a *mail.Address) { 150 | if !strings.Contains(a.Address, "@") { 151 | a.Address += "@" + Domain 152 | } 153 | } 154 | 155 | func FixLocalAddrs(list []*mail.Address) { 156 | for _, a := range list { 157 | FixLocalAddr(a) 158 | } 159 | } 160 | 161 | func Mail(msg *Message) { 162 | FixLocalAddr(msg.From) 163 | FixLocalAddrs(msg.To) 164 | FixLocalAddrs(msg.CC) 165 | FixLocalAddrs(msg.BCC) 166 | 167 | var allTo []*mail.Address 168 | allTo = append(allTo, msg.To...) 169 | allTo = append(allTo, msg.CC...) 170 | allTo = append(allTo, msg.BCC...) 171 | 172 | w, end := startPost(msg.From, allTo, "messages") 173 | check(w.WriteField("from", msg.From.String())) 174 | for _, a := range msg.To { 175 | check(w.WriteField("to", a.String())) 176 | } 177 | for _, a := range msg.CC { 178 | check(w.WriteField("cc", a.String())) 179 | } 180 | for _, a := range msg.BCC { 181 | check(w.WriteField("bcc", a.String())) 182 | } 183 | if msg.Subject != "" { 184 | check(w.WriteField("subject", msg.Subject)) 185 | } 186 | check(w.WriteField("text", msg.Body)) 187 | 188 | for _, file := range msg.Attachments { 189 | ww, err := w.CreateFormFile("attachment", filepath.Base(file)) 190 | check(err) 191 | f, err := os.Open(file) 192 | if err != nil { 193 | Die(fmt.Errorf("attaching file: %v", err)) 194 | } 195 | if _, err := io.Copy(ww, f); err != nil { 196 | Die(fmt.Errorf("attaching file: %v", err)) 197 | } 198 | f.Close() 199 | } 200 | check(w.Close()) 201 | end() 202 | } 203 | 204 | func startPost(from *mail.Address, to []*mail.Address, endpoint string) (w *multipart.Writer, end func()) { 205 | pr, pw := io.Pipe() 206 | w = multipart.NewWriter(pw) 207 | endpoint = "https://api.mailgun.net/v3/" + Domain + "/" + endpoint 208 | c := make(chan int) 209 | go runPost(from, to, endpoint, w.FormDataContentType(), pr, c) 210 | end = func() { 211 | pw.Close() 212 | <-c 213 | } 214 | return w, end 215 | } 216 | 217 | type countingReader struct { 218 | total int64 219 | r io.Reader 220 | } 221 | 222 | func (c *countingReader) Read(b []byte) (int, error) { 223 | n, err := c.r.Read(b) 224 | c.total += int64(n) 225 | return n, err 226 | } 227 | 228 | func runPost(from *mail.Address, to []*mail.Address, endpoint, bodytype string, body io.Reader, c chan int) { 229 | cr := &countingReader{r: body} 230 | req, err := http.NewRequest("POST", endpoint, cr) 231 | check(err) 232 | req.Header.Set("Content-Type", bodytype) 233 | req.SetBasicAuth("api", APIKey) 234 | 235 | if DebugHTTP { 236 | dump, err := httputil.DumpRequest(req, true) 237 | if err != nil { 238 | Die(fmt.Errorf("dumping request: %v", err)) 239 | } 240 | os.Stderr.Write(dump) 241 | } 242 | 243 | if DisableMail { 244 | fmt.Fprintf(os.Stderr, "not sending mail (disabled)\n") 245 | io.Copy(ioutil.Discard, body) 246 | c <- 1 247 | return 248 | } 249 | 250 | resp, err := http.DefaultClient.Do(req) 251 | if err != nil { 252 | Die(fmt.Errorf("sending mail: %v", err)) 253 | } 254 | 255 | if DebugHTTP { 256 | dump, err := httputil.DumpResponse(resp, true) 257 | if err != nil { 258 | Die(fmt.Errorf("dumping response: %v", err)) 259 | } 260 | os.Stderr.Write(dump) 261 | } 262 | 263 | data, err := ioutil.ReadAll(resp.Body) 264 | if resp.StatusCode != 200 { 265 | Die(fmt.Errorf("sending mail: %v\n%s", resp.Status, data)) 266 | } 267 | if err != nil { 268 | Die(fmt.Errorf("sending mail: %v\n%s", err, data)) 269 | } 270 | 271 | var mailResp struct { 272 | Message string `json:"message"` 273 | ID string `json:"id"` 274 | } 275 | if err := json.Unmarshal(data, &mailResp); err != nil { 276 | Die(fmt.Errorf("sending mail: invalid JSON response: %v\n%s", err, data)) 277 | } 278 | var compact bytes.Buffer 279 | json.Compact(&compact, data) 280 | log.Printf("[%s]%q from=%q to=%q len=%d resp=%s", User, os.Args, from, to, cr.total, compact.Bytes()) 281 | if IsTTY || Verbose { 282 | fmt.Fprintf(os.Stderr, "mailgun: %s\n", mailResp.Message) 283 | } 284 | c <- 1 285 | } 286 | func check(err error) { 287 | if err != nil { 288 | Die(fmt.Errorf("creating mailgun API request: %v", err)) 289 | } 290 | } 291 | 292 | func MailMIME(from *mail.Address, to []*mail.Address, mime io.Reader) { 293 | FixLocalAddr(from) 294 | FixLocalAddrs(to) 295 | 296 | w, end := startPost(from, to, "messages.mime") 297 | for _, a := range to { 298 | check(w.WriteField("to", a.String())) 299 | } 300 | ww, err := w.CreateFormFile("message", "mime.msg") 301 | check(err) 302 | _, err = io.Copy(ww, mime) 303 | check(err) 304 | check(w.Close()) 305 | end() 306 | } 307 | 308 | func Logf(format string, args ...interface{}) { 309 | log.Printf("[%s]%q %s", User, os.Args, fmt.Sprintf(format, args...)) 310 | } 311 | -------------------------------------------------------------------------------- /vendor/rsc.io/getopt/getopt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 getopt parses command lines using getopt(3) syntax. 6 | // It is a replacement for flag.Parse but still expects flags themselves 7 | // to be defined in package flag. 8 | // 9 | // Flags defined with one-letter names are available as short flags 10 | // (invoked using one dash, as in -x) and all flags are available as 11 | // long flags (invoked using two dashes, as in --x or --xylophone). 12 | // 13 | // To use, define flags as usual with package flag. Then introduce 14 | // any aliases by calling getopt.Alias: 15 | // 16 | // getopt.Alias("n", "dry-run") 17 | // getopt.Alias("v", "verbose") 18 | // 19 | // Or call getopt.Aliases to define a list of aliases: 20 | // 21 | // getopt.Aliases( 22 | // "n", "dry-run", 23 | // "v", "verbose", 24 | // ) 25 | // 26 | // One name in each pair must already be defined in package flag 27 | // (so either "n" or "dry-run", and also either "v" or "verbose"). 28 | // 29 | // Then parse the command-line: 30 | // 31 | // getopt.Parse() 32 | // 33 | // If it encounters an error, Parse calls flag.Usage and then exits the program. 34 | // 35 | // When writing a custom flag.Usage function, call getopt.PrintDefaults 36 | // instead of flag.PrintDefaults to get a usage message that includes the 37 | // names of aliases in flag descriptions. 38 | // 39 | // At initialization time, this package installs a new flag.Usage that is the 40 | // same as the default flag.Usage except that it calls getopt.PrintDefaults 41 | // instead of flag.PrintDefaults. 42 | // 43 | // This package also defines a FlagSet wrapping the standard flag.FlagSet. 44 | // 45 | // Caveat 46 | // 47 | // In general Go flag parsing is preferred for new programs, because 48 | // it is not as pedantic about the number of dashes used to invoke 49 | // a flag (you can write -verbose or --verbose and the program 50 | // does not care). This package is meant to be used in situations 51 | // where, for legacy reasons, it is important to use exactly getopt(3) 52 | // syntax, such as when rewriting in Go an existing tool that already 53 | // uses getopt(3). 54 | package getopt // import "rsc.io/getopt" 55 | 56 | import ( 57 | "flag" 58 | "fmt" 59 | "io" 60 | "os" 61 | "reflect" 62 | "strings" 63 | "unicode/utf8" 64 | ) 65 | 66 | func init() { 67 | flag.Usage = func() { 68 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) 69 | PrintDefaults() // ours not package flag's 70 | } 71 | 72 | CommandLine.FlagSet = flag.CommandLine 73 | CommandLine.name = os.Args[0] 74 | CommandLine.errorHandling = flag.ExitOnError 75 | CommandLine.outw = os.Stderr 76 | CommandLine.Usage = func() { flag.Usage() } 77 | } 78 | 79 | var CommandLine FlagSet 80 | 81 | // A FlagSet is a set of defined flags. 82 | // It wraps and provides the same interface as flag.FlagSet 83 | // but parses command line arguments using getopt syntax. 84 | // 85 | // Note that "go doc" shows only the methods customized 86 | // by package getopt; FlagSet also provides all the methods 87 | // of the embedded flag.FlagSet, like Bool, Int, NArg, and so on. 88 | type FlagSet struct { 89 | *flag.FlagSet 90 | 91 | alias map[string]string 92 | unalias map[string]string 93 | name string 94 | errorHandling flag.ErrorHandling 95 | outw io.Writer 96 | } 97 | 98 | func (f *FlagSet) out() io.Writer { 99 | if f.outw == nil { 100 | return os.Stderr 101 | } 102 | return f.outw 103 | } 104 | 105 | // SetOutput sets the destination for usage and error messages. 106 | // If output is nil, os.Stderr is used. 107 | func (f *FlagSet) SetOutput(output io.Writer) { 108 | f.FlagSet.SetOutput(output) 109 | f.outw = output 110 | } 111 | 112 | // NewFlagSet returns a new, empty flag set with the specified name and error 113 | // handling property. 114 | func NewFlagSet(name string, errorHandling flag.ErrorHandling) *FlagSet { 115 | f := new(FlagSet) 116 | f.Init(name, errorHandling) 117 | return f 118 | } 119 | 120 | // Init sets the name and error handling proprety for a flag set. 121 | func (f *FlagSet) Init(name string, errorHandling flag.ErrorHandling) { 122 | if f.FlagSet == nil { 123 | f.FlagSet = new(flag.FlagSet) 124 | } 125 | f.FlagSet.Init(name, errorHandling) 126 | f.name = name 127 | f.errorHandling = errorHandling 128 | f.FlagSet.Usage = f.defaultUsage 129 | } 130 | 131 | func (f *FlagSet) init() { 132 | if f.alias == nil { 133 | f.alias = make(map[string]string) 134 | f.unalias = make(map[string]string) 135 | } 136 | } 137 | 138 | // Lookup returns the Flag structure of the named flag, 139 | // returning nil if none exists. 140 | // If name is a defined alias for a defined flag, 141 | // Lookup returns the original flag; in this case 142 | // the Name field in the result will differ from the 143 | // name passed to Lookup. 144 | func (f *FlagSet) Lookup(name string) *flag.Flag { 145 | if x, ok := f.alias[name]; ok { 146 | name = x 147 | } 148 | return f.FlagSet.Lookup(name) 149 | } 150 | 151 | // Alias introduces an alias for an existing flag name. 152 | // The short name must be a single letter, and the long name must be multiple letters. 153 | // Exactly one name must be defined as a flag already: the undefined name is introduced 154 | // as an alias for the defined name. 155 | // Alias panics if both names are already defined or if both are undefined. 156 | // 157 | // For example, if a flag named "v" is already defined using package flag, 158 | // then it is available as -v (or --v). Calling Alias("v", "verbose") makes the same 159 | // flag also available as --verbose. 160 | func Alias(short, long string) { 161 | CommandLine.Alias(short, long) 162 | } 163 | 164 | // Alias introduces an alias for an existing flag name. 165 | // The short name must be a single letter, and the long name must be multiple letters. 166 | // Exactly one name must be defined as a flag already: the undefined name is introduced 167 | // as an alias for the defined name. 168 | // Alias panics if both names are already defined or if both are undefined. 169 | // 170 | // For example, if a flag named "v" is already defined using package flag, 171 | // then it is available as -v (or --v). Calling Alias("v", "verbose") makes the same 172 | // flag also available as --verbose. 173 | func (f *FlagSet) Alias(short, long string) { 174 | f.init() 175 | if short == "" || long == "" { 176 | panic("Alias: invalid empty flag name") 177 | } 178 | if utf8.RuneCountInString(short) != 1 { 179 | panic("Alias: invalid short flag name -" + short) 180 | } 181 | if utf8.RuneCountInString(long) == 1 { 182 | panic("Alias: invalid long flag name --" + long) 183 | } 184 | 185 | f1 := f.Lookup(short) 186 | f2 := f.Lookup(long) 187 | if f1 == nil && f2 == nil { 188 | panic("Alias: neither -" + short + " nor -" + long + " is a defined flag") 189 | } 190 | if f1 != nil && f2 != nil { 191 | panic("Alias: both -" + short + " and -" + long + " are defined flags") 192 | } 193 | 194 | if f1 != nil { 195 | f.alias[long] = short 196 | f.unalias[short] = long 197 | } else { 198 | f.alias[short] = long 199 | f.unalias[long] = short 200 | } 201 | } 202 | 203 | // Aliases introduces zero or more aliases. The argument list must consist of an 204 | // even number of strings making up a sequence of short, long pairs to be passed 205 | // to Alias. 206 | func Aliases(list ...string) { 207 | CommandLine.Aliases(list...) 208 | } 209 | 210 | // Aliases introduces zero or more aliases. The argument list must consist of an 211 | // even number of strings making up a sequence of short, long pairs to be passed 212 | // to Alias. 213 | func (f *FlagSet) Aliases(list ...string) { 214 | if len(list)%2 != 0 { 215 | panic("getopt: Aliases not invoked with pairs") 216 | } 217 | for i := 0; i < len(list); i += 2 { 218 | f.Alias(list[i], list[i+1]) 219 | } 220 | } 221 | 222 | type boolFlag interface { 223 | IsBoolFlag() bool 224 | } 225 | 226 | func (f *FlagSet) failf(format string, args ...interface{}) error { 227 | err := fmt.Errorf(format, args...) 228 | fmt.Fprintln(f.out(), err) 229 | f.Usage() 230 | return err 231 | } 232 | 233 | // defaultUsage is the default function to print a usage message. 234 | func (f *FlagSet) defaultUsage() { 235 | if f.name == "" { 236 | fmt.Fprintf(f.out(), "Usage:\n") 237 | } else { 238 | fmt.Fprintf(f.out(), "Usage of %s:\n", f.name) 239 | } 240 | f.PrintDefaults() 241 | } 242 | 243 | // Parse parses the command-line flags from os.Args[1:]. 244 | func Parse() { 245 | CommandLine.Parse(os.Args[1:]) 246 | } 247 | 248 | // Parse parses flag definitions from the argument list, 249 | // which should not include the command name. 250 | // Parse must be called after all flags and aliases in the FlagSet are defined 251 | // and before flags are accessed by the program. 252 | // The return value will be flag.ErrHelp if -h or --help were used but not defined. 253 | func (f *FlagSet) Parse(args []string) error { 254 | for len(args) > 0 { 255 | arg := args[0] 256 | if len(arg) < 2 || arg[0] != '-' { 257 | break 258 | } 259 | args = args[1:] 260 | if arg[:2] == "--" { 261 | // Process single long option. 262 | if arg == "--" { 263 | break 264 | } 265 | name := arg[2:] 266 | value := "" 267 | haveValue := false 268 | if i := strings.Index(name, "="); i >= 0 { 269 | name, value = name[:i], name[i+1:] 270 | haveValue = true 271 | } 272 | fg := f.Lookup(name) 273 | if fg == nil { 274 | if name == "h" || name == "help" { 275 | // TODO ErrHelp 276 | } 277 | return f.failf("flag provided but not defined: --%s", name) 278 | } 279 | if b, ok := fg.Value.(boolFlag); ok && b.IsBoolFlag() { 280 | if haveValue { 281 | if err := fg.Value.Set(value); err != nil { 282 | return f.failf("invalid boolean value %q for --%s: %v", value, name, err) 283 | } 284 | } else { 285 | if err := fg.Value.Set("true"); err != nil { 286 | return f.failf("invalid boolean flag %s: %v", name, err) 287 | } 288 | } 289 | continue 290 | } 291 | if !haveValue { 292 | if len(args) == 0 { 293 | return f.failf("missing argument for --%s", name) 294 | } 295 | value, args = args[0], args[1:] 296 | } 297 | if err := fg.Value.Set(value); err != nil { 298 | return f.failf("invalid value %q for flag --%s: %v", value, name, err) 299 | } 300 | continue 301 | } 302 | 303 | // Process one or more short options. 304 | for arg = arg[1:]; arg != ""; { 305 | r, size := utf8.DecodeRuneInString(arg) 306 | if r == utf8.RuneError && size == 1 { 307 | return f.failf("invalid UTF8 in command-line flags") 308 | } 309 | name := arg[:size] 310 | arg = arg[size:] 311 | fg := f.Lookup(name) 312 | if fg == nil { 313 | if name == "h" { 314 | // TODO ErrHelp 315 | } 316 | return f.failf("flag provided but not defined: -%s", name) 317 | } 318 | if b, ok := fg.Value.(boolFlag); ok && b.IsBoolFlag() { 319 | if err := fg.Value.Set("true"); err != nil { 320 | return f.failf("invalid boolean flag %s: %v", name, err) 321 | } 322 | continue 323 | } 324 | if arg == "" { 325 | if len(args) == 0 { 326 | return f.failf("missing argument for -%s", name) 327 | } 328 | arg, args = args[0], args[1:] 329 | } 330 | if err := fg.Value.Set(arg); err != nil { 331 | return f.failf("invalid value %q for flag -%s: %v", arg, name, err) 332 | } 333 | break // consumed arg 334 | } 335 | } 336 | 337 | // Arrange for flag.NArg, flag.Args, etc to work properly. 338 | f.FlagSet.Parse(append([]string{"--"}, args...)) 339 | return nil 340 | } 341 | 342 | // PrintDefaults is like flag.PrintDefaults but includes information 343 | // about short/long alias pairs and prints the correct syntax for 344 | // long flags. 345 | func PrintDefaults() { 346 | CommandLine.PrintDefaults() 347 | } 348 | 349 | // PrintDefaults is like flag.PrintDefaults but includes information 350 | // about short/long alias pairs and prints the correct syntax for 351 | // long flags. 352 | func (f *FlagSet) PrintDefaults() { 353 | f.FlagSet.VisitAll(func(fg *flag.Flag) { 354 | name := fg.Name 355 | short, long := "", "" 356 | other := f.unalias[name] 357 | if utf8.RuneCountInString(name) > 1 { 358 | long, short = name, other 359 | } else { 360 | short, long = name, other 361 | } 362 | var s string 363 | if short != "" { 364 | s = fmt.Sprintf(" -%s", short) // Two spaces before -; see next two comments. 365 | if long != "" { 366 | s += ", --" + long 367 | } 368 | } else { 369 | s = fmt.Sprintf(" --%s", long) // Two spaces before -; see next two comments. 370 | } 371 | name, usage := flag.UnquoteUsage(fg) 372 | if len(name) > 0 { 373 | s += " " + name 374 | } 375 | 376 | // Boolean flags of one ASCII letter are so common we 377 | // treat them specially, putting their usage on the same line. 378 | if len(s) <= 4 { // space, space, '-', 'x'. 379 | s += "\t" 380 | } else { 381 | // Four spaces before the tab triggers good alignment 382 | // for both 4- and 8-space tab stops. 383 | s += "\n \t" 384 | } 385 | s += usage 386 | if !isZeroValue(fg, fg.DefValue) { 387 | if strings.HasSuffix(reflect.TypeOf(fg.Value).String(), "stringValue") { 388 | // put quotes on the value 389 | s += fmt.Sprintf(" (default %q)", fg.DefValue) 390 | } else { 391 | s += fmt.Sprintf(" (default %v)", fg.DefValue) 392 | } 393 | } 394 | fmt.Fprint(f.out(), s, "\n") 395 | }) 396 | } 397 | 398 | // isZeroValue guesses whether the string represents the zero 399 | // value for a flag. It is not accurate but in practice works OK. 400 | func isZeroValue(f *flag.Flag, value string) bool { 401 | // Build a zero value of the flag's Value type, and see if the 402 | // result of calling its String method equals the value passed in. 403 | // This works unless the Value type is itself an interface type. 404 | typ := reflect.TypeOf(f.Value) 405 | var z reflect.Value 406 | if typ.Kind() == reflect.Ptr { 407 | z = reflect.New(typ.Elem()) 408 | } else { 409 | z = reflect.Zero(typ) 410 | } 411 | if value == z.Interface().(flag.Value).String() { 412 | return true 413 | } 414 | 415 | switch value { 416 | case "false": 417 | return true 418 | case "": 419 | return true 420 | case "0": 421 | return true 422 | } 423 | return false 424 | } 425 | --------------------------------------------------------------------------------