Vendored external dependencies. Thx SamWhited.
parent
17a4d864bb
commit
f9af5a75d3
@ -0,0 +1,5 @@
|
||||
language: go
|
||||
go:
|
||||
- tip
|
||||
script:
|
||||
- go test
|
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,6 @@
|
||||
go-xmpp
|
||||
=======
|
||||
|
||||
go xmpp library (original was written by russ cox )
|
||||
|
||||
[Documentation](https://godoc.org/github.com/mattn/go-xmpp)
|
@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"github.com/mattn/go-gtk/gtk"
|
||||
"github.com/mattn/go-xmpp"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gtk.Init(&os.Args)
|
||||
|
||||
window := gtk.NewWindow(gtk.WINDOW_TOPLEVEL)
|
||||
window.SetTitle("GoTalk")
|
||||
window.Connect("destroy", func() {
|
||||
gtk.MainQuit()
|
||||
})
|
||||
vbox := gtk.NewVBox(false, 1)
|
||||
scrolledwin := gtk.NewScrolledWindow(nil, nil)
|
||||
textview := gtk.NewTextView()
|
||||
textview.SetEditable(false)
|
||||
textview.SetCursorVisible(false)
|
||||
scrolledwin.Add(textview)
|
||||
vbox.Add(scrolledwin)
|
||||
|
||||
buffer := textview.GetBuffer()
|
||||
|
||||
entry := gtk.NewEntry()
|
||||
vbox.PackEnd(entry, false, false, 0)
|
||||
|
||||
window.Add(vbox)
|
||||
window.SetSizeRequest(300, 400)
|
||||
window.ShowAll()
|
||||
|
||||
dialog := gtk.NewDialog()
|
||||
dialog.SetTitle(window.GetTitle())
|
||||
sgroup := gtk.NewSizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
|
||||
|
||||
hbox := gtk.NewHBox(false, 1)
|
||||
dialog.GetVBox().Add(hbox)
|
||||
label := gtk.NewLabel("username:")
|
||||
sgroup.AddWidget(label)
|
||||
hbox.Add(label)
|
||||
username := gtk.NewEntry()
|
||||
hbox.Add(username)
|
||||
|
||||
hbox = gtk.NewHBox(false, 1)
|
||||
dialog.GetVBox().Add(hbox)
|
||||
label = gtk.NewLabel("password:")
|
||||
sgroup.AddWidget(label)
|
||||
hbox.Add(label)
|
||||
password := gtk.NewEntry()
|
||||
password.SetVisibility(false)
|
||||
hbox.Add(password)
|
||||
|
||||
dialog.AddButton(gtk.STOCK_OK, gtk.RESPONSE_OK)
|
||||
dialog.AddButton(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
|
||||
dialog.SetDefaultResponse(gtk.RESPONSE_OK)
|
||||
dialog.SetTransientFor(window)
|
||||
dialog.ShowAll()
|
||||
res := dialog.Run()
|
||||
username_ := username.GetText()
|
||||
password_ := password.GetText()
|
||||
dialog.Destroy()
|
||||
if res != gtk.RESPONSE_OK {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
xmpp.DefaultConfig = tls.Config{
|
||||
ServerName: "talk.google.com",
|
||||
InsecureSkipVerify: false,
|
||||
}
|
||||
|
||||
talk, err := xmpp.NewClient("talk.google.com:443", username_, password_, false)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
entry.Connect("activate", func() {
|
||||
text := entry.GetText()
|
||||
tokens := strings.SplitN(text, " ", 2)
|
||||
if len(tokens) == 2 {
|
||||
func() {
|
||||
defer recover()
|
||||
talk.Send(xmpp.Chat{Remote: tokens[0], Type: "chat", Text: tokens[1]})
|
||||
entry.SetText("")
|
||||
}()
|
||||
}
|
||||
})
|
||||
|
||||
go func() {
|
||||
for {
|
||||
func() {
|
||||
defer recover()
|
||||
chat, err := talk.Recv()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var iter gtk.TextIter
|
||||
buffer.GetStartIter(&iter)
|
||||
if msg, ok := chat.(xmpp.Chat); ok {
|
||||
buffer.Insert(&iter, msg.Remote+": "+msg.Text+"\n")
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
gtk.Main()
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/mattn/go-xmpp"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var server = flag.String("server", "talk.google.com:443", "server")
|
||||
var username = flag.String("username", "", "username")
|
||||
var password = flag.String("password", "", "password")
|
||||
var status = flag.String("status", "xa", "status")
|
||||
var statusMessage = flag.String("status-msg", "I for one welcome our new codebot overlords.", "status message")
|
||||
var notls = flag.Bool("notls", false, "No TLS")
|
||||
var debug = flag.Bool("debug", false, "debug output")
|
||||
var session = flag.Bool("session", false, "use server session")
|
||||
|
||||
func serverName(host string) string {
|
||||
return strings.Split(host, ":")[0]
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "usage: example [options]\n")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
flag.Parse()
|
||||
if *username == "" || *password == "" {
|
||||
if *debug && *username == "" && *password == "" {
|
||||
fmt.Fprintf(os.Stderr, "no username or password were given; attempting ANONYMOUS auth\n")
|
||||
} else if *username != "" || *password != "" {
|
||||
flag.Usage()
|
||||
}
|
||||
}
|
||||
|
||||
if !*notls {
|
||||
xmpp.DefaultConfig = tls.Config{
|
||||
ServerName: serverName(*server),
|
||||
InsecureSkipVerify: false,
|
||||
}
|
||||
}
|
||||
|
||||
var talk *xmpp.Client
|
||||
var err error
|
||||
options := xmpp.Options{Host: *server,
|
||||
User: *username,
|
||||
Password: *password,
|
||||
NoTLS: *notls,
|
||||
Debug: *debug,
|
||||
Session: *session,
|
||||
Status: *status,
|
||||
StatusMessage: *statusMessage,
|
||||
}
|
||||
|
||||
talk, err = options.NewClient()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
chat, err := talk.Recv()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
switch v := chat.(type) {
|
||||
case xmpp.Chat:
|
||||
fmt.Println(v.Remote, v.Text)
|
||||
case xmpp.Presence:
|
||||
fmt.Println(v.From, v.Show)
|
||||
}
|
||||
}
|
||||
}()
|
||||
for {
|
||||
in := bufio.NewReader(os.Stdin)
|
||||
line, err := in.ReadString('\n')
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
line = strings.TrimRight(line, "\n")
|
||||
|
||||
tokens := strings.SplitN(line, " ", 2)
|
||||
if len(tokens) == 2 {
|
||||
talk.Send(xmpp.Chat{Remote: tokens[0], Type: "chat", Text: tokens[1]})
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,965 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// TODO(rsc):
|
||||
// More precise error handling.
|
||||
// Presence functionality.
|
||||
// TODO(mattn):
|
||||
// Add proxy authentication.
|
||||
|
||||
// Package xmpp implements a simple Google Talk client
|
||||
// using the XMPP protocol described in RFC 3920 and RFC 3921.
|
||||
package xmpp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
nsStream = "http://etherx.jabber.org/streams"
|
||||
nsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
|
||||
nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
|
||||
nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
|
||||
nsClient = "jabber:client"
|
||||
nsSession = "urn:ietf:params:xml:ns:xmpp-session"
|
||||
)
|
||||
|
||||
// Default TLS configuration options
|
||||
var DefaultConfig tls.Config
|
||||
|
||||
// DebugWriter is the writer used to write debugging output to.
|
||||
var DebugWriter io.Writer = os.Stderr
|
||||
|
||||
// Cookie is a unique XMPP session identifier
|
||||
type Cookie uint64
|
||||
|
||||
func getCookie() Cookie {
|
||||
var buf [8]byte
|
||||
if _, err := rand.Reader.Read(buf[:]); err != nil {
|
||||
panic("Failed to read random bytes: " + err.Error())
|
||||
}
|
||||
return Cookie(binary.LittleEndian.Uint64(buf[:]))
|
||||
}
|
||||
|
||||
// Client holds XMPP connection opitons
|
||||
type Client struct {
|
||||
conn net.Conn // connection to server
|
||||
jid string // Jabber ID for our connection
|
||||
domain string
|
||||
p *xml.Decoder
|
||||
}
|
||||
|
||||
func (c *Client) JID() string {
|
||||
return c.jid
|
||||
}
|
||||
|
||||
func containsIgnoreCase(s, substr string) bool {
|
||||
s, substr = strings.ToUpper(s), strings.ToUpper(substr)
|
||||
return strings.Contains(s, substr)
|
||||
}
|
||||
|
||||
func connect(host, user, passwd string) (net.Conn, error) {
|
||||
addr := host
|
||||
|
||||
if strings.TrimSpace(host) == "" {
|
||||
a := strings.SplitN(user, "@", 2)
|
||||
if len(a) == 2 {
|
||||
addr = a[1]
|
||||
}
|
||||
}
|
||||
a := strings.SplitN(host, ":", 2)
|
||||
if len(a) == 1 {
|
||||
addr += ":5222"
|
||||
}
|
||||
|
||||
proxy := os.Getenv("HTTP_PROXY")
|
||||
if proxy == "" {
|
||||
proxy = os.Getenv("http_proxy")
|
||||
}
|
||||
// test for no proxy, takes a comma separated list with substrings to match
|
||||
if proxy != "" {
|
||||
noproxy := os.Getenv("NO_PROXY")
|
||||
if noproxy == "" {
|
||||
noproxy = os.Getenv("no_proxy")
|
||||
}
|
||||
if noproxy != "" {
|
||||
nplist := strings.Split(noproxy, ",")
|
||||
for _, s := range nplist {
|
||||
if containsIgnoreCase(addr, s) {
|
||||
proxy = ""
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if proxy != "" {
|
||||
url, err := url.Parse(proxy)
|
||||
if err == nil {
|
||||
addr = url.Host
|
||||
}
|
||||
}
|
||||
|
||||
c, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if proxy != "" {
|
||||
fmt.Fprintf(c, "CONNECT %s HTTP/1.1\r\n", host)
|
||||
fmt.Fprintf(c, "Host: %s\r\n", host)
|
||||
fmt.Fprintf(c, "\r\n")
|
||||
br := bufio.NewReader(c)
|
||||
req, _ := http.NewRequest("CONNECT", host, nil)
|
||||
resp, err := http.ReadResponse(br, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
f := strings.SplitN(resp.Status, " ", 2)
|
||||
return nil, errors.New(f[1])
|
||||
}
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Options are used to specify additional options for new clients, such as a Resource.
|
||||
type Options struct {
|
||||
// Host specifies what host to connect to, as either "hostname" or "hostname:port"
|
||||
// If host is not specified, the DNS SRV should be used to find the host from the domainpart of the JID.
|
||||
// Default the port to 5222.
|
||||
Host string
|
||||
|
||||
// User specifies what user to authenticate to the remote server.
|
||||
User string
|
||||
|
||||
// Password supplies the password to use for authentication with the remote server.
|
||||
Password string
|
||||
|
||||
// Resource specifies an XMPP client resource, like "bot", instead of accepting one
|
||||
// from the server. Use "" to let the server generate one for your client.
|
||||
Resource string
|
||||
|
||||
// OAuthScope provides go-xmpp the required scope for OAuth2 authentication.
|
||||
OAuthScope string
|
||||
|
||||
// OAuthToken provides go-xmpp with the required OAuth2 token used to authenticate
|
||||
OAuthToken string
|
||||
|
||||
// OAuthXmlNs provides go-xmpp with the required namespaced used for OAuth2 authentication. This is
|
||||
// provided to the server as the xmlns:auth attribute of the OAuth2 authentication request.
|
||||
OAuthXmlNs string
|
||||
|
||||
// TLS Config
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// InsecureAllowUnencryptedAuth permits authentication over a TCP connection that has not been promoted to
|
||||
// TLS by STARTTLS; this could leak authentication information over the network, or permit man in the middle
|
||||
// attacks.
|
||||
InsecureAllowUnencryptedAuth bool
|
||||
|
||||
// NoTLS directs go-xmpp to not use TLS initially to contact the server; instead, a plain old unencrypted
|
||||
// TCP connection should be used. (Can be combined with StartTLS to support STARTTLS-based servers.)
|
||||
NoTLS bool
|
||||
|
||||
// StartTLS directs go-xmpp to STARTTLS if the server supports it; go-xmpp will automatically STARTTLS
|
||||
// if the server requires it regardless of this option.
|
||||
StartTLS bool
|
||||
|
||||
// Debug output
|
||||
Debug bool
|
||||
|
||||
// Use server sessions
|
||||
Session bool
|
||||
|
||||
// Presence Status
|
||||
Status string
|
||||
|
||||
// Status message
|
||||
StatusMessage string
|
||||
}
|
||||
|
||||
// NewClient establishes a new Client connection based on a set of Options.
|
||||
func (o Options) NewClient() (*Client, error) {
|
||||
host := o.Host
|
||||
c, err := connect(host, o.User, o.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.LastIndex(o.Host, ":") > 0 {
|
||||
host = host[:strings.LastIndex(o.Host, ":")]
|
||||
}
|
||||
|
||||
client := new(Client)
|
||||
if o.NoTLS {
|
||||
client.conn = c
|
||||
} else {
|
||||
var tlsconn *tls.Conn
|
||||
if o.TLSConfig != nil {
|
||||
tlsconn = tls.Client(c, o.TLSConfig)
|
||||
} else {
|
||||
DefaultConfig.ServerName = host
|
||||
newconfig := DefaultConfig
|
||||
newconfig.ServerName = host
|
||||
tlsconn = tls.Client(c, &newconfig)
|
||||
}
|
||||
if err = tlsconn.Handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
insecureSkipVerify := DefaultConfig.InsecureSkipVerify
|
||||
if o.TLSConfig != nil {
|
||||
insecureSkipVerify = o.TLSConfig.InsecureSkipVerify
|
||||
}
|
||||
if !insecureSkipVerify {
|
||||
if err = tlsconn.VerifyHostname(host); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
client.conn = tlsconn
|
||||
}
|
||||
|
||||
if err := client.init(&o); err != nil {
|
||||
client.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// NewClient creates a new connection to a host given as "hostname" or "hostname:port".
|
||||
// If host is not specified, the DNS SRV should be used to find the host from the domainpart of the JID.
|
||||
// Default the port to 5222.
|
||||
func NewClient(host, user, passwd string, debug bool) (*Client, error) {
|
||||
opts := Options{
|
||||
Host: host,
|
||||
User: user,
|
||||
Password: passwd,
|
||||
Debug: debug,
|
||||
Session: false,
|
||||
}
|
||||
return opts.NewClient()
|
||||
}
|
||||
|
||||
// NewClientNoTLS creates a new client without TLS
|
||||
func NewClientNoTLS(host, user, passwd string, debug bool) (*Client, error) {
|
||||
opts := Options{
|
||||
Host: host,
|
||||
User: user,
|
||||
Password: passwd,
|
||||
NoTLS: true,
|
||||
Debug: debug,
|
||||
Session: false,
|
||||
}
|
||||
return opts.NewClient()
|
||||
}
|
||||
|
||||
// Close closes the XMPP connection
|
||||
func (c *Client) Close() error {
|
||||
if c.conn != (*tls.Conn)(nil) {
|
||||
return c.conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, authenticate, digestURI, nonceCountStr string) string {
|
||||
h := func(text string) []byte {
|
||||
h := md5.New()
|
||||
h.Write([]byte(text))
|
||||
return h.Sum(nil)
|
||||
}
|
||||
hex := func(bytes []byte) string {
|
||||
return fmt.Sprintf("%x", bytes)
|
||||
}
|
||||
kd := func(secret, data string) []byte {
|
||||
return h(secret + ":" + data)
|
||||
}
|
||||
|
||||
a1 := string(h(username+":"+realm+":"+passwd)) + ":" + nonce + ":" + cnonceStr
|
||||
a2 := authenticate + ":" + digestURI
|
||||
response := hex(kd(hex(h(a1)), nonce+":"+nonceCountStr+":"+cnonceStr+":auth:"+hex(h(a2))))
|
||||
return response
|
||||
}
|
||||
|
||||
func cnonce() string {
|
||||
randSize := big.NewInt(0)
|
||||
randSize.Lsh(big.NewInt(1), 64)
|
||||
cn, err := rand.Int(rand.Reader, randSize)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%016x", cn)
|
||||
}
|
||||
|
||||
func (c *Client) init(o *Options) error {
|
||||
|
||||
var domain string
|
||||
var user string
|
||||
a := strings.SplitN(o.User, "@", 2)
|
||||
if len(o.User) > 0 {
|
||||
if len(a) != 2 {
|
||||
return errors.New("xmpp: invalid username (want user@domain): " + o.User)
|
||||
}
|
||||
user = a[0]
|
||||
domain = a[1]
|
||||
} // Otherwise, we'll be attempting ANONYMOUS
|
||||
|
||||
// Declare intent to be a jabber client and gather stream features.
|
||||
f, err := c.startStream(o, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the server requires we STARTTLS, attempt to do so.
|
||||
if f, err = c.startTLSIfRequired(f, o, domain); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.User == "" && o.Password == "" {
|
||||
foundAnonymous := false
|
||||
for _, m := range f.Mechanisms.Mechanism {
|
||||
if m == "ANONYMOUS" {
|
||||
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='ANONYMOUS' />\n", nsSASL)
|
||||
foundAnonymous = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundAnonymous {
|
||||
return fmt.Errorf("ANONYMOUS authentication is not an option and username and password were not specified")
|
||||
}
|
||||
} else {
|
||||
// Even digest forms of authentication are unsafe if we do not know that the host
|
||||
// we are talking to is the actual server, and not a man in the middle playing
|
||||
// proxy.
|
||||
if !c.IsEncrypted() && !o.InsecureAllowUnencryptedAuth {
|
||||
return errors.New("refusing to authenticate over unencrypted TCP connection")
|
||||
}
|
||||
|
||||
mechanism := ""
|
||||
for _, m := range f.Mechanisms.Mechanism {
|
||||
if m == "X-OAUTH2" && o.OAuthToken != "" && o.OAuthScope != "" {
|
||||
mechanism = m
|
||||
// Oauth authentication: send base64-encoded \x00 user \x00 token.
|
||||
raw := "\x00" + user + "\x00" + o.OAuthToken
|
||||
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
|
||||
base64.StdEncoding.Encode(enc, []byte(raw))
|
||||
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='X-OAUTH2' auth:service='oauth2' "+
|
||||
"xmlns:auth='%s'>%s</auth>\n", nsSASL, o.OAuthXmlNs, enc)
|
||||
break
|
||||
}
|
||||
if m == "PLAIN" {
|
||||
mechanism = m
|
||||
// Plain authentication: send base64-encoded \x00 user \x00 password.
|
||||
raw := "\x00" + user + "\x00" + o.Password
|
||||
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
|
||||
base64.StdEncoding.Encode(enc, []byte(raw))
|
||||
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>\n", nsSASL, enc)
|
||||
break
|
||||
}
|
||||
if m == "DIGEST-MD5" {
|
||||
mechanism = m
|
||||
// Digest-MD5 authentication
|
||||
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='DIGEST-MD5'/>\n", nsSASL)
|
||||
var ch saslChallenge
|
||||
if err = c.p.DecodeElement(&ch, nil); err != nil {
|
||||
return errors.New("unmarshal <challenge>: " + err.Error())
|
||||
}
|
||||
b, err := base64.StdEncoding.DecodeString(string(ch))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tokens := map[string]string{}
|
||||
for _, token := range strings.Split(string(b), ",") {
|
||||
kv := strings.SplitN(strings.TrimSpace(token), "=", 2)
|
||||
if len(kv) == 2 {
|
||||
if kv[1][0] == '"' && kv[1][len(kv[1])-1] == '"' {
|
||||
kv[1] = kv[1][1 : len(kv[1])-1]
|
||||
}
|
||||
tokens[kv[0]] = kv[1]
|
||||
}
|
||||
}
|
||||
realm, _ := tokens["realm"]
|
||||
nonce, _ := tokens["nonce"]
|
||||
qop, _ := tokens["qop"]
|
||||
charset, _ := tokens["charset"]
|
||||
cnonceStr := cnonce()
|
||||
digestURI := "xmpp/" + domain
|
||||
nonceCount := fmt.Sprintf("%08x", 1)
|
||||
digest := saslDigestResponse(user, realm, o.Password, nonce, cnonceStr, "AUTHENTICATE", digestURI, nonceCount)
|
||||
message := "username=\"" + user + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", cnonce=\"" + cnonceStr +
|
||||
"\", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=\"" + digestURI + "\", response=" + digest + ", charset=" + charset
|
||||
|
||||
fmt.Fprintf(c.conn, "<response xmlns='%s'>%s</response>\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message)))
|
||||
|
||||
var rspauth saslRspAuth
|
||||
if err = c.p.DecodeElement(&rspauth, nil); err != nil {
|
||||
return errors.New("unmarshal <challenge>: " + err.Error())
|
||||
}
|
||||
b, err = base64.StdEncoding.DecodeString(string(rspauth))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(c.conn, "<response xmlns='%s'/>\n", nsSASL)
|
||||
break
|
||||
}
|
||||
}
|
||||
if mechanism == "" {
|
||||
return fmt.Errorf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism)
|
||||
}
|
||||
}
|
||||
// Next message should be either success or failure.
|
||||
name, val, err := next(c.p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch v := val.(type) {
|
||||
case *saslSuccess:
|
||||
case *saslFailure:
|
||||
errorMessage := v.Text
|
||||
if errorMessage == "" {
|
||||
// v.Any is type of sub-element in failure,
|
||||
// which gives a description of what failed if there was no text element
|
||||
errorMessage = v.Any.Local
|
||||
}
|
||||
return errors.New("auth failure: " + errorMessage)
|
||||
default:
|
||||
return errors.New("expected <success> or <failure>, got <" + name.Local + "> in " + name.Space)
|
||||
}
|
||||
|
||||
// Now that we're authenticated, we're supposed to start the stream over again.
|
||||
// Declare intent to be a jabber client.
|
||||
if f, err = c.startStream(o, domain); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate a unique cookie
|
||||
cookie := getCookie()
|
||||
|
||||
// Send IQ message asking to bind to the local user name.
|
||||
if o.Resource == "" {
|
||||
fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'></bind></iq>\n", cookie, nsBind)
|
||||
} else {
|
||||
fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'><resource>%s</resource></bind></iq>\n", cookie, nsBind, o.Resource)
|
||||
}
|
||||
var iq clientIQ
|
||||
if err = c.p.DecodeElement(&iq, nil); err != nil {
|
||||
return errors.New("unmarshal <iq>: " + err.Error())
|
||||
}
|
||||
if &iq.Bind == nil {
|
||||
return errors.New("<iq> result missing <bind>")
|
||||
}
|
||||
c.jid = iq.Bind.Jid // our local id
|
||||
c.domain = domain
|
||||
|
||||
if o.Session {
|
||||
//if server support session, open it
|
||||
fmt.Fprintf(c.conn, "<iq to='%s' type='set' id='%x'><session xmlns='%s'/></iq>", xmlEscape(domain), cookie, nsSession)
|
||||
}
|
||||
|
||||
// We're connected and can now receive and send messages.
|
||||
fmt.Fprintf(c.conn, "<presence xml:lang='en'><show>%s</show><status>%s</status></presence>", o.Status, o.StatusMessage)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// startTlsIfRequired examines the server's stream features and, if STARTTLS is required or supported, performs the TLS handshake.
|
||||
// f will be updated if the handshake completes, as the new stream's features are typically different from the original.
|
||||
func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string) (*streamFeatures, error) {
|
||||
// whether we start tls is a matter of opinion: the server's and the user's.
|
||||
switch {
|
||||
case f.StartTLS == nil:
|
||||
// the server does not support STARTTLS
|
||||
return f, nil
|
||||
case f.StartTLS.Required != nil:
|
||||
// the server requires STARTTLS.
|
||||
case !o.StartTLS:
|
||||
// the user wants STARTTLS and the server supports it.
|
||||
}
|
||||
var err error
|
||||
|
||||
fmt.Fprintf(c.conn, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n")
|
||||
var k tlsProceed
|
||||
if err = c.p.DecodeElement(&k, nil); err != nil {
|
||||
return f, errors.New("unmarshal <proceed>: " + err.Error())
|
||||
}
|
||||
|
||||
tc := o.TLSConfig
|
||||
if tc == nil {
|
||||
tc = new(tls.Config)
|
||||
*tc = DefaultConfig
|
||||
//TODO(scott): we should consider using the server's address or reverse lookup
|
||||
tc.ServerName = domain
|
||||
}
|
||||
t := tls.Client(c.conn, tc)
|
||||
|
||||
if err = t.Handshake(); err != nil {
|
||||
return f, errors.New("starttls handshake: " + err.Error())
|
||||
}
|
||||
c.conn = t
|
||||
|
||||
// restart our declaration of XMPP stream intentions.
|
||||
tf, err := c.startStream(o, domain)
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
return tf, nil
|
||||
}
|
||||
|
||||
// startStream will start a new XML decoder for the connection, signal the start of a stream to the server and verify that the server has
|
||||
// also started the stream; if o.Debug is true, startStream will tee decoded XML data to stderr. The features advertised by the server
|
||||
// will be returned.
|
||||
func (c *Client) startStream(o *Options, domain string) (*streamFeatures, error) {
|
||||
if o.Debug {
|
||||
c.p = xml.NewDecoder(tee{c.conn, DebugWriter})
|
||||
} else {
|
||||
c.p = xml.NewDecoder(c.conn)
|
||||
}
|
||||
|
||||
_, err := fmt.Fprintf(c.conn, "<?xml version='1.0'?>\n"+
|
||||
"<stream:stream to='%s' xmlns='%s'\n"+
|
||||
" xmlns:stream='%s' version='1.0'>\n",
|
||||
xmlEscape(domain), nsClient, nsStream)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We expect the server to start a <stream>.
|
||||
se, err := nextStart(c.p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if se.Name.Space != nsStream || se.Name.Local != "stream" {
|
||||
return nil, fmt.Errorf("expected <stream> but got <%v> in %v", se.Name.Local, se.Name.Space)
|
||||
}
|
||||
|
||||
// Now we're in the stream and can use Unmarshal.
|
||||
// Next message should be <features> to tell us authentication options.
|
||||
// See section 4.6 in RFC 3920.
|
||||
f := new(streamFeatures)
|
||||
if err = c.p.DecodeElement(f, nil); err != nil {
|
||||
return f, errors.New("unmarshal <features>: " + err.Error())
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// IsEncrypted will return true if the client is connected using a TLS transport, either because it used.
|
||||
// TLS to connect from the outset, or because it successfully used STARTTLS to promote a TCP connection to TLS.
|
||||
func (c *Client) IsEncrypted() bool {
|
||||
_, ok := c.conn.(*tls.Conn)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Chat is an incoming or outgoing XMPP chat message.
|
||||
type Chat struct {
|
||||
Remote string
|
||||
Type string
|
||||
Text string
|
||||
Subject string
|
||||
Thread string
|
||||
Roster Roster
|
||||
Other []string
|
||||
OtherElem []XMLElement
|
||||
Stamp time.Time
|
||||
}
|
||||
|
||||
type Roster []Contact
|
||||
|
||||
type Contact struct {
|
||||
Remote string
|
||||
Name string
|
||||
Group []string
|
||||
}
|
||||
|
||||
// Presence is an XMPP presence notification.
|
||||
type Presence struct {
|
||||
From string
|
||||
To string
|
||||
Type string
|
||||
Show string
|
||||
Status string
|
||||
}
|
||||
|
||||
type IQ struct {
|
||||
ID string
|
||||
From string
|
||||
To string
|
||||
Type string
|
||||
Query []byte
|
||||
}
|
||||
|
||||
// Recv waits to receive the next XMPP stanza.
|
||||
// Return type is either a presence notification or a chat message.
|
||||
func (c *Client) Recv() (stanza interface{}, err error) {
|
||||
for {
|
||||
_, val, err := next(c.p)
|
||||
if err != nil {
|
||||
return Chat{}, err
|
||||
}
|
||||
switch v := val.(type) {
|
||||
case *clientMessage:
|
||||
stamp, _ := time.Parse(
|
||||
"2006-01-02T15:04:05Z",
|
||||
v.Delay.Stamp,
|
||||
)
|
||||
chat := Chat{
|
||||
Remote: v.From,
|
||||
Type: v.Type,
|
||||
Text: v.Body,
|
||||
Subject: v.Subject,
|
||||
Thread: v.Thread,
|
||||
Other: v.OtherStrings(),
|
||||
OtherElem: v.Other,
|
||||
Stamp: stamp,
|
||||
}
|
||||
return chat, nil
|
||||
case *clientQuery:
|
||||
var r Roster
|
||||
for _, item := range v.Item {
|
||||
r = append(r, Contact{item.Jid, item.Name, item.Group})
|
||||
}
|
||||
return Chat{Type: "roster", Roster: r}, nil
|
||||
case *clientPresence:
|
||||
return Presence{v.From, v.To, v.Type, v.Show, v.Status}, nil
|
||||
case *clientIQ:
|
||||
// TODO check more strictly
|
||||
if bytes.Equal(bytes.TrimSpace(v.Query), []byte(`<ping xmlns='urn:xmpp:ping'/>`)) || bytes.Equal(bytes.TrimSpace(v.Query), []byte(`<ping xmlns="urn:xmpp:ping"/>`)) {
|
||||
err := c.SendResultPing(v.ID, v.From)
|
||||
if err != nil {
|
||||
return Chat{}, err
|
||||
}
|
||||
}
|
||||
return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, Query: v.Query}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send sends the message wrapped inside an XMPP message stanza body.
|
||||
func (c *Client) Send(chat Chat) (n int, err error) {
|
||||
var subtext = ``
|
||||
var thdtext = ``
|
||||
if chat.Subject != `` {
|
||||
subtext = `<subject>` + xmlEscape(chat.Subject) + `</subject>`
|
||||
}
|
||||
if chat.Thread != `` {
|
||||
thdtext = `<thread>` + xmlEscape(chat.Thread) + `</thread>`
|
||||
}
|
||||
|
||||
stanza := "<message to='%s' type='%s' id='%s' xml:lang='en'>" + subtext + "<body>%s</body>" + thdtext + "</message>"
|
||||
|
||||
return fmt.Fprintf(c.conn, stanza,
|
||||
xmlEscape(chat.Remote), xmlEscape(chat.Type), cnonce(), xmlEscape(chat.Text))
|
||||
}
|
||||
|
||||
// SendOrg sends the original text without being wrapped in an XMPP message stanza.
|
||||
func (c *Client) SendOrg(org string) (n int, err error) {
|
||||
return fmt.Fprint(c.conn, org)
|
||||
}
|
||||
|
||||
func (c *Client) SendPresence(presence Presence) (n int, err error) {
|
||||
return fmt.Fprintf(c.conn, "<presence from='%s' to='%s'/>", xmlEscape(presence.From), xmlEscape(presence.To))
|
||||
}
|
||||
|
||||
// SendKeepAlive sends a "whitespace keepalive" as described in chapter 4.6.1 of RFC6120.
|
||||
func (c *Client) SendKeepAlive() (n int, err error) {
|
||||
return fmt.Fprintf(c.conn, " ")
|
||||
}
|
||||
|
||||
// SendHtml sends the message as HTML as defined by XEP-0071
|
||||
func (c *Client) SendHtml(chat Chat) (n int, err error) {
|
||||
return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+
|
||||
"<body>%s</body>"+
|
||||
"<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html></message>",
|
||||
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text), chat.Text)
|
||||
}
|
||||
|
||||
// Roster asks for the chat roster.
|
||||
func (c *Client) Roster() error {
|
||||
fmt.Fprintf(c.conn, "<iq from='%s' type='get' id='roster1'><query xmlns='jabber:iq:roster'/></iq>\n", xmlEscape(c.jid))
|
||||
return nil
|
||||
}
|
||||
|
||||
// RFC 3920 C.1 Streams name space
|
||||
type streamFeatures struct {
|
||||
XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"`
|
||||
StartTLS *tlsStartTLS
|
||||
Mechanisms saslMechanisms
|
||||
Bind bindBind
|
||||
Session bool
|
||||
}
|
||||
|
||||
type streamError struct {
|
||||
XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"`
|
||||
Any xml.Name
|
||||
Text string
|
||||
}
|
||||
|
||||
// RFC 3920 C.3 TLS name space
|
||||
type tlsStartTLS struct {
|
||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"`
|
||||
Required *string `xml:"required"`
|
||||
}
|
||||
|
||||
type tlsProceed struct {
|
||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"`
|
||||
}
|
||||
|
||||
type tlsFailure struct {
|
||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls failure"`
|
||||
}
|
||||
|
||||
// RFC 3920 C.4 SASL name space
|
||||
type saslMechanisms struct {
|
||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
|
||||
Mechanism []string `xml:"mechanism"`
|
||||
}
|
||||
|
||||
type saslAuth struct {
|
||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
|
||||
Mechanism string `xml:",attr"`
|
||||
}
|
||||
|
||||
type saslChallenge string
|
||||
|
||||
type saslRspAuth string
|
||||
|
||||
type saslResponse string
|
||||
|
||||
type saslAbort struct {
|
||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl abort"`
|
||||
}
|
||||
|
||||
type saslSuccess struct {
|
||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
|
||||
}
|
||||
|
||||
type saslFailure struct {
|
||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
|
||||
Any xml.Name `xml:",any"`
|
||||
Text string `xml:"text"`
|
||||
}
|
||||
|
||||
// RFC 3920 C.5 Resource binding name space
|
||||
type bindBind struct {
|
||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
|
||||
Resource string
|
||||
Jid string `xml:"jid"`
|
||||
}
|
||||
|
||||
// RFC 3921 B.1 jabber:client
|
||||
type clientMessage struct {
|
||||
XMLName xml.Name `xml:"jabber:client message"`
|
||||
From string `xml:"from,attr"`
|
||||
ID string `xml:"id,attr"`
|
||||
To string `xml:"to,attr"`
|
||||
Type string `xml:"type,attr"` // chat, error, groupchat, headline, or normal
|
||||
|
||||
// These should technically be []clientText, but string is much more convenient.
|
||||
Subject string `xml:"subject"`
|
||||
Body string `xml:"body"`
|
||||
Thread string `xml:"thread"`
|
||||
|
||||
// Any hasn't matched element
|
||||
Other []XMLElement `xml:",any"`
|
||||
|
||||
Delay Delay `xml:"delay"`
|
||||
}
|
||||
|
||||
func (m *clientMessage) OtherStrings() []string {
|
||||
a := make([]string, len(m.Other))
|
||||
for i, e := range m.Other {
|
||||
a[i] = e.String()
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
type XMLElement struct {
|
||||
XMLName xml.Name
|
||||
InnerXML string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
func (e *XMLElement) String() string {
|
||||
r := bytes.NewReader([]byte(e.InnerXML))
|
||||
d := xml.NewDecoder(r)
|
||||
var buf bytes.Buffer
|
||||
for {
|
||||
tok, err := d.Token()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch v := tok.(type) {
|
||||
case xml.StartElement:
|
||||
err = d.Skip()
|
||||
case xml.CharData:
|
||||
_, err = buf.Write(v)
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type Delay struct {
|
||||
Stamp string `xml:"stamp,attr"`
|
||||
}
|
||||
|
||||
type clientText struct {
|
||||
Lang string `xml:",attr"`
|
||||
Body string `xml:"chardata"`
|
||||
}
|
||||
|
||||
type clientPresence struct {
|
||||
XMLName xml.Name `xml:"jabber:client presence"`
|
||||
From string `xml:"from,attr"`
|
||||
ID string `xml:"id,attr"`
|
||||
To string `xml:"to,attr"`
|
||||
Type string `xml:"type,attr"` // error, probe, subscribe, subscribed, unavailable, unsubscribe, unsubscribed
|
||||
Lang string `xml:"lang,attr"`
|
||||
|
||||
Show string `xml:"show"` // away, chat, dnd, xa
|
||||
Status string `xml:"status"` // sb []clientText
|
||||
Priority string `xml:"priority,attr"`
|
||||
Error *clientError
|
||||
}
|
||||
|
||||
type clientIQ struct {
|
||||
// info/query
|
||||
XMLName xml.Name `xml:"jabber:client iq"`
|
||||
From string `xml:"from,attr"`
|
||||
ID string `xml:"id,attr"`
|
||||
To string `xml:"to,attr"`
|
||||
Type string `xml:"type,attr"` // error, get, result, set
|
||||
Query []byte `xml:",innerxml"`
|
||||
Error clientError
|
||||
Bind bindBind
|
||||
}
|
||||
|
||||
type clientError struct {
|
||||
XMLName xml.Name `xml:"jabber:client error"`
|
||||
Code string `xml:",attr"`
|
||||
Type string `xml:",attr"`
|
||||
Any xml.Name
|
||||
Text string
|
||||
}
|
||||
|
||||
type clientQuery struct {
|
||||
Item []rosterItem
|
||||
}
|
||||
|
||||
type rosterItem struct {
|
||||
XMLName xml.Name `xml:"jabber:iq:roster item"`
|
||||
Jid string `xml:",attr"`
|
||||
Name string `xml:",attr"`
|
||||
Subscription string `xml:",attr"`
|
||||
Group []string
|
||||
}
|
||||
|
||||
// Scan XML token stream to find next StartElement.
|
||||
func nextStart(p *xml.Decoder) (xml.StartElement, error) {
|
||||
for {
|
||||
t, err := p.Token()
|
||||
if err != nil || t == nil {
|
||||
return xml.StartElement{}, err
|
||||
}
|
||||
switch t := t.(type) {
|
||||
case xml.StartElement:
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scan XML token stream for next element and save into val.
|
||||
// If val == nil, allocate new element based on proto map.
|
||||
// Either way, return val.
|
||||
func next(p *xml.Decoder) (xml.Name, interface{}, error) {
|
||||
// Read start element to find out what type we want.
|
||||
se, err := nextStart(p)
|
||||
if err != nil {
|
||||
return xml.Name{}, nil, err
|
||||
}
|
||||
|
||||
// Put it in an interface and allocate one.
|
||||
var nv interface{}
|
||||
switch se.Name.Space + " " + se.Name.Local {
|
||||
case nsStream + " features":
|
||||
nv = &streamFeatures{}
|
||||
case nsStream + " error":
|
||||
nv = &streamError{}
|
||||
case nsTLS + " starttls":
|
||||
nv = &tlsStartTLS{}
|
||||
case nsTLS + " proceed":
|
||||
nv = &tlsProceed{}
|
||||
case nsTLS + " failure":
|
||||
nv = &tlsFailure{}
|
||||
case nsSASL + " mechanisms":
|
||||
nv = &saslMechanisms{}
|
||||
case nsSASL + " challenge":
|
||||
nv = ""
|
||||
case nsSASL + " response":
|
||||
nv = ""
|
||||
case nsSASL + " abort":
|
||||
nv = &saslAbort{}
|
||||
case nsSASL + " success":
|
||||
nv = &saslSuccess{}
|
||||
case nsSASL + " failure":
|
||||
nv = &saslFailure{}
|
||||
case nsBind + " bind":
|
||||
nv = &bindBind{}
|
||||
case nsClient + " message":
|
||||
nv = &clientMessage{}
|
||||
case nsClient + " presence":
|
||||
nv = &clientPresence{}
|
||||
case nsClient + " iq":
|
||||
nv = &clientIQ{}
|
||||
case nsClient + " error":
|
||||
nv = &clientError{}
|
||||
default:
|
||||
return xml.Name{}, nil, errors.New("unexpected XMPP message " +
|
||||
se.Name.Space + " <" + se.Name.Local + "/>")
|
||||
}
|
||||
|
||||
// Unmarshal into that storage.
|
||||
if err = p.DecodeElement(nv, &se); err != nil {
|
||||
return xml.Name{}, nil, err
|
||||
}
|
||||
|
||||
return se.Name, nv, err
|
||||
}
|
||||
|
||||
func xmlEscape(s string) string {
|
||||
var b bytes.Buffer
|
||||
xml.Escape(&b, []byte(s))
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
type tee struct {
|
||||
r io.Reader
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (t tee) Read(p []byte) (n int, err error) {
|
||||
n, err = t.r.Read(p)
|
||||
if n > 0 {
|
||||
t.w.Write(p[0:n])
|
||||
t.w.Write([]byte("\n"))
|
||||
}
|
||||
return
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package xmpp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const IQTypeGet = "get"
|
||||
const IQTypeSet = "set"
|
||||
const IQTypeResult = "result"
|
||||
|
||||
func (c *Client) Discovery() (string, error) {
|
||||
const namespace = "http://jabber.org/protocol/disco#items"
|
||||
// use getCookie for a pseudo random id.
|
||||
reqID := strconv.FormatUint(uint64(getCookie()), 10)
|
||||
return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, namespace, "")
|
||||
}
|
||||
|
||||
// RawInformationQuery sends an information query request to the server.
|
||||
func (c *Client) RawInformationQuery(from, to, id, iqType, requestNamespace, body string) (string, error) {
|
||||
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'><query xmlns='%s'>%s</query></iq>"
|
||||
_, err := fmt.Fprintf(c.conn, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body)
|
||||
return id, err
|
||||
}
|
||||
|
||||
// rawInformation send a IQ request with the the payload body to the server
|
||||
func (c *Client) RawInformation(from, to, id, iqType, body string) (string, error) {
|
||||
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'>%s</iq>"
|
||||
_, err := fmt.Fprintf(c.conn, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, body)
|
||||
return id, err
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
// Copyright 2013 Flo Lauber <dev@qatfy.at>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// TODO(flo):
|
||||
// - support password protected MUC rooms
|
||||
// - cleanup signatures of join/leave functions
|
||||
package xmpp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
nsMUC = "http://jabber.org/protocol/muc"
|
||||
nsMUCUser = "http://jabber.org/protocol/muc#user"
|
||||
NoHistory = 0
|
||||
CharHistory = 1
|
||||
StanzaHistory = 2
|
||||
SecondsHistory = 3
|
||||
SinceHistory = 4
|
||||
)
|
||||
|
||||
// Send sends room topic wrapped inside an XMPP message stanza body.
|
||||
func (c *Client) SendTopic(chat Chat) (n int, err error) {
|
||||
return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>",
|
||||
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
|
||||
}
|
||||
|
||||
func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) {
|
||||
if nick == "" {
|
||||
nick = c.jid
|
||||
}
|
||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
|
||||
"<x xmlns='%s'>"+
|
||||
"<history maxchars='0'/></x>\n"+
|
||||
"</presence>",
|
||||
xmlEscape(jid), xmlEscape(nick), nsMUC)
|
||||
}
|
||||
|
||||
// xep-0045 7.2
|
||||
func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_date *time.Time) (n int, err error) {
|
||||
if nick == "" {
|
||||
nick = c.jid
|
||||
}
|
||||
switch history_type {
|
||||
case NoHistory:
|
||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
|
||||
"<x xmlns='%s' />\n"+
|
||||
"</presence>",
|
||||
xmlEscape(jid), xmlEscape(nick), nsMUC)
|
||||
case CharHistory:
|
||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
|
||||
"<x xmlns='%s'>\n"+
|
||||
"<history maxchars='%d'/></x>\n"+
|
||||
"</presence>",
|
||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
||||
case StanzaHistory:
|
||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
|
||||
"<x xmlns='%s'>\n"+
|
||||
"<history maxstanzas='%d'/></x>\n"+
|
||||
"</presence>",
|
||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
||||
case SecondsHistory:
|
||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
|
||||
"<x xmlns='%s'>\n"+
|
||||
"<history seconds='%d'/></x>\n"+
|
||||
"</presence>",
|
||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
||||
case SinceHistory:
|
||||
if history_date != nil {
|
||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
|
||||
"<x xmlns='%s'>\n"+
|
||||
"<history since='%s'/></x>\n"+
|
||||
"</presence>",
|
||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
return 0, errors.New("Unknown history option")
|
||||
}
|
||||
|
||||
// xep-0045 7.2.6
|
||||
func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_type, history int, history_date *time.Time) (n int, err error) {
|
||||
if nick == "" {
|
||||
nick = c.jid
|
||||
}
|
||||
switch history_type {
|
||||
case NoHistory:
|
||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
|
||||
"<x xmlns='%s'>\n"+
|
||||
"<password>%s</password>"+
|
||||
"</x>\n"+
|
||||
"</presence>",
|
||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password))
|
||||
case CharHistory:
|
||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
|
||||
"<x xmlns='%s'>\n"+
|
||||
"<password>%s</password>\n"+
|
||||
"<history maxchars='%d'/></x>\n"+
|
||||
"</presence>",
|
||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
||||
case StanzaHistory:
|
||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
|
||||
"<x xmlns='%s'>\n"+
|
||||
"<password>%s</password>\n"+
|
||||
"<history maxstanzas='%d'/></x>\n"+
|
||||
"</presence>",
|
||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
||||
case SecondsHistory:
|
||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
|
||||
"<x xmlns='%s'>\n"+
|
||||
"<password>%s</password>\n"+
|
||||
"<history seconds='%d'/></x>\n"+
|
||||
"</presence>",
|
||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
||||
case SinceHistory:
|
||||
if history_date != nil {
|
||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
|
||||
"<x xmlns='%s'>\n"+
|
||||
"<password>%s</password>\n"+
|
||||
"<history since='%s'/></x>\n"+
|
||||
"</presence>",
|
||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
return 0, errors.New("Unknown history option")
|
||||
}
|
||||
|
||||
// xep-0045 7.14
|
||||
func (c *Client) LeaveMUC(jid string) (n int, err error) {
|
||||
return fmt.Fprintf(c.conn, "<presence from='%s' to='%s' type='unavailable' />",
|
||||
c.jid, xmlEscape(jid))
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package xmpp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (c *Client) PingC2S(jid, server string) error {
|
||||
if jid == "" {
|
||||
jid = c.jid
|
||||
}
|
||||
if server == "" {
|
||||
server = c.domain
|
||||
}
|
||||
_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+
|
||||
"<ping xmlns='urn:xmpp:ping'/>\n"+
|
||||
"</iq>",
|
||||
xmlEscape(jid), xmlEscape(server))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) PingS2S(fromServer, toServer string) error {
|
||||
_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='s2s1' type='get'>\n"+
|
||||
"<ping xmlns='urn:xmpp:ping'/>\n"+
|
||||
"</iq>",
|
||||
xmlEscape(fromServer), xmlEscape(toServer))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) SendResultPing(id, toServer string) error {
|
||||
_, err := fmt.Fprintf(c.conn, "<iq type='result' to='%s' id='%s'/>",
|
||||
xmlEscape(toServer), xmlEscape(id))
|
||||
return err
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package xmpp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (c *Client) ApproveSubscription(jid string) {
|
||||
fmt.Fprintf(c.conn, "<presence to='%s' type='subscribed'/>",
|
||||
xmlEscape(jid))
|
||||
}
|
||||
|
||||
func (c *Client) RevokeSubscription(jid string) {
|
||||
fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribed'/>",
|
||||
xmlEscape(jid))
|
||||
}
|
||||
|
||||
func (c *Client) RequestSubscription(jid string) {
|
||||
fmt.Fprintf(c.conn, "<presence to='%s' type='subscribe'/>",
|
||||
xmlEscape(jid))
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
package xmpp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type localAddr struct{}
|
||||
|
||||
func (a *localAddr) Network() string {
|
||||
return "tcp"
|
||||
}
|
||||
|
||||
func (addr *localAddr) String() string {
|
||||
return "localhost:5222"
|
||||
}
|
||||
|
||||
type testConn struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func tConnect(s string) net.Conn {
|
||||
var conn testConn
|
||||
conn.Buffer = bytes.NewBufferString(s)
|
||||
return &conn
|
||||
}
|
||||
|
||||
func (*testConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*testConn) LocalAddr() net.Addr {
|
||||
return &localAddr{}
|
||||
}
|
||||
|
||||
func (*testConn) RemoteAddr() net.Addr {
|
||||
return &localAddr{}
|
||||
}
|
||||
|
||||
func (*testConn) SetDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*testConn) SetReadDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*testConn) SetWriteDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var text = strings.TrimSpace(`
|
||||
<message xmlns="jabber:client" id="3" type="error" to="123456789@gcm.googleapis.com/ABC">
|
||||
<gcm xmlns="google:mobile:data">
|
||||
{"random": "<text>"}
|
||||
</gcm>
|
||||
<error code="400" type="modify">
|
||||
<bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
|
||||
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
|
||||
InvalidJson: JSON_PARSING_ERROR : Missing Required Field: message_id\n
|
||||
</text>
|
||||
</error>
|
||||
</message>
|
||||
`)
|
||||
|
||||
func TestStanzaError(t *testing.T) {
|
||||
var c Client
|
||||
c.conn = tConnect(text)
|
||||
c.p = xml.NewDecoder(c.conn)
|
||||
v, err := c.Recv()
|
||||
if err != nil {
|
||||
t.Fatalf("Recv() = %v", err)
|
||||
}
|
||||
|
||||
chat := Chat{
|
||||
Type: "error",
|
||||
Other: []string{
|
||||
"\n\t\t{\"random\": \"<text>\"}\n\t",
|
||||
"\n\t\t\n\t\t\n\t",
|
||||
},
|
||||
OtherElem: []XMLElement{
|
||||
XMLElement{
|
||||
XMLName: xml.Name{Space: "google:mobile:data", Local: "gcm"},
|
||||
InnerXML: "\n\t\t{\"random\": \"<text>\"}\n\t",
|
||||
},
|
||||
XMLElement{
|
||||
XMLName: xml.Name{Space: "jabber:client", Local: "error"},
|
||||
InnerXML: `
|
||||
<bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
|
||||
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
|
||||
InvalidJson: JSON_PARSING_ERROR : Missing Required Field: message_id\n
|
||||
</text>
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(v, chat) {
|
||||
t.Errorf("Recv() = %#v; want %#v", v, chat)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEOFError(t *testing.T) {
|
||||
var c Client
|
||||
c.conn = tConnect("")
|
||||
c.p = xml.NewDecoder(c.conn)
|
||||
_, err := c.Recv()
|
||||
if err != io.EOF {
|
||||
t.Errorf("Recv() did not return io.EOF on end of input stream")
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
Paul Borman <paul@borman.com>
|
@ -0,0 +1,10 @@
|
||||
# How to contribute
|
||||
|
||||
We definitely welcome patches and contribution to this project!
|
||||
|
||||
### Legal requirements
|
||||
|
||||
In order to protect both you and ourselves, you will need to sign the
|
||||
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
||||
|
||||
You may have already signed it for other Google projects.
|
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2017 Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google, nor the names of other
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,226 @@
|
||||
# getopt
|
||||
|
||||
Package getopt provides traditional getopt processing for implementing
|
||||
commands that use traditional command lines. The standard Go flag package
|
||||
cannot be used to write a program that parses flags the way ls or ssh does,
|
||||
for example. There are two versions, v1 and v2, both named getopt, that
|
||||
use the following import paths:
|
||||
|
||||
```
|
||||
"github.com/pborman/getopt" // version 1
|
||||
"github.com/pborman/getopt/v2" // version 2
|
||||
```
|
||||
|
||||
This README describes version 2 of the package, which has a simplified API.
|
||||
|
||||
## Usage
|
||||
|
||||
Getopt supports functionality found in both the standard BSD getopt as well
|
||||
as (one of the many versions of) the GNU getopt_long. Being a Go package,
|
||||
this package makes common usage easy, but still enables more controlled usage
|
||||
if needed.
|
||||
|
||||
Typical usage:
|
||||
|
||||
```
|
||||
Declare flags and have getopt return pointers to the values.
|
||||
helpFlag := getopt.Bool('?', "display help")
|
||||
cmdFlag := getopt.StringLong("command", 'c', "default", "the command)
|
||||
|
||||
Declare flags against existing variables.
|
||||
var {
|
||||
fileName = "/the/default/path"
|
||||
timeout = time.Second * 5
|
||||
verbose bool
|
||||
}
|
||||
func init() {
|
||||
getopt.Flag(&verbose, 'v', "be verbose")
|
||||
getopt.FlagLong(&fileName, "path", 0, "the path")
|
||||
getopt.FlagLong(&timeout, "timeout", 't', "some timeout")
|
||||
}
|
||||
|
||||
func main() {
|
||||
Parse the program arguments
|
||||
getopt.Parse()
|
||||
Get the remaining positional parameters
|
||||
args := getopt.Args()
|
||||
...
|
||||
```
|
||||
|
||||
If you don't want the program to exit on error, use getopt.Getopt:
|
||||
|
||||
```
|
||||
err := getopt.Getopt(nil)
|
||||
if err != nil {
|
||||
code to handle error
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
```
|
||||
|
||||
## Flag Syntax
|
||||
|
||||
Support is provided for both short (-f) and long (--flag) options. A single
|
||||
option may have both a short and a long name. Each option may be a flag or a
|
||||
value. A value takes an argument.
|
||||
|
||||
Declaring no long names causes this package to process arguments like the
|
||||
traditional BSD getopt.
|
||||
|
||||
Short flags may be combined into a single parameter. For example, "-a -b -c"
|
||||
may also be expressed "-abc". Long flags must stand on their own "--alpha
|
||||
--beta"
|
||||
|
||||
Values require an argument. For short options the argument may either be
|
||||
immediately following the short name or as the next argument. Only one short
|
||||
value may be combined with short flags in a single argument; the short value
|
||||
must be after all short flags. For example, if f is a flag and v is a value,
|
||||
then:
|
||||
|
||||
```
|
||||
-vvalue (sets v to "value")
|
||||
-v value (sets v to "value")
|
||||
-fvvalue (sets f, and sets v to "value")
|
||||
-fv value (sets f, and sets v to "value")
|
||||
-vf value (set v to "f" and value is the first parameter)
|
||||
```
|
||||
|
||||
For the long value option val:
|
||||
|
||||
```
|
||||
--val value (sets val to "value")
|
||||
--val=value (sets val to "value")
|
||||
--valvalue (invalid option "valvalue")
|
||||
```
|
||||
|
||||
Values with an optional value only set the value if the value is part of the
|
||||
same argument. In any event, the option count is increased and the option is
|
||||
marked as seen.
|
||||
|
||||
```
|
||||
-v -f (sets v and f as being seen)
|
||||
-vvalue -f (sets v to "value" and sets f)
|
||||
--val -f (sets v and f as being seen)
|
||||
--val=value -f (sets v to "value" and sets f)
|
||||
```
|
||||
|
||||
There is no convience function defined for making the value optional. The
|
||||
SetOptional method must be called on the actual Option.
|
||||
|
||||
```
|
||||
v := String("val", 'v', "", "the optional v")
|
||||
Lookup("v").SetOptional()
|
||||
|
||||
var s string
|
||||
FlagLong(&s, "val", 'v', "the optional v).SetOptional()
|
||||
```
|
||||
|
||||
Parsing continues until the first non-option or "--" is encountered.
|
||||
|
||||
The short name "-" can be used, but it either is specified as "-" or as part
|
||||
of a group of options, for example "-f-". If there are no long options
|
||||
specified then "--f" could also be used. If "-" is not declared as an option
|
||||
then the single "-" will also terminate the option processing but unlike
|
||||
"--", the "-" will be part of the remaining arguments.
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
Normally the parsing is performed by calling the Parse function. If it is
|
||||
important to see the order of the options then the Getopt function should be
|
||||
used. The standard Parse function does the equivalent of:
|
||||
|
||||
```
|
||||
func Parse() {
|
||||
if err := getopt.Getopt(os.Args, nil); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
s.usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When calling Getopt it is the responsibility of the caller to print any
|
||||
errors.
|
||||
|
||||
Normally the default option set, CommandLine, is used. Other option sets may
|
||||
be created with New.
|
||||
|
||||
After parsing, the sets Args will contain the non-option arguments. If an
|
||||
error is encountered then Args will begin with argument that caused the
|
||||
error.
|
||||
|
||||
It is valid to call a set's Parse a second time to amend the current set of
|
||||
flags or values. As an example:
|
||||
|
||||
```
|
||||
var a = getopt.Bool('a', "", "The a flag")
|
||||
var b = getopt.Bool('b', "", "The a flag")
|
||||
var cmd = ""
|
||||
|
||||
var opts = getopt.CommandLine
|
||||
|
||||
opts.Parse(os.Args)
|
||||
if opts.NArgs() > 0 {
|
||||
cmd = opts.Arg(0)
|
||||
opts.Parse(opts.Args())
|
||||
}
|
||||
```
|
||||
|
||||
If called with set to { "prog", "-a", "cmd", "-b", "arg" } then both and and
|
||||
b would be set, cmd would be set to "cmd", and opts.Args() would return {
|
||||
"arg" }.
|
||||
|
||||
Unless an option type explicitly prohibits it, an option may appear more than
|
||||
once in the arguments. The last value provided to the option is the value.
|
||||
|
||||
## Builtin Types
|
||||
|
||||
The Flag and FlagLong functions support most standard Go types. For the
|
||||
list, see the description of FlagLong below for a list of supported types.
|
||||
|
||||
There are also helper routines to allow single line flag declarations. These
|
||||
types are: Bool, Counter, Duration, Enum, Int16, Int32, Int64, Int, List,
|
||||
Signed, String, Uint16, Uint32, Uint64, Uint, and Unsigned.
|
||||
|
||||
Each comes in a short and long flavor, e.g., Bool and BoolLong and include
|
||||
functions to set the flags on the standard command line or for a specific Set
|
||||
of flags.
|
||||
|
||||
Except for the Counter, Enum, Signed and Unsigned types, all of these types
|
||||
can be declared using Flag and FlagLong by passing in a pointer to the
|
||||
appropriate type.
|
||||
|
||||
## Declaring New Flag Types
|
||||
|
||||
A pointer to any type that implements the Value interface may be passed to
|
||||
Flag or FlagLong.
|
||||
|
||||
## VALUEHELP
|
||||
|
||||
All non-flag options are created with a "valuehelp" as the last parameter.
|
||||
Valuehelp should be 0, 1, or 2 strings. The first string, if provided, is
|
||||
the usage message for the option. If the second string, if provided, is the
|
||||
name to use for the value when displaying the usage. If not provided the
|
||||
term "value" is assumed.
|
||||
|
||||
The usage message for the option created with
|
||||
|
||||
```
|
||||
StringLong("option", 'o', "defval", "a string of letters")
|
||||
```
|
||||
|
||||
is
|
||||
|
||||
```
|
||||
-o, -option=value
|
||||
```
|
||||
while the usage message for the option created with
|
||||
|
||||
```
|
||||
StringLong("option", 'o', "defval", "a string of letters", "string")
|
||||
```
|
||||
|
||||
is
|
||||
|
||||
```
|
||||
-o, -option=string
|
||||
```
|
@ -0,0 +1,74 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type boolValue bool
|
||||
|
||||
func (b *boolValue) Set(value string, opt Option) error {
|
||||
switch strings.ToLower(value) {
|
||||
case "", "1", "true", "on", "t":
|
||||
*b = true
|
||||
case "0", "false", "off", "f":
|
||||
*b = false
|
||||
default:
|
||||
return fmt.Errorf("invalid value for bool %s: %q", opt.Name(), value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *boolValue) String() string {
|
||||
if *b {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
// Bool creates a flag option that is a bool. Bools normally do not take a
|
||||
// value however one can be assigned by using the long form of the option:
|
||||
//
|
||||
// --option=true
|
||||
// --o=false
|
||||
//
|
||||
// Its value is case insenstive and one of true, false, t, f, on, off, t and 0.
|
||||
func Bool(name rune, helpvalue ...string) *bool {
|
||||
return CommandLine.Bool(name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Bool(name rune, helpvalue ...string) *bool {
|
||||
var p bool
|
||||
s.BoolVarLong(&p, "", name, helpvalue...)
|
||||
return &p
|
||||
}
|
||||
|
||||
func BoolLong(name string, short rune, helpvalue ...string) *bool {
|
||||
return CommandLine.BoolLong(name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) BoolLong(name string, short rune, helpvalue ...string) *bool {
|
||||
var p bool
|
||||
s.BoolVarLong(&p, name, short, helpvalue...)
|
||||
return &p
|
||||
}
|
||||
|
||||
func BoolVar(p *bool, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.BoolVar(p, name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) BoolVar(p *bool, name rune, helpvalue ...string) Option {
|
||||
return s.BoolVarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func BoolVarLong(p *bool, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.BoolVarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) BoolVarLong(p *bool, name string, short rune, helpvalue ...string) Option {
|
||||
return s.VarLong((*boolValue)(p), name, short, helpvalue...).SetFlag()
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var boolTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
f bool
|
||||
fc int
|
||||
opt bool
|
||||
optc int
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
false, 0,
|
||||
false, 0,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-f", "--opt"},
|
||||
true, 1,
|
||||
true, 1,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--f", "--opt"},
|
||||
true, 1,
|
||||
true, 1,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-ff", "-f", "--opt", "--opt"},
|
||||
true, 3,
|
||||
true, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--opt", "--opt=false"},
|
||||
false, 0,
|
||||
false, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-f", "false"},
|
||||
true, 1,
|
||||
false, 0,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-f=false"},
|
||||
true, 1,
|
||||
false, 0,
|
||||
"test: unknown option: -=\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-f", "false"},
|
||||
true, 1,
|
||||
false, 0,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestBool(t *testing.T) {
|
||||
for x, tt := range boolTests {
|
||||
reset()
|
||||
f := Bool('f')
|
||||
opt := BoolLong("opt", 0)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *f, tt.f; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.opt; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := GetCount('f'), tt.fc; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := GetCount("opt"), tt.optc; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var breakupTests = []struct {
|
||||
in string
|
||||
max int
|
||||
out []string
|
||||
}{
|
||||
{"", 8, []string{}},
|
||||
{"a fox", 8, []string{"a fox"}},
|
||||
{"a foxhound is sly", 2, []string{"a", "foxhound", "is", "sly"}},
|
||||
{"a foxhound is sly", 5, []string{"a", "foxhound", "is", "sly"}},
|
||||
{"a foxhound is sly", 6, []string{"a", "foxhound", "is sly"}},
|
||||
{"a foxhound is sly", 7, []string{"a", "foxhound", "is sly"}},
|
||||
{"a foxhound is sly", 8, []string{"a", "foxhound", "is sly"}},
|
||||
{"a foxhound is sly", 9, []string{"a", "foxhound", "is sly"}},
|
||||
{"a foxhound is sly", 10, []string{"a foxhound", "is sly"}},
|
||||
}
|
||||
|
||||
func TestBreakup(t *testing.T) {
|
||||
for x, tt := range breakupTests {
|
||||
out := breakup(tt.in, tt.max)
|
||||
if badSlice(out, tt.out) {
|
||||
t.Errorf("#%d: got %v, want %v", x, out, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type counterValue int
|
||||
|
||||
func (b *counterValue) Set(value string, opt Option) error {
|
||||
if value == "" {
|
||||
*b++
|
||||
} else {
|
||||
v, err := strconv.ParseInt(value, 0, strconv.IntSize)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
*b = counterValue(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *counterValue) String() string {
|
||||
return strconv.Itoa(int(*b))
|
||||
}
|
||||
|
||||
// Counter creates a counting flag stored as an int. Each time the option
|
||||
// is seen while parsing the value is incremented. The value of the counter
|
||||
// may be explicitly set by using the long form:
|
||||
//
|
||||
// --counter=5
|
||||
// --c=5
|
||||
//
|
||||
// Further instances of the option will increment from the set value.
|
||||
func Counter(name rune, helpvalue ...string) *int {
|
||||
return CommandLine.Counter(name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Counter(name rune, helpvalue ...string) *int {
|
||||
var p int
|
||||
s.CounterVarLong(&p, "", name, helpvalue...)
|
||||
return &p
|
||||
}
|
||||
|
||||
func CounterLong(name string, short rune, helpvalue ...string) *int {
|
||||
return CommandLine.CounterLong(name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) CounterLong(name string, short rune, helpvalue ...string) *int {
|
||||
var p int
|
||||
s.CounterVarLong(&p, name, short, helpvalue...)
|
||||
return &p
|
||||
}
|
||||
|
||||
func CounterVar(p *int, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.CounterVar(p, name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) CounterVar(p *int, name rune, helpvalue ...string) Option {
|
||||
return s.CounterVarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func CounterVarLong(p *int, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.CounterVarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) CounterVarLong(p *int, name string, short rune, helpvalue ...string) Option {
|
||||
return s.VarLong((*counterValue)(p), name, short, helpvalue...).SetFlag()
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var counterTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
c int
|
||||
cnt int
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-c", "--cnt"},
|
||||
1,
|
||||
1,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-cc", "-c", "--cnt", "--cnt"},
|
||||
3,
|
||||
2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--c=17", "--cnt=42"},
|
||||
17,
|
||||
42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--cnt=false"},
|
||||
0, 0,
|
||||
"test: not a valid number: false\n",
|
||||
},
|
||||
}
|
||||
|
||||
func TestCounter(t *testing.T) {
|
||||
for x, tt := range counterTests {
|
||||
reset()
|
||||
c := Counter('c')
|
||||
cnt := CounterLong("cnt", 0)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *c, tt.c; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *cnt, tt.cnt; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
reset()
|
||||
c := 5
|
||||
opt := CounterVar(&c, 'c')
|
||||
parse([]string{"test", "-c"})
|
||||
if c != 6 {
|
||||
t.Errorf("got %d, want 6", c)
|
||||
}
|
||||
if opt.Count() != 1 {
|
||||
t.Errorf("got %d, want 1", c)
|
||||
}
|
||||
Reset()
|
||||
if c != 5 {
|
||||
t.Errorf("got %d, want 5", c)
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import "time"
|
||||
|
||||
type durationValue time.Duration
|
||||
|
||||
func (d *durationValue) Set(value string, opt Option) error {
|
||||
v, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = durationValue(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *durationValue) String() string {
|
||||
return time.Duration(*d).String()
|
||||
}
|
||||
|
||||
// Duration creates an option that parses its value as a time.Duration.
|
||||
func Duration(name rune, value time.Duration, helpvalue ...string) *time.Duration {
|
||||
return CommandLine.Duration(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Duration(name rune, value time.Duration, helpvalue ...string) *time.Duration {
|
||||
return s.DurationLong("", name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func DurationLong(name string, short rune, value time.Duration, helpvalue ...string) *time.Duration {
|
||||
return CommandLine.DurationLong(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) DurationLong(name string, short rune, value time.Duration, helpvalue ...string) *time.Duration {
|
||||
s.DurationVarLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func DurationVar(p *time.Duration, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.DurationVar(p, name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) DurationVar(p *time.Duration, name rune, helpvalue ...string) Option {
|
||||
return s.DurationVarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func DurationVarLong(p *time.Duration, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.DurationVarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) DurationVarLong(p *time.Duration, name string, short rune, helpvalue ...string) Option {
|
||||
return s.VarLong((*durationValue)(p), name, short, helpvalue...)
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var durationTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
d time.Duration
|
||||
dur time.Duration
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-d", "1s", "--duration", "2s"},
|
||||
time.Second, 2 * time.Second,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-d1s", "-d2s"},
|
||||
2 * time.Second, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-d1"},
|
||||
17, 42,
|
||||
"test: time: missing unit in duration 1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--duration", "foo"},
|
||||
17, 42,
|
||||
"test: time: invalid duration foo\n",
|
||||
},
|
||||
}
|
||||
|
||||
func TestDuration(t *testing.T) {
|
||||
for x, tt := range durationTests {
|
||||
reset()
|
||||
d := Duration('d', 17)
|
||||
opt := DurationLong("duration", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *d, tt.d; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.dur; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import "errors"
|
||||
|
||||
type enumValue string
|
||||
|
||||
var enumValues = make(map[*enumValue]map[string]struct{})
|
||||
|
||||
func (s *enumValue) Set(value string, opt Option) error {
|
||||
es, ok := enumValues[s]
|
||||
if !ok || es == nil {
|
||||
return errors.New("this option has no values")
|
||||
}
|
||||
if _, ok := es[value]; !ok {
|
||||
return errors.New("invalid value: " + value)
|
||||
}
|
||||
*s = enumValue(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *enumValue) String() string {
|
||||
return string(*s)
|
||||
}
|
||||
|
||||
// Enum creates an option that can only be set to one of the enumerated strings
|
||||
// passed in values. Passing nil or an empty slice results in an option that
|
||||
// will always fail.
|
||||
func Enum(name rune, values []string, helpvalue ...string) *string {
|
||||
return CommandLine.Enum(name, values, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Enum(name rune, values []string, helpvalue ...string) *string {
|
||||
var p string
|
||||
s.EnumVarLong(&p, "", name, values, helpvalue...)
|
||||
return &p
|
||||
}
|
||||
|
||||
func EnumLong(name string, short rune, values []string, helpvalue ...string) *string {
|
||||
return CommandLine.EnumLong(name, short, values, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) EnumLong(name string, short rune, values []string, helpvalue ...string) *string {
|
||||
var p string
|
||||
s.EnumVarLong(&p, name, short, values, helpvalue...)
|
||||
return &p
|
||||
}
|
||||
|
||||
// EnumVar creates an enum option that defaults to the starting value of *p.
|
||||
// If *p is not found in values then a reset of this option will fail.
|
||||
func EnumVar(p *string, name rune, values []string, helpvalue ...string) Option {
|
||||
return CommandLine.EnumVar(p, name, values, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) EnumVar(p *string, name rune, values []string, helpvalue ...string) Option {
|
||||
return s.EnumVarLong(p, "", name, values, helpvalue...)
|
||||
}
|
||||
|
||||
func EnumVarLong(p *string, name string, short rune, values []string, helpvalue ...string) Option {
|
||||
return CommandLine.EnumVarLong(p, name, short, values, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) EnumVarLong(p *string, name string, short rune, values []string, helpvalue ...string) Option {
|
||||
m := make(map[string]struct{})
|
||||
for _, v := range values {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
enumValues[(*enumValue)(p)] = m
|
||||
return s.VarLong((*enumValue)(p), name, short, helpvalue...)
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var enumTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
values []string
|
||||
out string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
nil,
|
||||
[]string{},
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-e", "val1"},
|
||||
[]string{"val1", "val2"},
|
||||
"val1",
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-e", "val1", "-e", "val2"},
|
||||
[]string{"val1", "val2"},
|
||||
"val2",
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-e", "val3"},
|
||||
[]string{"val1", "val2"},
|
||||
"",
|
||||
"test: invalid value: val3\n",
|
||||
},
|
||||
}
|
||||
|
||||
func TestEnum(t *testing.T) {
|
||||
for x, tt := range enumTests {
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
reset()
|
||||
e := Enum('e', tt.values)
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if *e != tt.out {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, *e, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import "fmt"
|
||||
|
||||
// An Error is returned by Getopt when it encounters an error.
|
||||
type Error struct {
|
||||
ErrorCode // General reason of failure.
|
||||
Err error // The actual error.
|
||||
Parameter string // Parameter passed to option, if any
|
||||
Name string // Option that cause error, if any
|
||||
}
|
||||
|
||||
// Error returns the error message, implementing the error interface.
|
||||
func (i *Error) Error() string { return i.Err.Error() }
|
||||
|
||||
// An ErrorCode indicates what sort of error was encountered.
|
||||
type ErrorCode int
|
||||
|
||||
const (
|
||||
NoError = ErrorCode(iota)
|
||||
UnknownOption // an invalid option was encountered
|
||||
MissingParameter // the options parameter is missing
|
||||
ExtraParameter // a value was set to a long flag
|
||||
Invalid // attempt to set an invalid value
|
||||
)
|
||||
|
||||
func (e ErrorCode) String() string {
|
||||
switch e {
|
||||
case UnknownOption:
|
||||
return "unknow option"
|
||||
case MissingParameter:
|
||||
return "missing argument"
|
||||
case ExtraParameter:
|
||||
return "unxpected value"
|
||||
case Invalid:
|
||||
return "error setting value"
|
||||
}
|
||||
return "unknown error"
|
||||
}
|
||||
|
||||
// unknownOption returns an Error indicating an unknown option was
|
||||
// encountered.
|
||||
func unknownOption(name interface{}) *Error {
|
||||
i := &Error{ErrorCode: UnknownOption}
|
||||
switch n := name.(type) {
|
||||
case rune:
|
||||
if n == '-' {
|
||||
i.Name = "-"
|
||||
} else {
|
||||
i.Name = "-" + string(n)
|
||||
}
|
||||
case string:
|
||||
i.Name = "--" + n
|
||||
}
|
||||
i.Err = fmt.Errorf("unknown option: %s", i.Name)
|
||||
return i
|
||||
}
|
||||
|
||||
// missingArg returns an Error inidicating option o was not passed
|
||||
// a required paramter.
|
||||
func missingArg(o Option) *Error {
|
||||
return &Error{
|
||||
ErrorCode: MissingParameter,
|
||||
Name: o.Name(),
|
||||
Err: fmt.Errorf("missing parameter for %s", o.Name()),
|
||||
}
|
||||
}
|
||||
|
||||
// extraArg returns an Error inidicating option o was passed the
|
||||
// unexpected paramter value.
|
||||
func extraArg(o Option, value string) *Error {
|
||||
return &Error{
|
||||
ErrorCode: ExtraParameter,
|
||||
Name: o.Name(),
|
||||
Parameter: value,
|
||||
Err: fmt.Errorf("unexpected parameter passed to %s: %q", o.Name(), value),
|
||||
}
|
||||
}
|
||||
|
||||
// setError returns an Error inidicating option o and the specified
|
||||
// error while setting it to value.
|
||||
func setError(o Option, value string, err error) *Error {
|
||||
return &Error{
|
||||
ErrorCode: Invalid,
|
||||
Name: o.Name(),
|
||||
Parameter: value,
|
||||
Err: err,
|
||||
}
|
||||
}
|
@ -0,0 +1,536 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package getopt (v1) provides traditional getopt processing for implementing
|
||||
// commands that use traditional command lines. The standard Go flag package
|
||||
// cannot be used to write a program that parses flags the way ls or ssh does,
|
||||
// for example.
|
||||
//
|
||||
// A new version of this package (v2) (whose package name is also getopt) is
|
||||
// available as:
|
||||
//
|
||||
// "github.com/pborman/getopt/v2"
|
||||
//
|
||||
// Getopt supports functionality found in both the standard BSD getopt as well
|
||||
// as (one of the many versions of) the GNU getopt_long. Being a Go package,
|
||||
// this package makes common usage easy, but still enables more controlled usage
|
||||
// if needed.
|
||||
//
|
||||
// Typical usage:
|
||||
//
|
||||
// // Declare the flags to be used
|
||||
// helpFlag := getopt.Bool('?', "display help")
|
||||
// cmdFlag := getopt.StringLong("command", 'c', "", "the command)
|
||||
//
|
||||
// func main() {
|
||||
// // Parse the program arguments
|
||||
// getopt.Parse()
|
||||
// // Get the remaining positional parameters
|
||||
// args := getopt.Args()
|
||||
//
|
||||
// If you don't want the program to exit on error, use getopt.Getopt:
|
||||
//
|
||||
// err := getopt.Getopt(nil)
|
||||
// if err != nil {
|
||||
// // code to handle error
|
||||
// fmt.Fprintln(os.Stderr, err)
|
||||
// }
|
||||
//
|
||||
// Support is provided for both short (-f) and long (--flag) options. A single
|
||||
// option may have both a short and a long name. Each option may be a flag or a
|
||||
// value. A value takes an argument.
|
||||
//
|
||||
// Declaring no long names causes this package to process arguments like the
|
||||
// traditional BSD getopt.
|
||||
//
|
||||
// Short flags may be combined into a single parameter. For example, "-a -b -c"
|
||||
// may also be expressed "-abc". Long flags must stand on their own "--alpha
|
||||
// --beta"
|
||||
//
|
||||
// Values require an argument. For short options the argument may either be
|
||||
// immediately following the short name or as the next argument. Only one short
|
||||
// value may be combined with short flags in a single argument; the short value
|
||||
// must be after all short flags. For example, if f is a flag and v is a value,
|
||||
// then:
|
||||
//
|
||||
// -vvalue (sets v to "value")
|
||||
// -v value (sets v to "value")
|
||||
// -fvvalue (sets f, and sets v to "value")
|
||||
// -fv value (sets f, and sets v to "value")
|
||||
// -vf value (set v to "f" and value is the first parameter)
|
||||
//
|
||||
// For the long value option val:
|
||||
//
|
||||
// --val value (sets val to "value")
|
||||
// --val=value (sets val to "value")
|
||||
// --valvalue (invalid option "valvalue")
|
||||
//
|
||||
// Values with an optional value only set the value if the value is part of the
|
||||
// same argument. In any event, the option count is increased and the option is
|
||||
// marked as seen.
|
||||
//
|
||||
// -v -f (sets v and f as being seen)
|
||||
// -vvalue -f (sets v to "value" and sets f)
|
||||
// --val -f (sets v and f as being seen)
|
||||
// --val=value -f (sets v to "value" and sets f)
|
||||
//
|
||||
// There is no convience function defined for making the value optional. The
|
||||
// SetOptional method must be called on the actual Option.
|
||||
//
|
||||
// v := String("val", 'v', "", "the optional v")
|
||||
// Lookup("v").SetOptional()
|
||||
//
|
||||
// var s string
|
||||
// StringVar(&s, "val", 'v', "the optional v).SetOptional()
|
||||
//
|
||||
// Parsing continues until the first non-option or "--" is encountered.
|
||||
//
|
||||
// The short name "-" can be used, but it either is specified as "-" or as part
|
||||
// of a group of options, for example "-f-". If there are no long options
|
||||
// specified then "--f" could also be used. If "-" is not declared as an option
|
||||
// then the single "-" will also terminate the option processing but unlike
|
||||
// "--", the "-" will be part of the remaining arguments.
|
||||
//
|
||||
// Normally the parsing is performed by calling the Parse function. If it is
|
||||
// important to see the order of the options then the Getopt function should be
|
||||
// used. The standard Parse function does the equivalent of:
|
||||
//
|
||||
// func Parse() {
|
||||
// if err := getopt.Getopt(os.Args, nil); err != nil {
|
||||
// fmt.Fprintln(os.Stderr, err)
|
||||
// s.usage()
|
||||
// os.Exit(1)
|
||||
// }
|
||||
//
|
||||
// When calling Getopt it is the responsibility of the caller to print any
|
||||
// errors.
|
||||
//
|
||||
// Normally the default option set, CommandLine, is used. Other option sets may
|
||||
// be created with New.
|
||||
//
|
||||
// After parsing, the sets Args will contain the non-option arguments. If an
|
||||
// error is encountered then Args will begin with argument that caused the
|
||||
// error.
|
||||
//
|
||||
// It is valid to call a set's Parse a second time to amend the current set of
|
||||
// flags or values. As an example:
|
||||
//
|
||||
// var a = getopt.Bool('a', "", "The a flag")
|
||||
// var b = getopt.Bool('b', "", "The a flag")
|
||||
// var cmd = ""
|
||||
//
|
||||
// var opts = getopt.CommandLine
|
||||
//
|
||||
// opts.Parse(os.Args)
|
||||
// if opts.NArgs() > 0 {
|
||||
// cmd = opts.Arg(0)
|
||||
// opts.Parse(opts.Args())
|
||||
// }
|
||||
//
|
||||
// If called with set to { "prog", "-a", "cmd", "-b", "arg" } then both and and
|
||||
// b would be set, cmd would be set to "cmd", and opts.Args() would return {
|
||||
// "arg" }.
|
||||
//
|
||||
// Unless an option type explicitly prohibits it, an option may appear more than
|
||||
// once in the arguments. The last value provided to the option is the value.
|
||||
//
|
||||
// SYNTAX
|
||||
//
|
||||
// For each option type there are an unfortunately large number of ways, 8, to
|
||||
// initialize the option. This number is derived from three attributes:
|
||||
//
|
||||
// 1) Short or Long name
|
||||
// 2) Normal vs Var
|
||||
// 3) Command Line vs Option Set
|
||||
//
|
||||
// The first two variations provide 4 signature:
|
||||
//
|
||||
// Option(name rune, [value type,] helpvalue... string)
|
||||
// OptionLong(name string, short rune, [value type,] helpvalue... string)
|
||||
// OptionVar(p *type, name rune, helpvalue... string)
|
||||
// OptionVarLong(p *type, name string, short rune, helpvalue... string)
|
||||
//
|
||||
// Foo can actually be expressed in terms of FooLong:
|
||||
//
|
||||
// func Foo(name rune, value type, helpvalue... string) *type {
|
||||
// return FooLong("", name, value, helpvalue...)
|
||||
// }
|
||||
//
|
||||
// Normally Foo is used, unless long options are needed. Setting short to 0
|
||||
// creates only a long option.
|
||||
//
|
||||
// The difference bentween Foo and FooVar is that you pass a pointer, p, to the
|
||||
// location of the value to FooVar. The default value is simply *p. The
|
||||
// initial value of *p is the defaut value of the option.
|
||||
//
|
||||
// Foo is actually a wrapper around FooVar:
|
||||
//
|
||||
// func Foo(name rune, value type, helpvalue... string) *type {
|
||||
// p := value
|
||||
// FooVar(&p, name, helpvalue... string)
|
||||
// return &p
|
||||
// }
|
||||
//
|
||||
//
|
||||
// The third variation provides a top-level function and a method on a Set:
|
||||
//
|
||||
// func Option(...)
|
||||
// func (s *Set) Option(...)
|
||||
//
|
||||
// The top-level function is simply:
|
||||
//
|
||||
// func Option(...) *type {
|
||||
// return CommandLine.Option(...) {
|
||||
// }
|
||||
//
|
||||
// To simplfy documentation, typically only the main top-level function is fully
|
||||
// documented. The others will have documentation when there is something
|
||||
// special about them.
|
||||
//
|
||||
// VALUEHELP
|
||||
//
|
||||
// All non-flag options are created with a "valuehelp" as the last parameter.
|
||||
// Valuehelp should be 0, 1, or 2 strings. The first string, if provided, is
|
||||
// the usage message for the option. If the second string, if provided, is the
|
||||
// name to use for the value when displaying the usage. If not provided the
|
||||
// term "value" is assumed.
|
||||
//
|
||||
// The usage message for the option created with
|
||||
//
|
||||
// StringLong("option", 'o', "defval", "a string of letters")
|
||||
//
|
||||
// is
|
||||
//
|
||||
// -o, -option=value
|
||||
//
|
||||
// StringLong("option", 'o', "defval", "a string of letters", "string")
|
||||
//
|
||||
// is
|
||||
//
|
||||
// -o, -option=string
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// stderr allows tests to capture output to standard error.
|
||||
var stderr io.Writer = os.Stderr
|
||||
|
||||
// exit allows tests to capture an os.Exit call
|
||||
var exit = os.Exit
|
||||
|
||||
// DisplayWidth is used to determine where to split usage long lines.
|
||||
var DisplayWidth = 80
|
||||
|
||||
// HelpColumn is the maximum column position that help strings start to display
|
||||
// at. If the option usage is too long then the help string will be displayed
|
||||
// on the next line. For example:
|
||||
//
|
||||
// -a this is the a flag
|
||||
// -u, --under=location
|
||||
// the u flag's usage is quite long
|
||||
var HelpColumn = 20
|
||||
|
||||
// PrintUsage prints the usage of the program to w.
|
||||
func (s *Set) PrintUsage(w io.Writer) {
|
||||
sort.Sort(s.options)
|
||||
flags := ""
|
||||
|
||||
// Build up the list of short flag names and also compute
|
||||
// how to display the option in the longer help listing.
|
||||
// We also keep track of the longest option usage string
|
||||
// that is no more than HelpColumn-3 bytes (at which point
|
||||
// we use two lines to display the help). The three
|
||||
// is for the leading space and the two spaces before the
|
||||
// help string.
|
||||
for _, opt := range s.options {
|
||||
if opt.name == "" {
|
||||
opt.name = "value"
|
||||
}
|
||||
if opt.uname == "" {
|
||||
opt.uname = opt.usageName()
|
||||
}
|
||||
if opt.flag && opt.short != 0 && opt.short != '-' {
|
||||
flags += string(opt.short)
|
||||
}
|
||||
}
|
||||
|
||||
var opts []string
|
||||
|
||||
// The short option - is special
|
||||
if s.shortOptions['-'] != nil {
|
||||
opts = append(opts, "-")
|
||||
}
|
||||
|
||||
// If we have a bundle of flags, add them to the list
|
||||
if flags != "" {
|
||||
opts = append(opts, "-"+flags)
|
||||
}
|
||||
|
||||
// Now append all the long options and options that require
|
||||
// values.
|
||||
for _, opt := range s.options {
|
||||
if opt.flag {
|
||||
if opt.short != 0 {
|
||||
continue
|
||||
}
|
||||
flags = "--" + opt.long
|
||||
} else if opt.short != 0 {
|
||||
flags = "-" + string(opt.short) + " " + opt.name
|
||||
} else {
|
||||
flags = "--" + string(opt.long) + " " + opt.name
|
||||
}
|
||||
opts = append(opts, flags)
|
||||
}
|
||||
flags = strings.Join(opts, "] [")
|
||||
if flags != "" {
|
||||
flags = " [" + flags + "]"
|
||||
}
|
||||
if s.parameters != "" {
|
||||
flags += " " + s.parameters
|
||||
}
|
||||
fmt.Fprintf(w, "Usage: %s%s\n", s.program, flags)
|
||||
s.PrintOptions(w)
|
||||
}
|
||||
|
||||
// PrintOptions prints the list of options in s to w.
|
||||
func (s *Set) PrintOptions(w io.Writer) {
|
||||
sort.Sort(s.options)
|
||||
max := 4
|
||||
for _, opt := range s.options {
|
||||
if opt.name == "" {
|
||||
opt.name = "value"
|
||||
}
|
||||
if opt.uname == "" {
|
||||
opt.uname = opt.usageName()
|
||||
}
|
||||
if max < len(opt.uname) && len(opt.uname) <= HelpColumn-3 {
|
||||
max = len(opt.uname)
|
||||
}
|
||||
}
|
||||
// Now print one or more usage lines per option.
|
||||
for _, opt := range s.options {
|
||||
if opt.uname != "" {
|
||||
opt.help = strings.TrimSpace(opt.help)
|
||||
if len(opt.help) == 0 {
|
||||
fmt.Fprintf(w, " %s\n", opt.uname)
|
||||
continue
|
||||
}
|
||||
help := strings.Split(opt.help, "\n")
|
||||
// If they did not put in newlines then we will insert
|
||||
// them to keep the help messages from wrapping.
|
||||
if len(help) == 1 {
|
||||
help = breakup(help[0], DisplayWidth-HelpColumn)
|
||||
}
|
||||
if len(opt.uname) <= max {
|
||||
fmt.Fprintf(w, " %-*s %s\n", max, opt.uname, help[0])
|
||||
help = help[1:]
|
||||
} else {
|
||||
fmt.Fprintf(w, " %s\n", opt.uname)
|
||||
}
|
||||
for _, s := range help {
|
||||
fmt.Fprintf(w, " %-*s %s\n", max, " ", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// breakup breaks s up into strings no longer than max bytes.
|
||||
func breakup(s string, max int) []string {
|
||||
var a []string
|
||||
|
||||
for {
|
||||
// strip leading spaces
|
||||
for len(s) > 0 && s[0] == ' ' {
|
||||
s = s[1:]
|
||||
}
|
||||
// If the option is no longer than the max just return it
|
||||
if len(s) <= max {
|
||||
if len(s) != 0 {
|
||||
a = append(a, s)
|
||||
}
|
||||
return a
|
||||
}
|
||||
x := max
|
||||
for s[x] != ' ' {
|
||||
// the first word is too long?!
|
||||
if x == 0 {
|
||||
x = max
|
||||
for x < len(s) && s[x] != ' ' {
|
||||
x++
|
||||
}
|
||||
if x == len(s) {
|
||||
x--
|
||||
}
|
||||
break
|
||||
}
|
||||
x--
|
||||
}
|
||||
for s[x] == ' ' {
|
||||
x--
|
||||
}
|
||||
a = append(a, s[:x+1])
|
||||
s = s[x+1:]
|
||||
}
|
||||
}
|
||||
|
||||
// Parse uses Getopt to parse args using the options set for s. The first
|
||||
// element of args is used to assign the program for s if it is not yet set. On
|
||||
// error, Parse displays the error message as well as a usage message on
|
||||
// standard error and then exits the program.
|
||||
func (s *Set) Parse(args []string) {
|
||||
if err := s.Getopt(args, nil); err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
s.usage()
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse uses Getopt to parse args using the options set for s. The first
|
||||
// element of args is used to assign the program for s if it is not yet set.
|
||||
// Getop calls fn, if not nil, for each option parsed.
|
||||
//
|
||||
// Getopt returns nil when all options have been processed (a non-option
|
||||
// argument was encountered, "--" was encountered, or fn returned false).
|
||||
//
|
||||
// On error getopt returns a refernce to an InvalidOption (which implements
|
||||
// the error interface).
|
||||
func (s *Set) Getopt(args []string, fn func(Option) bool) (err error) {
|
||||
s.State = InProgress
|
||||
defer func() {
|
||||
if s.State == InProgress {
|
||||
switch {
|
||||
case err != nil:
|
||||
s.State = Failure
|
||||
case len(s.args) == 0:
|
||||
s.State = EndOfArguments
|
||||
default:
|
||||
s.State = Unknown
|
||||
}
|
||||
}
|
||||
}()
|
||||
if fn == nil {
|
||||
fn = func(Option) bool { return true }
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if s.program == "" {
|
||||
s.program = path.Base(args[0])
|
||||
}
|
||||
args = args[1:]
|
||||
Parsing:
|
||||
for len(args) > 0 {
|
||||
arg := args[0]
|
||||
s.args = args
|
||||
args = args[1:]
|
||||
|
||||
// end of options?
|
||||
if arg == "" || arg[0] != '-' {
|
||||
s.State = EndOfOptions
|
||||
return nil
|
||||
}
|
||||
|
||||
if arg == "-" {
|
||||
goto ShortParsing
|
||||
}
|
||||
|
||||
// explicitly request end of options?
|
||||
if arg == "--" {
|
||||
s.args = args
|
||||
s.State = DashDash
|
||||
return nil
|
||||
}
|
||||
|
||||
// Long option processing
|
||||
if len(s.longOptions) > 0 && arg[1] == '-' {
|
||||
e := strings.IndexRune(arg, '=')
|
||||
var value string
|
||||
if e > 0 {
|
||||
value = arg[e+1:]
|
||||
arg = arg[:e]
|
||||
}
|
||||
opt := s.longOptions[arg[2:]]
|
||||
// If we are processing long options then --f is -f
|
||||
// if f is not defined as a long option.
|
||||
// This lets you say --f=false
|
||||
if opt == nil && len(arg[2:]) == 1 {
|
||||
opt = s.shortOptions[rune(arg[2])]
|
||||
}
|
||||
if opt == nil {
|
||||
return unknownOption(arg[2:])
|
||||
}
|
||||
opt.isLong = true
|
||||
// If we require an option and did not have an =
|
||||
// then use the next argument as an option.
|
||||
if !opt.flag && e < 0 && !opt.optional {
|
||||
if len(args) == 0 {
|
||||
return missingArg(opt)
|
||||
}
|
||||
value = args[0]
|
||||
args = args[1:]
|
||||
}
|
||||
opt.count++
|
||||
|
||||
if err := opt.value.Set(value, opt); err != nil {
|
||||
return setError(opt, value, err)
|
||||
}
|
||||
|
||||
if !fn(opt) {
|
||||
s.State = Terminated
|
||||
return nil
|
||||
}
|
||||
continue Parsing
|
||||
}
|
||||
|
||||
// Short option processing
|
||||
arg = arg[1:] // strip -
|
||||
ShortParsing:
|
||||
for i, c := range arg {
|
||||
opt := s.shortOptions[c]
|
||||
if opt == nil {
|
||||
// In traditional getopt, if - is not registered
|
||||
// as an option, a lone - is treated as
|
||||
// if there were a -- in front of it.
|
||||
if arg == "-" {
|
||||
s.State = Dash
|
||||
return nil
|
||||
}
|
||||
return unknownOption(c)
|
||||
}
|
||||
opt.isLong = false
|
||||
opt.count++
|
||||
var value string
|
||||
if !opt.flag {
|
||||
value = arg[1+i:]
|
||||
if value == "" && !opt.optional {
|
||||
if len(args) == 0 {
|
||||
return missingArg(opt)
|
||||
}
|
||||
value = args[0]
|
||||
args = args[1:]
|
||||
}
|
||||
}
|
||||
if err := opt.value.Set(value, opt); err != nil {
|
||||
return setError(opt, value, err)
|
||||
}
|
||||
if !fn(opt) {
|
||||
s.State = Terminated
|
||||
return nil
|
||||
}
|
||||
if !opt.flag {
|
||||
continue Parsing
|
||||
}
|
||||
}
|
||||
}
|
||||
s.args = []string{}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type intValue int
|
||||
|
||||
func (i *intValue) Set(value string, opt Option) error {
|
||||
v, err := strconv.ParseInt(value, 0, strconv.IntSize)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
*i = intValue(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *intValue) String() string {
|
||||
return strconv.FormatInt(int64(*i), 10)
|
||||
}
|
||||
|
||||
// Int creates an option that parses its value as an integer.
|
||||
func Int(name rune, value int, helpvalue ...string) *int {
|
||||
return CommandLine.Int(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int(name rune, value int, helpvalue ...string) *int {
|
||||
return s.IntLong("", name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func IntLong(name string, short rune, value int, helpvalue ...string) *int {
|
||||
return CommandLine.IntLong(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) IntLong(name string, short rune, value int, helpvalue ...string) *int {
|
||||
s.IntVarLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func IntVar(p *int, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.IntVar(p, name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) IntVar(p *int, name rune, helpvalue ...string) Option {
|
||||
return s.IntVarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func IntVarLong(p *int, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.IntVarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) IntVarLong(p *int, name string, short rune, helpvalue ...string) Option {
|
||||
return s.VarLong((*intValue)(p), name, short, helpvalue...)
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type int16Value int16
|
||||
|
||||
func (i *int16Value) Set(value string, opt Option) error {
|
||||
v, err := strconv.ParseInt(value, 0, 16)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
*i = int16Value(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *int16Value) String() string {
|
||||
return strconv.FormatInt(int64(*i), 10)
|
||||
}
|
||||
|
||||
// Int16 creates an option that parses its value as an int16.
|
||||
func Int16(name rune, value int16, helpvalue ...string) *int16 {
|
||||
return CommandLine.Int16(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int16(name rune, value int16, helpvalue ...string) *int16 {
|
||||
return s.Int16Long("", name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func Int16Long(name string, short rune, value int16, helpvalue ...string) *int16 {
|
||||
return CommandLine.Int16Long(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int16Long(name string, short rune, value int16, helpvalue ...string) *int16 {
|
||||
s.Int16VarLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func Int16Var(p *int16, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.Int16Var(p, name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int16Var(p *int16, name rune, helpvalue ...string) Option {
|
||||
return s.Int16VarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func Int16VarLong(p *int16, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.Int16VarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int16VarLong(p *int16, name string, short rune, helpvalue ...string) Option {
|
||||
return s.VarLong((*int16Value)(p), name, short, helpvalue...)
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var int16Tests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i int16
|
||||
int16 int16
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--int16", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--int16=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestInt16(t *testing.T) {
|
||||
for x, tt := range int16Tests {
|
||||
reset()
|
||||
i := Int16('i', 17)
|
||||
opt := Int16Long("int16", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.int16; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type int32Value int32
|
||||
|
||||
func (i *int32Value) Set(value string, opt Option) error {
|
||||
v, err := strconv.ParseInt(value, 0, 32)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
*i = int32Value(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *int32Value) String() string {
|
||||
return strconv.FormatInt(int64(*i), 10)
|
||||
}
|
||||
|
||||
// Int32 creates an option that parses its value as an int32.
|
||||
func Int32(name rune, value int32, helpvalue ...string) *int32 {
|
||||
return CommandLine.Int32(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int32(name rune, value int32, helpvalue ...string) *int32 {
|
||||
return s.Int32Long("", name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func Int32Long(name string, short rune, value int32, helpvalue ...string) *int32 {
|
||||
return CommandLine.Int32Long(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int32Long(name string, short rune, value int32, helpvalue ...string) *int32 {
|
||||
s.Int32VarLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func Int32Var(p *int32, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.Int32Var(p, name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int32Var(p *int32, name rune, helpvalue ...string) Option {
|
||||
return s.Int32VarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func Int32VarLong(p *int32, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.Int32VarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int32VarLong(p *int32, name string, short rune, helpvalue ...string) Option {
|
||||
return s.VarLong((*int32Value)(p), name, short, helpvalue...)
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var int32Tests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i int32
|
||||
int32 int32
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--int32", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--int32=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestInt32(t *testing.T) {
|
||||
for x, tt := range int32Tests {
|
||||
reset()
|
||||
i := Int32('i', 17)
|
||||
opt := Int32Long("int32", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.int32; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type int64Value int64
|
||||
|
||||
func (i *int64Value) Set(value string, opt Option) error {
|
||||
v, err := strconv.ParseInt(value, 0, 64)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
*i = int64Value(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *int64Value) String() string {
|
||||
return strconv.FormatInt(int64(*i), 10)
|
||||
}
|
||||
|
||||
// Int64 creates an option that parses its value as an int64.
|
||||
func Int64(name rune, value int64, helpvalue ...string) *int64 {
|
||||
return CommandLine.Int64(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int64(name rune, value int64, helpvalue ...string) *int64 {
|
||||
return s.Int64Long("", name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func Int64Long(name string, short rune, value int64, helpvalue ...string) *int64 {
|
||||
return CommandLine.Int64Long(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int64Long(name string, short rune, value int64, helpvalue ...string) *int64 {
|
||||
s.Int64VarLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func Int64Var(p *int64, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.Int64Var(p, name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int64Var(p *int64, name rune, helpvalue ...string) Option {
|
||||
return s.Int64VarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func Int64VarLong(p *int64, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.Int64VarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int64VarLong(p *int64, name string, short rune, helpvalue ...string) Option {
|
||||
return s.VarLong((*int64Value)(p), name, short, helpvalue...)
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var int64Tests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i int64
|
||||
int64 int64
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--int64", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--int64=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestInt64(t *testing.T) {
|
||||
for x, tt := range int64Tests {
|
||||
reset()
|
||||
i := Int64('i', 17)
|
||||
opt := Int64Long("int64", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.int64; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var intTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i int
|
||||
int int
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--int", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--int=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestInt(t *testing.T) {
|
||||
for x, tt := range intTests {
|
||||
reset()
|
||||
i := Int('i', 17)
|
||||
opt := IntLong("int", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.int; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import "strings"
|
||||
|
||||
type listValue []string
|
||||
|
||||
func (s *listValue) Set(value string, opt Option) error {
|
||||
a := strings.Split(value, ",")
|
||||
// If this is the first time we are seen then nil out the
|
||||
// default value.
|
||||
if opt.Count() <= 1 {
|
||||
*s = nil
|
||||
}
|
||||
*s = append(*s, a...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *listValue) String() string {
|
||||
return strings.Join([]string(*s), ",")
|
||||
}
|
||||
|
||||
// List creates an option that returns a slice of strings. The parameters
|
||||
// passed are converted from a comma seperated value list into a slice.
|
||||
// Subsequent occurrences append to the list.
|
||||
func List(name rune, helpvalue ...string) *[]string {
|
||||
return CommandLine.List(name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) List(name rune, helpvalue ...string) *[]string {
|
||||
p := []string{}
|
||||
s.ListVar(&p, name, helpvalue...)
|
||||
return &p
|
||||
}
|
||||
|
||||
func ListLong(name string, short rune, helpvalue ...string) *[]string {
|
||||
return CommandLine.ListLong(name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) ListLong(name string, short rune, helpvalue ...string) *[]string {
|
||||
p := []string{}
|
||||
s.ListVarLong(&p, name, short, helpvalue...)
|
||||
return &p
|
||||
}
|
||||
|
||||
// ListVar creats a list option and places the values in p. If p is pointing
|
||||
// to a list of values then those are considered the default values. The first
|
||||
// time name is seen in the options the list will be set to list specified by
|
||||
// the parameter to the option. Subsequent instances of the option will append
|
||||
// to the list.
|
||||
func ListVar(p *[]string, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.ListVar(p, name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) ListVar(p *[]string, name rune, helpvalue ...string) Option {
|
||||
return s.ListVarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func ListVarLong(p *[]string, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.ListVarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) ListVarLong(p *[]string, name string, short rune, helpvalue ...string) Option {
|
||||
opt := s.VarLong((*listValue)(p), name, short, helpvalue...)
|
||||
return opt
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import "testing"
|
||||
|
||||
var listTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
l, list []string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
nil, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-l", "one"},
|
||||
[]string{"one"}, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-lone", "-ltwo"},
|
||||
[]string{"one", "two"}, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--list", "one"},
|
||||
nil, []string{"one"},
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--list=one", "--list=two"},
|
||||
nil, []string{"one", "two"},
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--list=one,two"},
|
||||
nil, []string{"one", "two"},
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
for _, tt := range listTests {
|
||||
reset()
|
||||
l := List('l')
|
||||
list := ListLong("list", 0)
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if badSlice(*l, tt.l) {
|
||||
t.Errorf("%s: got s = %q, want %q", tt.where, *l, tt.l)
|
||||
}
|
||||
if badSlice(*list, tt.list) {
|
||||
t.Errorf("%s: got s = %q, want %q", tt.where, *list, tt.list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultList(t *testing.T) {
|
||||
reset()
|
||||
list := []string{"d1", "d2", "d3"}
|
||||
ListVar(&list, 'l')
|
||||
parse([]string{"test"})
|
||||
|
||||
want := []string{"d1", "d2", "d3"}
|
||||
if badSlice(list, want) {
|
||||
t.Errorf("got s = %q, want %q", list, want)
|
||||
}
|
||||
|
||||
parse([]string{"test", "-l", "one"})
|
||||
want = []string{"one"}
|
||||
if badSlice(list, want) {
|
||||
t.Errorf("got s = %q, want %q", list, want)
|
||||
}
|
||||
|
||||
parse([]string{"test", "-l", "two"})
|
||||
want = []string{"one", "two"}
|
||||
if badSlice(list, want) {
|
||||
t.Errorf("got s = %q, want %q", list, want)
|
||||
}
|
||||
Lookup('l').Reset()
|
||||
want = []string{"d1", "d2", "d3"}
|
||||
if badSlice(list, want) {
|
||||
t.Errorf("got s = %q, want %q", list, want)
|
||||
}
|
||||
}
|
@ -0,0 +1,193 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// An Option can be either a Flag or a Value
|
||||
type Option interface {
|
||||
// Name returns the name of the option. If the option has been seen
|
||||
// then the last way it was referenced (short or long) is returned
|
||||
// otherwise if there is a short name then this will be the short name
|
||||
// as a string, else it will be the long name.
|
||||
Name() string
|
||||
|
||||
// IsFlag returns true if Option is a flag.
|
||||
IsFlag() bool
|
||||
|
||||
// Seen returns true if the flag was seen.
|
||||
Seen() bool
|
||||
|
||||
// Count returns the number of times the flag was seen.
|
||||
Count() int
|
||||
|
||||
// String returns the last value the option was set to.
|
||||
String() string
|
||||
|
||||
// Value returns the Value of the option.
|
||||
Value() Value
|
||||
|
||||
// SetOptional makes the value optional. The option and value are
|
||||
// always a single argument. Either --option or --option=value. In
|
||||
// the former case the value of the option does not change but the Set()
|
||||
// will return true and the value returned by Count() is incremented.
|
||||
// The short form is either -o or -ovalue. SetOptional returns
|
||||
// the Option
|
||||
SetOptional() Option
|
||||
|
||||
// SetFlag makes the value a flag. Flags are boolean values and
|
||||
// normally do not taken a value. They are set to true when seen.
|
||||
// If a value is passed in the long form then it must be on, case
|
||||
// insenstive, one of "true", "false", "t", "f", "on", "off", "1", "0".
|
||||
// SetFlag returns the Option
|
||||
SetFlag() Option
|
||||
|
||||
// Reset resets the state of the option so it appears it has not
|
||||
// yet been seen, including resetting the value of the option
|
||||
// to its original default state.
|
||||
Reset()
|
||||
}
|
||||
|
||||
type option struct {
|
||||
short rune // 0 means no short name
|
||||
long string // "" means no long name
|
||||
isLong bool // True if they used the long name
|
||||
flag bool // true if a boolean flag
|
||||
defval string // default value
|
||||
optional bool // true if we take an optional value
|
||||
help string // help message
|
||||
where string // file where the option was defined
|
||||
value Value // current value of option
|
||||
count int // number of times we have seen this option
|
||||
name string // name of the value (for usage)
|
||||
uname string // name of the option (for usage)
|
||||
}
|
||||
|
||||
// usageName returns the name of the option for printing usage lines in one
|
||||
// of the following forms:
|
||||
//
|
||||
// -f
|
||||
// --flag
|
||||
// -f, --flag
|
||||
// -s value
|
||||
// --set=value
|
||||
// -s, --set=value
|
||||
func (o *option) usageName() string {
|
||||
// Don't print help messages if we have none and there is only one
|
||||
// way to specify the option.
|
||||
if o.help == "" && (o.short == 0 || o.long == "") {
|
||||
return ""
|
||||
}
|
||||
n := ""
|
||||
|
||||
switch {
|
||||
case o.short != 0 && o.long == "":
|
||||
n = "-" + string(o.short)
|
||||
case o.short == 0 && o.long != "":
|
||||
n = " --" + o.long
|
||||
case o.short != 0 && o.long != "":
|
||||
n = "-" + string(o.short) + ", --" + o.long
|
||||
}
|
||||
|
||||
switch {
|
||||
case o.flag:
|
||||
return n
|
||||
case o.optional:
|
||||
return n + "[=" + o.name + "]"
|
||||
case o.long != "":
|
||||
return n + "=" + o.name
|
||||
}
|
||||
return n + " " + o.name
|
||||
}
|
||||
|
||||
// sortName returns the name to sort the option on.
|
||||
func (o *option) sortName() string {
|
||||
if o.short != 0 {
|
||||
return string(o.short) + o.long
|
||||
}
|
||||
return o.long[:1] + o.long
|
||||
}
|
||||
|
||||
func (o *option) Seen() bool { return o.count > 0 }
|
||||
func (o *option) Count() int { return o.count }
|
||||
func (o *option) IsFlag() bool { return o.flag }
|
||||
func (o *option) String() string { return o.value.String() }
|
||||
func (o *option) SetOptional() Option { o.optional = true; return o }
|
||||
func (o *option) SetFlag() Option { o.flag = true; return o }
|
||||
|
||||
func (o *option) Value() Value {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.value
|
||||
}
|
||||
|
||||
func (o *option) Name() string {
|
||||
if !o.isLong && o.short != 0 {
|
||||
return "-" + string(o.short)
|
||||
}
|
||||
return "--" + o.long
|
||||
}
|
||||
|
||||
// Reset rests an option so that it appears it has not yet been seen.
|
||||
func (o *option) Reset() {
|
||||
o.isLong = false
|
||||
o.count = 0
|
||||
o.value.Set(o.defval, o)
|
||||
}
|
||||
|
||||
type optionList []*option
|
||||
|
||||
func (ol optionList) Len() int { return len(ol) }
|
||||
func (ol optionList) Swap(i, j int) { ol[i], ol[j] = ol[j], ol[i] }
|
||||
func (ol optionList) Less(i, j int) bool {
|
||||
// first check the short names (or the first letter of the long name)
|
||||
// If they are not equal (case insensitive) then we have our answer
|
||||
n1 := ol[i].sortName()
|
||||
n2 := ol[j].sortName()
|
||||
l1 := strings.ToLower(n1)
|
||||
l2 := strings.ToLower(n2)
|
||||
if l1 < l2 {
|
||||
return true
|
||||
}
|
||||
if l2 < l1 {
|
||||
return false
|
||||
}
|
||||
return n1 < n2
|
||||
}
|
||||
|
||||
// AddOption add the option o to set CommandLine if o is not already in set
|
||||
// CommandLine.
|
||||
func AddOption(o Option) {
|
||||
CommandLine.AddOption(o)
|
||||
}
|
||||
|
||||
// AddOption add the option o to set s if o is not already in set s.
|
||||
func (s *Set) AddOption(o Option) {
|
||||
opt := o.(*option)
|
||||
for _, eopt := range s.options {
|
||||
if opt == eopt {
|
||||
return
|
||||
}
|
||||
}
|
||||
if opt.short != 0 {
|
||||
if oo, ok := s.shortOptions[opt.short]; ok {
|
||||
fmt.Fprintf(stderr, "%s: -%c already declared at %s\n", opt.where, opt.short, oo.where)
|
||||
exit(1)
|
||||
}
|
||||
s.shortOptions[opt.short] = opt
|
||||
}
|
||||
if opt.long != "" {
|
||||
if oo, ok := s.longOptions[opt.long]; ok {
|
||||
fmt.Fprintf(stderr, "%s: --%s already declared at %s\n", opt.where, opt.long, oo.where)
|
||||
exit(1)
|
||||
}
|
||||
s.longOptions[opt.long] = opt
|
||||
}
|
||||
s.options = append(s.options, opt)
|
||||
}
|
@ -0,0 +1,268 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// A Termination says why Getopt returned.
|
||||
type State int
|
||||
|
||||
const (
|
||||
InProgress = State(iota) // Getopt is still running
|
||||
Dash // Returned on "-"
|
||||
DashDash // Returned on "--"
|
||||
EndOfOptions // End of options reached
|
||||
EndOfArguments // No more arguments
|
||||
Terminated // Terminated by callback function
|
||||
Failure // Terminated due to error
|
||||
Unknown // Indicates internal error
|
||||
)
|
||||
|
||||
type Set struct {
|
||||
State // State of getopt
|
||||
|
||||
// args are the parameters remaining after parsing the optoins.
|
||||
args []string
|
||||
|
||||
// program is the name of the program for usage and error messages.
|
||||
// If not set it will automatically be set to the base name of the
|
||||
// first argument passed to parse.
|
||||
program string
|
||||
|
||||
// parameters is what is displayed on the usage line after displaying
|
||||
// the various options.
|
||||
parameters string
|
||||
|
||||
usage func() // usage should print the programs usage and exit.
|
||||
|
||||
shortOptions map[rune]*option
|
||||
longOptions map[string]*option
|
||||
options optionList
|
||||
}
|
||||
|
||||
// New returns a newly created option set.
|
||||
func New() *Set {
|
||||
s := &Set{
|
||||
shortOptions: make(map[rune]*option),
|
||||
longOptions: make(map[string]*option),
|
||||
parameters: "[parameters ...]",
|
||||
}
|
||||
|
||||
s.usage = func() {
|
||||
s.PrintUsage(stderr)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// The default set of command-line options.
|
||||
var CommandLine = New()
|
||||
|
||||
// PrintUsage calls PrintUsage in the default option set.
|
||||
func PrintUsage(w io.Writer) { CommandLine.PrintUsage(w) }
|
||||
|
||||
// Usage calls the usage function in the default option set.
|
||||
func Usage() { CommandLine.usage() }
|
||||
|
||||
// Parse calls Parse in the default option set with the command line arguments
|
||||
// found in os.Args.
|
||||
func Parse() { CommandLine.Parse(os.Args) }
|
||||
|
||||
// Getops returns the result of calling Getop in the default option set with the
|
||||
// command line arguments found in os.Args. The fn function, which may be nil,
|
||||
// is passed to Getopt.
|
||||
func Getopt(fn func(Option) bool) error { return CommandLine.Getopt(os.Args, fn) }
|
||||
|
||||
// Arg returns the n'th command-line argument. Arg(0) is the first remaining
|
||||
// argument after options have been processed.
|
||||
func Arg(n int) string {
|
||||
if n >= 0 && n < len(CommandLine.args) {
|
||||
return CommandLine.args[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Arg returns the n'th argument. Arg(0) is the first remaining
|
||||
// argument after options have been processed.
|
||||
func (s *Set) Arg(n int) string {
|
||||
if n >= 0 && n < len(s.args) {
|
||||
return s.args[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Args returns the non-option command line arguments.
|
||||
func Args() []string {
|
||||
return CommandLine.args
|
||||
}
|
||||
|
||||
// Args returns the non-option arguments.
|
||||
func (s *Set) Args() []string {
|
||||
return s.args
|
||||
}
|
||||
|
||||
// NArgs returns the number of non-option command line arguments.
|
||||
func NArgs() int {
|
||||
return len(CommandLine.args)
|
||||
}
|
||||
|
||||
// NArgs returns the number of non-option arguments.
|
||||
func (s *Set) NArgs() int {
|
||||
return len(s.args)
|
||||
}
|
||||
|
||||
// SetParameters sets the parameters string for printing the command line
|
||||
// usage. It defaults to "[parameters ...]"
|
||||
func SetParameters(parameters string) {
|
||||
CommandLine.parameters = parameters
|
||||
}
|
||||
|
||||
// SetParameters sets the parameters string for printing the s's usage.
|
||||
// It defaults to "[parameters ...]"
|
||||
func (s *Set) SetParameters(parameters string) {
|
||||
s.parameters = parameters
|
||||
}
|
||||
|
||||
// SetProgram sets the program name to program. Nomrally it is determined
|
||||
// from the zeroth command line argument (see os.Args).
|
||||
func SetProgram(program string) {
|
||||
CommandLine.program = program
|
||||
}
|
||||
|
||||
// SetProgram sets s's program name to program. Nomrally it is determined
|
||||
// from the zeroth argument passed to Getopt or Parse.
|
||||
func (s *Set) SetProgram(program string) {
|
||||
s.program = program
|
||||
}
|
||||
|
||||
// SetUsage sets the function used by Parse to display the commands usage
|
||||
// on error. It defaults to calling PrintUsage(os.Stderr).
|
||||
func SetUsage(usage func()) {
|
||||
CommandLine.usage = usage
|
||||
}
|
||||
|
||||
// SetUsage sets the function used by Parse to display usage on error. It
|
||||
// defaults to calling f.PrintUsage(os.Stderr).
|
||||
func (s *Set) SetUsage(usage func()) {
|
||||
s.usage = usage
|
||||
}
|
||||
|
||||
// Lookup returns the Option associated with name. Name should either be
|
||||
// a rune (the short name) or a string (the long name).
|
||||
func Lookup(name interface{}) Option {
|
||||
return CommandLine.Lookup(name)
|
||||
}
|
||||
|
||||
// Lookup returns the Option associated with name in s. Name should either be
|
||||
// a rune (the short name) or a string (the long name).
|
||||
func (s *Set) Lookup(name interface{}) Option {
|
||||
switch v := name.(type) {
|
||||
case rune:
|
||||
return s.shortOptions[v]
|
||||
case int:
|
||||
return s.shortOptions[rune(v)]
|
||||
case string:
|
||||
return s.longOptions[v]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsSet returns true if the Option associated with name was seen while
|
||||
// parsing the command line arguments. Name should either be a rune (the
|
||||
// short name) or a string (the long name).
|
||||
func IsSet(name interface{}) bool {
|
||||
return CommandLine.IsSet(name)
|
||||
}
|
||||
|
||||
// IsSet returns true if the Option associated with name was seen while
|
||||
// parsing s. Name should either be a rune (the short name) or a string (the
|
||||
// long name).
|
||||
func (s *Set) IsSet(name interface{}) bool {
|
||||
if opt := s.Lookup(name); opt != nil {
|
||||
return opt.Seen()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetCount returns the number of times the Option associated with name has been
|
||||
// seen while parsing the command line arguments. Name should either be a rune
|
||||
// (the short name) or a string (the long name).
|
||||
func GetCount(name interface{}) int {
|
||||
return CommandLine.GetCount(name)
|
||||
}
|
||||
|
||||
// GetCount returns the number of times the Option associated with name has been
|
||||
// seen while parsing s's arguments. Name should either be a rune (the short
|
||||
// name) or a string (the long name).
|
||||
func (s *Set) GetCount(name interface{}) int {
|
||||
if opt := s.Lookup(name); opt != nil {
|
||||
return opt.Count()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetValue returns the final value set to the command-line Option with name.
|
||||
// If the option has not been seen while parsing s then the default value is
|
||||
// returned. Name should either be a rune (the short name) or a string (the
|
||||
// long name).
|
||||
func GetValue(name interface{}) string {
|
||||
return CommandLine.GetValue(name)
|
||||
}
|
||||
|
||||
// GetValue returns the final value set to the Option in s associated with name.
|
||||
// If the option has not been seen while parsing s then the default value is
|
||||
// returned. Name should either be a rune (the short name) or a string (the
|
||||
// long name).
|
||||
func (s *Set) GetValue(name interface{}) string {
|
||||
if opt := s.Lookup(name); opt != nil {
|
||||
return opt.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Visit visits the command-line options in lexicographical order, calling fn
|
||||
// for each. It visits only those options that have been set.
|
||||
func Visit(fn func(Option)) { CommandLine.Visit(fn) }
|
||||
|
||||
// Visit visits the options in s in lexicographical order, calling fn
|
||||
// for each. It visits only those options that have been set.
|
||||
func (s *Set) Visit(fn func(Option)) {
|
||||
sort.Sort(s.options)
|
||||
for _, opt := range s.options {
|
||||
if opt.count > 0 {
|
||||
fn(opt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VisitAll visits the options in s in lexicographical order, calling fn
|
||||
// for each. It visits all options, even those not set.
|
||||
func VisitAll(fn func(Option)) { CommandLine.VisitAll(fn) }
|
||||
|
||||
// VisitAll visits the command-line flags in lexicographical order, calling fn
|
||||
// for each. It visits all flags, even those not set.
|
||||
func (s *Set) VisitAll(fn func(Option)) {
|
||||
sort.Sort(s.options)
|
||||
for _, opt := range s.options {
|
||||
fn(opt)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets all the command line options to the initial state so it
|
||||
// appears none of them have been seen.
|
||||
func Reset() {
|
||||
CommandLine.Reset()
|
||||
}
|
||||
|
||||
// Reset resets all the options in s to the initial state so it
|
||||
// appears none of them have been seen.
|
||||
func (s *Set) Reset() {
|
||||
for _, opt := range s.options {
|
||||
opt.Reset()
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type signed int64
|
||||
|
||||
type SignedLimit struct {
|
||||
Base int // Base for conversion as per strconv.ParseInt
|
||||
Bits int // Number of bits as per strconv.ParseInt
|
||||
Min int64 // Minimum allowed value if both Min and Max are not 0
|
||||
Max int64 // Maximum allowed value if both Min and Max are not 0
|
||||
}
|
||||
|
||||
var signedLimits = make(map[*signed]*SignedLimit)
|
||||
|
||||
func (n *signed) Set(value string, opt Option) error {
|
||||
l := signedLimits[n]
|
||||
if l == nil {
|
||||
return fmt.Errorf("no limits defined for %s", opt.Name())
|
||||
}
|
||||
v, err := strconv.ParseInt(value, l.Base, l.Bits)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
if l.Min != 0 || l.Max != 0 {
|
||||
if v < l.Min {
|
||||
return fmt.Errorf("value out of range (<%v): %s", l.Min, value)
|
||||
}
|
||||
if v > l.Max {
|
||||
return fmt.Errorf("value out of range (>%v): %s", l.Max, value)
|
||||
}
|
||||
}
|
||||
*n = signed(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *signed) String() string {
|
||||
l := signedLimits[n]
|
||||
if l != nil && l.Base != 0 {
|
||||
return strconv.FormatInt(int64(*n), l.Base)
|
||||
}
|
||||
return strconv.FormatInt(int64(*n), 10)
|
||||
}
|
||||
|
||||
// Signed creates an option that is stored in an int64 and is constrained
|
||||
// by the limits pointed to by l. The Max and Min values are only used if
|
||||
// at least one of the values are not 0. If Base is 0, the base is implied by
|
||||
// the string's prefix: base 16 for "0x", base 8 for "0", and base 10 otherwise.
|
||||
func Signed(name rune, value int64, l *SignedLimit, helpvalue ...string) *int64 {
|
||||
return CommandLine.Signed(name, value, l, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Signed(name rune, value int64, l *SignedLimit, helpvalue ...string) *int64 {
|
||||
return s.SignedLong("", name, value, l, helpvalue...)
|
||||
}
|
||||
|
||||
func SignedLong(name string, short rune, value int64, l *SignedLimit, helpvalue ...string) *int64 {
|
||||
return CommandLine.SignedLong(name, short, value, l, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) SignedLong(name string, short rune, value int64, l *SignedLimit, helpvalue ...string) *int64 {
|
||||
s.SignedVarLong(&value, name, short, l, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func SignedVar(p *int64, name rune, l *SignedLimit, helpvalue ...string) Option {
|
||||
return CommandLine.SignedVar(p, name, l, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) SignedVar(p *int64, name rune, l *SignedLimit, helpvalue ...string) Option {
|
||||
return s.SignedVarLong(p, "", name, l, helpvalue...)
|
||||
}
|
||||
|
||||
func SignedVarLong(p *int64, name string, short rune, l *SignedLimit, helpvalue ...string) Option {
|
||||
return CommandLine.SignedVarLong(p, name, short, l, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) SignedVarLong(p *int64, name string, short rune, l *SignedLimit, helpvalue ...string) Option {
|
||||
opt := s.VarLong((*signed)(p), name, short, helpvalue...)
|
||||
if l.Base > 36 || l.Base == 1 || l.Base < 0 {
|
||||
fmt.Fprintf(stderr, "invalid base for %s: %d\n", opt.Name(), l.Base)
|
||||
exit(1)
|
||||
}
|
||||
if l.Bits < 0 || l.Bits > 64 {
|
||||
fmt.Fprintf(stderr, "invalid bit size for %s: %d\n", opt.Name(), l.Bits)
|
||||
exit(1)
|
||||
}
|
||||
if l.Min > l.Max {
|
||||
fmt.Fprintf(stderr, "min greater than max for %s\n", opt.Name())
|
||||
exit(1)
|
||||
}
|
||||
lim := *l
|
||||
signedLimits[(*signed)(p)] = &lim
|
||||
return opt
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var signedNumberTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
l SignedLimit
|
||||
out int64
|
||||
err string
|
||||
}{
|
||||
{
|
||||
where: loc(),
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "1010"},
|
||||
SignedLimit{Base: 2, Bits: 5},
|
||||
10,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "1010"},
|
||||
SignedLimit{Base: 2, Bits: 4},
|
||||
0,
|
||||
"test: value out of range: 1010\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "-1000"},
|
||||
SignedLimit{Base: 2, Bits: 4},
|
||||
-8,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "3"},
|
||||
SignedLimit{Min: 4, Max: 6},
|
||||
0,
|
||||
"test: value out of range (<4): 3\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "4"},
|
||||
SignedLimit{Min: 4, Max: 6},
|
||||
4,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "5"},
|
||||
SignedLimit{Min: 4, Max: 6},
|
||||
5,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "6"},
|
||||
SignedLimit{Min: 4, Max: 6},
|
||||
6,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "7"},
|
||||
SignedLimit{Min: 4, Max: 6},
|
||||
0,
|
||||
"test: value out of range (>6): 7\n",
|
||||
},
|
||||
}
|
||||
|
||||
func TestSigneds(t *testing.T) {
|
||||
for x, tt := range signedNumberTests {
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
reset()
|
||||
n := Signed('n', 0, &tt.l)
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if *n != tt.out {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, *n, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
type stringValue string
|
||||
|
||||
func (s *stringValue) Set(value string, opt Option) error {
|
||||
*s = stringValue(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringValue) String() string {
|
||||
return string(*s)
|
||||
}
|
||||
|
||||
// String returns a value option that stores is value as a string. The
|
||||
// initial value of the string is passed in value.
|
||||
func String(name rune, value string, helpvalue ...string) *string {
|
||||
return CommandLine.String(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) String(name rune, value string, helpvalue ...string) *string {
|
||||
p := value
|
||||
s.StringVarLong(&p, "", name, helpvalue...)
|
||||
return &p
|
||||
}
|
||||
|
||||
func StringLong(name string, short rune, value string, helpvalue ...string) *string {
|
||||
return CommandLine.StringLong(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) StringLong(name string, short rune, value string, helpvalue ...string) *string {
|
||||
s.StringVarLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func StringVar(p *string, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.StringVar(p, name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) StringVar(p *string, name rune, helpvalue ...string) Option {
|
||||
return s.VarLong((*stringValue)(p), "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func StringVarLong(p *string, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.StringVarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) StringVarLong(p *string, name string, short rune, helpvalue ...string) Option {
|
||||
return s.VarLong((*stringValue)(p), name, short, helpvalue...)
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import "testing"
|
||||
|
||||
var stringTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
sout string
|
||||
optout string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
"one",
|
||||
"two",
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-s"},
|
||||
"one",
|
||||
"two",
|
||||
"test: missing parameter for -s\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--opt"},
|
||||
"one",
|
||||
"two",
|
||||
"test: missing parameter for --opt\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-svalue", "--opt=option"},
|
||||
"value",
|
||||
"option",
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-s", "value", "--opt", "option"},
|
||||
"value",
|
||||
"option",
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-swrong", "--opt=wrong", "-s", "value", "--opt", "option"},
|
||||
"value",
|
||||
"option",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
for _, tt := range stringTests {
|
||||
reset()
|
||||
s := String('s', "one")
|
||||
opt := StringLong("opt", 0, "two")
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if *s != tt.sout {
|
||||
t.Errorf("%s: got s = %q, want %q", tt.where, *s, tt.sout)
|
||||
}
|
||||
if *opt != tt.optout {
|
||||
t.Errorf("%s: got opt = %q, want %q", tt.where, *opt, tt.optout)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type uintValue uint
|
||||
|
||||
func (i *uintValue) Set(value string, opt Option) error {
|
||||
v, err := strconv.ParseUint(value, 0, strconv.IntSize)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
*i = uintValue(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *uintValue) String() string {
|
||||
return strconv.FormatUint(uint64(*i), 10)
|
||||
}
|
||||
|
||||
// Uint creates an option that parses its value as an unsigned integer.
|
||||
func Uint(name rune, value uint, helpvalue ...string) *uint {
|
||||
return CommandLine.Uint(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint(name rune, value uint, helpvalue ...string) *uint {
|
||||
return s.UintLong("", name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func UintLong(name string, short rune, value uint, helpvalue ...string) *uint {
|
||||
return CommandLine.UintLong(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) UintLong(name string, short rune, value uint, helpvalue ...string) *uint {
|
||||
s.UintVarLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func UintVar(p *uint, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.UintVar(p, name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) UintVar(p *uint, name rune, helpvalue ...string) Option {
|
||||
return s.UintVarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func UintVarLong(p *uint, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.UintVarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) UintVarLong(p *uint, name string, short rune, helpvalue ...string) Option {
|
||||
return s.VarLong((*uintValue)(p), name, short, helpvalue...)
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type uint16Value uint16
|
||||
|
||||
func (i *uint16Value) Set(value string, opt Option) error {
|
||||
v, err := strconv.ParseUint(value, 0, 16)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
*i = uint16Value(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *uint16Value) String() string {
|
||||
return strconv.FormatUint(uint64(*i), 10)
|
||||
}
|
||||
|
||||
// Uint16 creates an option that parses its value as an uint16.
|
||||
func Uint16(name rune, value uint16, helpvalue ...string) *uint16 {
|
||||
return CommandLine.Uint16(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint16(name rune, value uint16, helpvalue ...string) *uint16 {
|
||||
return s.Uint16Long("", name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func Uint16Long(name string, short rune, value uint16, helpvalue ...string) *uint16 {
|
||||
return CommandLine.Uint16Long(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint16Long(name string, short rune, value uint16, helpvalue ...string) *uint16 {
|
||||
s.Uint16VarLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func Uint16Var(p *uint16, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.Uint16Var(p, name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint16Var(p *uint16, name rune, helpvalue ...string) Option {
|
||||
return s.Uint16VarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func Uint16VarLong(p *uint16, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.Uint16VarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint16VarLong(p *uint16, name string, short rune, helpvalue ...string) Option {
|
||||
return s.VarLong((*uint16Value)(p), name, short, helpvalue...)
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var uint16Tests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i uint16
|
||||
uint16 uint16
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--uint16", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--uint16=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestUint16(t *testing.T) {
|
||||
for x, tt := range uint16Tests {
|
||||
reset()
|
||||
i := Uint16('i', 17)
|
||||
opt := Uint16Long("uint16", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.uint16; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type uint32Value uint32
|
||||
|
||||
func (i *uint32Value) Set(value string, opt Option) error {
|
||||
v, err := strconv.ParseUint(value, 0, 32)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
*i = uint32Value(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *uint32Value) String() string {
|
||||
return strconv.FormatUint(uint64(*i), 10)
|
||||
}
|
||||
|
||||
// Uint32 creates an option that parses its value as an uint32.
|
||||
func Uint32(name rune, value uint32, helpvalue ...string) *uint32 {
|
||||
return CommandLine.Uint32(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint32(name rune, value uint32, helpvalue ...string) *uint32 {
|
||||
return s.Uint32Long("", name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func Uint32Long(name string, short rune, value uint32, helpvalue ...string) *uint32 {
|
||||
return CommandLine.Uint32Long(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint32Long(name string, short rune, value uint32, helpvalue ...string) *uint32 {
|
||||
s.Uint32VarLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func Uint32Var(p *uint32, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.Uint32Var(p, name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint32Var(p *uint32, name rune, helpvalue ...string) Option {
|
||||
return s.Uint32VarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func Uint32VarLong(p *uint32, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.Uint32VarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint32VarLong(p *uint32, name string, short rune, helpvalue ...string) Option {
|
||||
return s.VarLong((*uint32Value)(p), name, short, helpvalue...)
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var uint32Tests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i uint32
|
||||
uint32 uint32
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--uint32", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--uint32=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestUint32(t *testing.T) {
|
||||
for x, tt := range uint32Tests {
|
||||
reset()
|
||||
i := Uint32('i', 17)
|
||||
opt := Uint32Long("uint32", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.uint32; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type uint64Value uint64
|
||||
|
||||
func (i *uint64Value) Set(value string, opt Option) error {
|
||||
v, err := strconv.ParseUint(value, 0, 64)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
*i = uint64Value(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *uint64Value) String() string {
|
||||
return strconv.FormatUint(uint64(*i), 10)
|
||||
}
|
||||
|
||||
// Uint64 creates an option that parses its value as a uint64.
|
||||
func Uint64(name rune, value uint64, helpvalue ...string) *uint64 {
|
||||
return CommandLine.Uint64(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint64(name rune, value uint64, helpvalue ...string) *uint64 {
|
||||
return s.Uint64Long("", name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func Uint64Long(name string, short rune, value uint64, helpvalue ...string) *uint64 {
|
||||
return CommandLine.Uint64Long(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint64Long(name string, short rune, value uint64, helpvalue ...string) *uint64 {
|
||||
s.Uint64VarLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func Uint64Var(p *uint64, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.Uint64Var(p, name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint64Var(p *uint64, name rune, helpvalue ...string) Option {
|
||||
return s.Uint64VarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func Uint64VarLong(p *uint64, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.Uint64VarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint64VarLong(p *uint64, name string, short rune, helpvalue ...string) Option {
|
||||
return s.VarLong((*uint64Value)(p), name, short, helpvalue...)
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var uint64Tests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i uint64
|
||||
uint64 uint64
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--uint64", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--uint64=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestUint64(t *testing.T) {
|
||||
for x, tt := range uint64Tests {
|
||||
reset()
|
||||
i := Uint64('i', 17)
|
||||
opt := Uint64Long("uint64", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.uint64; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var uintTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i uint
|
||||
uint uint
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--uint", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--uint=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestUint(t *testing.T) {
|
||||
for x, tt := range uintTests {
|
||||
reset()
|
||||
i := Uint('i', 17)
|
||||
opt := UintLong("uint", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.uint; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type unsigned uint64
|
||||
|
||||
type UnsignedLimit struct {
|
||||
Base int // Base for conversion as per strconv.ParseInt
|
||||
Bits int // Number of bits as per strconv.ParseInt
|
||||
Min uint64 // Minimum allowed value if both Min and Max are not 0
|
||||
Max uint64 // Maximum allowed value if both Min and Max are not 0
|
||||
}
|
||||
|
||||
var unsignedLimits = make(map[*unsigned]*UnsignedLimit)
|
||||
|
||||
func (n *unsigned) Set(value string, opt Option) error {
|
||||
l := unsignedLimits[n]
|
||||
if l == nil {
|
||||
return fmt.Errorf("no limits defined for %s", opt.Name())
|
||||
}
|
||||
v, err := strconv.ParseUint(value, l.Base, l.Bits)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
if l.Min != 0 || l.Max != 0 {
|
||||
if v < l.Min {
|
||||
return fmt.Errorf("value out of range (<%v): %s", l.Min, value)
|
||||
}
|
||||
if v > l.Max {
|
||||
return fmt.Errorf("value out of range (>%v): %s", l.Max, value)
|
||||
}
|
||||
}
|
||||
*n = unsigned(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *unsigned) String() string {
|
||||
l := unsignedLimits[n]
|
||||
if l != nil && l.Base != 0 {
|
||||
return strconv.FormatUint(uint64(*n), l.Base)
|
||||
}
|
||||
return strconv.FormatUint(uint64(*n), 10)
|
||||
}
|
||||
|
||||
// Unsigned creates an option that is stored in a uint64 and is
|
||||
// constrained by the limits pointed to by l. The Max and Min values are only
|
||||
// used if at least one of the values are not 0. If Base is 0, the base is
|
||||
// implied by the string's prefix: base 16 for "0x", base 8 for "0", and base
|
||||
// 10 otherwise.
|
||||
func Unsigned(name rune, value uint64, l *UnsignedLimit, helpvalue ...string) *uint64 {
|
||||
return CommandLine.Unsigned(name, value, l, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Unsigned(name rune, value uint64, l *UnsignedLimit, helpvalue ...string) *uint64 {
|
||||
return s.UnsignedLong("", name, value, l, helpvalue...)
|
||||
}
|
||||
|
||||
func UnsignedLong(name string, short rune, value uint64, l *UnsignedLimit, helpvalue ...string) *uint64 {
|
||||
return CommandLine.UnsignedLong(name, short, value, l, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) UnsignedLong(name string, short rune, value uint64, l *UnsignedLimit, helpvalue ...string) *uint64 {
|
||||
s.UnsignedVarLong(&value, name, short, l, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func UnsignedVar(p *uint64, name rune, l *UnsignedLimit, helpvalue ...string) Option {
|
||||
return CommandLine.UnsignedVar(p, name, l, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) UnsignedVar(p *uint64, name rune, l *UnsignedLimit, helpvalue ...string) Option {
|
||||
return s.UnsignedVarLong(p, "", name, l, helpvalue...)
|
||||
}
|
||||
|
||||
func UnsignedVarLong(p *uint64, name string, short rune, l *UnsignedLimit, helpvalue ...string) Option {
|
||||
return CommandLine.UnsignedVarLong(p, name, short, l, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) UnsignedVarLong(p *uint64, name string, short rune, l *UnsignedLimit, helpvalue ...string) Option {
|
||||
opt := s.VarLong((*unsigned)(p), name, short, helpvalue...)
|
||||
if l.Base > 36 || l.Base == 1 || l.Base < 0 {
|
||||
fmt.Fprintf(stderr, "invalid base for %s: %d\n", opt.Name(), l.Base)
|
||||
exit(1)
|
||||
}
|
||||
if l.Bits < 0 || l.Bits > 64 {
|
||||
fmt.Fprintf(stderr, "invalid bit size for %s: %d\n", opt.Name(), l.Bits)
|
||||
exit(1)
|
||||
}
|
||||
if l.Min > l.Max {
|
||||
fmt.Fprintf(stderr, "min greater than max for %s\n", opt.Name())
|
||||
exit(1)
|
||||
}
|
||||
lim := *l
|
||||
unsignedLimits[(*unsigned)(p)] = &lim
|
||||
return opt
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var unsignedTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
l UnsignedLimit
|
||||
out uint64
|
||||
err string
|
||||
}{
|
||||
{
|
||||
where: loc(),
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "1010"},
|
||||
UnsignedLimit{Base: 2, Bits: 5},
|
||||
10,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "1010"},
|
||||
UnsignedLimit{Base: 2, Bits: 4},
|
||||
10,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "1010"},
|
||||
UnsignedLimit{Base: 2, Bits: 3},
|
||||
0,
|
||||
"test: value out of range: 1010\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "3"},
|
||||
UnsignedLimit{Min: 4, Max: 6},
|
||||
0,
|
||||
"test: value out of range (<4): 3\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "4"},
|
||||
UnsignedLimit{Min: 4, Max: 6},
|
||||
4,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "5"},
|
||||
UnsignedLimit{Min: 4, Max: 6},
|
||||
5,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "6"},
|
||||
UnsignedLimit{Min: 4, Max: 6},
|
||||
6,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "7"},
|
||||
UnsignedLimit{Min: 4, Max: 6},
|
||||
0,
|
||||
"test: value out of range (>6): 7\n",
|
||||
},
|
||||
}
|
||||
|
||||
func TestUnsigneds(t *testing.T) {
|
||||
for x, tt := range unsignedTests {
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
reset()
|
||||
n := Unsigned('n', 0, &tt.l)
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if *n != tt.out {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, *n, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errorString string
|
||||
|
||||
func reset() {
|
||||
CommandLine.shortOptions = make(map[rune]*option)
|
||||
CommandLine.longOptions = make(map[string]*option)
|
||||
CommandLine.options = nil
|
||||
CommandLine.args = nil
|
||||
CommandLine.program = ""
|
||||
errorString = ""
|
||||
}
|
||||
|
||||
func parse(args []string) {
|
||||
err := CommandLine.Getopt(args, nil)
|
||||
if err != nil {
|
||||
b := &bytes.Buffer{}
|
||||
|
||||
fmt.Fprintln(b, CommandLine.program+": "+err.Error())
|
||||
CommandLine.PrintUsage(b)
|
||||
errorString = b.String()
|
||||
}
|
||||
}
|
||||
|
||||
func badSlice(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return true
|
||||
}
|
||||
for x, v := range a {
|
||||
if b[x] != v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func loc() string {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
return fmt.Sprintf("%s:%d", path.Base(file), line)
|
||||
}
|
||||
|
||||
func (o *option) Equal(opt *option) bool {
|
||||
if o.value != nil && opt.value == nil {
|
||||
return false
|
||||
}
|
||||
if o.value == nil && opt.value != nil {
|
||||
return false
|
||||
}
|
||||
if o.value != nil && o.value.String() != opt.value.String() {
|
||||
return false
|
||||
}
|
||||
|
||||
oc := *o
|
||||
optc := *opt
|
||||
oc.value = nil
|
||||
optc.value = nil
|
||||
return reflect.DeepEqual(&oc, &optc)
|
||||
}
|
||||
|
||||
func newStringValue(s string) *stringValue { return (*stringValue)(&s) }
|
||||
|
||||
func checkError(err string) string {
|
||||
switch {
|
||||
case err == errorString:
|
||||
return ""
|
||||
case err == "":
|
||||
return fmt.Sprintf("unexpected error %q", errorString)
|
||||
case errorString == "":
|
||||
return fmt.Sprintf("did not get expected error %q", err)
|
||||
case !strings.HasPrefix(errorString, err):
|
||||
return fmt.Sprintf("got error %q, want %q", errorString, err)
|
||||
}
|
||||
return ""
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
// Bool creates a flag option that is a bool. Bools normally do not take a
|
||||
// value however one can be assigned by using the long form of the option:
|
||||
//
|
||||
// --option=true
|
||||
// --o=false
|
||||
//
|
||||
// The value is case insensitive and one of true, false, t, f, on, off, t and 0.
|
||||
func Bool(name rune, helpvalue ...string) *bool {
|
||||
var b bool
|
||||
CommandLine.Flag(&b, name, helpvalue...)
|
||||
return &b
|
||||
}
|
||||
|
||||
func BoolLong(name string, short rune, helpvalue ...string) *bool {
|
||||
var p bool
|
||||
CommandLine.FlagLong(&p, name, short, helpvalue...)
|
||||
return &p
|
||||
}
|
||||
|
||||
func (s *Set) Bool(name rune, helpvalue ...string) *bool {
|
||||
var b bool
|
||||
s.Flag(&b, name, helpvalue...)
|
||||
return &b
|
||||
}
|
||||
|
||||
func (s *Set) BoolLong(name string, short rune, helpvalue ...string) *bool {
|
||||
var p bool
|
||||
s.FlagLong(&p, name, short, helpvalue...)
|
||||
return &p
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var boolTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
f bool
|
||||
fc int
|
||||
opt bool
|
||||
optc int
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
false, 0,
|
||||
false, 0,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-f", "--opt"},
|
||||
true, 1,
|
||||
true, 1,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--f", "--opt"},
|
||||
true, 1,
|
||||
true, 1,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-ff", "-f", "--opt", "--opt"},
|
||||
true, 3,
|
||||
true, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--opt", "--opt=false"},
|
||||
false, 0,
|
||||
false, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-f", "false"},
|
||||
true, 1,
|
||||
false, 0,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-f=false"},
|
||||
true, 1,
|
||||
false, 0,
|
||||
"test: unknown option: -=\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-f", "false"},
|
||||
true, 1,
|
||||
false, 0,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestBool(t *testing.T) {
|
||||
for x, tt := range boolTests {
|
||||
reset()
|
||||
f := Bool('f')
|
||||
opt := BoolLong("opt", 0)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *f, tt.f; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.opt; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := GetCount('f'), tt.fc; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := GetCount("opt"), tt.optc; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var breakupTests = []struct {
|
||||
in string
|
||||
max int
|
||||
out []string
|
||||
}{
|
||||
{"", 8, []string{}},
|
||||
{"a fox", 8, []string{"a fox"}},
|
||||
{"a foxhound is sly", 2, []string{"a", "foxhound", "is", "sly"}},
|
||||
{"a foxhound is sly", 5, []string{"a", "foxhound", "is", "sly"}},
|
||||
{"a foxhound is sly", 6, []string{"a", "foxhound", "is sly"}},
|
||||
{"a foxhound is sly", 7, []string{"a", "foxhound", "is sly"}},
|
||||
{"a foxhound is sly", 8, []string{"a", "foxhound", "is sly"}},
|
||||
{"a foxhound is sly", 9, []string{"a", "foxhound", "is sly"}},
|
||||
{"a foxhound is sly", 10, []string{"a foxhound", "is sly"}},
|
||||
}
|
||||
|
||||
func TestBreakup(t *testing.T) {
|
||||
for x, tt := range breakupTests {
|
||||
out := breakup(tt.in, tt.max)
|
||||
if badSlice(out, tt.out) {
|
||||
t.Errorf("#%d: got %v, want %v", x, out, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type counterValue int
|
||||
|
||||
func (b *counterValue) Set(value string, opt Option) error {
|
||||
if value == "" {
|
||||
*b++
|
||||
} else {
|
||||
v, err := strconv.ParseInt(value, 0, strconv.IntSize)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
*b = counterValue(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *counterValue) String() string {
|
||||
return strconv.Itoa(int(*b))
|
||||
}
|
||||
|
||||
// Counter creates a counting flag stored as an int. Each time the option
|
||||
// is seen while parsing the value is incremented. The value of the counter
|
||||
// may be explicitly set by using the long form:
|
||||
//
|
||||
// --counter=5
|
||||
// --c=5
|
||||
//
|
||||
// Further instances of the option will increment from the set value.
|
||||
func Counter(name rune, helpvalue ...string) *int {
|
||||
var p int
|
||||
CommandLine.FlagLong((*counterValue)(&p), "", name, helpvalue...).SetFlag()
|
||||
return &p
|
||||
}
|
||||
|
||||
func (s *Set) Counter(name rune, helpvalue ...string) *int {
|
||||
var p int
|
||||
s.FlagLong((*counterValue)(&p), "", name, helpvalue...).SetFlag()
|
||||
return &p
|
||||
}
|
||||
|
||||
func CounterLong(name string, short rune, helpvalue ...string) *int {
|
||||
var p int
|
||||
CommandLine.FlagLong((*counterValue)(&p), name, short, helpvalue...).SetFlag()
|
||||
return &p
|
||||
}
|
||||
|
||||
func (s *Set) CounterLong(name string, short rune, helpvalue ...string) *int {
|
||||
var p int
|
||||
s.FlagLong((*counterValue)(&p), name, short, helpvalue...).SetFlag()
|
||||
return &p
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var counterTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
c int
|
||||
cnt int
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-c", "--cnt"},
|
||||
1,
|
||||
1,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-cc", "-c", "--cnt", "--cnt"},
|
||||
3,
|
||||
2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--c=17", "--cnt=42"},
|
||||
17,
|
||||
42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--cnt=false"},
|
||||
0, 0,
|
||||
"test: not a valid number: false\n",
|
||||
},
|
||||
}
|
||||
|
||||
func TestCounter(t *testing.T) {
|
||||
for x, tt := range counterTests {
|
||||
reset()
|
||||
c := Counter('c')
|
||||
cnt := CounterLong("cnt", 0)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *c, tt.c; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *cnt, tt.cnt; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import "time"
|
||||
|
||||
// Duration creates an option that parses its value as a time.Duration.
|
||||
func Duration(name rune, value time.Duration, helpvalue ...string) *time.Duration {
|
||||
CommandLine.FlagLong(&value, "", name, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func (s *Set) Duration(name rune, value time.Duration, helpvalue ...string) *time.Duration {
|
||||
s.FlagLong(&value, "", name, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func DurationLong(name string, short rune, value time.Duration, helpvalue ...string) *time.Duration {
|
||||
CommandLine.FlagLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func (s *Set) DurationLong(name string, short rune, value time.Duration, helpvalue ...string) *time.Duration {
|
||||
s.FlagLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var durationTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
d time.Duration
|
||||
dur time.Duration
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-d", "1s", "--duration", "2s"},
|
||||
time.Second, 2 * time.Second,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-d1s", "-d2s"},
|
||||
2 * time.Second, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-d1"},
|
||||
17, 42,
|
||||
"test: time: missing unit in duration 1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--duration", "foo"},
|
||||
17, 42,
|
||||
"test: time: invalid duration foo\n",
|
||||
},
|
||||
}
|
||||
|
||||
func TestDuration(t *testing.T) {
|
||||
for x, tt := range durationTests {
|
||||
reset()
|
||||
d := Duration('d', 17)
|
||||
opt := DurationLong("duration", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *d, tt.d; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.dur; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type enumValue string
|
||||
|
||||
var (
|
||||
enumValuesMu sync.Mutex
|
||||
enumValues = make(map[*enumValue]map[string]struct{})
|
||||
)
|
||||
|
||||
func (s *enumValue) Set(value string, opt Option) error {
|
||||
enumValuesMu.Lock()
|
||||
es, ok := enumValues[s]
|
||||
enumValuesMu.Unlock()
|
||||
if !ok || es == nil {
|
||||
return errors.New("this option has no values")
|
||||
}
|
||||
if _, ok := es[value]; !ok {
|
||||
return errors.New("invalid value: " + value)
|
||||
}
|
||||
*s = enumValue(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *enumValue) String() string {
|
||||
return string(*s)
|
||||
}
|
||||
|
||||
// Enum creates an option that can only be set to one of the enumerated strings
|
||||
// passed in values. Passing nil or an empty slice results in an option that
|
||||
// will always fail. If not "", value is the default value of the enum. If
|
||||
// value is not listed in values then Enum will produce an error on standard
|
||||
// error and then exit the program with a status of 1.
|
||||
func Enum(name rune, values []string, value string, helpvalue ...string) *string {
|
||||
return CommandLine.Enum(name, values, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Enum(name rune, values []string, value string, helpvalue ...string) *string {
|
||||
var p enumValue
|
||||
p.define(values, value, &option{short: name})
|
||||
s.FlagLong(&p, "", name, helpvalue...)
|
||||
return (*string)(&p)
|
||||
}
|
||||
|
||||
func EnumLong(name string, short rune, values []string, value string, helpvalue ...string) *string {
|
||||
return CommandLine.EnumLong(name, short, values, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) EnumLong(name string, short rune, values []string, value string, helpvalue ...string) *string {
|
||||
var p enumValue
|
||||
p.define(values, value, &option{short: short, long: name})
|
||||
s.FlagLong(&p, name, short, helpvalue...)
|
||||
return (*string)(&p)
|
||||
}
|
||||
|
||||
func (e *enumValue) define(values []string, def string, opt Option) {
|
||||
m := make(map[string]struct{})
|
||||
for _, v := range values {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
enumValuesMu.Lock()
|
||||
enumValues[e] = m
|
||||
enumValuesMu.Unlock()
|
||||
if def != "" {
|
||||
if err := e.Set(def, nil); err != nil {
|
||||
fmt.Fprintf(stderr, "setting default for %s: %v\n", opt.Name(), err)
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var enumTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
values []string
|
||||
def string
|
||||
out string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
nil,
|
||||
[]string{},
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-e", "val1"},
|
||||
[]string{"val1", "val2"},
|
||||
"",
|
||||
"val1",
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-e", "val1", "-e", "val2"},
|
||||
[]string{"val1", "val2"},
|
||||
"",
|
||||
"val2",
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test"},
|
||||
[]string{"val1", "val2"},
|
||||
"val2",
|
||||
"val2",
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-e", "val3"},
|
||||
[]string{"val1", "val2"},
|
||||
"",
|
||||
"",
|
||||
"test: invalid value: val3\n",
|
||||
},
|
||||
}
|
||||
|
||||
func TestEnum(t *testing.T) {
|
||||
for x, tt := range enumTests {
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
reset()
|
||||
e := Enum('e', tt.values, tt.def)
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if *e != tt.out {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, *e, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import "fmt"
|
||||
|
||||
// An Error is returned by Getopt when it encounters an error.
|
||||
type Error struct {
|
||||
ErrorCode // General reason of failure.
|
||||
Err error // The actual error.
|
||||
Parameter string // Parameter passed to option, if any
|
||||
Name string // Option that cause error, if any
|
||||
}
|
||||
|
||||
// Error returns the error message, implementing the error interface.
|
||||
func (i *Error) Error() string { return i.Err.Error() }
|
||||
|
||||
// An ErrorCode indicates what sort of error was encountered.
|
||||
type ErrorCode int
|
||||
|
||||
const (
|
||||
NoError = ErrorCode(iota)
|
||||
UnknownOption // an invalid option was encountered
|
||||
MissingParameter // the options parameter is missing
|
||||
ExtraParameter // a value was set to a long flag
|
||||
Invalid // attempt to set an invalid value
|
||||
)
|
||||
|
||||
func (e ErrorCode) String() string {
|
||||
switch e {
|
||||
case UnknownOption:
|
||||
return "unknow option"
|
||||
case MissingParameter:
|
||||
return "missing argument"
|
||||
case ExtraParameter:
|
||||
return "unxpected value"
|
||||
case Invalid:
|
||||
return "error setting value"
|
||||
}
|
||||
return "unknown error"
|
||||
}
|
||||
|
||||
// unknownOption returns an Error indicating an unknown option was
|
||||
// encountered.
|
||||
func unknownOption(name interface{}) *Error {
|
||||
i := &Error{ErrorCode: UnknownOption}
|
||||
switch n := name.(type) {
|
||||
case rune:
|
||||
if n == '-' {
|
||||
i.Name = "-"
|
||||
} else {
|
||||
i.Name = "-" + string(n)
|
||||
}
|
||||
case string:
|
||||
i.Name = "--" + n
|
||||
}
|
||||
i.Err = fmt.Errorf("unknown option: %s", i.Name)
|
||||
return i
|
||||
}
|
||||
|
||||
// missingArg returns an Error inidicating option o was not passed
|
||||
// a required paramter.
|
||||
func missingArg(o Option) *Error {
|
||||
return &Error{
|
||||
ErrorCode: MissingParameter,
|
||||
Name: o.Name(),
|
||||
Err: fmt.Errorf("missing parameter for %s", o.Name()),
|
||||
}
|
||||
}
|
||||
|
||||
// extraArg returns an Error inidicating option o was passed the
|
||||
// unexpected paramter value.
|
||||
func extraArg(o Option, value string) *Error {
|
||||
return &Error{
|
||||
ErrorCode: ExtraParameter,
|
||||
Name: o.Name(),
|
||||
Parameter: value,
|
||||
Err: fmt.Errorf("unexpected parameter passed to %s: %q", o.Name(), value),
|
||||
}
|
||||
}
|
||||
|
||||
// setError returns an Error inidicating option o and the specified
|
||||
// error while setting it to value.
|
||||
func setError(o Option, value string, err error) *Error {
|
||||
return &Error{
|
||||
ErrorCode: Invalid,
|
||||
Name: o.Name(),
|
||||
Parameter: value,
|
||||
Err: err,
|
||||
}
|
||||
}
|
@ -0,0 +1,238 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type generic struct {
|
||||
p interface{}
|
||||
}
|
||||
|
||||
// Flag is shorthand for CommandLine.Flag.
|
||||
func Flag(v interface{}, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.long(v, "", short, helpvalue...)
|
||||
}
|
||||
|
||||
// FlagLong is shorthand for CommandLine.LongFlag.
|
||||
func FlagLong(v interface{}, long string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.long(v, long, short, helpvalue...)
|
||||
}
|
||||
|
||||
// Flag calls FlagLong with only a short flag name.
|
||||
func (s *Set) Flag(v interface{}, short rune, helpvalue ...string) Option {
|
||||
return s.long(v, "", short, helpvalue...)
|
||||
}
|
||||
|
||||
// FlagLong returns an Option in Set s for setting v. If long is not "" then
|
||||
// the option has a long name, and if short is not 0, the option has a short
|
||||
// name. v must either be of type getopt.Value or a pointer to one of the
|
||||
// supported builtin types:
|
||||
//
|
||||
// bool, string, []string
|
||||
// int, int8, int16, int32, int64
|
||||
// uint, uint8, uint16, uint32, uint64
|
||||
// float32, float64
|
||||
// time.Duration
|
||||
//
|
||||
// FlagLong will panic if v is not a getopt.Value or one of the supported
|
||||
// builtin types.
|
||||
//
|
||||
// The default value of the flag is the value of v at the time FlagLong is
|
||||
// called.
|
||||
func (s *Set) FlagLong(v interface{}, long string, short rune, helpvalue ...string) Option {
|
||||
return s.long(v, long, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) long(v interface{}, long string, short rune, helpvalue ...string) (opt Option) {
|
||||
// Fix up our location when we return.
|
||||
if where := calledFrom(); where != "" {
|
||||
defer func() {
|
||||
if opt, ok := opt.(*option); ok {
|
||||
opt.where = where
|
||||
}
|
||||
}()
|
||||
}
|
||||
switch p := v.(type) {
|
||||
case Value:
|
||||
return s.addFlag(p, long, short, helpvalue...)
|
||||
case *bool:
|
||||
return s.addFlag(&generic{v}, long, short, helpvalue...).SetFlag()
|
||||
case *string, *[]string:
|
||||
return s.addFlag(&generic{v}, long, short, helpvalue...)
|
||||
case *int, *int8, *int16, *int32, *int64:
|
||||
return s.addFlag(&generic{v}, long, short, helpvalue...)
|
||||
case *uint, *uint8, *uint16, *uint32, *uint64:
|
||||
return s.addFlag(&generic{v}, long, short, helpvalue...)
|
||||
case *float32, *float64:
|
||||
return s.addFlag(&generic{v}, long, short, helpvalue...)
|
||||
case *time.Duration:
|
||||
return s.addFlag(&generic{v}, long, short, helpvalue...)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported flag type: %T", v))
|
||||
}
|
||||
}
|
||||
|
||||
func (g *generic) Set(value string, opt Option) error {
|
||||
strconvErr := func(err error) error {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
switch p := g.p.(type) {
|
||||
case *bool:
|
||||
switch strings.ToLower(value) {
|
||||
case "", "1", "true", "on", "t":
|
||||
*p = true
|
||||
case "0", "false", "off", "f":
|
||||
*p = false
|
||||
default:
|
||||
return fmt.Errorf("invalid value for bool %s: %q", opt.Name(), value)
|
||||
}
|
||||
return nil
|
||||
case *string:
|
||||
*p = value
|
||||
return nil
|
||||
case *[]string:
|
||||
a := strings.Split(value, ",")
|
||||
// If this is the first time we are seen then nil out the
|
||||
// default value.
|
||||
if opt.Count() <= 1 {
|
||||
*p = nil
|
||||
}
|
||||
*p = append(*p, a...)
|
||||
return nil
|
||||
case *int:
|
||||
i64, err := strconv.ParseInt(value, 0, strconv.IntSize)
|
||||
if err == nil {
|
||||
*p = int(i64)
|
||||
}
|
||||
return strconvErr(err)
|
||||
case *int8:
|
||||
i64, err := strconv.ParseInt(value, 0, 8)
|
||||
if err == nil {
|
||||
*p = int8(i64)
|
||||
}
|
||||
return strconvErr(err)
|
||||
case *int16:
|
||||
i64, err := strconv.ParseInt(value, 0, 16)
|
||||
if err == nil {
|
||||
*p = int16(i64)
|
||||
}
|
||||
return strconvErr(err)
|
||||
case *int32:
|
||||
i64, err := strconv.ParseInt(value, 0, 32)
|
||||
if err == nil {
|
||||
*p = int32(i64)
|
||||
}
|
||||
return strconvErr(err)
|
||||
case *int64:
|
||||
i64, err := strconv.ParseInt(value, 0, 64)
|
||||
if err == nil {
|
||||
*p = i64
|
||||
}
|
||||
return strconvErr(err)
|
||||
case *uint:
|
||||
u64, err := strconv.ParseUint(value, 0, strconv.IntSize)
|
||||
if err == nil {
|
||||
*p = uint(u64)
|
||||
}
|
||||
return strconvErr(err)
|
||||
case *uint8:
|
||||
u64, err := strconv.ParseUint(value, 0, 8)
|
||||
if err == nil {
|
||||
*p = uint8(u64)
|
||||
}
|
||||
return strconvErr(err)
|
||||
case *uint16:
|
||||
u64, err := strconv.ParseUint(value, 0, 16)
|
||||
if err == nil {
|
||||
*p = uint16(u64)
|
||||
}
|
||||
return strconvErr(err)
|
||||
case *uint32:
|
||||
u64, err := strconv.ParseUint(value, 0, 32)
|
||||
if err == nil {
|
||||
*p = uint32(u64)
|
||||
}
|
||||
return strconvErr(err)
|
||||
case *uint64:
|
||||
u64, err := strconv.ParseUint(value, 0, 64)
|
||||
if err == nil {
|
||||
*p = u64
|
||||
}
|
||||
return strconvErr(err)
|
||||
case *float32:
|
||||
f64, err := strconv.ParseFloat(value, 32)
|
||||
if err == nil {
|
||||
*p = float32(f64)
|
||||
}
|
||||
return strconvErr(err)
|
||||
case *float64:
|
||||
f64, err := strconv.ParseFloat(value, 64)
|
||||
if err == nil {
|
||||
*p = f64
|
||||
}
|
||||
return strconvErr(err)
|
||||
case *time.Duration:
|
||||
v, err := time.ParseDuration(value)
|
||||
if err == nil {
|
||||
*p = v
|
||||
}
|
||||
return err
|
||||
}
|
||||
panic("internal error")
|
||||
}
|
||||
|
||||
func (g *generic) String() string {
|
||||
switch p := g.p.(type) {
|
||||
case *bool:
|
||||
if *p {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
case *string:
|
||||
return *p
|
||||
case *[]string:
|
||||
return strings.Join([]string(*p), ",")
|
||||
case *int:
|
||||
return strconv.FormatInt(int64(*p), 10)
|
||||
case *int8:
|
||||
return strconv.FormatInt(int64(*p), 10)
|
||||
case *int16:
|
||||
return strconv.FormatInt(int64(*p), 10)
|
||||
case *int32:
|
||||
return strconv.FormatInt(int64(*p), 10)
|
||||
case *int64:
|
||||
return strconv.FormatInt(*p, 10)
|
||||
case *uint:
|
||||
return strconv.FormatUint(uint64(*p), 10)
|
||||
case *uint8:
|
||||
return strconv.FormatUint(uint64(*p), 10)
|
||||
case *uint16:
|
||||
return strconv.FormatUint(uint64(*p), 10)
|
||||
case *uint32:
|
||||
return strconv.FormatUint(uint64(*p), 10)
|
||||
case *uint64:
|
||||
return strconv.FormatUint(*p, 10)
|
||||
case *float32:
|
||||
return strconv.FormatFloat(float64(*p), 'g', -1, 32)
|
||||
case *float64:
|
||||
return strconv.FormatFloat(*p, 'g', -1, 64)
|
||||
case *time.Duration:
|
||||
return p.String()
|
||||
}
|
||||
panic("internal error")
|
||||
}
|
@ -0,0 +1,319 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGeneric(t *testing.T) {
|
||||
const (
|
||||
shortTest = iota
|
||||
longTest
|
||||
bothTest
|
||||
)
|
||||
for _, tt := range []struct {
|
||||
where string
|
||||
kind int
|
||||
val interface{}
|
||||
str string
|
||||
def interface{}
|
||||
in []string
|
||||
err string
|
||||
}{
|
||||
// Do all four tests for string, the rest can mostly just use
|
||||
// shortTest (the 0 value).
|
||||
{
|
||||
where: loc(),
|
||||
kind: shortTest,
|
||||
val: "42",
|
||||
str: "42",
|
||||
in: []string{"test", "-s", "42"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
kind: longTest,
|
||||
val: "42",
|
||||
str: "42",
|
||||
in: []string{"test", "--long", "42"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
kind: bothTest,
|
||||
val: "42",
|
||||
str: "42",
|
||||
in: []string{"test", "--both", "42"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
kind: bothTest,
|
||||
val: "42",
|
||||
str: "42",
|
||||
in: []string{"test", "-b", "42"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: "42",
|
||||
def: "42",
|
||||
str: "42",
|
||||
in: []string{"test"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: "42",
|
||||
def: "43",
|
||||
str: "42",
|
||||
in: []string{"test", "-s", "42"},
|
||||
},
|
||||
|
||||
{
|
||||
where: loc(),
|
||||
val: true,
|
||||
str: "true",
|
||||
in: []string{"test", "-s"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: true,
|
||||
def: true,
|
||||
str: "true",
|
||||
in: []string{"test"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
kind: longTest,
|
||||
val: false,
|
||||
str: "false",
|
||||
in: []string{"test", "--long=false"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
kind: longTest,
|
||||
val: false,
|
||||
def: true,
|
||||
str: "false",
|
||||
in: []string{"test", "--long=false"},
|
||||
},
|
||||
|
||||
{
|
||||
where: loc(),
|
||||
val: int(42),
|
||||
str: "42",
|
||||
in: []string{"test", "-s", "42"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: int8(42),
|
||||
str: "42",
|
||||
in: []string{"test", "-s", "42"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: int16(42),
|
||||
str: "42",
|
||||
in: []string{"test", "-s", "42"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: int32(42),
|
||||
str: "42",
|
||||
in: []string{"test", "-s", "42"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: int64(42),
|
||||
str: "42",
|
||||
in: []string{"test", "-s", "42"},
|
||||
},
|
||||
|
||||
{
|
||||
where: loc(),
|
||||
val: uint(42),
|
||||
str: "42",
|
||||
in: []string{"test", "-s", "42"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: uint8(42),
|
||||
str: "42",
|
||||
in: []string{"test", "-s", "42"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: uint16(42),
|
||||
str: "42",
|
||||
in: []string{"test", "-s", "42"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: uint32(42),
|
||||
str: "42",
|
||||
in: []string{"test", "-s", "42"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: uint64(42),
|
||||
str: "42",
|
||||
in: []string{"test", "-s", "42"},
|
||||
},
|
||||
|
||||
{
|
||||
where: loc(),
|
||||
val: float32(4.2),
|
||||
str: "4.2",
|
||||
in: []string{"test", "-s", "4.2"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: float64(4.2),
|
||||
str: "4.2",
|
||||
in: []string{"test", "-s", "4.2"},
|
||||
},
|
||||
|
||||
{
|
||||
where: loc(),
|
||||
val: time.Duration(time.Second * 42),
|
||||
def: time.Second * 2,
|
||||
str: "42s",
|
||||
in: []string{"test", "-s", "42s"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: time.Duration(time.Second * 42),
|
||||
def: time.Second * 2,
|
||||
str: "42s",
|
||||
in: []string{"test", "-s42s"},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: time.Duration(time.Second * 2),
|
||||
def: time.Second * 2,
|
||||
in: []string{"test", "-s42"},
|
||||
str: "2s",
|
||||
err: "test: time: missing unit in duration 42",
|
||||
},
|
||||
|
||||
{
|
||||
where: loc(),
|
||||
val: []string{"42", "."},
|
||||
str: "42,.",
|
||||
def: []string{"one", "two", "three"},
|
||||
in: []string{"test", "-s42", "-s."},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: []string{"42", "."},
|
||||
str: "42,.",
|
||||
def: []string{"one", "two", "three"},
|
||||
in: []string{"test", "-s42,."},
|
||||
},
|
||||
{
|
||||
where: loc(),
|
||||
val: []string{"one", "two", "three"},
|
||||
def: []string{"one", "two", "three"},
|
||||
str: "one,two,three",
|
||||
in: []string{"test"},
|
||||
},
|
||||
} {
|
||||
reset()
|
||||
var opt Option
|
||||
val := reflect.New(reflect.TypeOf(tt.val)).Interface()
|
||||
if tt.def != nil {
|
||||
reflect.ValueOf(val).Elem().Set(reflect.ValueOf(tt.def))
|
||||
}
|
||||
switch tt.kind {
|
||||
case shortTest:
|
||||
opt = Flag(val, 's')
|
||||
case longTest:
|
||||
opt = FlagLong(val, "long", 0)
|
||||
case bothTest:
|
||||
opt = FlagLong(val, "both", 'b')
|
||||
}
|
||||
_ = opt
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
continue
|
||||
}
|
||||
got := reflect.ValueOf(val).Elem().Interface()
|
||||
want := reflect.ValueOf(tt.val).Interface()
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if str := opt.String(); str != tt.str {
|
||||
t.Errorf("%s: got string %q, want %q", tt.where, str, tt.str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericDup(t *testing.T) {
|
||||
defer func() {
|
||||
stderr = os.Stderr
|
||||
exit = os.Exit
|
||||
}()
|
||||
|
||||
reset()
|
||||
var v1, v2 string
|
||||
type myPanic struct{}
|
||||
var errbuf bytes.Buffer
|
||||
stderr = &errbuf
|
||||
_, file, line, _ := runtime.Caller(0)
|
||||
Flag(&v1, 's')
|
||||
line++ // line is now the line number of the first call to Flag.
|
||||
|
||||
exit = func(i int) { panic(myPanic{}) }
|
||||
defer func() {
|
||||
p := recover()
|
||||
if _, ok := p.(myPanic); ok {
|
||||
err := errbuf.String()
|
||||
if !strings.Contains(err, "-s already declared") || !strings.Contains(err, fmt.Sprintf("%s:%d", file, line)) {
|
||||
t.Errorf("unexpected error: %q\nshould contain \"-s already declared\" and \"%s:%d\"", err, file, line)
|
||||
}
|
||||
} else if p == nil {
|
||||
t.Errorf("Second call to Flag did not fail")
|
||||
} else {
|
||||
t.Errorf("panic %v", p)
|
||||
}
|
||||
}()
|
||||
Flag(&v2, 's')
|
||||
}
|
||||
|
||||
func TestGenericDupNested(t *testing.T) {
|
||||
defer func() {
|
||||
stderr = os.Stderr
|
||||
exit = os.Exit
|
||||
}()
|
||||
|
||||
reset()
|
||||
type myPanic struct{}
|
||||
var errbuf bytes.Buffer
|
||||
stderr = &errbuf
|
||||
_, file, line, _ := runtime.Caller(0)
|
||||
String('s', "default")
|
||||
line++ // line is now the line number of the first call to Flag.
|
||||
|
||||
exit = func(i int) { panic(myPanic{}) }
|
||||
defer func() {
|
||||
p := recover()
|
||||
if _, ok := p.(myPanic); ok {
|
||||
err := errbuf.String()
|
||||
if !strings.Contains(err, "-s already declared") || !strings.Contains(err, fmt.Sprintf("%s:%d", file, line)) {
|
||||
t.Errorf("unexpected error: %q\nshould contain \"-s already declared\" and \"%s:%d\"", err, file, line)
|
||||
}
|
||||
} else if p == nil {
|
||||
t.Errorf("Second call to Flag did not fail")
|
||||
} else {
|
||||
t.Errorf("panic %v", p)
|
||||
}
|
||||
}()
|
||||
String('s', "default")
|
||||
}
|
@ -0,0 +1,534 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package getopt (v2) provides traditional getopt processing for implementing
|
||||
// commands that use traditional command lines. The standard Go flag package
|
||||
// cannot be used to write a program that parses flags the way ls or ssh does,
|
||||
// for example. Version 2 of this package has a simplified API.
|
||||
//
|
||||
// See the github.com/pborman/options package for a simple structure based
|
||||
// interface to this package.
|
||||
//
|
||||
// USAGE
|
||||
//
|
||||
// Getopt supports functionality found in both the standard BSD getopt as well
|
||||
// as (one of the many versions of) the GNU getopt_long. Being a Go package,
|
||||
// this package makes common usage easy, but still enables more controlled usage
|
||||
// if needed.
|
||||
//
|
||||
// Typical usage:
|
||||
//
|
||||
// // Declare flags and have getopt return pointers to the values.
|
||||
// helpFlag := getopt.Bool('?', "display help")
|
||||
// cmdFlag := getopt.StringLong("command", 'c', "default", "the command)
|
||||
//
|
||||
// // Declare flags against existing variables.
|
||||
// var {
|
||||
// fileName = "/the/default/path"
|
||||
// timeout = time.Second * 5
|
||||
// verbose bool
|
||||
// }
|
||||
// func init() {
|
||||
// getopt.Flag(&verbose, 'v', "be verbose")
|
||||
// getopt.FlagLong(&fileName, "path", 0, "the path")
|
||||
// getopt.FlagLong(&timeout, "timeout", 't', "some timeout")
|
||||
// }
|
||||
//
|
||||
// func main() {
|
||||
// // Parse the program arguments
|
||||
// getopt.Parse()
|
||||
// // Get the remaining positional parameters
|
||||
// args := getopt.Args()
|
||||
// ...
|
||||
//
|
||||
// If you don't want the program to exit on error, use getopt.Getopt:
|
||||
//
|
||||
// err := getopt.Getopt(nil)
|
||||
// if err != nil {
|
||||
// // code to handle error
|
||||
// fmt.Fprintln(os.Stderr, err)
|
||||
// }
|
||||
//
|
||||
// FLAG SYNTAX
|
||||
//
|
||||
// Support is provided for both short (-f) and long (--flag) options. A single
|
||||
// option may have both a short and a long name. Each option may be a flag or a
|
||||
// value. A value takes an argument.
|
||||
//
|
||||
// Declaring no long names causes this package to process arguments like the
|
||||
// traditional BSD getopt.
|
||||
//
|
||||
// Short flags may be combined into a single parameter. For example, "-a -b -c"
|
||||
// may also be expressed "-abc". Long flags must stand on their own "--alpha
|
||||
// --beta"
|
||||
//
|
||||
// Values require an argument. For short options the argument may either be
|
||||
// immediately following the short name or as the next argument. Only one short
|
||||
// value may be combined with short flags in a single argument; the short value
|
||||
// must be after all short flags. For example, if f is a flag and v is a value,
|
||||
// then:
|
||||
//
|
||||
// -vvalue (sets v to "value")
|
||||
// -v value (sets v to "value")
|
||||
// -fvvalue (sets f, and sets v to "value")
|
||||
// -fv value (sets f, and sets v to "value")
|
||||
// -vf value (set v to "f" and value is the first parameter)
|
||||
//
|
||||
// For the long value option val:
|
||||
//
|
||||
// --val value (sets val to "value")
|
||||
// --val=value (sets val to "value")
|
||||
// --valvalue (invalid option "valvalue")
|
||||
//
|
||||
// Values with an optional value only set the value if the value is part of the
|
||||
// same argument. In any event, the option count is increased and the option is
|
||||
// marked as seen.
|
||||
//
|
||||
// -v -f (sets v and f as being seen)
|
||||
// -vvalue -f (sets v to "value" and sets f)
|
||||
// --val -f (sets v and f as being seen)
|
||||
// --val=value -f (sets v to "value" and sets f)
|
||||
//
|
||||
// There is no convenience function defined for making the value optional. The
|
||||
// SetOptional method must be called on the actual Option.
|
||||
//
|
||||
// v := String("val", 'v', "", "the optional v")
|
||||
// Lookup("v").SetOptional()
|
||||
//
|
||||
// var s string
|
||||
// FlagLong(&s, "val", 'v', "the optional v).SetOptional()
|
||||
//
|
||||
// Parsing continues until the first non-option or "--" is encountered.
|
||||
//
|
||||
// The short name "-" can be used, but it either is specified as "-" or as part
|
||||
// of a group of options, for example "-f-". If there are no long options
|
||||
// specified then "--f" could also be used. If "-" is not declared as an option
|
||||
// then the single "-" will also terminate the option processing but unlike
|
||||
// "--", the "-" will be part of the remaining arguments.
|
||||
//
|
||||
// ADVANCED USAGE
|
||||
//
|
||||
// Normally the parsing is performed by calling the Parse function. If it is
|
||||
// important to see the order of the options then the Getopt function should be
|
||||
// used. The standard Parse function does the equivalent of:
|
||||
//
|
||||
// func Parse() {
|
||||
// if err := getopt.Getopt(os.Args, nil); err != nil {
|
||||
// fmt.Fprintln(os.Stderr, err)
|
||||
// s.usage()
|
||||
// os.Exit(1)
|
||||
// }
|
||||
//
|
||||
// When calling Getopt it is the responsibility of the caller to print any
|
||||
// errors.
|
||||
//
|
||||
// Normally the default option set, CommandLine, is used. Other option sets may
|
||||
// be created with New.
|
||||
//
|
||||
// After parsing, the sets Args will contain the non-option arguments. If an
|
||||
// error is encountered then Args will begin with argument that caused the
|
||||
// error.
|
||||
//
|
||||
// It is valid to call a set's Parse a second time to amen flags or values. As
|
||||
// an example:
|
||||
//
|
||||
// var a = getopt.Bool('a', "", "The a flag")
|
||||
// var b = getopt.Bool('b', "", "The a flag")
|
||||
// var cmd = ""
|
||||
//
|
||||
// var opts = getopt.CommandLine
|
||||
//
|
||||
// opts.Parse(os.Args)
|
||||
// if opts.NArgs() > 0 {
|
||||
// cmd = opts.Arg(0)
|
||||
// opts.Parse(opts.Args())
|
||||
// }
|
||||
//
|
||||
// If called with set to { "prog", "-a", "cmd", "-b", "arg" } then both and and
|
||||
// b would be set, cmd would be set to "cmd", and opts.Args() would return {
|
||||
// "arg" }.
|
||||
//
|
||||
// Unless an option type explicitly prohibits it, an option may appear more than
|
||||
// once in the arguments. The last value provided to the option is the value.
|
||||
//
|
||||
// BUILTIN TYPES
|
||||
//
|
||||
// The Flag and FlagLong functions support most standard Go types. For the
|
||||
// list, see the description of FlagLong below for a list of supported types.
|
||||
//
|
||||
// There are also helper routines to allow single line flag declarations. These
|
||||
// types are: Bool, Counter, Duration, Enum, Int16, Int32, Int64, Int, List,
|
||||
// Signed, String, Uint16, Uint32, Uint64, Uint, and Unsigned.
|
||||
//
|
||||
// Each comes in a short and long flavor, e.g., Bool and BoolLong and include
|
||||
// functions to set the flags on the standard command line or for a specific Set
|
||||
// of flags.
|
||||
//
|
||||
// Except for the Counter, Enum, Signed and Unsigned types, all of these types
|
||||
// can be declared using Flag and FlagLong by passing in a pointer to the
|
||||
// appropriate type.
|
||||
//
|
||||
// DECLARING NEW FLAG TYPES
|
||||
//
|
||||
// A pointer to any type that implements the Value interface may be passed to
|
||||
// Flag or FlagLong.
|
||||
//
|
||||
// VALUEHELP
|
||||
//
|
||||
// All non-flag options are created with a "valuehelp" as the last parameter.
|
||||
// Valuehelp should be 0, 1, or 2 strings. The first string, if provided, is
|
||||
// the usage message for the option. If the second string, if provided, is the
|
||||
// name to use for the value when displaying the usage. If not provided the
|
||||
// term "value" is assumed.
|
||||
//
|
||||
// The usage message for the option created with
|
||||
//
|
||||
// StringLong("option", 'o', "defval", "a string of letters")
|
||||
//
|
||||
// is
|
||||
//
|
||||
// -o, -option=value
|
||||
//
|
||||
// StringLong("option", 'o', "defval", "a string of letters", "string")
|
||||
//
|
||||
// is
|
||||
//
|
||||
// -o, -option=string
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// stderr allows tests to capture output to standard error.
|
||||
var stderr io.Writer = os.Stderr
|
||||
|
||||
// exit allows tests to capture an os.Exit call
|
||||
var exit = os.Exit
|
||||
|
||||
// DisplayWidth is used to determine where to split usage long lines.
|
||||
var DisplayWidth = 80
|
||||
|
||||
// HelpColumn is the maximum column position that help strings start to display
|
||||
// at. If the option usage is too long then the help string will be displayed
|
||||
// on the next line. For example:
|
||||
//
|
||||
// -a this is the a flag
|
||||
// -u, --under=location
|
||||
// the u flag's usage is quite long
|
||||
var HelpColumn = 20
|
||||
|
||||
// PrintUsage prints the usage line and set of options of set S to w.
|
||||
func (s *Set) PrintUsage(w io.Writer) {
|
||||
parts := make([]string, 2, 4)
|
||||
parts[0] = "Usage:"
|
||||
parts[1] = s.program
|
||||
if usage := s.UsageLine(); usage != "" {
|
||||
parts = append(parts, usage)
|
||||
}
|
||||
if s.parameters != "" {
|
||||
parts = append(parts, s.parameters)
|
||||
}
|
||||
fmt.Fprintln(w, strings.Join(parts, " "))
|
||||
s.PrintOptions(w)
|
||||
}
|
||||
|
||||
// UsageLine returns the usage line for the set s. The set's program name and
|
||||
// parameters, if any, are not included.
|
||||
func (s *Set) UsageLine() string {
|
||||
sort.Sort(s.options)
|
||||
flags := ""
|
||||
|
||||
// Build up the list of short flag names and also compute
|
||||
// how to display the option in the longer help listing.
|
||||
// We also keep track of the longest option usage string
|
||||
// that is no more than HelpColumn-3 bytes (at which point
|
||||
// we use two lines to display the help). The three
|
||||
// is for the leading space and the two spaces before the
|
||||
// help string.
|
||||
for _, opt := range s.options {
|
||||
if opt.name == "" {
|
||||
opt.name = "value"
|
||||
}
|
||||
if opt.uname == "" {
|
||||
opt.uname = opt.usageName()
|
||||
}
|
||||
if opt.flag && opt.short != 0 && opt.short != '-' {
|
||||
flags += string(opt.short)
|
||||
}
|
||||
}
|
||||
|
||||
var opts []string
|
||||
|
||||
// The short option - is special
|
||||
if s.shortOptions['-'] != nil {
|
||||
opts = append(opts, "-")
|
||||
}
|
||||
|
||||
// If we have a bundle of flags, add them to the list
|
||||
if flags != "" {
|
||||
opts = append(opts, "-"+flags)
|
||||
}
|
||||
|
||||
// Now append all the long options and options that require
|
||||
// values.
|
||||
for _, opt := range s.options {
|
||||
if opt.flag {
|
||||
if opt.short != 0 {
|
||||
continue
|
||||
}
|
||||
flags = "--" + opt.long
|
||||
} else if opt.short != 0 {
|
||||
flags = "-" + string(opt.short) + " " + opt.name
|
||||
} else {
|
||||
flags = "--" + string(opt.long) + " " + opt.name
|
||||
}
|
||||
opts = append(opts, flags)
|
||||
}
|
||||
flags = strings.Join(opts, "] [")
|
||||
if flags != "" {
|
||||
flags = "[" + flags + "]"
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
||||
// PrintOptions prints the list of options in s to w.
|
||||
func (s *Set) PrintOptions(w io.Writer) {
|
||||
sort.Sort(s.options)
|
||||
max := 4
|
||||
for _, opt := range s.options {
|
||||
if opt.name == "" {
|
||||
opt.name = "value"
|
||||
}
|
||||
if opt.uname == "" {
|
||||
opt.uname = opt.usageName()
|
||||
}
|
||||
if max < len(opt.uname) && len(opt.uname) <= HelpColumn-3 {
|
||||
max = len(opt.uname)
|
||||
}
|
||||
}
|
||||
// Now print one or more usage lines per option.
|
||||
for _, opt := range s.options {
|
||||
if opt.uname != "" {
|
||||
opt.help = strings.TrimSpace(opt.help)
|
||||
if len(opt.help) == 0 {
|
||||
fmt.Fprintf(w, " %s\n", opt.uname)
|
||||
continue
|
||||
}
|
||||
help := strings.Split(opt.help, "\n")
|
||||
// If they did not put in newlines then we will insert
|
||||
// them to keep the help messages from wrapping.
|
||||
if len(help) == 1 {
|
||||
help = breakup(help[0], DisplayWidth-HelpColumn)
|
||||
}
|
||||
if len(opt.uname) <= max {
|
||||
fmt.Fprintf(w, " %-*s %s\n", max, opt.uname, help[0])
|
||||
help = help[1:]
|
||||
} else {
|
||||
fmt.Fprintf(w, " %s\n", opt.uname)
|
||||
}
|
||||
for _, s := range help {
|
||||
fmt.Fprintf(w, " %-*s %s\n", max, " ", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// breakup breaks s up into strings no longer than max bytes.
|
||||
func breakup(s string, max int) []string {
|
||||
var a []string
|
||||
|
||||
for {
|
||||
// strip leading spaces
|
||||
for len(s) > 0 && s[0] == ' ' {
|
||||
s = s[1:]
|
||||
}
|
||||
// If the option is no longer than the max just return it
|
||||
if len(s) <= max {
|
||||
if len(s) != 0 {
|
||||
a = append(a, s)
|
||||
}
|
||||
return a
|
||||
}
|
||||
x := max
|
||||
for s[x] != ' ' {
|
||||
// the first word is too long?!
|
||||
if x == 0 {
|
||||
x = max
|
||||
for x < len(s) && s[x] != ' ' {
|
||||
x++
|
||||
}
|
||||
if x == len(s) {
|
||||
x--
|
||||
}
|
||||
break
|
||||
}
|
||||
x--
|
||||
}
|
||||
for s[x] == ' ' {
|
||||
x--
|
||||
}
|
||||
a = append(a, s[:x+1])
|
||||
s = s[x+1:]
|
||||
}
|
||||
}
|
||||
|
||||
// Parse uses Getopt to parse args using the options set for s. The first
|
||||
// element of args is used to assign the program for s if it is not yet set. On
|
||||
// error, Parse displays the error message as well as a usage message on
|
||||
// standard error and then exits the program.
|
||||
func (s *Set) Parse(args []string) {
|
||||
if err := s.Getopt(args, nil); err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
s.usage()
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse uses Getopt to parse args using the options set for s. The first
|
||||
// element of args is used to assign the program for s if it is not yet set.
|
||||
// Getop calls fn, if not nil, for each option parsed.
|
||||
//
|
||||
// Getopt returns nil when all options have been processed (a non-option
|
||||
// argument was encountered, "--" was encountered, or fn returned false).
|
||||
//
|
||||
// On error getopt returns a reference to an InvalidOption (which implements the
|
||||
// error interface).
|
||||
func (s *Set) Getopt(args []string, fn func(Option) bool) (err error) {
|
||||
s.setState(InProgress)
|
||||
defer func() {
|
||||
if s.State() == InProgress {
|
||||
switch {
|
||||
case err != nil:
|
||||
s.setState(Failure)
|
||||
case len(s.args) == 0:
|
||||
s.setState(EndOfArguments)
|
||||
default:
|
||||
s.setState(Unknown)
|
||||
}
|
||||
}
|
||||
}()
|
||||
if fn == nil {
|
||||
fn = func(Option) bool { return true }
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if s.program == "" {
|
||||
s.program = path.Base(args[0])
|
||||
}
|
||||
args = args[1:]
|
||||
Parsing:
|
||||
for len(args) > 0 {
|
||||
arg := args[0]
|
||||
s.args = args
|
||||
args = args[1:]
|
||||
|
||||
// end of options?
|
||||
if arg == "" || arg[0] != '-' {
|
||||
s.setState(EndOfOptions)
|
||||
return nil
|
||||
}
|
||||
|
||||
if arg == "-" {
|
||||
goto ShortParsing
|
||||
}
|
||||
|
||||
// explicitly request end of options?
|
||||
if arg == "--" {
|
||||
s.args = args
|
||||
s.setState(DashDash)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Long option processing
|
||||
if len(s.longOptions) > 0 && arg[1] == '-' {
|
||||
e := strings.IndexRune(arg, '=')
|
||||
var value string
|
||||
if e > 0 {
|
||||
value = arg[e+1:]
|
||||
arg = arg[:e]
|
||||
}
|
||||
opt := s.longOptions[arg[2:]]
|
||||
// If we are processing long options then --f is -f
|
||||
// if f is not defined as a long option.
|
||||
// This lets you say --f=false
|
||||
if opt == nil && len(arg[2:]) == 1 {
|
||||
opt = s.shortOptions[rune(arg[2])]
|
||||
}
|
||||
if opt == nil {
|
||||
return unknownOption(arg[2:])
|
||||
}
|
||||
opt.isLong = true
|
||||
// If we require an option and did not have an =
|
||||
// then use the next argument as an option.
|
||||
if !opt.flag && e < 0 && !opt.optional {
|
||||
if len(args) == 0 {
|
||||
return missingArg(opt)
|
||||
}
|
||||
value = args[0]
|
||||
args = args[1:]
|
||||
}
|
||||
opt.count++
|
||||
|
||||
if err := opt.value.Set(value, opt); err != nil {
|
||||
return setError(opt, value, err)
|
||||
}
|
||||
|
||||
if !fn(opt) {
|
||||
s.setState(Terminated)
|
||||
return nil
|
||||
}
|
||||
continue Parsing
|
||||
}
|
||||
|
||||
// Short option processing
|
||||
arg = arg[1:] // strip -
|
||||
ShortParsing:
|
||||
for i, c := range arg {
|
||||
opt := s.shortOptions[c]
|
||||
if opt == nil {
|
||||
// In traditional getopt, if - is not registered
|
||||
// as an option, a lone - is treated as
|
||||
// if there were a -- in front of it.
|
||||
if arg == "-" {
|
||||
s.setState(Dash)
|
||||
return nil
|
||||
}
|
||||
return unknownOption(c)
|
||||
}
|
||||
opt.isLong = false
|
||||
opt.count++
|
||||
var value string
|
||||
if !opt.flag {
|
||||
value = arg[1+i:]
|
||||
if value == "" && !opt.optional {
|
||||
if len(args) == 0 {
|
||||
return missingArg(opt)
|
||||
}
|
||||
value = args[0]
|
||||
args = args[1:]
|
||||
}
|
||||
}
|
||||
if err := opt.value.Set(value, opt); err != nil {
|
||||
return setError(opt, value, err)
|
||||
}
|
||||
if !fn(opt) {
|
||||
s.setState(Terminated)
|
||||
return nil
|
||||
}
|
||||
if !opt.flag {
|
||||
continue Parsing
|
||||
}
|
||||
}
|
||||
}
|
||||
s.args = []string{}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
// Int creates an option that parses its value as an integer.
|
||||
func Int(name rune, value int, helpvalue ...string) *int {
|
||||
return CommandLine.Int(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int(name rune, value int, helpvalue ...string) *int {
|
||||
s.Flag(&value, name, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func IntLong(name string, short rune, value int, helpvalue ...string) *int {
|
||||
return CommandLine.IntLong(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) IntLong(name string, short rune, value int, helpvalue ...string) *int {
|
||||
s.FlagLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
// Int16 creates an option that parses its value as a 16 bit integer.
|
||||
func Int16(name rune, value int16, helpvalue ...string) *int16 {
|
||||
return CommandLine.Int16(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int16(name rune, value int16, helpvalue ...string) *int16 {
|
||||
s.Flag(&value, name, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func Int16Long(name string, short rune, value int16, helpvalue ...string) *int16 {
|
||||
return CommandLine.Int16Long(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int16Long(name string, short rune, value int16, helpvalue ...string) *int16 {
|
||||
s.FlagLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
// Int32 creates an option that parses its value as a 32 bit integer.
|
||||
func Int32(name rune, value int32, helpvalue ...string) *int32 {
|
||||
return CommandLine.Int32(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int32(name rune, value int32, helpvalue ...string) *int32 {
|
||||
s.Flag(&value, name, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func Int32Long(name string, short rune, value int32, helpvalue ...string) *int32 {
|
||||
return CommandLine.Int32Long(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int32Long(name string, short rune, value int32, helpvalue ...string) *int32 {
|
||||
s.FlagLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
// Int64 creates an option that parses its value as a 64 bit integer.
|
||||
func Int64(name rune, value int64, helpvalue ...string) *int64 {
|
||||
return CommandLine.Int64(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int64(name rune, value int64, helpvalue ...string) *int64 {
|
||||
s.Flag(&value, name, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func Int64Long(name string, short rune, value int64, helpvalue ...string) *int64 {
|
||||
return CommandLine.Int64Long(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Int64Long(name string, short rune, value int64, helpvalue ...string) *int64 {
|
||||
s.FlagLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
// Uint creates an option that parses its value as an unsigned integer.
|
||||
func Uint(name rune, value uint, helpvalue ...string) *uint {
|
||||
return CommandLine.Uint(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint(name rune, value uint, helpvalue ...string) *uint {
|
||||
s.Flag(&value, name, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func UintLong(name string, short rune, value uint, helpvalue ...string) *uint {
|
||||
return CommandLine.UintLong(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) UintLong(name string, short rune, value uint, helpvalue ...string) *uint {
|
||||
s.FlagLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
// Uint16 creates an option that parses its value as a 16 bit unsigned integer.
|
||||
func Uint16(name rune, value uint16, helpvalue ...string) *uint16 {
|
||||
return CommandLine.Uint16(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint16(name rune, value uint16, helpvalue ...string) *uint16 {
|
||||
s.Flag(&value, name, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func Uint16Long(name string, short rune, value uint16, helpvalue ...string) *uint16 {
|
||||
return CommandLine.Uint16Long(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint16Long(name string, short rune, value uint16, helpvalue ...string) *uint16 {
|
||||
s.FlagLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
// Uint32 creates an option that parses its value as a 32 bit unsigned integer.
|
||||
func Uint32(name rune, value uint32, helpvalue ...string) *uint32 {
|
||||
return CommandLine.Uint32(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint32(name rune, value uint32, helpvalue ...string) *uint32 {
|
||||
s.Flag(&value, name, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func Uint32Long(name string, short rune, value uint32, helpvalue ...string) *uint32 {
|
||||
return CommandLine.Uint32Long(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint32Long(name string, short rune, value uint32, helpvalue ...string) *uint32 {
|
||||
s.FlagLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
// Uint64 creates an option that parses its value as a 64 bit unsigned integer.
|
||||
func Uint64(name rune, value uint64, helpvalue ...string) *uint64 {
|
||||
return CommandLine.Uint64(name, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint64(name rune, value uint64, helpvalue ...string) *uint64 {
|
||||
s.Flag(&value, name, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func Uint64Long(name string, short rune, value uint64, helpvalue ...string) *uint64 {
|
||||
return CommandLine.Uint64Long(name, short, value, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Uint64Long(name string, short rune, value uint64, helpvalue ...string) *uint64 {
|
||||
s.FlagLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
@ -0,0 +1,595 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var intTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i int
|
||||
int int
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--int", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--int=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestInt(t *testing.T) {
|
||||
for x, tt := range intTests {
|
||||
reset()
|
||||
i := Int('i', 17)
|
||||
opt := IntLong("int", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.int; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var int16Tests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i int16
|
||||
int16 int16
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--int16", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--int16=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestInt16(t *testing.T) {
|
||||
for x, tt := range int16Tests {
|
||||
reset()
|
||||
i := Int16('i', 17)
|
||||
opt := Int16Long("int16", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.int16; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var int32Tests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i int32
|
||||
int32 int32
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--int32", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--int32=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestInt32(t *testing.T) {
|
||||
for x, tt := range int32Tests {
|
||||
reset()
|
||||
i := Int32('i', 17)
|
||||
opt := Int32Long("int32", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.int32; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var int64Tests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i int64
|
||||
int64 int64
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--int64", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--int64=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestInt64(t *testing.T) {
|
||||
for x, tt := range int64Tests {
|
||||
reset()
|
||||
i := Int64('i', 17)
|
||||
opt := Int64Long("int64", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.int64; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var uintTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i uint
|
||||
uint uint
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--uint", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--uint=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestUint(t *testing.T) {
|
||||
for x, tt := range uintTests {
|
||||
reset()
|
||||
i := Uint('i', 17)
|
||||
opt := UintLong("uint", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.uint; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var uint16Tests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i uint16
|
||||
uint16 uint16
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--uint16", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--uint16=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestUint16(t *testing.T) {
|
||||
for x, tt := range uint16Tests {
|
||||
reset()
|
||||
i := Uint16('i', 17)
|
||||
opt := Uint16Long("uint16", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.uint16; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var uint32Tests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i uint32
|
||||
uint32 uint32
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--uint32", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--uint32=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestUint32(t *testing.T) {
|
||||
for x, tt := range uint32Tests {
|
||||
reset()
|
||||
i := Uint32('i', 17)
|
||||
opt := Uint32Long("uint32", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.uint32; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var uint64Tests = []struct {
|
||||
where string
|
||||
in []string
|
||||
i uint64
|
||||
uint64 uint64
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
17, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i", "1", "--uint64", "2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "--uint64=2"},
|
||||
1, 2,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i1", "-i2"},
|
||||
2, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i=1"},
|
||||
17, 42,
|
||||
"test: not a valid number: =1\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i0x20"},
|
||||
0x20, 42,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-i010"},
|
||||
8, 42,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestUint64(t *testing.T) {
|
||||
for x, tt := range uint64Tests {
|
||||
reset()
|
||||
i := Uint64('i', 17)
|
||||
opt := Uint64Long("uint64", 0, 42)
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if got, want := *i, tt.i; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
if got, want := *opt, tt.uint64; got != want {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
// List creates an option that returns a slice of strings. The parameters
|
||||
// passed are converted from a comma separated value list into a slice.
|
||||
// Subsequent occurrences append to the list.
|
||||
func List(name rune, helpvalue ...string) *[]string {
|
||||
p := []string{}
|
||||
CommandLine.Flag(&p, name, helpvalue...)
|
||||
return &p
|
||||
}
|
||||
|
||||
func (s *Set) List(name rune, helpvalue ...string) *[]string {
|
||||
p := []string{}
|
||||
s.Flag(&p, name, helpvalue...)
|
||||
return &p
|
||||
}
|
||||
|
||||
func ListLong(name string, short rune, helpvalue ...string) *[]string {
|
||||
p := []string{}
|
||||
CommandLine.FlagLong(&p, name, short, helpvalue...)
|
||||
return &p
|
||||
}
|
||||
|
||||
func (s *Set) ListLong(name string, short rune, helpvalue ...string) *[]string {
|
||||
p := []string{}
|
||||
s.FlagLong(&p, name, short, helpvalue...)
|
||||
return &p
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import "testing"
|
||||
|
||||
var listTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
l, list []string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
nil, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-l", "one"},
|
||||
[]string{"one"}, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-lone", "-ltwo"},
|
||||
[]string{"one", "two"}, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--list", "one"},
|
||||
nil, []string{"one"},
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--list=one", "--list=two"},
|
||||
nil, []string{"one", "two"},
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--list=one,two"},
|
||||
nil, []string{"one", "two"},
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
for _, tt := range listTests {
|
||||
reset()
|
||||
l := List('l')
|
||||
list := ListLong("list", 0)
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if badSlice(*l, tt.l) {
|
||||
t.Errorf("%s: got s = %q, want %q", tt.where, *l, tt.l)
|
||||
}
|
||||
if badSlice(*list, tt.list) {
|
||||
t.Errorf("%s: got s = %q, want %q", tt.where, *list, tt.list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultList(t *testing.T) {
|
||||
reset()
|
||||
list := []string{"d1", "d2", "d3"}
|
||||
Flag(&list, 'l')
|
||||
parse([]string{"test"})
|
||||
|
||||
want := []string{"d1", "d2", "d3"}
|
||||
if badSlice(list, want) {
|
||||
t.Errorf("got s = %q, want %q", list, want)
|
||||
}
|
||||
|
||||
parse([]string{"test", "-l", "one"})
|
||||
want = []string{"one"}
|
||||
if badSlice(list, want) {
|
||||
t.Errorf("got s = %q, want %q", list, want)
|
||||
}
|
||||
|
||||
parse([]string{"test", "-l", "two"})
|
||||
want = []string{"one", "two"}
|
||||
if badSlice(list, want) {
|
||||
t.Errorf("got s = %q, want %q", list, want)
|
||||
}
|
||||
Lookup('l').Reset()
|
||||
want = []string{"d1", "d2", "d3"}
|
||||
if badSlice(list, want) {
|
||||
t.Errorf("got s = %q, want %q", list, want)
|
||||
}
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// An Option can be either a Flag or a Value
|
||||
type Option interface {
|
||||
// Name returns the name of the option. If the option has been seen
|
||||
// then the last way it was referenced (short or long) is returned
|
||||
// otherwise if there is a short name then this will be the short name
|
||||
// as a string, else it will be the long name.
|
||||
Name() string
|
||||
|
||||
// ShortName always returns the short name of the option, or "" if there
|
||||
// is no short name. The name does not include the "-".
|
||||
ShortName() string
|
||||
|
||||
// LongName always returns the long name of the option, or "" if there
|
||||
// is no long name. The name does not include the "--".
|
||||
LongName() string
|
||||
|
||||
// IsFlag returns true if Option is a flag.
|
||||
IsFlag() bool
|
||||
|
||||
// Seen returns true if the flag was seen.
|
||||
Seen() bool
|
||||
|
||||
// Count returns the number of times the flag was seen.
|
||||
Count() int
|
||||
|
||||
// String returns the last value the option was set to.
|
||||
String() string
|
||||
|
||||
// Value returns the Value of the option.
|
||||
Value() Value
|
||||
|
||||
// SetOptional makes the value optional. The option and value are
|
||||
// always a single argument. Either --option or --option=value. In
|
||||
// the former case the value of the option does not change but the Set()
|
||||
// will return true and the value returned by Count() is incremented.
|
||||
// The short form is either -o or -ovalue. SetOptional returns
|
||||
// the Option
|
||||
SetOptional() Option
|
||||
|
||||
// SetFlag makes the value a flag. Flags are boolean values and
|
||||
// normally do not taken a value. They are set to true when seen.
|
||||
// If a value is passed in the long form then it must be on, case
|
||||
// insensitivinsensitive, one of "true", "false", "t", "f", "on", "off", "1", "0".
|
||||
// SetFlag returns the Option
|
||||
SetFlag() Option
|
||||
|
||||
// Reset resets the state of the option so it appears it has not
|
||||
// yet been seen, including resetting the value of the option
|
||||
// to its original default state.
|
||||
Reset()
|
||||
}
|
||||
|
||||
type option struct {
|
||||
short rune // 0 means no short name
|
||||
long string // "" means no long name
|
||||
isLong bool // True if they used the long name
|
||||
flag bool // true if a boolean flag
|
||||
defval string // default value
|
||||
optional bool // true if we take an optional value
|
||||
help string // help message
|
||||
where string // file where the option was defined
|
||||
value Value // current value of option
|
||||
count int // number of times we have seen this option
|
||||
name string // name of the value (for usage)
|
||||
uname string // name of the option (for usage)
|
||||
}
|
||||
|
||||
// usageName returns the name of the option for printing usage lines in one
|
||||
// of the following forms:
|
||||
//
|
||||
// -f
|
||||
// --flag
|
||||
// -f, --flag
|
||||
// -s value
|
||||
// --set=value
|
||||
// -s, --set=value
|
||||
func (o *option) usageName() string {
|
||||
// Don't print help messages if we have none and there is only one
|
||||
// way to specify the option.
|
||||
if o.help == "" && (o.short == 0 || o.long == "") {
|
||||
return ""
|
||||
}
|
||||
n := ""
|
||||
|
||||
switch {
|
||||
case o.short != 0 && o.long == "":
|
||||
n = "-" + string(o.short)
|
||||
case o.short == 0 && o.long != "":
|
||||
n = " --" + o.long
|
||||
case o.short != 0 && o.long != "":
|
||||
n = "-" + string(o.short) + ", --" + o.long
|
||||
}
|
||||
|
||||
switch {
|
||||
case o.flag:
|
||||
return n
|
||||
case o.optional:
|
||||
return n + "[=" + o.name + "]"
|
||||
case o.long != "":
|
||||
return n + "=" + o.name
|
||||
}
|
||||
return n + " " + o.name
|
||||
}
|
||||
|
||||
// sortName returns the name to sort the option on.
|
||||
func (o *option) sortName() string {
|
||||
if o.short != 0 {
|
||||
return string(o.short) + o.long
|
||||
}
|
||||
return o.long[:1] + o.long
|
||||
}
|
||||
|
||||
func (o *option) Seen() bool { return o.count > 0 }
|
||||
func (o *option) Count() int { return o.count }
|
||||
func (o *option) IsFlag() bool { return o.flag }
|
||||
func (o *option) String() string { return o.value.String() }
|
||||
func (o *option) SetOptional() Option { o.optional = true; return o }
|
||||
func (o *option) SetFlag() Option { o.flag = true; return o }
|
||||
|
||||
func (o *option) Value() Value {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.value
|
||||
}
|
||||
|
||||
func (o *option) Name() string {
|
||||
if !o.isLong && o.short != 0 {
|
||||
return "-" + string(o.short)
|
||||
}
|
||||
return "--" + o.long
|
||||
}
|
||||
|
||||
func (o *option) ShortName() string {
|
||||
if o.short != 0 {
|
||||
return string(o.short)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (o *option) LongName() string {
|
||||
return o.long
|
||||
}
|
||||
|
||||
// Reset rests an option so that it appears it has not yet been seen.
|
||||
func (o *option) Reset() {
|
||||
o.isLong = false
|
||||
o.count = 0
|
||||
o.value.Set(o.defval, o)
|
||||
}
|
||||
|
||||
type optionList []*option
|
||||
|
||||
func (ol optionList) Len() int { return len(ol) }
|
||||
func (ol optionList) Swap(i, j int) { ol[i], ol[j] = ol[j], ol[i] }
|
||||
func (ol optionList) Less(i, j int) bool {
|
||||
// first check the short names (or the first letter of the long name)
|
||||
// If they are not equal (case insensitive) then we have our answer
|
||||
n1 := ol[i].sortName()
|
||||
n2 := ol[j].sortName()
|
||||
l1 := strings.ToLower(n1)
|
||||
l2 := strings.ToLower(n2)
|
||||
if l1 < l2 {
|
||||
return true
|
||||
}
|
||||
if l2 < l1 {
|
||||
return false
|
||||
}
|
||||
return n1 < n2
|
||||
}
|
||||
|
||||
// AddOption add the option o to set CommandLine if o is not already in set
|
||||
// CommandLine.
|
||||
func AddOption(o Option) {
|
||||
CommandLine.AddOption(o)
|
||||
}
|
||||
|
||||
// AddOption add the option o to set s if o is not already in set s.
|
||||
func (s *Set) AddOption(o Option) {
|
||||
opt := o.(*option)
|
||||
for _, eopt := range s.options {
|
||||
if opt == eopt {
|
||||
return
|
||||
}
|
||||
}
|
||||
if opt.short != 0 {
|
||||
if oo, ok := s.shortOptions[opt.short]; ok {
|
||||
fmt.Fprintf(stderr, "%s: -%c already declared at %s\n", opt.where, opt.short, oo.where)
|
||||
exit(1)
|
||||
}
|
||||
s.shortOptions[opt.short] = opt
|
||||
}
|
||||
if opt.long != "" {
|
||||
if oo, ok := s.longOptions[opt.long]; ok {
|
||||
fmt.Fprintf(stderr, "%s: --%s already declared at %s\n", opt.where, opt.long, oo.where)
|
||||
exit(1)
|
||||
}
|
||||
s.longOptions[opt.long] = opt
|
||||
}
|
||||
s.options = append(s.options, opt)
|
||||
}
|
@ -0,0 +1,291 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A State is why the Getopt returned.
|
||||
type State int
|
||||
|
||||
const (
|
||||
InProgress = State(iota) // Getopt is still running
|
||||
Dash // Returned on "-"
|
||||
DashDash // Returned on "--"
|
||||
EndOfOptions // End of options reached
|
||||
EndOfArguments // No more arguments
|
||||
Terminated // Terminated by callback function
|
||||
Failure // Terminated due to error
|
||||
Unknown // Indicates internal error
|
||||
)
|
||||
|
||||
type Set struct {
|
||||
stateMu sync.Mutex
|
||||
state State
|
||||
|
||||
// args are the parameters remaining after parsing the optoins.
|
||||
args []string
|
||||
|
||||
// program is the name of the program for usage and error messages.
|
||||
// If not set it will automatically be set to the base name of the
|
||||
// first argument passed to parse.
|
||||
program string
|
||||
|
||||
// parameters is what is displayed on the usage line after displaying
|
||||
// the various options.
|
||||
parameters string
|
||||
|
||||
usage func() // usage should print the programs usage and exit.
|
||||
|
||||
shortOptions map[rune]*option
|
||||
longOptions map[string]*option
|
||||
options optionList
|
||||
}
|
||||
|
||||
// New returns a newly created option set.
|
||||
func New() *Set {
|
||||
s := &Set{
|
||||
shortOptions: make(map[rune]*option),
|
||||
longOptions: make(map[string]*option),
|
||||
parameters: "[parameters ...]",
|
||||
}
|
||||
|
||||
s.usage = func() {
|
||||
s.PrintUsage(stderr)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Set) setState(state State) {
|
||||
s.stateMu.Lock()
|
||||
s.state = state
|
||||
s.stateMu.Unlock()
|
||||
}
|
||||
|
||||
// State returns the current state of the Set s. The state is normally the
|
||||
// reason the most recent call to Getopt returned.
|
||||
func (s *Set) State() State {
|
||||
s.stateMu.Lock()
|
||||
defer s.stateMu.Unlock()
|
||||
return s.state
|
||||
}
|
||||
|
||||
// The default set of command-line options.
|
||||
var CommandLine = New()
|
||||
|
||||
// PrintUsage calls PrintUsage in the default option set.
|
||||
func PrintUsage(w io.Writer) { CommandLine.PrintUsage(w) }
|
||||
|
||||
// Usage calls the usage function in the default option set.
|
||||
func Usage() { CommandLine.usage() }
|
||||
|
||||
// Parse calls Parse in the default option set with the command line arguments
|
||||
// found in os.Args.
|
||||
func Parse() { CommandLine.Parse(os.Args) }
|
||||
|
||||
// Getops returns the result of calling Getop in the default option set with the
|
||||
// command line arguments found in os.Args. The fn function, which may be nil,
|
||||
// is passed to Getopt.
|
||||
func Getopt(fn func(Option) bool) error { return CommandLine.Getopt(os.Args, fn) }
|
||||
|
||||
// Arg returns the n'th command-line argument. Arg(0) is the first remaining
|
||||
// argument after options have been processed.
|
||||
func Arg(n int) string {
|
||||
if n >= 0 && n < len(CommandLine.args) {
|
||||
return CommandLine.args[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Arg returns the n'th argument. Arg(0) is the first remaining
|
||||
// argument after options have been processed.
|
||||
func (s *Set) Arg(n int) string {
|
||||
if n >= 0 && n < len(s.args) {
|
||||
return s.args[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Args returns the non-option command line arguments.
|
||||
func Args() []string {
|
||||
return CommandLine.args
|
||||
}
|
||||
|
||||
// Args returns the non-option arguments.
|
||||
func (s *Set) Args() []string {
|
||||
return s.args
|
||||
}
|
||||
|
||||
// NArgs returns the number of non-option command line arguments.
|
||||
func NArgs() int {
|
||||
return len(CommandLine.args)
|
||||
}
|
||||
|
||||
// NArgs returns the number of non-option arguments.
|
||||
func (s *Set) NArgs() int {
|
||||
return len(s.args)
|
||||
}
|
||||
|
||||
// SetParameters sets the parameters string for printing the command line
|
||||
// usage. It defaults to "[parameters ...]"
|
||||
func SetParameters(parameters string) {
|
||||
CommandLine.parameters = parameters
|
||||
}
|
||||
|
||||
// SetParameters sets the parameters string for printing the s's usage.
|
||||
// It defaults to "[parameters ...]"
|
||||
func (s *Set) SetParameters(parameters string) {
|
||||
s.parameters = parameters
|
||||
}
|
||||
|
||||
// Parameters returns the parameters set by SetParameters on s.
|
||||
func (s *Set) Parameters() string { return s.parameters }
|
||||
|
||||
|
||||
// SetProgram sets the program name to program. Normally it is determined
|
||||
// from the zeroth command line argument (see os.Args).
|
||||
func SetProgram(program string) {
|
||||
CommandLine.program = program
|
||||
}
|
||||
|
||||
// SetProgram sets s's program name to program. Normally it is determined
|
||||
// from the zeroth argument passed to Getopt or Parse.
|
||||
func (s *Set) SetProgram(program string) {
|
||||
s.program = program
|
||||
}
|
||||
|
||||
// Program returns the program name associated with Set s.
|
||||
func (s *Set) Program() string { return s.program }
|
||||
|
||||
// SetUsage sets the function used by Parse to display the commands usage
|
||||
// on error. It defaults to calling PrintUsage(os.Stderr).
|
||||
func SetUsage(usage func()) {
|
||||
CommandLine.usage = usage
|
||||
}
|
||||
|
||||
// SetUsage sets the function used by Parse to display usage on error. It
|
||||
// defaults to calling f.PrintUsage(os.Stderr).
|
||||
func (s *Set) SetUsage(usage func()) {
|
||||
s.usage = usage
|
||||
}
|
||||
|
||||
// Lookup returns the Option associated with name. Name should either be
|
||||
// a rune (the short name) or a string (the long name).
|
||||
func Lookup(name interface{}) Option {
|
||||
return CommandLine.Lookup(name)
|
||||
}
|
||||
|
||||
// Lookup returns the Option associated with name in s. Name should either be
|
||||
// a rune (the short name) or a string (the long name).
|
||||
func (s *Set) Lookup(name interface{}) Option {
|
||||
switch v := name.(type) {
|
||||
case rune:
|
||||
return s.shortOptions[v]
|
||||
case int:
|
||||
return s.shortOptions[rune(v)]
|
||||
case string:
|
||||
return s.longOptions[v]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsSet returns true if the Option associated with name was seen while
|
||||
// parsing the command line arguments. Name should either be a rune (the
|
||||
// short name) or a string (the long name).
|
||||
func IsSet(name interface{}) bool {
|
||||
return CommandLine.IsSet(name)
|
||||
}
|
||||
|
||||
// IsSet returns true if the Option associated with name was seen while
|
||||
// parsing s. Name should either be a rune (the short name) or a string (the
|
||||
// long name).
|
||||
func (s *Set) IsSet(name interface{}) bool {
|
||||
if opt := s.Lookup(name); opt != nil {
|
||||
return opt.Seen()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetCount returns the number of times the Option associated with name has been
|
||||
// seen while parsing the command line arguments. Name should either be a rune
|
||||
// (the short name) or a string (the long name).
|
||||
func GetCount(name interface{}) int {
|
||||
return CommandLine.GetCount(name)
|
||||
}
|
||||
|
||||
// GetCount returns the number of times the Option associated with name has been
|
||||
// seen while parsing s's arguments. Name should either be a rune (the short
|
||||
// name) or a string (the long name).
|
||||
func (s *Set) GetCount(name interface{}) int {
|
||||
if opt := s.Lookup(name); opt != nil {
|
||||
return opt.Count()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetValue returns the final value set to the command-line Option with name.
|
||||
// If the option has not been seen while parsing s then the default value is
|
||||
// returned. Name should either be a rune (the short name) or a string (the
|
||||
// long name).
|
||||
func GetValue(name interface{}) string {
|
||||
return CommandLine.GetValue(name)
|
||||
}
|
||||
|
||||
// GetValue returns the final value set to the Option in s associated with name.
|
||||
// If the option has not been seen while parsing s then the default value is
|
||||
// returned. Name should either be a rune (the short name) or a string (the
|
||||
// long name).
|
||||
func (s *Set) GetValue(name interface{}) string {
|
||||
if opt := s.Lookup(name); opt != nil {
|
||||
return opt.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Visit visits the command-line options in lexicographical order, calling fn
|
||||
// for each. It visits only those options that have been set.
|
||||
func Visit(fn func(Option)) { CommandLine.Visit(fn) }
|
||||
|
||||
// Visit visits the options in s in lexicographical order, calling fn
|
||||
// for each. It visits only those options that have been set.
|
||||
func (s *Set) Visit(fn func(Option)) {
|
||||
sort.Sort(s.options)
|
||||
for _, opt := range s.options {
|
||||
if opt.count > 0 {
|
||||
fn(opt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VisitAll visits the options in s in lexicographical order, calling fn
|
||||
// for each. It visits all options, even those not set.
|
||||
func VisitAll(fn func(Option)) { CommandLine.VisitAll(fn) }
|
||||
|
||||
// VisitAll visits the command-line flags in lexicographical order, calling fn
|
||||
// for each. It visits all flags, even those not set.
|
||||
func (s *Set) VisitAll(fn func(Option)) {
|
||||
sort.Sort(s.options)
|
||||
for _, opt := range s.options {
|
||||
fn(opt)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets all the command line options to the initial state so it
|
||||
// appears none of them have been seen.
|
||||
func Reset() {
|
||||
CommandLine.Reset()
|
||||
}
|
||||
|
||||
// Reset resets all the options in s to the initial state so it
|
||||
// appears none of them have been seen.
|
||||
func (s *Set) Reset() {
|
||||
for _, opt := range s.options {
|
||||
opt.Reset()
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type signed int64
|
||||
|
||||
type SignedLimit struct {
|
||||
Base int // Base for conversion as per strconv.ParseInt
|
||||
Bits int // Number of bits as per strconv.ParseInt
|
||||
Min int64 // Minimum allowed value if both Min and Max are not 0
|
||||
Max int64 // Maximum allowed value if both Min and Max are not 0
|
||||
}
|
||||
|
||||
var (
|
||||
signedLimitsMu sync.Mutex
|
||||
signedLimits = make(map[*signed]*SignedLimit)
|
||||
)
|
||||
|
||||
func (n *signed) Set(value string, opt Option) error {
|
||||
signedLimitsMu.Lock()
|
||||
l := signedLimits[n]
|
||||
signedLimitsMu.Unlock()
|
||||
if l == nil {
|
||||
return fmt.Errorf("no limits defined for %s", opt.Name())
|
||||
}
|
||||
v, err := strconv.ParseInt(value, l.Base, l.Bits)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
if l.Min != 0 || l.Max != 0 {
|
||||
if v < l.Min {
|
||||
return fmt.Errorf("value out of range (<%v): %s", l.Min, value)
|
||||
}
|
||||
if v > l.Max {
|
||||
return fmt.Errorf("value out of range (>%v): %s", l.Max, value)
|
||||
}
|
||||
}
|
||||
*n = signed(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *signed) String() string {
|
||||
signedLimitsMu.Lock()
|
||||
l := signedLimits[n]
|
||||
signedLimitsMu.Unlock()
|
||||
if l != nil && l.Base != 0 {
|
||||
return strconv.FormatInt(int64(*n), l.Base)
|
||||
}
|
||||
return strconv.FormatInt(int64(*n), 10)
|
||||
}
|
||||
|
||||
// Signed creates an option that is stored in an int64 and is constrained
|
||||
// by the limits pointed to by l. The Max and Min values are only used if
|
||||
// at least one of the values are not 0. If Base is 0, the base is implied by
|
||||
// the string's prefix: base 16 for "0x", base 8 for "0", and base 10 otherwise.
|
||||
func Signed(name rune, value int64, l *SignedLimit, helpvalue ...string) *int64 {
|
||||
CommandLine.signedOption(&value, "", name, l, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func (s *Set) Signed(name rune, value int64, l *SignedLimit, helpvalue ...string) *int64 {
|
||||
s.signedOption(&value, "", name, l, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func SignedLong(name string, short rune, value int64, l *SignedLimit, helpvalue ...string) *int64 {
|
||||
CommandLine.signedOption(&value, name, short, l, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func (s *Set) SignedLong(name string, short rune, value int64, l *SignedLimit, helpvalue ...string) *int64 {
|
||||
s.signedOption(&value, name, short, l, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func (s *Set) signedOption(p *int64, name string, short rune, l *SignedLimit, helpvalue ...string) {
|
||||
opt := s.FlagLong((*signed)(p), name, short, helpvalue...)
|
||||
if l.Base > 36 || l.Base == 1 || l.Base < 0 {
|
||||
fmt.Fprintf(stderr, "invalid base for %s: %d\n", opt.Name(), l.Base)
|
||||
exit(1)
|
||||
}
|
||||
if l.Bits < 0 || l.Bits > 64 {
|
||||
fmt.Fprintf(stderr, "invalid bit size for %s: %d\n", opt.Name(), l.Bits)
|
||||
exit(1)
|
||||
}
|
||||
if l.Min > l.Max {
|
||||
fmt.Fprintf(stderr, "min greater than max for %s\n", opt.Name())
|
||||
exit(1)
|
||||
}
|
||||
lim := *l
|
||||
signedLimitsMu.Lock()
|
||||
signedLimits[(*signed)(p)] = &lim
|
||||
signedLimitsMu.Unlock()
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var signedNumberTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
l SignedLimit
|
||||
out int64
|
||||
err string
|
||||
}{
|
||||
{
|
||||
where: loc(),
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "1010"},
|
||||
SignedLimit{Base: 2, Bits: 5},
|
||||
10,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "1010"},
|
||||
SignedLimit{Base: 2, Bits: 4},
|
||||
0,
|
||||
"test: value out of range: 1010\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "-1000"},
|
||||
SignedLimit{Base: 2, Bits: 4},
|
||||
-8,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "3"},
|
||||
SignedLimit{Min: 4, Max: 6},
|
||||
0,
|
||||
"test: value out of range (<4): 3\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "4"},
|
||||
SignedLimit{Min: 4, Max: 6},
|
||||
4,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "5"},
|
||||
SignedLimit{Min: 4, Max: 6},
|
||||
5,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "6"},
|
||||
SignedLimit{Min: 4, Max: 6},
|
||||
6,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "7"},
|
||||
SignedLimit{Min: 4, Max: 6},
|
||||
0,
|
||||
"test: value out of range (>6): 7\n",
|
||||
},
|
||||
}
|
||||
|
||||
func TestSigneds(t *testing.T) {
|
||||
for x, tt := range signedNumberTests {
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
reset()
|
||||
n := Signed('n', 0, &tt.l)
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if *n != tt.out {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, *n, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
// String returns a value option that stores is value as a string. The
|
||||
// initial value of the string is passed in value.
|
||||
func String(name rune, value string, helpvalue ...string) *string {
|
||||
CommandLine.Flag(&value, name, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func (s *Set) String(name rune, value string, helpvalue ...string) *string {
|
||||
s.Flag(&value, name, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func StringLong(name string, short rune, value string, helpvalue ...string) *string {
|
||||
CommandLine.FlagLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func (s *Set) StringLong(name string, short rune, value string, helpvalue ...string) *string {
|
||||
s.FlagLong(&value, name, short, helpvalue...)
|
||||
return &value
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import "testing"
|
||||
|
||||
var stringTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
sout string
|
||||
optout string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
loc(),
|
||||
[]string{},
|
||||
"one",
|
||||
"two",
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-s"},
|
||||
"one",
|
||||
"two",
|
||||
"test: missing parameter for -s\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "--opt"},
|
||||
"one",
|
||||
"two",
|
||||
"test: missing parameter for --opt\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-svalue", "--opt=option"},
|
||||
"value",
|
||||
"option",
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-s", "value", "--opt", "option"},
|
||||
"value",
|
||||
"option",
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-swrong", "--opt=wrong", "-s", "value", "--opt", "option"},
|
||||
"value",
|
||||
"option",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
for _, tt := range stringTests {
|
||||
reset()
|
||||
s := String('s', "one")
|
||||
opt := StringLong("opt", 0, "two")
|
||||
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if *s != tt.sout {
|
||||
t.Errorf("%s: got s = %q, want %q", tt.where, *s, tt.sout)
|
||||
}
|
||||
if *opt != tt.optout {
|
||||
t.Errorf("%s: got opt = %q, want %q", tt.where, *opt, tt.optout)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type unsigned uint64
|
||||
|
||||
type UnsignedLimit struct {
|
||||
Base int // Base for conversion as per strconv.ParseInt
|
||||
Bits int // Number of bits as per strconv.ParseInt
|
||||
Min uint64 // Minimum allowed value if both Min and Max are not 0
|
||||
Max uint64 // Maximum allowed value if both Min and Max are not 0
|
||||
}
|
||||
|
||||
var (
|
||||
unsignedLimitsMu sync.Mutex
|
||||
unsignedLimits = make(map[*unsigned]*UnsignedLimit)
|
||||
)
|
||||
|
||||
func (n *unsigned) Set(value string, opt Option) error {
|
||||
unsignedLimitsMu.Lock()
|
||||
l := unsignedLimits[n]
|
||||
unsignedLimitsMu.Unlock()
|
||||
if l == nil {
|
||||
return fmt.Errorf("no limits defined for %s", opt.Name())
|
||||
}
|
||||
v, err := strconv.ParseUint(value, l.Base, l.Bits)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok {
|
||||
switch e.Err {
|
||||
case strconv.ErrRange:
|
||||
err = fmt.Errorf("value out of range: %s", value)
|
||||
case strconv.ErrSyntax:
|
||||
err = fmt.Errorf("not a valid number: %s", value)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
if l.Min != 0 || l.Max != 0 {
|
||||
if v < l.Min {
|
||||
return fmt.Errorf("value out of range (<%v): %s", l.Min, value)
|
||||
}
|
||||
if v > l.Max {
|
||||
return fmt.Errorf("value out of range (>%v): %s", l.Max, value)
|
||||
}
|
||||
}
|
||||
*n = unsigned(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *unsigned) String() string {
|
||||
unsignedLimitsMu.Lock()
|
||||
l := unsignedLimits[n]
|
||||
unsignedLimitsMu.Unlock()
|
||||
if l != nil && l.Base != 0 {
|
||||
return strconv.FormatUint(uint64(*n), l.Base)
|
||||
}
|
||||
return strconv.FormatUint(uint64(*n), 10)
|
||||
}
|
||||
|
||||
// Unsigned creates an option that is stored in a uint64 and is
|
||||
// constrained by the limits pointed to by l. The Max and Min values are only
|
||||
// used if at least one of the values are not 0. If Base is 0, the base is
|
||||
// implied by the string's prefix: base 16 for "0x", base 8 for "0", and base
|
||||
// 10 otherwise.
|
||||
func Unsigned(name rune, value uint64, l *UnsignedLimit, helpvalue ...string) *uint64 {
|
||||
CommandLine.unsignedOption(&value, "", name, l, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func (s *Set) Unsigned(name rune, value uint64, l *UnsignedLimit, helpvalue ...string) *uint64 {
|
||||
s.unsignedOption(&value, "", name, l, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func UnsignedLong(name string, short rune, value uint64, l *UnsignedLimit, helpvalue ...string) *uint64 {
|
||||
CommandLine.unsignedOption(&value, name, short, l, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func (s *Set) UnsignedLong(name string, short rune, value uint64, l *UnsignedLimit, helpvalue ...string) *uint64 {
|
||||
s.unsignedOption(&value, name, short, l, helpvalue...)
|
||||
return &value
|
||||
}
|
||||
|
||||
func (s *Set) unsignedOption(p *uint64, name string, short rune, l *UnsignedLimit, helpvalue ...string) {
|
||||
opt := s.FlagLong((*unsigned)(p), name, short, helpvalue...)
|
||||
if l.Base > 36 || l.Base == 1 || l.Base < 0 {
|
||||
fmt.Fprintf(stderr, "invalid base for %s: %d\n", opt.Name(), l.Base)
|
||||
exit(1)
|
||||
}
|
||||
if l.Bits < 0 || l.Bits > 64 {
|
||||
fmt.Fprintf(stderr, "invalid bit size for %s: %d\n", opt.Name(), l.Bits)
|
||||
exit(1)
|
||||
}
|
||||
if l.Min > l.Max {
|
||||
fmt.Fprintf(stderr, "min greater than max for %s\n", opt.Name())
|
||||
exit(1)
|
||||
}
|
||||
lim := *l
|
||||
unsignedLimitsMu.Lock()
|
||||
unsignedLimits[(*unsigned)(p)] = &lim
|
||||
unsignedLimitsMu.Unlock()
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var unsignedTests = []struct {
|
||||
where string
|
||||
in []string
|
||||
l UnsignedLimit
|
||||
out uint64
|
||||
err string
|
||||
}{
|
||||
{
|
||||
where: loc(),
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "1010"},
|
||||
UnsignedLimit{Base: 2, Bits: 5},
|
||||
10,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "1010"},
|
||||
UnsignedLimit{Base: 2, Bits: 4},
|
||||
10,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "1010"},
|
||||
UnsignedLimit{Base: 2, Bits: 3},
|
||||
0,
|
||||
"test: value out of range: 1010\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "3"},
|
||||
UnsignedLimit{Min: 4, Max: 6},
|
||||
0,
|
||||
"test: value out of range (<4): 3\n",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "4"},
|
||||
UnsignedLimit{Min: 4, Max: 6},
|
||||
4,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "5"},
|
||||
UnsignedLimit{Min: 4, Max: 6},
|
||||
5,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "6"},
|
||||
UnsignedLimit{Min: 4, Max: 6},
|
||||
6,
|
||||
"",
|
||||
},
|
||||
{
|
||||
loc(),
|
||||
[]string{"test", "-n", "7"},
|
||||
UnsignedLimit{Min: 4, Max: 6},
|
||||
0,
|
||||
"test: value out of range (>6): 7\n",
|
||||
},
|
||||
}
|
||||
|
||||
func TestUnsigneds(t *testing.T) {
|
||||
for x, tt := range unsignedTests {
|
||||
if strings.Index(tt.where, ":-") > 0 {
|
||||
tt.where = fmt.Sprintf("#%d", x)
|
||||
}
|
||||
|
||||
reset()
|
||||
n := Unsigned('n', 0, &tt.l)
|
||||
parse(tt.in)
|
||||
if s := checkError(tt.err); s != "" {
|
||||
t.Errorf("%s: %s", tt.where, s)
|
||||
}
|
||||
if *n != tt.out {
|
||||
t.Errorf("%s: got %v, want %v", tt.where, *n, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errorString string
|
||||
|
||||
func reset() {
|
||||
CommandLine.shortOptions = make(map[rune]*option)
|
||||
CommandLine.longOptions = make(map[string]*option)
|
||||
CommandLine.options = nil
|
||||
CommandLine.args = nil
|
||||
CommandLine.program = ""
|
||||
errorString = ""
|
||||
}
|
||||
|
||||
func parse(args []string) {
|
||||
err := CommandLine.Getopt(args, nil)
|
||||
if err != nil {
|
||||
b := &bytes.Buffer{}
|
||||
|
||||
fmt.Fprintln(b, CommandLine.program+": "+err.Error())
|
||||
CommandLine.PrintUsage(b)
|
||||
errorString = b.String()
|
||||
}
|
||||
}
|
||||
|
||||
func badSlice(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return true
|
||||
}
|
||||
for x, v := range a {
|
||||
if b[x] != v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func loc() string {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
return fmt.Sprintf("%s:%d", path.Base(file), line)
|
||||
}
|
||||
|
||||
func (o *option) Equal(opt *option) bool {
|
||||
if o.value != nil && opt.value == nil {
|
||||
return false
|
||||
}
|
||||
if o.value == nil && opt.value != nil {
|
||||
return false
|
||||
}
|
||||
if o.value != nil && o.value.String() != opt.value.String() {
|
||||
return false
|
||||
}
|
||||
|
||||
oc := *o
|
||||
optc := *opt
|
||||
oc.value = nil
|
||||
optc.value = nil
|
||||
return reflect.DeepEqual(&oc, &optc)
|
||||
}
|
||||
|
||||
func checkError(err string) string {
|
||||
switch {
|
||||
case err == errorString:
|
||||
return ""
|
||||
case err == "":
|
||||
return fmt.Sprintf("unexpected error %q", errorString)
|
||||
case errorString == "":
|
||||
return fmt.Sprintf("did not get expected error %q", err)
|
||||
case !strings.HasPrefix(errorString, err):
|
||||
return fmt.Sprintf("got error %q, want %q", errorString, err)
|
||||
}
|
||||
return ""
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Value is the interface to the dynamic value stored in a flag. Flags of type
|
||||
// Value are declared using the Flag and FlagLong functions.
|
||||
type Value interface {
|
||||
// Set converts value into the appropriate type and assigns it to the
|
||||
// receiver value. Option details are provided via opt (such as the
|
||||
// flags name).
|
||||
//
|
||||
// Set is used to reset the value of an option to its default value
|
||||
// (which is stored in string form internally).
|
||||
Set(value string, opt Option) error
|
||||
|
||||
// String returns the value of the flag as a string.
|
||||
String() string
|
||||
}
|
||||
|
||||
var thisPackage string
|
||||
|
||||
// init initializes thisPackage to our full package with the trailing .
|
||||
// included.
|
||||
func init() {
|
||||
pc, _, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
f := runtime.FuncForPC(pc)
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
thisPackage = f.Name()
|
||||
x := strings.LastIndex(thisPackage, "/")
|
||||
if x < 0 {
|
||||
return
|
||||
}
|
||||
y := strings.Index(thisPackage[x:], ".")
|
||||
if y < 0 {
|
||||
return
|
||||
}
|
||||
// thisPackage includes the trailing . after the package name.
|
||||
thisPackage = thisPackage[:x+y+1]
|
||||
}
|
||||
|
||||
// calledFrom returns a string containing the file and linenumber of the first
|
||||
// stack frame above us that is not part of this package and is not a test.
|
||||
// This is used to determine where a flag was initialized.
|
||||
func calledFrom() string {
|
||||
for i := 2; ; i++ {
|
||||
pc, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if !strings.HasSuffix(file, "_test.go") {
|
||||
f := runtime.FuncForPC(pc)
|
||||
if f != nil && strings.HasPrefix(f.Name(), thisPackage) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Set) addFlag(p Value, name string, short rune, helpvalue ...string) Option {
|
||||
opt := &option{
|
||||
short: short,
|
||||
long: name,
|
||||
value: p,
|
||||
defval: p.String(),
|
||||
}
|
||||
|
||||
switch len(helpvalue) {
|
||||
case 2:
|
||||
opt.name = helpvalue[1]
|
||||
fallthrough
|
||||
case 1:
|
||||
opt.help = helpvalue[0]
|
||||
case 0:
|
||||
default:
|
||||
panic("Too many strings for String helpvalue")
|
||||
}
|
||||
if where := calledFrom(); where != "" {
|
||||
opt.where = where
|
||||
}
|
||||
if opt.short == 0 && opt.long == "" {
|
||||
fmt.Fprintf(stderr, opt.where+": no short or long option given")
|
||||
exit(1)
|
||||
}
|
||||
s.AddOption(opt)
|
||||
return opt
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package getopt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Value is the interface to the dynamic value stored in a flag. (The default
|
||||
// value is represented as a string.) Set is passed the string to set the
|
||||
// value to as well as the Option that is being processed.
|
||||
type Value interface {
|
||||
Set(string, Option) error
|
||||
String() string
|
||||
}
|
||||
|
||||
// Var creates an option of the specified name. The type and value of the option
|
||||
// are represented by the first argument, of type Value, which typically holds a
|
||||
// user-defined implementation of Value. All options are ultimately created
|
||||
// as a Var.
|
||||
func Var(p Value, name rune, helpvalue ...string) Option {
|
||||
return CommandLine.VarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func VarLong(p Value, name string, short rune, helpvalue ...string) Option {
|
||||
return CommandLine.VarLong(p, name, short, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) Var(p Value, name rune, helpvalue ...string) Option {
|
||||
return s.VarLong(p, "", name, helpvalue...)
|
||||
}
|
||||
|
||||
func (s *Set) VarLong(p Value, name string, short rune, helpvalue ...string) Option {
|
||||
opt := &option{
|
||||
short: short,
|
||||
long: name,
|
||||
value: p,
|
||||
defval: p.String(),
|
||||
}
|
||||
|
||||
switch len(helpvalue) {
|
||||
case 2:
|
||||
opt.name = helpvalue[1]
|
||||
fallthrough
|
||||
case 1:
|
||||
opt.help = helpvalue[0]
|
||||
case 0:
|
||||
default:
|
||||
panic("Too many strings for String helpvalue")
|
||||
}
|
||||
if _, file, line, ok := runtime.Caller(1); ok {
|
||||
opt.where = fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
if opt.short == 0 && opt.long == "" {
|
||||
fmt.Fprintf(stderr, opt.where+": no short or long option given")
|
||||
exit(1)
|
||||
}
|
||||
s.AddOption(opt)
|
||||
return opt
|
||||
}
|
Loading…
Reference in New Issue