mirror of
https://github.com/restic/restic.git
synced 2026-08-25 06:53:18 +00:00
Move things around for gb
This moves all restic source files to src/, and all vendored dependencies to vendor/src.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
*.go filter=gofmt
|
||||
*.cgo filter=gofmt
|
||||
@@ -0,0 +1,11 @@
|
||||
*~
|
||||
.#*
|
||||
## the next line needs to start with a backslash to avoid looking like
|
||||
## a comment
|
||||
\#*#
|
||||
.*.swp
|
||||
|
||||
*.test
|
||||
|
||||
/clockfs
|
||||
/hellofs
|
||||
Vendored
+93
@@ -0,0 +1,93 @@
|
||||
Copyright (c) 2013-2015 Tommi Virtanen.
|
||||
Copyright (c) 2009, 2011, 2012 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.
|
||||
|
||||
|
||||
|
||||
The following included software components have additional copyright
|
||||
notices and license terms that may differ from the above.
|
||||
|
||||
|
||||
File fuse.go:
|
||||
|
||||
// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c,
|
||||
// which carries this notice:
|
||||
//
|
||||
// The files in this directory are subject to the following license.
|
||||
//
|
||||
// The author of this software is Russ Cox.
|
||||
//
|
||||
// Copyright (c) 2006 Russ Cox
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose without fee is hereby granted, provided that this entire notice
|
||||
// is included in all copies of any software which is or includes a copy
|
||||
// or modification of this software and in all copies of the supporting
|
||||
// documentation for such software.
|
||||
//
|
||||
// THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
// WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY
|
||||
// OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
|
||||
// FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
|
||||
|
||||
File fuse_kernel.go:
|
||||
|
||||
// Derived from FUSE's fuse_kernel.h
|
||||
/*
|
||||
This file defines the kernel interface of FUSE
|
||||
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
|
||||
This -- and only this -- header file may also be distributed under
|
||||
the terms of the BSD Licence as follows:
|
||||
|
||||
Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. 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.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
|
||||
*/
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
bazil.org/fuse -- Filesystems in Go
|
||||
===================================
|
||||
|
||||
`bazil.org/fuse` is a Go library for writing FUSE userspace
|
||||
filesystems.
|
||||
|
||||
It is a from-scratch implementation of the kernel-userspace
|
||||
communication protocol, and does not use the C library from the
|
||||
project called FUSE. `bazil.org/fuse` embraces Go fully for safety and
|
||||
ease of programming.
|
||||
|
||||
Here’s how to get going:
|
||||
|
||||
go get bazil.org/fuse
|
||||
|
||||
Website: http://bazil.org/fuse/
|
||||
|
||||
Github repository: https://github.com/bazil/fuse
|
||||
|
||||
API docs: http://godoc.org/bazil.org/fuse
|
||||
|
||||
Our thanks to Russ Cox for his fuse library, which this project is
|
||||
based on.
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
package fuse
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// buffer provides a mechanism for constructing a message from
|
||||
// multiple segments.
|
||||
type buffer []byte
|
||||
|
||||
// alloc allocates size bytes and returns a pointer to the new
|
||||
// segment.
|
||||
func (w *buffer) alloc(size uintptr) unsafe.Pointer {
|
||||
s := int(size)
|
||||
if len(*w)+s > cap(*w) {
|
||||
old := *w
|
||||
*w = make([]byte, len(*w), 2*cap(*w)+s)
|
||||
copy(*w, old)
|
||||
}
|
||||
l := len(*w)
|
||||
*w = (*w)[:l+s]
|
||||
return unsafe.Pointer(&(*w)[l])
|
||||
}
|
||||
|
||||
// reset clears out the contents of the buffer.
|
||||
func (w *buffer) reset() {
|
||||
for i := range (*w)[:cap(*w)] {
|
||||
(*w)[i] = 0
|
||||
}
|
||||
*w = (*w)[:0]
|
||||
}
|
||||
|
||||
func newBuffer(extra uintptr) buffer {
|
||||
const hdrSize = unsafe.Sizeof(outHeader{})
|
||||
buf := make(buffer, hdrSize, hdrSize+extra)
|
||||
return buf
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func stack() string {
|
||||
buf := make([]byte, 1024)
|
||||
return string(buf[:runtime.Stack(buf, false)])
|
||||
}
|
||||
|
||||
func nop(msg interface{}) {}
|
||||
|
||||
// Debug is called to output debug messages, including protocol
|
||||
// traces. The default behavior is to do nothing.
|
||||
//
|
||||
// The messages have human-friendly string representations and are
|
||||
// safe to marshal to JSON.
|
||||
//
|
||||
// Implementations must not retain msg.
|
||||
var Debug func(msg interface{}) = nop
|
||||
@@ -0,0 +1,4 @@
|
||||
/*.seq.svg
|
||||
|
||||
# not ignoring *.seq.png; we want those committed to the repo
|
||||
# for embedding on Github
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
# bazil.org/fuse documentation
|
||||
|
||||
See also API docs at http://godoc.org/bazil.org/fuse
|
||||
|
||||
- [The mount sequence](mount-sequence.md)
|
||||
- [Writing documentation](writing-docs.md)
|
||||
@@ -0,0 +1,32 @@
|
||||
seqdiag {
|
||||
app;
|
||||
fuse [label="bazil.org/fuse"];
|
||||
fusermount;
|
||||
kernel;
|
||||
mounts;
|
||||
|
||||
app;
|
||||
fuse [label="bazil.org/fuse"];
|
||||
fusermount;
|
||||
kernel;
|
||||
mounts;
|
||||
|
||||
app -> fuse [label="Mount"];
|
||||
fuse -> fusermount [label="spawn, pass socketpair fd"];
|
||||
fusermount -> kernel [label="open /dev/fuse"];
|
||||
fusermount -> kernel [label="mount(2)"];
|
||||
kernel ->> mounts [label="mount is visible"];
|
||||
fusermount <-- kernel [label="mount(2) returns"];
|
||||
fuse <<-- fusermount [diagonal, label="exit, receive /dev/fuse fd", leftnote="on Linux, successful exit here\nmeans the mount has happened,\nthough InitRequest might not have yet"];
|
||||
app <-- fuse [label="Mount returns\nConn.Ready is already closed"];
|
||||
|
||||
app -> fuse [label="fs.Serve"];
|
||||
fuse => kernel [label="read /dev/fuse fd", note="starts with InitRequest"];
|
||||
fuse -> app [label="Init"];
|
||||
fuse <-- app [color=red];
|
||||
fuse -> kernel [label="write /dev/fuse fd", color=red];
|
||||
kernel -> kernel [label="set connection\nstate to error", color=red];
|
||||
fuse <-- kernel;
|
||||
... conn.MountError == nil, so it is still mounted ...
|
||||
... call conn.Close to clean up ...
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
+41
@@ -0,0 +1,41 @@
|
||||
seqdiag {
|
||||
// seqdiag -T svg -o doc/mount-osx.svg doc/mount-osx.seq
|
||||
app;
|
||||
fuse [label="bazil.org/fuse"];
|
||||
fusermount;
|
||||
kernel;
|
||||
mounts;
|
||||
|
||||
app -> fuse [label="Mount"];
|
||||
fuse -> fusermount [label="spawn, pass socketpair fd"];
|
||||
fusermount -> kernel [label="open /dev/fuse"];
|
||||
fusermount -> kernel [label="mount(2)"];
|
||||
kernel ->> mounts [label="mount is visible"];
|
||||
fusermount <-- kernel [label="mount(2) returns"];
|
||||
fuse <<-- fusermount [diagonal, label="exit, receive /dev/fuse fd", leftnote="on Linux, successful exit here\nmeans the mount has happened,\nthough InitRequest might not have yet"];
|
||||
app <-- fuse [label="Mount returns\nConn.Ready is already closed", rightnote="InitRequest and StatfsRequest\nmay or may not be seen\nbefore Conn.Ready,\ndepending on platform"];
|
||||
|
||||
app -> fuse [label="fs.Serve"];
|
||||
fuse => kernel [label="read /dev/fuse fd", note="starts with InitRequest"];
|
||||
fuse => app [label="FS/Node/Handle methods"];
|
||||
fuse => kernel [label="write /dev/fuse fd"];
|
||||
... repeat ...
|
||||
|
||||
... shutting down ...
|
||||
app -> fuse [label="Unmount"];
|
||||
fuse -> fusermount [label="fusermount -u"];
|
||||
fusermount -> kernel;
|
||||
kernel <<-- mounts;
|
||||
fusermount <-- kernel;
|
||||
fuse <<-- fusermount [diagonal];
|
||||
app <-- fuse [label="Unmount returns"];
|
||||
|
||||
// actually triggers before above
|
||||
fuse <<-- kernel [diagonal, label="/dev/fuse EOF"];
|
||||
app <-- fuse [label="fs.Serve returns"];
|
||||
|
||||
app -> fuse [label="conn.Close"];
|
||||
fuse -> kernel [label="close /dev/fuse fd"];
|
||||
fuse <-- kernel;
|
||||
app <-- fuse;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,32 @@
|
||||
seqdiag {
|
||||
app;
|
||||
fuse [label="bazil.org/fuse"];
|
||||
wait [label="callMount\nhelper goroutine"];
|
||||
mount_osxfusefs;
|
||||
kernel;
|
||||
|
||||
app -> fuse [label="Mount"];
|
||||
fuse -> kernel [label="open /dev/osxfuseN"];
|
||||
fuse -> mount_osxfusefs [label="spawn, pass fd"];
|
||||
fuse -> wait [label="goroutine", note="blocks on cmd.Wait"];
|
||||
app <-- fuse [label="Mount returns"];
|
||||
|
||||
mount_osxfusefs -> kernel [label="mount(2)"];
|
||||
|
||||
app -> fuse [label="fs.Serve"];
|
||||
fuse => kernel [label="read /dev/osxfuseN fd", note="starts with InitRequest,\nalso seen before mount exits:\ntwo StatfsRequest calls"];
|
||||
fuse -> app [label="Init"];
|
||||
fuse <-- app [color=red];
|
||||
fuse -> kernel [label="write /dev/osxfuseN fd", color=red];
|
||||
fuse <-- kernel;
|
||||
|
||||
mount_osxfusefs <-- kernel [label="mount(2) returns", color=red];
|
||||
wait <<-- mount_osxfusefs [diagonal, label="exit", color=red];
|
||||
app <<-- wait [diagonal, label="mount has failed,\nclose Conn.Ready", color=red];
|
||||
|
||||
// actually triggers before above
|
||||
fuse <<-- kernel [diagonal, label="/dev/osxfuseN EOF"];
|
||||
app <-- fuse [label="fs.Serve returns"];
|
||||
... conn.MountError != nil, so it was was never mounted ...
|
||||
... call conn.Close to clean up ...
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
+45
@@ -0,0 +1,45 @@
|
||||
seqdiag {
|
||||
// seqdiag -T svg -o doc/mount-osx.svg doc/mount-osx.seq
|
||||
app;
|
||||
fuse [label="bazil.org/fuse"];
|
||||
wait [label="callMount\nhelper goroutine"];
|
||||
mount_osxfusefs;
|
||||
kernel;
|
||||
mounts;
|
||||
|
||||
app -> fuse [label="Mount"];
|
||||
fuse -> kernel [label="open /dev/osxfuseN"];
|
||||
fuse -> mount_osxfusefs [label="spawn, pass fd"];
|
||||
fuse -> wait [label="goroutine", note="blocks on cmd.Wait"];
|
||||
app <-- fuse [label="Mount returns"];
|
||||
|
||||
mount_osxfusefs -> kernel [label="mount(2)"];
|
||||
|
||||
app -> fuse [label="fs.Serve"];
|
||||
fuse => kernel [label="read /dev/osxfuseN fd", note="starts with InitRequest,\nalso seen before mount exits:\ntwo StatfsRequest calls"];
|
||||
fuse => app [label="FS/Node/Handle methods"];
|
||||
fuse => kernel [label="write /dev/osxfuseN fd"];
|
||||
... repeat ...
|
||||
|
||||
kernel ->> mounts [label="mount is visible"];
|
||||
mount_osxfusefs <-- kernel [label="mount(2) returns"];
|
||||
wait <<-- mount_osxfusefs [diagonal, label="exit", leftnote="on OS X, successful exit\nhere means we finally know\nthe mount has happened\n(can't trust InitRequest,\nkernel might have timed out\nwaiting for InitResponse)"];
|
||||
|
||||
app <<-- wait [diagonal, label="mount is ready,\nclose Conn.Ready", rightnote="InitRequest and StatfsRequest\nmay or may not be seen\nbefore Conn.Ready,\ndepending on platform"];
|
||||
|
||||
... shutting down ...
|
||||
app -> fuse [label="Unmount"];
|
||||
fuse -> kernel [label="umount(2)"];
|
||||
kernel <<-- mounts;
|
||||
fuse <-- kernel;
|
||||
app <-- fuse [label="Unmount returns"];
|
||||
|
||||
// actually triggers before above
|
||||
fuse <<-- kernel [diagonal, label="/dev/osxfuseN EOF"];
|
||||
app <-- fuse [label="fs.Serve returns"];
|
||||
|
||||
app -> fuse [label="conn.Close"];
|
||||
fuse -> kernel [label="close /dev/osxfuseN"];
|
||||
fuse <-- kernel;
|
||||
app <-- fuse;
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
+30
@@ -0,0 +1,30 @@
|
||||
# The mount sequence
|
||||
|
||||
FUSE mounting is a little bit tricky. There's a userspace helper tool
|
||||
that performs the handshake with the kernel, and then steps out of the
|
||||
way. This helper behaves differently on different platforms, forcing a
|
||||
more complex API on us.
|
||||
|
||||
## Successful runs
|
||||
|
||||
On Linux, the mount is immediate and file system accesses wait until
|
||||
the requests are served.
|
||||
|
||||

|
||||
|
||||
On OS X, the mount becomes visible only after `InitRequest` (and maybe
|
||||
more) have been served.
|
||||
|
||||

|
||||
|
||||
|
||||
## Errors
|
||||
|
||||
Let's see what happens if `InitRequest` gets an error response. On
|
||||
Linux, the mountpoint is there but all operations will fail:
|
||||
|
||||

|
||||
|
||||
On OS X, the mount never happened:
|
||||
|
||||

|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# Writing documentation
|
||||
|
||||
## Sequence diagrams
|
||||
|
||||
The sequence diagrams are generated with `seqdiag`:
|
||||
http://blockdiag.com/en/seqdiag/index.html
|
||||
|
||||
An easy way to work on them is to automatically update the generated
|
||||
files with https://github.com/cespare/reflex :
|
||||
|
||||
reflex -g 'doc/[^.]*.seq' -- seqdiag -T svg -o '{}.svg' '{}' &
|
||||
|
||||
reflex -g 'doc/[^.]*.seq' -- seqdiag -T png -o '{}.png' '{}' &
|
||||
|
||||
The markdown files refer to PNG images because of Github limitations,
|
||||
but the SVG is generally more pleasant to view.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
ENOATTR = Errno(syscall.ENOATTR)
|
||||
)
|
||||
|
||||
const (
|
||||
errNoXattr = ENOATTR
|
||||
)
|
||||
|
||||
func init() {
|
||||
errnoNames[errNoXattr] = "ENOATTR"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package fuse
|
||||
|
||||
import "syscall"
|
||||
|
||||
const (
|
||||
ENOATTR = Errno(syscall.ENOATTR)
|
||||
)
|
||||
|
||||
const (
|
||||
errNoXattr = ENOATTR
|
||||
)
|
||||
|
||||
func init() {
|
||||
errnoNames[errNoXattr] = "ENOATTR"
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
ENODATA = Errno(syscall.ENODATA)
|
||||
)
|
||||
|
||||
const (
|
||||
errNoXattr = ENODATA
|
||||
)
|
||||
|
||||
func init() {
|
||||
errnoNames[errNoXattr] = "ENODATA"
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package fuse
|
||||
|
||||
// There is very little commonality in extended attribute errors
|
||||
// across platforms.
|
||||
//
|
||||
// getxattr return value for "extended attribute does not exist" is
|
||||
// ENOATTR on OS X, and ENODATA on Linux and apparently at least
|
||||
// NetBSD. There may be a #define ENOATTR on Linux too, but the value
|
||||
// is ENODATA in the actual syscalls. FreeBSD and OpenBSD have no
|
||||
// ENODATA, only ENOATTR. ENOATTR is not in any of the standards,
|
||||
// ENODATA exists but is only used for STREAMs.
|
||||
//
|
||||
// Each platform will define it a errNoXattr constant, and this file
|
||||
// will enforce that it implements the right interfaces and hide the
|
||||
// implementation.
|
||||
//
|
||||
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/getxattr.2.html
|
||||
// http://mail-index.netbsd.org/tech-kern/2012/04/30/msg013090.html
|
||||
// http://mail-index.netbsd.org/tech-kern/2012/04/30/msg013097.html
|
||||
// http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html
|
||||
// http://www.freebsd.org/cgi/man.cgi?query=extattr_get_file&sektion=2
|
||||
// http://nixdoc.net/man-pages/openbsd/man2/extattr_get_file.2.html
|
||||
|
||||
// ErrNoXattr is a platform-independent error value meaning the
|
||||
// extended attribute was not found. It can be used to respond to
|
||||
// GetxattrRequest and such.
|
||||
const ErrNoXattr = errNoXattr
|
||||
|
||||
var _ error = ErrNoXattr
|
||||
var _ Errno = ErrNoXattr
|
||||
var _ ErrorNumber = ErrNoXattr
|
||||
@@ -0,0 +1,173 @@
|
||||
// Clockfs implements a file system with the current time in a file.
|
||||
// It was written to demonstrate kernel cache invalidation.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
_ "bazil.org/fuse/fs/fstestutil"
|
||||
"bazil.org/fuse/fuseutil"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, " %s MOUNTPOINT\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if flag.NArg() != 1 {
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
mountpoint := flag.Arg(0)
|
||||
|
||||
c, err := fuse.Mount(
|
||||
mountpoint,
|
||||
fuse.FSName("clock"),
|
||||
fuse.Subtype("clockfsfs"),
|
||||
fuse.LocalVolume(),
|
||||
fuse.VolumeName("Clock filesystem"),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
srv := fs.New(c, nil)
|
||||
filesys := &FS{
|
||||
// We pre-create the clock node so that it's always the same
|
||||
// object returned from all the Lookups. You could carefully
|
||||
// track its lifetime between Lookup&Forget, and have the
|
||||
// ticking & invalidation happen only when active, but let's
|
||||
// keep this example simple.
|
||||
clockFile: &File{
|
||||
fuse: srv,
|
||||
},
|
||||
}
|
||||
filesys.clockFile.tick()
|
||||
// This goroutine never exits. That's fine for this example.
|
||||
go filesys.clockFile.update()
|
||||
if err := srv.Serve(filesys); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Check if the mount process has an error to report.
|
||||
<-c.Ready
|
||||
if err := c.MountError; err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
type FS struct {
|
||||
clockFile *File
|
||||
}
|
||||
|
||||
var _ fs.FS = (*FS)(nil)
|
||||
|
||||
func (f *FS) Root() (fs.Node, error) {
|
||||
return &Dir{fs: f}, nil
|
||||
}
|
||||
|
||||
// Dir implements both Node and Handle for the root directory.
|
||||
type Dir struct {
|
||||
fs *FS
|
||||
}
|
||||
|
||||
var _ fs.Node = (*Dir)(nil)
|
||||
|
||||
func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
a.Inode = 1
|
||||
a.Mode = os.ModeDir | 0555
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ fs.NodeStringLookuper = (*Dir)(nil)
|
||||
|
||||
func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||
if name == "clock" {
|
||||
return d.fs.clockFile, nil
|
||||
}
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
var dirDirs = []fuse.Dirent{
|
||||
{Inode: 2, Name: "clock", Type: fuse.DT_File},
|
||||
}
|
||||
|
||||
var _ fs.HandleReadDirAller = (*Dir)(nil)
|
||||
|
||||
func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||
return dirDirs, nil
|
||||
}
|
||||
|
||||
type File struct {
|
||||
fs.NodeRef
|
||||
fuse *fs.Server
|
||||
content atomic.Value
|
||||
count uint64
|
||||
}
|
||||
|
||||
var _ fs.Node = (*File)(nil)
|
||||
|
||||
func (f *File) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
a.Inode = 2
|
||||
a.Mode = 0444
|
||||
t := f.content.Load().(string)
|
||||
a.Size = uint64(len(t))
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ fs.NodeOpener = (*File)(nil)
|
||||
|
||||
func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
|
||||
if !req.Flags.IsReadOnly() {
|
||||
return nil, fuse.Errno(syscall.EACCES)
|
||||
}
|
||||
resp.Flags |= fuse.OpenKeepCache
|
||||
return f, nil
|
||||
}
|
||||
|
||||
var _ fs.Handle = (*File)(nil)
|
||||
|
||||
var _ fs.HandleReader = (*File)(nil)
|
||||
|
||||
func (f *File) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
|
||||
t := f.content.Load().(string)
|
||||
fuseutil.HandleRead(req, resp, []byte(t))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *File) tick() {
|
||||
// Intentionally a variable-length format, to demonstrate size changes.
|
||||
f.count++
|
||||
s := fmt.Sprintf("%d\t%s\n", f.count, time.Now())
|
||||
f.content.Store(s)
|
||||
|
||||
// For simplicity, this example tries to send invalidate
|
||||
// notifications even when the kernel does not hold a reference to
|
||||
// the node, so be extra sure to ignore ErrNotCached.
|
||||
if err := f.fuse.InvalidateNodeData(f); err != nil && err != fuse.ErrNotCached {
|
||||
log.Printf("invalidate error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *File) update() {
|
||||
tick := time.NewTicker(1 * time.Second)
|
||||
defer tick.Stop()
|
||||
for range tick.C {
|
||||
f.tick()
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
// Hellofs implements a simple "hello world" file system.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
_ "bazil.org/fuse/fs/fstestutil"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, " %s MOUNTPOINT\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = Usage
|
||||
flag.Parse()
|
||||
|
||||
if flag.NArg() != 1 {
|
||||
Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
mountpoint := flag.Arg(0)
|
||||
|
||||
c, err := fuse.Mount(
|
||||
mountpoint,
|
||||
fuse.FSName("helloworld"),
|
||||
fuse.Subtype("hellofs"),
|
||||
fuse.LocalVolume(),
|
||||
fuse.VolumeName("Hello world!"),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
err = fs.Serve(c, FS{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// check if the mount process has an error to report
|
||||
<-c.Ready
|
||||
if err := c.MountError; err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// FS implements the hello world file system.
|
||||
type FS struct{}
|
||||
|
||||
func (FS) Root() (fs.Node, error) {
|
||||
return Dir{}, nil
|
||||
}
|
||||
|
||||
// Dir implements both Node and Handle for the root directory.
|
||||
type Dir struct{}
|
||||
|
||||
func (Dir) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
a.Inode = 1
|
||||
a.Mode = os.ModeDir | 0555
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||
if name == "hello" {
|
||||
return File{}, nil
|
||||
}
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
var dirDirs = []fuse.Dirent{
|
||||
{Inode: 2, Name: "hello", Type: fuse.DT_File},
|
||||
}
|
||||
|
||||
func (Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||
return dirDirs, nil
|
||||
}
|
||||
|
||||
// File implements both Node and Handle for the hello file.
|
||||
type File struct{}
|
||||
|
||||
const greeting = "hello, world\n"
|
||||
|
||||
func (File) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
a.Inode = 2
|
||||
a.Mode = 0444
|
||||
a.Size = uint64(len(greeting))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (File) ReadAll(ctx context.Context) ([]byte, error) {
|
||||
return []byte(greeting), nil
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
package bench_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"bazil.org/fuse/fs/fstestutil"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type benchConfig struct {
|
||||
directIO bool
|
||||
}
|
||||
|
||||
type benchFS struct {
|
||||
conf *benchConfig
|
||||
}
|
||||
|
||||
var _ = fs.FS(benchFS{})
|
||||
|
||||
func (f benchFS) Root() (fs.Node, error) {
|
||||
return benchDir{conf: f.conf}, nil
|
||||
}
|
||||
|
||||
type benchDir struct {
|
||||
conf *benchConfig
|
||||
}
|
||||
|
||||
var _ = fs.Node(benchDir{})
|
||||
var _ = fs.NodeStringLookuper(benchDir{})
|
||||
var _ = fs.Handle(benchDir{})
|
||||
var _ = fs.HandleReadDirAller(benchDir{})
|
||||
|
||||
func (benchDir) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
a.Inode = 1
|
||||
a.Mode = os.ModeDir | 0555
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d benchDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||
if name == "bench" {
|
||||
return benchFile{conf: d.conf}, nil
|
||||
}
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
func (benchDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||
l := []fuse.Dirent{
|
||||
{Inode: 2, Name: "bench", Type: fuse.DT_File},
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
type benchFile struct {
|
||||
conf *benchConfig
|
||||
}
|
||||
|
||||
var _ = fs.Node(benchFile{})
|
||||
var _ = fs.NodeOpener(benchFile{})
|
||||
var _ = fs.NodeFsyncer(benchFile{})
|
||||
var _ = fs.Handle(benchFile{})
|
||||
var _ = fs.HandleReader(benchFile{})
|
||||
var _ = fs.HandleWriter(benchFile{})
|
||||
|
||||
func (benchFile) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
a.Inode = 2
|
||||
a.Mode = 0644
|
||||
a.Size = 9999999999999999
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f benchFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
|
||||
if f.conf.directIO {
|
||||
resp.Flags |= fuse.OpenDirectIO
|
||||
}
|
||||
// TODO configurable?
|
||||
resp.Flags |= fuse.OpenKeepCache
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (benchFile) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
|
||||
resp.Data = resp.Data[:cap(resp.Data)]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (benchFile) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
|
||||
resp.Size = len(req.Data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (benchFile) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func benchmark(b *testing.B, fn func(b *testing.B, mnt string), conf *benchConfig) {
|
||||
filesys := benchFS{
|
||||
conf: conf,
|
||||
}
|
||||
mnt, err := fstestutil.Mounted(filesys, nil,
|
||||
fuse.MaxReadahead(64*1024*1024),
|
||||
fuse.AsyncRead(),
|
||||
fuse.WritebackCache(),
|
||||
)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
fn(b, mnt.Dir)
|
||||
}
|
||||
|
||||
type zero struct{}
|
||||
|
||||
func (zero) Read(p []byte) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
var Zero io.Reader = zero{}
|
||||
|
||||
func doWrites(size int64) func(b *testing.B, mnt string) {
|
||||
return func(b *testing.B, mnt string) {
|
||||
p := path.Join(mnt, "bench")
|
||||
|
||||
f, err := os.Create(p)
|
||||
if err != nil {
|
||||
b.Fatalf("create: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(size)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err = io.CopyN(f, Zero, size)
|
||||
if err != nil {
|
||||
b.Fatalf("write: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWrite100(b *testing.B) {
|
||||
benchmark(b, doWrites(100), &benchConfig{})
|
||||
}
|
||||
|
||||
func BenchmarkWrite10MB(b *testing.B) {
|
||||
benchmark(b, doWrites(10*1024*1024), &benchConfig{})
|
||||
}
|
||||
|
||||
func BenchmarkWrite100MB(b *testing.B) {
|
||||
benchmark(b, doWrites(100*1024*1024), &benchConfig{})
|
||||
}
|
||||
|
||||
func BenchmarkDirectWrite100(b *testing.B) {
|
||||
benchmark(b, doWrites(100), &benchConfig{
|
||||
directIO: true,
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDirectWrite10MB(b *testing.B) {
|
||||
benchmark(b, doWrites(10*1024*1024), &benchConfig{
|
||||
directIO: true,
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDirectWrite100MB(b *testing.B) {
|
||||
benchmark(b, doWrites(100*1024*1024), &benchConfig{
|
||||
directIO: true,
|
||||
})
|
||||
}
|
||||
|
||||
func doWritesSync(size int64) func(b *testing.B, mnt string) {
|
||||
return func(b *testing.B, mnt string) {
|
||||
p := path.Join(mnt, "bench")
|
||||
|
||||
f, err := os.Create(p)
|
||||
if err != nil {
|
||||
b.Fatalf("create: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(size)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err = io.CopyN(f, Zero, size)
|
||||
if err != nil {
|
||||
b.Fatalf("write: %v", err)
|
||||
}
|
||||
|
||||
if err := f.Sync(); err != nil {
|
||||
b.Fatalf("sync: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWriteSync100(b *testing.B) {
|
||||
benchmark(b, doWritesSync(100), &benchConfig{})
|
||||
}
|
||||
|
||||
func BenchmarkWriteSync10MB(b *testing.B) {
|
||||
benchmark(b, doWritesSync(10*1024*1024), &benchConfig{})
|
||||
}
|
||||
|
||||
func BenchmarkWriteSync100MB(b *testing.B) {
|
||||
benchmark(b, doWritesSync(100*1024*1024), &benchConfig{})
|
||||
}
|
||||
|
||||
func doReads(size int64) func(b *testing.B, mnt string) {
|
||||
return func(b *testing.B, mnt string) {
|
||||
p := path.Join(mnt, "bench")
|
||||
|
||||
f, err := os.Open(p)
|
||||
if err != nil {
|
||||
b.Fatalf("close: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(size)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
n, err := io.CopyN(ioutil.Discard, f, size)
|
||||
if err != nil {
|
||||
b.Fatalf("read: %v", err)
|
||||
}
|
||||
if n != size {
|
||||
b.Errorf("unexpected size: %d != %d", n, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRead100(b *testing.B) {
|
||||
benchmark(b, doReads(100), &benchConfig{})
|
||||
}
|
||||
|
||||
func BenchmarkRead10MB(b *testing.B) {
|
||||
benchmark(b, doReads(10*1024*1024), &benchConfig{})
|
||||
}
|
||||
|
||||
func BenchmarkRead100MB(b *testing.B) {
|
||||
benchmark(b, doReads(100*1024*1024), &benchConfig{})
|
||||
}
|
||||
|
||||
func BenchmarkDirectRead100(b *testing.B) {
|
||||
benchmark(b, doReads(100), &benchConfig{
|
||||
directIO: true,
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDirectRead10MB(b *testing.B) {
|
||||
benchmark(b, doReads(10*1024*1024), &benchConfig{
|
||||
directIO: true,
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDirectRead100MB(b *testing.B) {
|
||||
benchmark(b, doReads(100*1024*1024), &benchConfig{
|
||||
directIO: true,
|
||||
})
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// Package bench contains benchmarks.
|
||||
//
|
||||
// It is kept in a separate package to avoid conflicting with the
|
||||
// debug-heavy defaults for the actual tests.
|
||||
package bench
|
||||
@@ -0,0 +1,65 @@
|
||||
package fstestutil
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"bazil.org/fuse"
|
||||
)
|
||||
|
||||
type flagDebug bool
|
||||
|
||||
var debug flagDebug
|
||||
|
||||
var _ = flag.Value(&debug)
|
||||
|
||||
func (f *flagDebug) IsBoolFlag() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func nop(msg interface{}) {}
|
||||
|
||||
func (f *flagDebug) Set(s string) error {
|
||||
v, err := strconv.ParseBool(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f = flagDebug(v)
|
||||
if v {
|
||||
fuse.Debug = logMsg
|
||||
} else {
|
||||
fuse.Debug = nop
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *flagDebug) String() string {
|
||||
return strconv.FormatBool(bool(*f))
|
||||
}
|
||||
|
||||
func logMsg(msg interface{}) {
|
||||
log.Printf("FUSE: %s\n", msg)
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.Var(&debug, "fuse.debug", "log FUSE processing details")
|
||||
}
|
||||
|
||||
// DebugByDefault changes the default of the `-fuse.debug` flag to
|
||||
// true.
|
||||
//
|
||||
// This package registers a command line flag `-fuse.debug` and when
|
||||
// run with that flag (and activated inside the tests), logs FUSE
|
||||
// debug messages.
|
||||
//
|
||||
// This is disabled by default, as most callers probably won't care
|
||||
// about FUSE details. Use DebugByDefault for tests where you'd
|
||||
// normally be passing `-fuse.debug` all the time anyway.
|
||||
//
|
||||
// Call from an init function.
|
||||
func DebugByDefault() {
|
||||
f := flag.Lookup("fuse.debug")
|
||||
f.DefValue = "true"
|
||||
f.Value.Set(f.DefValue)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package fstestutil
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package fstestutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
)
|
||||
|
||||
// Mount contains information about the mount for the test to use.
|
||||
type Mount struct {
|
||||
// Dir is the temporary directory where the filesystem is mounted.
|
||||
Dir string
|
||||
|
||||
Conn *fuse.Conn
|
||||
Server *fs.Server
|
||||
|
||||
// Error will receive the return value of Serve.
|
||||
Error <-chan error
|
||||
|
||||
done <-chan struct{}
|
||||
closed bool
|
||||
}
|
||||
|
||||
// Close unmounts the filesystem and waits for fs.Serve to return. Any
|
||||
// returned error will be stored in Err. It is safe to call Close
|
||||
// multiple times.
|
||||
func (mnt *Mount) Close() {
|
||||
if mnt.closed {
|
||||
return
|
||||
}
|
||||
mnt.closed = true
|
||||
for tries := 0; tries < 1000; tries++ {
|
||||
err := fuse.Unmount(mnt.Dir)
|
||||
if err != nil {
|
||||
// TODO do more than log?
|
||||
log.Printf("unmount error: %v", err)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
<-mnt.done
|
||||
mnt.Conn.Close()
|
||||
os.Remove(mnt.Dir)
|
||||
}
|
||||
|
||||
// Mounted mounts the fuse.Server at a temporary directory.
|
||||
//
|
||||
// It also waits until the filesystem is known to be visible (OS X
|
||||
// workaround).
|
||||
//
|
||||
// After successful return, caller must clean up by calling Close.
|
||||
func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
|
||||
dir, err := ioutil.TempDir("", "fusetest")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := fuse.Mount(dir, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server := fs.New(c, conf)
|
||||
done := make(chan struct{})
|
||||
serveErr := make(chan error, 1)
|
||||
mnt := &Mount{
|
||||
Dir: dir,
|
||||
Conn: c,
|
||||
Server: server,
|
||||
Error: serveErr,
|
||||
done: done,
|
||||
}
|
||||
go func() {
|
||||
defer close(done)
|
||||
serveErr <- server.Serve(filesys)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-mnt.Conn.Ready:
|
||||
if err := mnt.Conn.MountError; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mnt, nil
|
||||
case err = <-mnt.Error:
|
||||
// Serve quit early
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errors.New("Serve exited early")
|
||||
}
|
||||
}
|
||||
|
||||
// MountedT mounts the filesystem at a temporary directory,
|
||||
// directing it's debug log to the testing logger.
|
||||
//
|
||||
// See Mounted for usage.
|
||||
//
|
||||
// The debug log is not enabled by default. Use `-fuse.debug` or call
|
||||
// DebugByDefault to enable.
|
||||
func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
|
||||
if conf == nil {
|
||||
conf = &fs.Config{}
|
||||
}
|
||||
if debug && conf.Debug == nil {
|
||||
conf.Debug = func(msg interface{}) {
|
||||
t.Logf("FUSE: %s", msg)
|
||||
}
|
||||
}
|
||||
return Mounted(filesys, conf, options...)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package fstestutil
|
||||
|
||||
// MountInfo describes a mounted file system.
|
||||
type MountInfo struct {
|
||||
FSName string
|
||||
Type string
|
||||
}
|
||||
|
||||
// GetMountInfo finds information about the mount at mnt. It is
|
||||
// intended for use by tests only, and only fetches information
|
||||
// relevant to the current tests.
|
||||
func GetMountInfo(mnt string) (*MountInfo, error) {
|
||||
return getMountInfo(mnt)
|
||||
}
|
||||
|
||||
// cstr converts a nil-terminated C string into a Go string
|
||||
func cstr(ca []int8) string {
|
||||
s := make([]byte, 0, len(ca))
|
||||
for _, c := range ca {
|
||||
if c == 0x00 {
|
||||
break
|
||||
}
|
||||
s = append(s, byte(c))
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package fstestutil
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var re = regexp.MustCompile(`\\(.)`)
|
||||
|
||||
// unescape removes backslash-escaping. The escaped characters are not
|
||||
// mapped in any way; that is, unescape(`\n` ) == `n`.
|
||||
func unescape(s string) string {
|
||||
return re.ReplaceAllString(s, `$1`)
|
||||
}
|
||||
|
||||
func getMountInfo(mnt string) (*MountInfo, error) {
|
||||
var st syscall.Statfs_t
|
||||
err := syscall.Statfs(mnt, &st)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := &MountInfo{
|
||||
// osx getmntent(3) fails to un-escape the data, so we do it..
|
||||
// this might lead to double-unescaping in the future. fun.
|
||||
// TestMountOptionFSNameEvilBackslashDouble checks for that.
|
||||
FSName: unescape(cstr(st.Mntfromname[:])),
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package fstestutil
|
||||
|
||||
import "errors"
|
||||
|
||||
func getMountInfo(mnt string) (*MountInfo, error) {
|
||||
return nil, errors.New("FreeBSD has no useful mount information")
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package fstestutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Linux /proc/mounts shows current mounts.
|
||||
// Same format as /etc/fstab. Quoting getmntent(3):
|
||||
//
|
||||
// Since fields in the mtab and fstab files are separated by whitespace,
|
||||
// octal escapes are used to represent the four characters space (\040),
|
||||
// tab (\011), newline (\012) and backslash (\134) in those files when
|
||||
// they occur in one of the four strings in a mntent structure.
|
||||
//
|
||||
// http://linux.die.net/man/3/getmntent
|
||||
|
||||
var fstabUnescape = strings.NewReplacer(
|
||||
`\040`, "\040",
|
||||
`\011`, "\011",
|
||||
`\012`, "\012",
|
||||
`\134`, "\134",
|
||||
)
|
||||
|
||||
var errNotFound = errors.New("mount not found")
|
||||
|
||||
func getMountInfo(mnt string) (*MountInfo, error) {
|
||||
data, err := ioutil.ReadFile("/proc/mounts")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 3 {
|
||||
continue
|
||||
}
|
||||
// Fields are: fsname dir type opts freq passno
|
||||
fsname := fstabUnescape.Replace(fields[0])
|
||||
dir := fstabUnescape.Replace(fields[1])
|
||||
fstype := fstabUnescape.Replace(fields[2])
|
||||
if mnt == dir {
|
||||
info := &MountInfo{
|
||||
FSName: fsname,
|
||||
Type: fstype,
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
}
|
||||
return nil, errNotFound
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package record
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Buffer is like bytes.Buffer but safe to access from multiple
|
||||
// goroutines.
|
||||
type Buffer struct {
|
||||
mu sync.Mutex
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
var _ = io.Writer(&Buffer{})
|
||||
|
||||
func (b *Buffer) Write(p []byte) (n int, err error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.buf.Write(p)
|
||||
}
|
||||
|
||||
func (b *Buffer) Bytes() []byte {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.buf.Bytes()
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
package record
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Writes gathers data from FUSE Write calls.
|
||||
type Writes struct {
|
||||
buf Buffer
|
||||
}
|
||||
|
||||
var _ = fs.HandleWriter(&Writes{})
|
||||
|
||||
func (w *Writes) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
|
||||
n, err := w.buf.Write(req.Data)
|
||||
resp.Size = n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Writes) RecordedWriteData() []byte {
|
||||
return w.buf.Bytes()
|
||||
}
|
||||
|
||||
// Counter records number of times a thing has occurred.
|
||||
type Counter struct {
|
||||
count uint32
|
||||
}
|
||||
|
||||
func (r *Counter) Inc() {
|
||||
atomic.AddUint32(&r.count, 1)
|
||||
}
|
||||
|
||||
func (r *Counter) Count() uint32 {
|
||||
return atomic.LoadUint32(&r.count)
|
||||
}
|
||||
|
||||
// MarkRecorder records whether a thing has occurred.
|
||||
type MarkRecorder struct {
|
||||
count Counter
|
||||
}
|
||||
|
||||
func (r *MarkRecorder) Mark() {
|
||||
r.count.Inc()
|
||||
}
|
||||
|
||||
func (r *MarkRecorder) Recorded() bool {
|
||||
return r.count.Count() > 0
|
||||
}
|
||||
|
||||
// Flushes notes whether a FUSE Flush call has been seen.
|
||||
type Flushes struct {
|
||||
rec MarkRecorder
|
||||
}
|
||||
|
||||
var _ = fs.HandleFlusher(&Flushes{})
|
||||
|
||||
func (r *Flushes) Flush(ctx context.Context, req *fuse.FlushRequest) error {
|
||||
r.rec.Mark()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Flushes) RecordedFlush() bool {
|
||||
return r.rec.Recorded()
|
||||
}
|
||||
|
||||
type Recorder struct {
|
||||
mu sync.Mutex
|
||||
val interface{}
|
||||
}
|
||||
|
||||
// Record that we've seen value. A nil value is indistinguishable from
|
||||
// no value recorded.
|
||||
func (r *Recorder) Record(value interface{}) {
|
||||
r.mu.Lock()
|
||||
r.val = value
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
func (r *Recorder) Recorded() interface{} {
|
||||
r.mu.Lock()
|
||||
val := r.val
|
||||
r.mu.Unlock()
|
||||
return val
|
||||
}
|
||||
|
||||
type RequestRecorder struct {
|
||||
rec Recorder
|
||||
}
|
||||
|
||||
// Record a fuse.Request, after zeroing header fields that are hard to
|
||||
// reproduce.
|
||||
//
|
||||
// Make sure to record a copy, not the original request.
|
||||
func (r *RequestRecorder) RecordRequest(req fuse.Request) {
|
||||
hdr := req.Hdr()
|
||||
*hdr = fuse.Header{}
|
||||
r.rec.Record(req)
|
||||
}
|
||||
|
||||
func (r *RequestRecorder) Recorded() fuse.Request {
|
||||
val := r.rec.Recorded()
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
return val.(fuse.Request)
|
||||
}
|
||||
|
||||
// Setattrs records a Setattr request and its fields.
|
||||
type Setattrs struct {
|
||||
rec RequestRecorder
|
||||
}
|
||||
|
||||
var _ = fs.NodeSetattrer(&Setattrs{})
|
||||
|
||||
func (r *Setattrs) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error {
|
||||
tmp := *req
|
||||
r.rec.RecordRequest(&tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Setattrs) RecordedSetattr() fuse.SetattrRequest {
|
||||
val := r.rec.Recorded()
|
||||
if val == nil {
|
||||
return fuse.SetattrRequest{}
|
||||
}
|
||||
return *(val.(*fuse.SetattrRequest))
|
||||
}
|
||||
|
||||
// Fsyncs records an Fsync request and its fields.
|
||||
type Fsyncs struct {
|
||||
rec RequestRecorder
|
||||
}
|
||||
|
||||
var _ = fs.NodeFsyncer(&Fsyncs{})
|
||||
|
||||
func (r *Fsyncs) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
|
||||
tmp := *req
|
||||
r.rec.RecordRequest(&tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Fsyncs) RecordedFsync() fuse.FsyncRequest {
|
||||
val := r.rec.Recorded()
|
||||
if val == nil {
|
||||
return fuse.FsyncRequest{}
|
||||
}
|
||||
return *(val.(*fuse.FsyncRequest))
|
||||
}
|
||||
|
||||
// Mkdirs records a Mkdir request and its fields.
|
||||
type Mkdirs struct {
|
||||
rec RequestRecorder
|
||||
}
|
||||
|
||||
var _ = fs.NodeMkdirer(&Mkdirs{})
|
||||
|
||||
// Mkdir records the request and returns an error. Most callers should
|
||||
// wrap this call in a function that returns a more useful result.
|
||||
func (r *Mkdirs) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
|
||||
tmp := *req
|
||||
r.rec.RecordRequest(&tmp)
|
||||
return nil, fuse.EIO
|
||||
}
|
||||
|
||||
// RecordedMkdir returns information about the Mkdir request.
|
||||
// If no request was seen, returns a zero value.
|
||||
func (r *Mkdirs) RecordedMkdir() fuse.MkdirRequest {
|
||||
val := r.rec.Recorded()
|
||||
if val == nil {
|
||||
return fuse.MkdirRequest{}
|
||||
}
|
||||
return *(val.(*fuse.MkdirRequest))
|
||||
}
|
||||
|
||||
// Symlinks records a Symlink request and its fields.
|
||||
type Symlinks struct {
|
||||
rec RequestRecorder
|
||||
}
|
||||
|
||||
var _ = fs.NodeSymlinker(&Symlinks{})
|
||||
|
||||
// Symlink records the request and returns an error. Most callers should
|
||||
// wrap this call in a function that returns a more useful result.
|
||||
func (r *Symlinks) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) {
|
||||
tmp := *req
|
||||
r.rec.RecordRequest(&tmp)
|
||||
return nil, fuse.EIO
|
||||
}
|
||||
|
||||
// RecordedSymlink returns information about the Symlink request.
|
||||
// If no request was seen, returns a zero value.
|
||||
func (r *Symlinks) RecordedSymlink() fuse.SymlinkRequest {
|
||||
val := r.rec.Recorded()
|
||||
if val == nil {
|
||||
return fuse.SymlinkRequest{}
|
||||
}
|
||||
return *(val.(*fuse.SymlinkRequest))
|
||||
}
|
||||
|
||||
// Links records a Link request and its fields.
|
||||
type Links struct {
|
||||
rec RequestRecorder
|
||||
}
|
||||
|
||||
var _ = fs.NodeLinker(&Links{})
|
||||
|
||||
// Link records the request and returns an error. Most callers should
|
||||
// wrap this call in a function that returns a more useful result.
|
||||
func (r *Links) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (fs.Node, error) {
|
||||
tmp := *req
|
||||
r.rec.RecordRequest(&tmp)
|
||||
return nil, fuse.EIO
|
||||
}
|
||||
|
||||
// RecordedLink returns information about the Link request.
|
||||
// If no request was seen, returns a zero value.
|
||||
func (r *Links) RecordedLink() fuse.LinkRequest {
|
||||
val := r.rec.Recorded()
|
||||
if val == nil {
|
||||
return fuse.LinkRequest{}
|
||||
}
|
||||
return *(val.(*fuse.LinkRequest))
|
||||
}
|
||||
|
||||
// Mknods records a Mknod request and its fields.
|
||||
type Mknods struct {
|
||||
rec RequestRecorder
|
||||
}
|
||||
|
||||
var _ = fs.NodeMknoder(&Mknods{})
|
||||
|
||||
// Mknod records the request and returns an error. Most callers should
|
||||
// wrap this call in a function that returns a more useful result.
|
||||
func (r *Mknods) Mknod(ctx context.Context, req *fuse.MknodRequest) (fs.Node, error) {
|
||||
tmp := *req
|
||||
r.rec.RecordRequest(&tmp)
|
||||
return nil, fuse.EIO
|
||||
}
|
||||
|
||||
// RecordedMknod returns information about the Mknod request.
|
||||
// If no request was seen, returns a zero value.
|
||||
func (r *Mknods) RecordedMknod() fuse.MknodRequest {
|
||||
val := r.rec.Recorded()
|
||||
if val == nil {
|
||||
return fuse.MknodRequest{}
|
||||
}
|
||||
return *(val.(*fuse.MknodRequest))
|
||||
}
|
||||
|
||||
// Opens records a Open request and its fields.
|
||||
type Opens struct {
|
||||
rec RequestRecorder
|
||||
}
|
||||
|
||||
var _ = fs.NodeOpener(&Opens{})
|
||||
|
||||
// Open records the request and returns an error. Most callers should
|
||||
// wrap this call in a function that returns a more useful result.
|
||||
func (r *Opens) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
|
||||
tmp := *req
|
||||
r.rec.RecordRequest(&tmp)
|
||||
return nil, fuse.EIO
|
||||
}
|
||||
|
||||
// RecordedOpen returns information about the Open request.
|
||||
// If no request was seen, returns a zero value.
|
||||
func (r *Opens) RecordedOpen() fuse.OpenRequest {
|
||||
val := r.rec.Recorded()
|
||||
if val == nil {
|
||||
return fuse.OpenRequest{}
|
||||
}
|
||||
return *(val.(*fuse.OpenRequest))
|
||||
}
|
||||
|
||||
// Getxattrs records a Getxattr request and its fields.
|
||||
type Getxattrs struct {
|
||||
rec RequestRecorder
|
||||
}
|
||||
|
||||
var _ = fs.NodeGetxattrer(&Getxattrs{})
|
||||
|
||||
// Getxattr records the request and returns an error. Most callers should
|
||||
// wrap this call in a function that returns a more useful result.
|
||||
func (r *Getxattrs) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
|
||||
tmp := *req
|
||||
r.rec.RecordRequest(&tmp)
|
||||
return fuse.ErrNoXattr
|
||||
}
|
||||
|
||||
// RecordedGetxattr returns information about the Getxattr request.
|
||||
// If no request was seen, returns a zero value.
|
||||
func (r *Getxattrs) RecordedGetxattr() fuse.GetxattrRequest {
|
||||
val := r.rec.Recorded()
|
||||
if val == nil {
|
||||
return fuse.GetxattrRequest{}
|
||||
}
|
||||
return *(val.(*fuse.GetxattrRequest))
|
||||
}
|
||||
|
||||
// Listxattrs records a Listxattr request and its fields.
|
||||
type Listxattrs struct {
|
||||
rec RequestRecorder
|
||||
}
|
||||
|
||||
var _ = fs.NodeListxattrer(&Listxattrs{})
|
||||
|
||||
// Listxattr records the request and returns an error. Most callers should
|
||||
// wrap this call in a function that returns a more useful result.
|
||||
func (r *Listxattrs) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
|
||||
tmp := *req
|
||||
r.rec.RecordRequest(&tmp)
|
||||
return fuse.ErrNoXattr
|
||||
}
|
||||
|
||||
// RecordedListxattr returns information about the Listxattr request.
|
||||
// If no request was seen, returns a zero value.
|
||||
func (r *Listxattrs) RecordedListxattr() fuse.ListxattrRequest {
|
||||
val := r.rec.Recorded()
|
||||
if val == nil {
|
||||
return fuse.ListxattrRequest{}
|
||||
}
|
||||
return *(val.(*fuse.ListxattrRequest))
|
||||
}
|
||||
|
||||
// Setxattrs records a Setxattr request and its fields.
|
||||
type Setxattrs struct {
|
||||
rec RequestRecorder
|
||||
}
|
||||
|
||||
var _ = fs.NodeSetxattrer(&Setxattrs{})
|
||||
|
||||
// Setxattr records the request and returns an error. Most callers should
|
||||
// wrap this call in a function that returns a more useful result.
|
||||
func (r *Setxattrs) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error {
|
||||
tmp := *req
|
||||
// The byte slice points to memory that will be reused, so make a
|
||||
// deep copy.
|
||||
tmp.Xattr = append([]byte(nil), req.Xattr...)
|
||||
r.rec.RecordRequest(&tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordedSetxattr returns information about the Setxattr request.
|
||||
// If no request was seen, returns a zero value.
|
||||
func (r *Setxattrs) RecordedSetxattr() fuse.SetxattrRequest {
|
||||
val := r.rec.Recorded()
|
||||
if val == nil {
|
||||
return fuse.SetxattrRequest{}
|
||||
}
|
||||
return *(val.(*fuse.SetxattrRequest))
|
||||
}
|
||||
|
||||
// Removexattrs records a Removexattr request and its fields.
|
||||
type Removexattrs struct {
|
||||
rec RequestRecorder
|
||||
}
|
||||
|
||||
var _ = fs.NodeRemovexattrer(&Removexattrs{})
|
||||
|
||||
// Removexattr records the request and returns an error. Most callers should
|
||||
// wrap this call in a function that returns a more useful result.
|
||||
func (r *Removexattrs) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error {
|
||||
tmp := *req
|
||||
r.rec.RecordRequest(&tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordedRemovexattr returns information about the Removexattr request.
|
||||
// If no request was seen, returns a zero value.
|
||||
func (r *Removexattrs) RecordedRemovexattr() fuse.RemovexattrRequest {
|
||||
val := r.rec.Recorded()
|
||||
if val == nil {
|
||||
return fuse.RemovexattrRequest{}
|
||||
}
|
||||
return *(val.(*fuse.RemovexattrRequest))
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package record
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type nothing struct{}
|
||||
|
||||
// ReleaseWaiter notes whether a FUSE Release call has been seen.
|
||||
//
|
||||
// Releases are not guaranteed to happen synchronously with any client
|
||||
// call, so they must be waited for.
|
||||
type ReleaseWaiter struct {
|
||||
once sync.Once
|
||||
seen chan nothing
|
||||
}
|
||||
|
||||
var _ = fs.HandleReleaser(&ReleaseWaiter{})
|
||||
|
||||
func (r *ReleaseWaiter) init() {
|
||||
r.once.Do(func() {
|
||||
r.seen = make(chan nothing, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *ReleaseWaiter) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
|
||||
r.init()
|
||||
close(r.seen)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForRelease waits for Release to be called.
|
||||
//
|
||||
// With zero duration, wait forever. Otherwise, timeout early
|
||||
// in a more controller way than `-test.timeout`.
|
||||
//
|
||||
// Returns whether a Release was seen. Always true if dur==0.
|
||||
func (r *ReleaseWaiter) WaitForRelease(dur time.Duration) bool {
|
||||
r.init()
|
||||
var timeout <-chan time.Time
|
||||
if dur > 0 {
|
||||
timeout = time.After(dur)
|
||||
}
|
||||
select {
|
||||
case <-r.seen:
|
||||
return true
|
||||
case <-timeout:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package fstestutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// SimpleFS is a trivial FS that just implements the Root method.
|
||||
type SimpleFS struct {
|
||||
Node fs.Node
|
||||
}
|
||||
|
||||
var _ = fs.FS(SimpleFS{})
|
||||
|
||||
func (f SimpleFS) Root() (fs.Node, error) {
|
||||
return f.Node, nil
|
||||
}
|
||||
|
||||
// File can be embedded in a struct to make it look like a file.
|
||||
type File struct{}
|
||||
|
||||
func (f File) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
a.Mode = 0666
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dir can be embedded in a struct to make it look like a directory.
|
||||
type Dir struct{}
|
||||
|
||||
func (f Dir) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
a.Mode = os.ModeDir | 0777
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChildMap is a directory with child nodes looked up from a map.
|
||||
type ChildMap map[string]fs.Node
|
||||
|
||||
var _ = fs.Node(&ChildMap{})
|
||||
var _ = fs.NodeStringLookuper(&ChildMap{})
|
||||
|
||||
func (f *ChildMap) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
a.Mode = os.ModeDir | 0777
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *ChildMap) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||
child, ok := (*f)[name]
|
||||
if !ok {
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
return child, nil
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package fs_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var childHelpers = map[string]func(){}
|
||||
|
||||
type childProcess struct {
|
||||
name string
|
||||
fn func()
|
||||
}
|
||||
|
||||
var _ flag.Value = (*childProcess)(nil)
|
||||
|
||||
func (c *childProcess) String() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c *childProcess) Set(s string) error {
|
||||
fn, ok := childHelpers[s]
|
||||
if !ok {
|
||||
return errors.New("helper not found")
|
||||
}
|
||||
c.name = s
|
||||
c.fn = fn
|
||||
return nil
|
||||
}
|
||||
|
||||
var childMode childProcess
|
||||
|
||||
func init() {
|
||||
flag.Var(&childMode, "fuse.internal.child", "internal use only")
|
||||
}
|
||||
|
||||
// childCmd prepares a test function to be run in a subprocess, with
|
||||
// childMode set to true. Caller must still call Run or Start.
|
||||
//
|
||||
// Re-using the test executable as the subprocess is useful because
|
||||
// now test executables can e.g. be cross-compiled, transferred
|
||||
// between hosts, and run in settings where the whole Go development
|
||||
// environment is not installed.
|
||||
func childCmd(childName string) (*exec.Cmd, error) {
|
||||
// caller may set cwd, so we can't rely on relative paths
|
||||
executable, err := filepath.Abs(os.Args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := exec.Command(executable, "-fuse.internal.child="+childName)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
if childMode.fn != nil {
|
||||
childMode.fn()
|
||||
os.Exit(0)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
+1567
File diff suppressed because it is too large
Load Diff
+2403
File diff suppressed because it is too large
Load Diff
Vendored
+99
@@ -0,0 +1,99 @@
|
||||
// FUSE directory tree, for servers that wish to use it with the service loop.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
pathpkg "path"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
import (
|
||||
"bazil.org/fuse"
|
||||
)
|
||||
|
||||
// A Tree implements a basic read-only directory tree for FUSE.
|
||||
// The Nodes contained in it may still be writable.
|
||||
type Tree struct {
|
||||
tree
|
||||
}
|
||||
|
||||
func (t *Tree) Root() (Node, error) {
|
||||
return &t.tree, nil
|
||||
}
|
||||
|
||||
// Add adds the path to the tree, resolving to the given node.
|
||||
// If path or a prefix of path has already been added to the tree,
|
||||
// Add panics.
|
||||
//
|
||||
// Add is only safe to call before starting to serve requests.
|
||||
func (t *Tree) Add(path string, node Node) {
|
||||
path = pathpkg.Clean("/" + path)[1:]
|
||||
elems := strings.Split(path, "/")
|
||||
dir := Node(&t.tree)
|
||||
for i, elem := range elems {
|
||||
dt, ok := dir.(*tree)
|
||||
if !ok {
|
||||
panic("fuse: Tree.Add for " + strings.Join(elems[:i], "/") + " and " + path)
|
||||
}
|
||||
n := dt.lookup(elem)
|
||||
if n != nil {
|
||||
if i+1 == len(elems) {
|
||||
panic("fuse: Tree.Add for " + path + " conflicts with " + elem)
|
||||
}
|
||||
dir = n
|
||||
} else {
|
||||
if i+1 == len(elems) {
|
||||
dt.add(elem, node)
|
||||
} else {
|
||||
dir = &tree{}
|
||||
dt.add(elem, dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type treeDir struct {
|
||||
name string
|
||||
node Node
|
||||
}
|
||||
|
||||
type tree struct {
|
||||
dir []treeDir
|
||||
}
|
||||
|
||||
func (t *tree) lookup(name string) Node {
|
||||
for _, d := range t.dir {
|
||||
if d.name == name {
|
||||
return d.node
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tree) add(name string, n Node) {
|
||||
t.dir = append(t.dir, treeDir{name, n})
|
||||
}
|
||||
|
||||
func (t *tree) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
a.Mode = os.ModeDir | 0555
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tree) Lookup(ctx context.Context, name string) (Node, error) {
|
||||
n := t.lookup(name)
|
||||
if n != nil {
|
||||
return n, nil
|
||||
}
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
func (t *tree) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||
var out []fuse.Dirent
|
||||
for _, d := range t.dir {
|
||||
out = append(out, fuse.Dirent{Name: d.name})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Vendored
+2193
File diff suppressed because it is too large
Load Diff
+766
@@ -0,0 +1,766 @@
|
||||
// See the file LICENSE for copyright and licensing information.
|
||||
|
||||
// Derived from FUSE's fuse_kernel.h, which carries this notice:
|
||||
/*
|
||||
This file defines the kernel interface of FUSE
|
||||
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
|
||||
This -- and only this -- header file may also be distributed under
|
||||
the terms of the BSD Licence as follows:
|
||||
|
||||
Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. 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.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
|
||||
*/
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// The FUSE version implemented by the package.
|
||||
const (
|
||||
protoVersionMinMajor = 7
|
||||
protoVersionMinMinor = 8
|
||||
protoVersionMaxMajor = 7
|
||||
protoVersionMaxMinor = 12
|
||||
)
|
||||
|
||||
const (
|
||||
rootID = 1
|
||||
)
|
||||
|
||||
type kstatfs struct {
|
||||
Blocks uint64
|
||||
Bfree uint64
|
||||
Bavail uint64
|
||||
Files uint64
|
||||
Ffree uint64
|
||||
Bsize uint32
|
||||
Namelen uint32
|
||||
Frsize uint32
|
||||
Padding uint32
|
||||
Spare [6]uint32
|
||||
}
|
||||
|
||||
type fileLock struct {
|
||||
Start uint64
|
||||
End uint64
|
||||
Type uint32
|
||||
Pid uint32
|
||||
}
|
||||
|
||||
// GetattrFlags are bit flags that can be seen in GetattrRequest.
|
||||
type GetattrFlags uint32
|
||||
|
||||
const (
|
||||
// Indicates the handle is valid.
|
||||
GetattrFh GetattrFlags = 1 << 0
|
||||
)
|
||||
|
||||
var getattrFlagsNames = []flagName{
|
||||
{uint32(GetattrFh), "GetattrFh"},
|
||||
}
|
||||
|
||||
func (fl GetattrFlags) String() string {
|
||||
return flagString(uint32(fl), getattrFlagsNames)
|
||||
}
|
||||
|
||||
// The SetattrValid are bit flags describing which fields in the SetattrRequest
|
||||
// are included in the change.
|
||||
type SetattrValid uint32
|
||||
|
||||
const (
|
||||
SetattrMode SetattrValid = 1 << 0
|
||||
SetattrUid SetattrValid = 1 << 1
|
||||
SetattrGid SetattrValid = 1 << 2
|
||||
SetattrSize SetattrValid = 1 << 3
|
||||
SetattrAtime SetattrValid = 1 << 4
|
||||
SetattrMtime SetattrValid = 1 << 5
|
||||
SetattrHandle SetattrValid = 1 << 6
|
||||
|
||||
// Linux only(?)
|
||||
SetattrAtimeNow SetattrValid = 1 << 7
|
||||
SetattrMtimeNow SetattrValid = 1 << 8
|
||||
SetattrLockOwner SetattrValid = 1 << 9 // http://www.mail-archive.com/git-commits-head@vger.kernel.org/msg27852.html
|
||||
|
||||
// OS X only
|
||||
SetattrCrtime SetattrValid = 1 << 28
|
||||
SetattrChgtime SetattrValid = 1 << 29
|
||||
SetattrBkuptime SetattrValid = 1 << 30
|
||||
SetattrFlags SetattrValid = 1 << 31
|
||||
)
|
||||
|
||||
func (fl SetattrValid) Mode() bool { return fl&SetattrMode != 0 }
|
||||
func (fl SetattrValid) Uid() bool { return fl&SetattrUid != 0 }
|
||||
func (fl SetattrValid) Gid() bool { return fl&SetattrGid != 0 }
|
||||
func (fl SetattrValid) Size() bool { return fl&SetattrSize != 0 }
|
||||
func (fl SetattrValid) Atime() bool { return fl&SetattrAtime != 0 }
|
||||
func (fl SetattrValid) Mtime() bool { return fl&SetattrMtime != 0 }
|
||||
func (fl SetattrValid) Handle() bool { return fl&SetattrHandle != 0 }
|
||||
func (fl SetattrValid) AtimeNow() bool { return fl&SetattrAtimeNow != 0 }
|
||||
func (fl SetattrValid) MtimeNow() bool { return fl&SetattrMtimeNow != 0 }
|
||||
func (fl SetattrValid) LockOwner() bool { return fl&SetattrLockOwner != 0 }
|
||||
func (fl SetattrValid) Crtime() bool { return fl&SetattrCrtime != 0 }
|
||||
func (fl SetattrValid) Chgtime() bool { return fl&SetattrChgtime != 0 }
|
||||
func (fl SetattrValid) Bkuptime() bool { return fl&SetattrBkuptime != 0 }
|
||||
func (fl SetattrValid) Flags() bool { return fl&SetattrFlags != 0 }
|
||||
|
||||
func (fl SetattrValid) String() string {
|
||||
return flagString(uint32(fl), setattrValidNames)
|
||||
}
|
||||
|
||||
var setattrValidNames = []flagName{
|
||||
{uint32(SetattrMode), "SetattrMode"},
|
||||
{uint32(SetattrUid), "SetattrUid"},
|
||||
{uint32(SetattrGid), "SetattrGid"},
|
||||
{uint32(SetattrSize), "SetattrSize"},
|
||||
{uint32(SetattrAtime), "SetattrAtime"},
|
||||
{uint32(SetattrMtime), "SetattrMtime"},
|
||||
{uint32(SetattrHandle), "SetattrHandle"},
|
||||
{uint32(SetattrAtimeNow), "SetattrAtimeNow"},
|
||||
{uint32(SetattrMtimeNow), "SetattrMtimeNow"},
|
||||
{uint32(SetattrLockOwner), "SetattrLockOwner"},
|
||||
{uint32(SetattrCrtime), "SetattrCrtime"},
|
||||
{uint32(SetattrChgtime), "SetattrChgtime"},
|
||||
{uint32(SetattrBkuptime), "SetattrBkuptime"},
|
||||
{uint32(SetattrFlags), "SetattrFlags"},
|
||||
}
|
||||
|
||||
// Flags that can be seen in OpenRequest.Flags.
|
||||
const (
|
||||
// Access modes. These are not 1-bit flags, but alternatives where
|
||||
// only one can be chosen. See the IsReadOnly etc convenience
|
||||
// methods.
|
||||
OpenReadOnly OpenFlags = syscall.O_RDONLY
|
||||
OpenWriteOnly OpenFlags = syscall.O_WRONLY
|
||||
OpenReadWrite OpenFlags = syscall.O_RDWR
|
||||
|
||||
OpenAppend OpenFlags = syscall.O_APPEND
|
||||
OpenCreate OpenFlags = syscall.O_CREAT
|
||||
OpenExclusive OpenFlags = syscall.O_EXCL
|
||||
OpenSync OpenFlags = syscall.O_SYNC
|
||||
OpenTruncate OpenFlags = syscall.O_TRUNC
|
||||
)
|
||||
|
||||
// OpenAccessModeMask is a bitmask that separates the access mode
|
||||
// from the other flags in OpenFlags.
|
||||
const OpenAccessModeMask OpenFlags = syscall.O_ACCMODE
|
||||
|
||||
// OpenFlags are the O_FOO flags passed to open/create/etc calls. For
|
||||
// example, os.O_WRONLY | os.O_APPEND.
|
||||
type OpenFlags uint32
|
||||
|
||||
func (fl OpenFlags) String() string {
|
||||
// O_RDONLY, O_RWONLY, O_RDWR are not flags
|
||||
s := accModeName(fl & OpenAccessModeMask)
|
||||
flags := uint32(fl &^ OpenAccessModeMask)
|
||||
if flags != 0 {
|
||||
s = s + "+" + flagString(flags, openFlagNames)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Return true if OpenReadOnly is set.
|
||||
func (fl OpenFlags) IsReadOnly() bool {
|
||||
return fl&OpenAccessModeMask == OpenReadOnly
|
||||
}
|
||||
|
||||
// Return true if OpenWriteOnly is set.
|
||||
func (fl OpenFlags) IsWriteOnly() bool {
|
||||
return fl&OpenAccessModeMask == OpenWriteOnly
|
||||
}
|
||||
|
||||
// Return true if OpenReadWrite is set.
|
||||
func (fl OpenFlags) IsReadWrite() bool {
|
||||
return fl&OpenAccessModeMask == OpenReadWrite
|
||||
}
|
||||
|
||||
func accModeName(flags OpenFlags) string {
|
||||
switch flags {
|
||||
case OpenReadOnly:
|
||||
return "OpenReadOnly"
|
||||
case OpenWriteOnly:
|
||||
return "OpenWriteOnly"
|
||||
case OpenReadWrite:
|
||||
return "OpenReadWrite"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
var openFlagNames = []flagName{
|
||||
{uint32(OpenCreate), "OpenCreate"},
|
||||
{uint32(OpenExclusive), "OpenExclusive"},
|
||||
{uint32(OpenTruncate), "OpenTruncate"},
|
||||
{uint32(OpenAppend), "OpenAppend"},
|
||||
{uint32(OpenSync), "OpenSync"},
|
||||
}
|
||||
|
||||
// The OpenResponseFlags are returned in the OpenResponse.
|
||||
type OpenResponseFlags uint32
|
||||
|
||||
const (
|
||||
OpenDirectIO OpenResponseFlags = 1 << 0 // bypass page cache for this open file
|
||||
OpenKeepCache OpenResponseFlags = 1 << 1 // don't invalidate the data cache on open
|
||||
OpenNonSeekable OpenResponseFlags = 1 << 2 // mark the file as non-seekable (not supported on OS X)
|
||||
|
||||
OpenPurgeAttr OpenResponseFlags = 1 << 30 // OS X
|
||||
OpenPurgeUBC OpenResponseFlags = 1 << 31 // OS X
|
||||
)
|
||||
|
||||
func (fl OpenResponseFlags) String() string {
|
||||
return flagString(uint32(fl), openResponseFlagNames)
|
||||
}
|
||||
|
||||
var openResponseFlagNames = []flagName{
|
||||
{uint32(OpenDirectIO), "OpenDirectIO"},
|
||||
{uint32(OpenKeepCache), "OpenKeepCache"},
|
||||
{uint32(OpenNonSeekable), "OpenNonSeekable"},
|
||||
{uint32(OpenPurgeAttr), "OpenPurgeAttr"},
|
||||
{uint32(OpenPurgeUBC), "OpenPurgeUBC"},
|
||||
}
|
||||
|
||||
// The InitFlags are used in the Init exchange.
|
||||
type InitFlags uint32
|
||||
|
||||
const (
|
||||
InitAsyncRead InitFlags = 1 << 0
|
||||
InitPosixLocks InitFlags = 1 << 1
|
||||
InitFileOps InitFlags = 1 << 2
|
||||
InitAtomicTrunc InitFlags = 1 << 3
|
||||
InitExportSupport InitFlags = 1 << 4
|
||||
InitBigWrites InitFlags = 1 << 5
|
||||
InitDontMask InitFlags = 1 << 6
|
||||
InitSpliceWrite InitFlags = 1 << 7
|
||||
InitSpliceMove InitFlags = 1 << 8
|
||||
InitSpliceRead InitFlags = 1 << 9
|
||||
InitFlockLocks InitFlags = 1 << 10
|
||||
InitHasIoctlDir InitFlags = 1 << 11
|
||||
InitAutoInvalData InitFlags = 1 << 12
|
||||
InitDoReaddirplus InitFlags = 1 << 13
|
||||
InitReaddirplusAuto InitFlags = 1 << 14
|
||||
InitAsyncDIO InitFlags = 1 << 15
|
||||
InitWritebackCache InitFlags = 1 << 16
|
||||
InitNoOpenSupport InitFlags = 1 << 17
|
||||
|
||||
InitCaseSensitive InitFlags = 1 << 29 // OS X only
|
||||
InitVolRename InitFlags = 1 << 30 // OS X only
|
||||
InitXtimes InitFlags = 1 << 31 // OS X only
|
||||
)
|
||||
|
||||
type flagName struct {
|
||||
bit uint32
|
||||
name string
|
||||
}
|
||||
|
||||
var initFlagNames = []flagName{
|
||||
{uint32(InitAsyncRead), "InitAsyncRead"},
|
||||
{uint32(InitPosixLocks), "InitPosixLocks"},
|
||||
{uint32(InitFileOps), "InitFileOps"},
|
||||
{uint32(InitAtomicTrunc), "InitAtomicTrunc"},
|
||||
{uint32(InitExportSupport), "InitExportSupport"},
|
||||
{uint32(InitBigWrites), "InitBigWrites"},
|
||||
{uint32(InitDontMask), "InitDontMask"},
|
||||
{uint32(InitSpliceWrite), "InitSpliceWrite"},
|
||||
{uint32(InitSpliceMove), "InitSpliceMove"},
|
||||
{uint32(InitSpliceRead), "InitSpliceRead"},
|
||||
{uint32(InitFlockLocks), "InitFlockLocks"},
|
||||
{uint32(InitHasIoctlDir), "InitHasIoctlDir"},
|
||||
{uint32(InitAutoInvalData), "InitAutoInvalData"},
|
||||
{uint32(InitDoReaddirplus), "InitDoReaddirplus"},
|
||||
{uint32(InitReaddirplusAuto), "InitReaddirplusAuto"},
|
||||
{uint32(InitAsyncDIO), "InitAsyncDIO"},
|
||||
{uint32(InitWritebackCache), "InitWritebackCache"},
|
||||
{uint32(InitNoOpenSupport), "InitNoOpenSupport"},
|
||||
|
||||
{uint32(InitCaseSensitive), "InitCaseSensitive"},
|
||||
{uint32(InitVolRename), "InitVolRename"},
|
||||
{uint32(InitXtimes), "InitXtimes"},
|
||||
}
|
||||
|
||||
func (fl InitFlags) String() string {
|
||||
return flagString(uint32(fl), initFlagNames)
|
||||
}
|
||||
|
||||
func flagString(f uint32, names []flagName) string {
|
||||
var s string
|
||||
|
||||
if f == 0 {
|
||||
return "0"
|
||||
}
|
||||
|
||||
for _, n := range names {
|
||||
if f&n.bit != 0 {
|
||||
s += "+" + n.name
|
||||
f &^= n.bit
|
||||
}
|
||||
}
|
||||
if f != 0 {
|
||||
s += fmt.Sprintf("%+#x", f)
|
||||
}
|
||||
return s[1:]
|
||||
}
|
||||
|
||||
// The ReleaseFlags are used in the Release exchange.
|
||||
type ReleaseFlags uint32
|
||||
|
||||
const (
|
||||
ReleaseFlush ReleaseFlags = 1 << 0
|
||||
)
|
||||
|
||||
func (fl ReleaseFlags) String() string {
|
||||
return flagString(uint32(fl), releaseFlagNames)
|
||||
}
|
||||
|
||||
var releaseFlagNames = []flagName{
|
||||
{uint32(ReleaseFlush), "ReleaseFlush"},
|
||||
}
|
||||
|
||||
// Opcodes
|
||||
const (
|
||||
opLookup = 1
|
||||
opForget = 2 // no reply
|
||||
opGetattr = 3
|
||||
opSetattr = 4
|
||||
opReadlink = 5
|
||||
opSymlink = 6
|
||||
opMknod = 8
|
||||
opMkdir = 9
|
||||
opUnlink = 10
|
||||
opRmdir = 11
|
||||
opRename = 12
|
||||
opLink = 13
|
||||
opOpen = 14
|
||||
opRead = 15
|
||||
opWrite = 16
|
||||
opStatfs = 17
|
||||
opRelease = 18
|
||||
opFsync = 20
|
||||
opSetxattr = 21
|
||||
opGetxattr = 22
|
||||
opListxattr = 23
|
||||
opRemovexattr = 24
|
||||
opFlush = 25
|
||||
opInit = 26
|
||||
opOpendir = 27
|
||||
opReaddir = 28
|
||||
opReleasedir = 29
|
||||
opFsyncdir = 30
|
||||
opGetlk = 31
|
||||
opSetlk = 32
|
||||
opSetlkw = 33
|
||||
opAccess = 34
|
||||
opCreate = 35
|
||||
opInterrupt = 36
|
||||
opBmap = 37
|
||||
opDestroy = 38
|
||||
opIoctl = 39 // Linux?
|
||||
opPoll = 40 // Linux?
|
||||
|
||||
// OS X
|
||||
opSetvolname = 61
|
||||
opGetxtimes = 62
|
||||
opExchange = 63
|
||||
)
|
||||
|
||||
type entryOut struct {
|
||||
Nodeid uint64 // Inode ID
|
||||
Generation uint64 // Inode generation
|
||||
EntryValid uint64 // Cache timeout for the name
|
||||
AttrValid uint64 // Cache timeout for the attributes
|
||||
EntryValidNsec uint32
|
||||
AttrValidNsec uint32
|
||||
Attr attr
|
||||
}
|
||||
|
||||
func entryOutSize(p Protocol) uintptr {
|
||||
switch {
|
||||
case p.LT(Protocol{7, 9}):
|
||||
return unsafe.Offsetof(entryOut{}.Attr) + unsafe.Offsetof(entryOut{}.Attr.Blksize)
|
||||
default:
|
||||
return unsafe.Sizeof(entryOut{})
|
||||
}
|
||||
}
|
||||
|
||||
type forgetIn struct {
|
||||
Nlookup uint64
|
||||
}
|
||||
|
||||
type getattrIn struct {
|
||||
GetattrFlags uint32
|
||||
dummy uint32
|
||||
Fh uint64
|
||||
}
|
||||
|
||||
type attrOut struct {
|
||||
AttrValid uint64 // Cache timeout for the attributes
|
||||
AttrValidNsec uint32
|
||||
Dummy uint32
|
||||
Attr attr
|
||||
}
|
||||
|
||||
func attrOutSize(p Protocol) uintptr {
|
||||
switch {
|
||||
case p.LT(Protocol{7, 9}):
|
||||
return unsafe.Offsetof(attrOut{}.Attr) + unsafe.Offsetof(attrOut{}.Attr.Blksize)
|
||||
default:
|
||||
return unsafe.Sizeof(attrOut{})
|
||||
}
|
||||
}
|
||||
|
||||
// OS X
|
||||
type getxtimesOut struct {
|
||||
Bkuptime uint64
|
||||
Crtime uint64
|
||||
BkuptimeNsec uint32
|
||||
CrtimeNsec uint32
|
||||
}
|
||||
|
||||
type mknodIn struct {
|
||||
Mode uint32
|
||||
Rdev uint32
|
||||
Umask uint32
|
||||
padding uint32
|
||||
// "filename\x00" follows.
|
||||
}
|
||||
|
||||
func mknodInSize(p Protocol) uintptr {
|
||||
switch {
|
||||
case p.LT(Protocol{7, 12}):
|
||||
return unsafe.Offsetof(mknodIn{}.Umask)
|
||||
default:
|
||||
return unsafe.Sizeof(mknodIn{})
|
||||
}
|
||||
}
|
||||
|
||||
type mkdirIn struct {
|
||||
Mode uint32
|
||||
Umask uint32
|
||||
// filename follows
|
||||
}
|
||||
|
||||
func mkdirInSize(p Protocol) uintptr {
|
||||
switch {
|
||||
case p.LT(Protocol{7, 12}):
|
||||
return unsafe.Offsetof(mkdirIn{}.Umask) + 4
|
||||
default:
|
||||
return unsafe.Sizeof(mkdirIn{})
|
||||
}
|
||||
}
|
||||
|
||||
type renameIn struct {
|
||||
Newdir uint64
|
||||
// "oldname\x00newname\x00" follows
|
||||
}
|
||||
|
||||
// OS X
|
||||
type exchangeIn struct {
|
||||
Olddir uint64
|
||||
Newdir uint64
|
||||
Options uint64
|
||||
}
|
||||
|
||||
type linkIn struct {
|
||||
Oldnodeid uint64
|
||||
}
|
||||
|
||||
type setattrInCommon struct {
|
||||
Valid uint32
|
||||
Padding uint32
|
||||
Fh uint64
|
||||
Size uint64
|
||||
LockOwner uint64 // unused on OS X?
|
||||
Atime uint64
|
||||
Mtime uint64
|
||||
Unused2 uint64
|
||||
AtimeNsec uint32
|
||||
MtimeNsec uint32
|
||||
Unused3 uint32
|
||||
Mode uint32
|
||||
Unused4 uint32
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
Unused5 uint32
|
||||
}
|
||||
|
||||
type openIn struct {
|
||||
Flags uint32
|
||||
Unused uint32
|
||||
}
|
||||
|
||||
type openOut struct {
|
||||
Fh uint64
|
||||
OpenFlags uint32
|
||||
Padding uint32
|
||||
}
|
||||
|
||||
type createIn struct {
|
||||
Flags uint32
|
||||
Mode uint32
|
||||
Umask uint32
|
||||
padding uint32
|
||||
}
|
||||
|
||||
func createInSize(p Protocol) uintptr {
|
||||
switch {
|
||||
case p.LT(Protocol{7, 12}):
|
||||
return unsafe.Offsetof(createIn{}.Umask)
|
||||
default:
|
||||
return unsafe.Sizeof(createIn{})
|
||||
}
|
||||
}
|
||||
|
||||
type releaseIn struct {
|
||||
Fh uint64
|
||||
Flags uint32
|
||||
ReleaseFlags uint32
|
||||
LockOwner uint32
|
||||
}
|
||||
|
||||
type flushIn struct {
|
||||
Fh uint64
|
||||
FlushFlags uint32
|
||||
Padding uint32
|
||||
LockOwner uint64
|
||||
}
|
||||
|
||||
type readIn struct {
|
||||
Fh uint64
|
||||
Offset uint64
|
||||
Size uint32
|
||||
ReadFlags uint32
|
||||
LockOwner uint64
|
||||
Flags uint32
|
||||
padding uint32
|
||||
}
|
||||
|
||||
func readInSize(p Protocol) uintptr {
|
||||
switch {
|
||||
case p.LT(Protocol{7, 9}):
|
||||
return unsafe.Offsetof(readIn{}.ReadFlags) + 4
|
||||
default:
|
||||
return unsafe.Sizeof(readIn{})
|
||||
}
|
||||
}
|
||||
|
||||
// The ReadFlags are passed in ReadRequest.
|
||||
type ReadFlags uint32
|
||||
|
||||
const (
|
||||
// LockOwner field is valid.
|
||||
ReadLockOwner ReadFlags = 1 << 1
|
||||
)
|
||||
|
||||
var readFlagNames = []flagName{
|
||||
{uint32(ReadLockOwner), "ReadLockOwner"},
|
||||
}
|
||||
|
||||
func (fl ReadFlags) String() string {
|
||||
return flagString(uint32(fl), readFlagNames)
|
||||
}
|
||||
|
||||
type writeIn struct {
|
||||
Fh uint64
|
||||
Offset uint64
|
||||
Size uint32
|
||||
WriteFlags uint32
|
||||
LockOwner uint64
|
||||
Flags uint32
|
||||
padding uint32
|
||||
}
|
||||
|
||||
func writeInSize(p Protocol) uintptr {
|
||||
switch {
|
||||
case p.LT(Protocol{7, 9}):
|
||||
return unsafe.Offsetof(writeIn{}.LockOwner)
|
||||
default:
|
||||
return unsafe.Sizeof(writeIn{})
|
||||
}
|
||||
}
|
||||
|
||||
type writeOut struct {
|
||||
Size uint32
|
||||
Padding uint32
|
||||
}
|
||||
|
||||
// The WriteFlags are passed in WriteRequest.
|
||||
type WriteFlags uint32
|
||||
|
||||
const (
|
||||
WriteCache WriteFlags = 1 << 0
|
||||
// LockOwner field is valid.
|
||||
WriteLockOwner WriteFlags = 1 << 1
|
||||
)
|
||||
|
||||
var writeFlagNames = []flagName{
|
||||
{uint32(WriteCache), "WriteCache"},
|
||||
{uint32(WriteLockOwner), "WriteLockOwner"},
|
||||
}
|
||||
|
||||
func (fl WriteFlags) String() string {
|
||||
return flagString(uint32(fl), writeFlagNames)
|
||||
}
|
||||
|
||||
const compatStatfsSize = 48
|
||||
|
||||
type statfsOut struct {
|
||||
St kstatfs
|
||||
}
|
||||
|
||||
type fsyncIn struct {
|
||||
Fh uint64
|
||||
FsyncFlags uint32
|
||||
Padding uint32
|
||||
}
|
||||
|
||||
type setxattrInCommon struct {
|
||||
Size uint32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
func (setxattrInCommon) position() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
type getxattrInCommon struct {
|
||||
Size uint32
|
||||
Padding uint32
|
||||
}
|
||||
|
||||
func (getxattrInCommon) position() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
type getxattrOut struct {
|
||||
Size uint32
|
||||
Padding uint32
|
||||
}
|
||||
|
||||
type lkIn struct {
|
||||
Fh uint64
|
||||
Owner uint64
|
||||
Lk fileLock
|
||||
LkFlags uint32
|
||||
padding uint32
|
||||
}
|
||||
|
||||
func lkInSize(p Protocol) uintptr {
|
||||
switch {
|
||||
case p.LT(Protocol{7, 9}):
|
||||
return unsafe.Offsetof(lkIn{}.LkFlags)
|
||||
default:
|
||||
return unsafe.Sizeof(lkIn{})
|
||||
}
|
||||
}
|
||||
|
||||
type lkOut struct {
|
||||
Lk fileLock
|
||||
}
|
||||
|
||||
type accessIn struct {
|
||||
Mask uint32
|
||||
Padding uint32
|
||||
}
|
||||
|
||||
type initIn struct {
|
||||
Major uint32
|
||||
Minor uint32
|
||||
MaxReadahead uint32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
const initInSize = int(unsafe.Sizeof(initIn{}))
|
||||
|
||||
type initOut struct {
|
||||
Major uint32
|
||||
Minor uint32
|
||||
MaxReadahead uint32
|
||||
Flags uint32
|
||||
Unused uint32
|
||||
MaxWrite uint32
|
||||
}
|
||||
|
||||
type interruptIn struct {
|
||||
Unique uint64
|
||||
}
|
||||
|
||||
type bmapIn struct {
|
||||
Block uint64
|
||||
BlockSize uint32
|
||||
Padding uint32
|
||||
}
|
||||
|
||||
type bmapOut struct {
|
||||
Block uint64
|
||||
}
|
||||
|
||||
type inHeader struct {
|
||||
Len uint32
|
||||
Opcode uint32
|
||||
Unique uint64
|
||||
Nodeid uint64
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
Pid uint32
|
||||
Padding uint32
|
||||
}
|
||||
|
||||
const inHeaderSize = int(unsafe.Sizeof(inHeader{}))
|
||||
|
||||
type outHeader struct {
|
||||
Len uint32
|
||||
Error int32
|
||||
Unique uint64
|
||||
}
|
||||
|
||||
type dirent struct {
|
||||
Ino uint64
|
||||
Off uint64
|
||||
Namelen uint32
|
||||
Type uint32
|
||||
Name [0]byte
|
||||
}
|
||||
|
||||
const direntSize = 8 + 8 + 4 + 4
|
||||
|
||||
const (
|
||||
notifyCodePoll int32 = 1
|
||||
notifyCodeInvalInode int32 = 2
|
||||
notifyCodeInvalEntry int32 = 3
|
||||
)
|
||||
|
||||
type notifyInvalInodeOut struct {
|
||||
Ino uint64
|
||||
Off int64
|
||||
Len int64
|
||||
}
|
||||
|
||||
type notifyInvalEntryOut struct {
|
||||
Parent uint64
|
||||
Namelen uint32
|
||||
padding uint32
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type attr struct {
|
||||
Ino uint64
|
||||
Size uint64
|
||||
Blocks uint64
|
||||
Atime uint64
|
||||
Mtime uint64
|
||||
Ctime uint64
|
||||
Crtime_ uint64 // OS X only
|
||||
AtimeNsec uint32
|
||||
MtimeNsec uint32
|
||||
CtimeNsec uint32
|
||||
CrtimeNsec uint32 // OS X only
|
||||
Mode uint32
|
||||
Nlink uint32
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
Rdev uint32
|
||||
Flags_ uint32 // OS X only; see chflags(2)
|
||||
Blksize uint32
|
||||
padding uint32
|
||||
}
|
||||
|
||||
func (a *attr) SetCrtime(s uint64, ns uint32) {
|
||||
a.Crtime_, a.CrtimeNsec = s, ns
|
||||
}
|
||||
|
||||
func (a *attr) SetFlags(f uint32) {
|
||||
a.Flags_ = f
|
||||
}
|
||||
|
||||
type setattrIn struct {
|
||||
setattrInCommon
|
||||
|
||||
// OS X only
|
||||
Bkuptime_ uint64
|
||||
Chgtime_ uint64
|
||||
Crtime uint64
|
||||
BkuptimeNsec uint32
|
||||
ChgtimeNsec uint32
|
||||
CrtimeNsec uint32
|
||||
Flags_ uint32 // see chflags(2)
|
||||
}
|
||||
|
||||
func (in *setattrIn) BkupTime() time.Time {
|
||||
return time.Unix(int64(in.Bkuptime_), int64(in.BkuptimeNsec))
|
||||
}
|
||||
|
||||
func (in *setattrIn) Chgtime() time.Time {
|
||||
return time.Unix(int64(in.Chgtime_), int64(in.ChgtimeNsec))
|
||||
}
|
||||
|
||||
func (in *setattrIn) Flags() uint32 {
|
||||
return in.Flags_
|
||||
}
|
||||
|
||||
func openFlags(flags uint32) OpenFlags {
|
||||
return OpenFlags(flags)
|
||||
}
|
||||
|
||||
type getxattrIn struct {
|
||||
getxattrInCommon
|
||||
|
||||
// OS X only
|
||||
Position uint32
|
||||
Padding uint32
|
||||
}
|
||||
|
||||
func (g *getxattrIn) position() uint32 {
|
||||
return g.Position
|
||||
}
|
||||
|
||||
type setxattrIn struct {
|
||||
setxattrInCommon
|
||||
|
||||
// OS X only
|
||||
Position uint32
|
||||
Padding uint32
|
||||
}
|
||||
|
||||
func (s *setxattrIn) position() uint32 {
|
||||
return s.Position
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package fuse
|
||||
|
||||
import "time"
|
||||
|
||||
type attr struct {
|
||||
Ino uint64
|
||||
Size uint64
|
||||
Blocks uint64
|
||||
Atime uint64
|
||||
Mtime uint64
|
||||
Ctime uint64
|
||||
AtimeNsec uint32
|
||||
MtimeNsec uint32
|
||||
CtimeNsec uint32
|
||||
Mode uint32
|
||||
Nlink uint32
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
Rdev uint32
|
||||
Blksize uint32
|
||||
padding uint32
|
||||
}
|
||||
|
||||
func (a *attr) Crtime() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (a *attr) SetCrtime(s uint64, ns uint32) {
|
||||
// ignored on freebsd
|
||||
}
|
||||
|
||||
func (a *attr) SetFlags(f uint32) {
|
||||
// ignored on freebsd
|
||||
}
|
||||
|
||||
type setattrIn struct {
|
||||
setattrInCommon
|
||||
}
|
||||
|
||||
func (in *setattrIn) BkupTime() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (in *setattrIn) Chgtime() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (in *setattrIn) Flags() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func openFlags(flags uint32) OpenFlags {
|
||||
return OpenFlags(flags)
|
||||
}
|
||||
|
||||
type getxattrIn struct {
|
||||
getxattrInCommon
|
||||
}
|
||||
|
||||
type setxattrIn struct {
|
||||
setxattrInCommon
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package fuse
|
||||
|
||||
import "time"
|
||||
|
||||
type attr struct {
|
||||
Ino uint64
|
||||
Size uint64
|
||||
Blocks uint64
|
||||
Atime uint64
|
||||
Mtime uint64
|
||||
Ctime uint64
|
||||
AtimeNsec uint32
|
||||
MtimeNsec uint32
|
||||
CtimeNsec uint32
|
||||
Mode uint32
|
||||
Nlink uint32
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
Rdev uint32
|
||||
Blksize uint32
|
||||
padding uint32
|
||||
}
|
||||
|
||||
func (a *attr) Crtime() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (a *attr) SetCrtime(s uint64, ns uint32) {
|
||||
// Ignored on Linux.
|
||||
}
|
||||
|
||||
func (a *attr) SetFlags(f uint32) {
|
||||
// Ignored on Linux.
|
||||
}
|
||||
|
||||
type setattrIn struct {
|
||||
setattrInCommon
|
||||
}
|
||||
|
||||
func (in *setattrIn) BkupTime() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (in *setattrIn) Chgtime() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (in *setattrIn) Flags() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func openFlags(flags uint32) OpenFlags {
|
||||
// on amd64, the 32-bit O_LARGEFILE flag is always seen;
|
||||
// on i386, the flag probably depends on the app
|
||||
// requesting, but in any case should be utterly
|
||||
// uninteresting to us here; our kernel protocol messages
|
||||
// are not directly related to the client app's kernel
|
||||
// API/ABI
|
||||
flags &^= 0x8000
|
||||
|
||||
return OpenFlags(flags)
|
||||
}
|
||||
|
||||
type getxattrIn struct {
|
||||
getxattrInCommon
|
||||
}
|
||||
|
||||
type setxattrIn struct {
|
||||
setxattrInCommon
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package fuse
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package fuse_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"bazil.org/fuse"
|
||||
)
|
||||
|
||||
func TestOpenFlagsAccmodeMask(t *testing.T) {
|
||||
var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC)
|
||||
if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadWrite; g != e {
|
||||
t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e)
|
||||
}
|
||||
if f.IsReadOnly() {
|
||||
t.Fatalf("IsReadOnly is wrong: %v", f)
|
||||
}
|
||||
if f.IsWriteOnly() {
|
||||
t.Fatalf("IsWriteOnly is wrong: %v", f)
|
||||
}
|
||||
if !f.IsReadWrite() {
|
||||
t.Fatalf("IsReadWrite is wrong: %v", f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenFlagsString(t *testing.T) {
|
||||
var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC | os.O_APPEND)
|
||||
if g, e := f.String(), "OpenReadWrite+OpenAppend+OpenSync"; g != e {
|
||||
t.Fatalf("OpenFlags.String: %q != %q", g, e)
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package fuseutil
|
||||
|
||||
import (
|
||||
"bazil.org/fuse"
|
||||
)
|
||||
|
||||
// HandleRead handles a read request assuming that data is the entire file content.
|
||||
// It adjusts the amount returned in resp according to req.Offset and req.Size.
|
||||
func HandleRead(req *fuse.ReadRequest, resp *fuse.ReadResponse, data []byte) {
|
||||
if req.Offset >= int64(len(data)) {
|
||||
data = nil
|
||||
} else {
|
||||
data = data[req.Offset:]
|
||||
}
|
||||
if len(data) > req.Size {
|
||||
data = data[:req.Size]
|
||||
}
|
||||
n := copy(resp.Data[:req.Size], data)
|
||||
resp.Data = resp.Data[:n]
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var errNoAvail = errors.New("no available fuse devices")
|
||||
|
||||
var errNotLoaded = errors.New("osxfusefs is not loaded")
|
||||
|
||||
func loadOSXFUSE() error {
|
||||
cmd := exec.Command("/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs")
|
||||
cmd.Dir = "/"
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
return err
|
||||
}
|
||||
|
||||
func openOSXFUSEDev() (*os.File, error) {
|
||||
var f *os.File
|
||||
var err error
|
||||
for i := uint64(0); ; i++ {
|
||||
path := "/dev/osxfuse" + strconv.FormatUint(i, 10)
|
||||
f, err = os.OpenFile(path, os.O_RDWR, 0000)
|
||||
if os.IsNotExist(err) {
|
||||
if i == 0 {
|
||||
// not even the first device was found -> fuse is not loaded
|
||||
return nil, errNotLoaded
|
||||
}
|
||||
|
||||
// we've run out of kernel-provided devices
|
||||
return nil, errNoAvail
|
||||
}
|
||||
|
||||
if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY {
|
||||
// try the next one
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
|
||||
func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error {
|
||||
bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs"
|
||||
|
||||
for k, v := range conf.options {
|
||||
if strings.Contains(k, ",") || strings.Contains(v, ",") {
|
||||
// Silly limitation but the mount helper does not
|
||||
// understand any escaping. See TestMountOptionCommaError.
|
||||
return fmt.Errorf("mount options cannot contain commas on darwin: %q=%q", k, v)
|
||||
}
|
||||
}
|
||||
cmd := exec.Command(
|
||||
bin,
|
||||
"-o", conf.getOptions(),
|
||||
// Tell osxfuse-kext how large our buffer is. It must split
|
||||
// writes larger than this into multiple writes.
|
||||
//
|
||||
// OSXFUSE seems to ignore InitResponse.MaxWrite, and uses
|
||||
// this instead.
|
||||
"-o", "iosize="+strconv.FormatUint(maxWrite, 10),
|
||||
// refers to fd passed in cmd.ExtraFiles
|
||||
"3",
|
||||
dir,
|
||||
)
|
||||
cmd.ExtraFiles = []*os.File{f}
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=")
|
||||
// TODO this is used for fs typenames etc, let app influence it
|
||||
cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_DAEMON_PATH="+bin)
|
||||
var buf bytes.Buffer
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
err := cmd.Wait()
|
||||
if err != nil {
|
||||
if buf.Len() > 0 {
|
||||
output := buf.Bytes()
|
||||
output = bytes.TrimRight(output, "\n")
|
||||
msg := err.Error() + ": " + string(output)
|
||||
err = errors.New(msg)
|
||||
}
|
||||
}
|
||||
*errp = err
|
||||
close(ready)
|
||||
}()
|
||||
return err
|
||||
}
|
||||
|
||||
func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
|
||||
f, err := openOSXFUSEDev()
|
||||
if err == errNotLoaded {
|
||||
err = loadOSXFUSE()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// try again
|
||||
f, err = openOSXFUSEDev()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = callMount(dir, conf, f, ready, errp)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
|
||||
for k, v := range conf.options {
|
||||
if strings.Contains(k, ",") || strings.Contains(v, ",") {
|
||||
// Silly limitation but the mount helper does not
|
||||
// understand any escaping. See TestMountOptionCommaError.
|
||||
return nil, fmt.Errorf("mount options cannot contain commas on FreeBSD: %q=%q", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
f, err := os.OpenFile("/dev/fuse", os.O_RDWR, 0000)
|
||||
if err != nil {
|
||||
*errp = err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(
|
||||
"/sbin/mount_fusefs",
|
||||
"--safe",
|
||||
"-o", conf.getOptions(),
|
||||
"3",
|
||||
dir,
|
||||
)
|
||||
cmd.ExtraFiles = []*os.File{f}
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mount_fusefs: %q, %v", out, err)
|
||||
}
|
||||
|
||||
close(ready)
|
||||
return f, nil
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func lineLogger(wg *sync.WaitGroup, prefix string, r io.ReadCloser) {
|
||||
defer wg.Done()
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
switch line := scanner.Text(); line {
|
||||
case `fusermount: failed to open /etc/fuse.conf: Permission denied`:
|
||||
// Silence this particular message, it occurs way too
|
||||
// commonly and isn't very relevant to whether the mount
|
||||
// succeeds or not.
|
||||
continue
|
||||
default:
|
||||
log.Printf("%s: %s", prefix, line)
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Printf("%s, error reading: %v", prefix, err)
|
||||
}
|
||||
}
|
||||
|
||||
func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) {
|
||||
// linux mount is never delayed
|
||||
close(ready)
|
||||
|
||||
fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("socketpair error: %v", err)
|
||||
}
|
||||
|
||||
writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes")
|
||||
defer writeFile.Close()
|
||||
|
||||
readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads")
|
||||
defer readFile.Close()
|
||||
|
||||
cmd := exec.Command(
|
||||
"fusermount",
|
||||
"-o", conf.getOptions(),
|
||||
"--",
|
||||
dir,
|
||||
)
|
||||
cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")
|
||||
|
||||
cmd.ExtraFiles = []*os.File{writeFile}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("setting up fusermount stderr: %v", err)
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("setting up fusermount stderr: %v", err)
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("fusermount: %v", err)
|
||||
}
|
||||
wg.Add(2)
|
||||
go lineLogger(&wg, "mount helper output", stdout)
|
||||
go lineLogger(&wg, "mount helper error", stderr)
|
||||
wg.Wait()
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return nil, fmt.Errorf("fusermount: %v", err)
|
||||
}
|
||||
|
||||
c, err := net.FileConn(readFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("FileConn from fusermount socket: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
uc, ok := c.(*net.UnixConn)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected FileConn type; expected UnixConn, got %T", c)
|
||||
}
|
||||
|
||||
buf := make([]byte, 32) // expect 1 byte
|
||||
oob := make([]byte, 32) // expect 24 bytes
|
||||
_, oobn, _, _, err := uc.ReadMsgUnix(buf, oob)
|
||||
scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ParseSocketControlMessage: %v", err)
|
||||
}
|
||||
if len(scms) != 1 {
|
||||
return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms)
|
||||
}
|
||||
scm := scms[0]
|
||||
gotFds, err := syscall.ParseUnixRights(&scm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err)
|
||||
}
|
||||
if len(gotFds) != 1 {
|
||||
return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds)
|
||||
}
|
||||
f := os.NewFile(uintptr(gotFds[0]), "/dev/fuse")
|
||||
return f, nil
|
||||
}
|
||||
Vendored
+170
@@ -0,0 +1,170 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func dummyOption(conf *mountConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// mountConfig holds the configuration for a mount operation.
|
||||
// Use it by passing MountOption values to Mount.
|
||||
type mountConfig struct {
|
||||
options map[string]string
|
||||
maxReadahead uint32
|
||||
initFlags InitFlags
|
||||
}
|
||||
|
||||
func escapeComma(s string) string {
|
||||
s = strings.Replace(s, `\`, `\\`, -1)
|
||||
s = strings.Replace(s, `,`, `\,`, -1)
|
||||
return s
|
||||
}
|
||||
|
||||
// getOptions makes a string of options suitable for passing to FUSE
|
||||
// mount flag `-o`. Returns an empty string if no options were set.
|
||||
// Any platform specific adjustments should happen before the call.
|
||||
func (m *mountConfig) getOptions() string {
|
||||
var opts []string
|
||||
for k, v := range m.options {
|
||||
k = escapeComma(k)
|
||||
if v != "" {
|
||||
k += "=" + escapeComma(v)
|
||||
}
|
||||
opts = append(opts, k)
|
||||
}
|
||||
return strings.Join(opts, ",")
|
||||
}
|
||||
|
||||
type mountOption func(*mountConfig) error
|
||||
|
||||
// MountOption is passed to Mount to change the behavior of the mount.
|
||||
type MountOption mountOption
|
||||
|
||||
// FSName sets the file system name (also called source) that is
|
||||
// visible in the list of mounted file systems.
|
||||
//
|
||||
// FreeBSD ignores this option.
|
||||
func FSName(name string) MountOption {
|
||||
return func(conf *mountConfig) error {
|
||||
conf.options["fsname"] = name
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Subtype sets the subtype of the mount. The main type is always
|
||||
// `fuse`. The type in a list of mounted file systems will look like
|
||||
// `fuse.foo`.
|
||||
//
|
||||
// OS X ignores this option.
|
||||
// FreeBSD ignores this option.
|
||||
func Subtype(fstype string) MountOption {
|
||||
return func(conf *mountConfig) error {
|
||||
conf.options["subtype"] = fstype
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// LocalVolume sets the volume to be local (instead of network),
|
||||
// changing the behavior of Finder, Spotlight, and such.
|
||||
//
|
||||
// OS X only. Others ignore this option.
|
||||
func LocalVolume() MountOption {
|
||||
return localVolume
|
||||
}
|
||||
|
||||
// VolumeName sets the volume name shown in Finder.
|
||||
//
|
||||
// OS X only. Others ignore this option.
|
||||
func VolumeName(name string) MountOption {
|
||||
return volumeName(name)
|
||||
}
|
||||
|
||||
var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOther and AllowRoot")
|
||||
|
||||
// AllowOther allows other users to access the file system.
|
||||
//
|
||||
// Only one of AllowOther or AllowRoot can be used.
|
||||
func AllowOther() MountOption {
|
||||
return func(conf *mountConfig) error {
|
||||
if _, ok := conf.options["allow_root"]; ok {
|
||||
return ErrCannotCombineAllowOtherAndAllowRoot
|
||||
}
|
||||
conf.options["allow_other"] = ""
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AllowRoot allows other users to access the file system.
|
||||
//
|
||||
// Only one of AllowOther or AllowRoot can be used.
|
||||
//
|
||||
// FreeBSD ignores this option.
|
||||
func AllowRoot() MountOption {
|
||||
return func(conf *mountConfig) error {
|
||||
if _, ok := conf.options["allow_other"]; ok {
|
||||
return ErrCannotCombineAllowOtherAndAllowRoot
|
||||
}
|
||||
conf.options["allow_root"] = ""
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultPermissions makes the kernel enforce access control based on
|
||||
// the file mode (as in chmod).
|
||||
//
|
||||
// Without this option, the Node itself decides what is and is not
|
||||
// allowed. This is normally ok because FUSE file systems cannot be
|
||||
// accessed by other users without AllowOther/AllowRoot.
|
||||
//
|
||||
// FreeBSD ignores this option.
|
||||
func DefaultPermissions() MountOption {
|
||||
return func(conf *mountConfig) error {
|
||||
conf.options["default_permissions"] = ""
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ReadOnly makes the mount read-only.
|
||||
func ReadOnly() MountOption {
|
||||
return func(conf *mountConfig) error {
|
||||
conf.options["ro"] = ""
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MaxReadahead sets the number of bytes that can be prefetched for
|
||||
// sequential reads. The kernel can enforce a maximum value lower than
|
||||
// this.
|
||||
//
|
||||
// This setting makes the kernel perform speculative reads that do not
|
||||
// originate from any client process. This usually tremendously
|
||||
// improves read performance.
|
||||
func MaxReadahead(n uint32) MountOption {
|
||||
return func(conf *mountConfig) error {
|
||||
conf.maxReadahead = n
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AsyncRead enables multiple outstanding read requests for the same
|
||||
// handle. Without this, there is at most one request in flight at a
|
||||
// time.
|
||||
func AsyncRead() MountOption {
|
||||
return func(conf *mountConfig) error {
|
||||
conf.initFlags |= InitAsyncRead
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WritebackCache enables the kernel to buffer writes before sending
|
||||
// them to the FUSE server. Without this, writethrough caching is
|
||||
// used.
|
||||
func WritebackCache() MountOption {
|
||||
return func(conf *mountConfig) error {
|
||||
conf.initFlags |= InitWritebackCache
|
||||
return nil
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package fuse
|
||||
|
||||
func localVolume(conf *mountConfig) error {
|
||||
conf.options["local"] = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func volumeName(name string) MountOption {
|
||||
return func(conf *mountConfig) error {
|
||||
conf.options["volname"] = name
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package fuse
|
||||
|
||||
func localVolume(conf *mountConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func volumeName(name string) MountOption {
|
||||
return dummyOption
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package fuse
|
||||
|
||||
// for TestMountOptionCommaError
|
||||
func ForTestSetMountOption(k, v string) MountOption {
|
||||
fn := func(conf *mountConfig) error {
|
||||
conf.options[k] = v
|
||||
return nil
|
||||
}
|
||||
return fn
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package fuse
|
||||
|
||||
func localVolume(conf *mountConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func volumeName(name string) MountOption {
|
||||
return dummyOption
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// This file contains tests for platforms that have no escape
|
||||
// mechanism for including commas in mount options.
|
||||
//
|
||||
// +build darwin
|
||||
|
||||
package fuse_test
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs/fstestutil"
|
||||
)
|
||||
|
||||
func TestMountOptionCommaError(t *testing.T) {
|
||||
t.Parallel()
|
||||
// this test is not tied to any specific option, it just needs
|
||||
// some string content
|
||||
var evil = "FuseTest,Marker"
|
||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
|
||||
fuse.ForTestSetMountOption("fusetest", evil),
|
||||
)
|
||||
if err == nil {
|
||||
mnt.Close()
|
||||
t.Fatal("expected an error about commas")
|
||||
}
|
||||
if g, e := err.Error(), `mount options cannot contain commas on `+runtime.GOOS+`: "fusetest"="FuseTest,Marker"`; g != e {
|
||||
t.Fatalf("wrong error: %q != %q", g, e)
|
||||
}
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
package fuse_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"bazil.org/fuse/fs/fstestutil"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func init() {
|
||||
fstestutil.DebugByDefault()
|
||||
}
|
||||
|
||||
func TestMountOptionFSName(t *testing.T) {
|
||||
if runtime.GOOS == "freebsd" {
|
||||
t.Skip("FreeBSD does not support FSName")
|
||||
}
|
||||
t.Parallel()
|
||||
const name = "FuseTestMarker"
|
||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
|
||||
fuse.FSName(name),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
info, err := fstestutil.GetMountInfo(mnt.Dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if g, e := info.FSName, name; g != e {
|
||||
t.Errorf("wrong FSName: %q != %q", g, e)
|
||||
}
|
||||
}
|
||||
|
||||
func testMountOptionFSNameEvil(t *testing.T, evil string) {
|
||||
if runtime.GOOS == "freebsd" {
|
||||
t.Skip("FreeBSD does not support FSName")
|
||||
}
|
||||
t.Parallel()
|
||||
var name = "FuseTest" + evil + "Marker"
|
||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
|
||||
fuse.FSName(name),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
info, err := fstestutil.GetMountInfo(mnt.Dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if g, e := info.FSName, name; g != e {
|
||||
t.Errorf("wrong FSName: %q != %q", g, e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountOptionFSNameEvilComma(t *testing.T) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
// see TestMountOptionCommaError for a test that enforces we
|
||||
// at least give a nice error, instead of corrupting the mount
|
||||
// options
|
||||
t.Skip("TODO: OS X gets this wrong, commas in mount options cannot be escaped at all")
|
||||
}
|
||||
testMountOptionFSNameEvil(t, ",")
|
||||
}
|
||||
|
||||
func TestMountOptionFSNameEvilSpace(t *testing.T) {
|
||||
testMountOptionFSNameEvil(t, " ")
|
||||
}
|
||||
|
||||
func TestMountOptionFSNameEvilTab(t *testing.T) {
|
||||
testMountOptionFSNameEvil(t, "\t")
|
||||
}
|
||||
|
||||
func TestMountOptionFSNameEvilNewline(t *testing.T) {
|
||||
testMountOptionFSNameEvil(t, "\n")
|
||||
}
|
||||
|
||||
func TestMountOptionFSNameEvilBackslash(t *testing.T) {
|
||||
testMountOptionFSNameEvil(t, `\`)
|
||||
}
|
||||
|
||||
func TestMountOptionFSNameEvilBackslashDouble(t *testing.T) {
|
||||
// catch double-unescaping, if it were to happen
|
||||
testMountOptionFSNameEvil(t, `\\`)
|
||||
}
|
||||
|
||||
func TestMountOptionSubtype(t *testing.T) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
t.Skip("OS X does not support Subtype")
|
||||
}
|
||||
if runtime.GOOS == "freebsd" {
|
||||
t.Skip("FreeBSD does not support Subtype")
|
||||
}
|
||||
t.Parallel()
|
||||
const name = "FuseTestMarker"
|
||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
|
||||
fuse.Subtype(name),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
info, err := fstestutil.GetMountInfo(mnt.Dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if g, e := info.Type, "fuse."+name; g != e {
|
||||
t.Errorf("wrong Subtype: %q != %q", g, e)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO test LocalVolume
|
||||
|
||||
// TODO test AllowOther; hard because needs system-level authorization
|
||||
|
||||
func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) {
|
||||
t.Parallel()
|
||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
|
||||
fuse.AllowOther(),
|
||||
fuse.AllowRoot(),
|
||||
)
|
||||
if err == nil {
|
||||
mnt.Close()
|
||||
}
|
||||
if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e {
|
||||
t.Fatalf("wrong error: %v != %v", g, e)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO test AllowRoot; hard because needs system-level authorization
|
||||
|
||||
func TestMountOptionAllowRootThenAllowOther(t *testing.T) {
|
||||
t.Parallel()
|
||||
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
|
||||
fuse.AllowRoot(),
|
||||
fuse.AllowOther(),
|
||||
)
|
||||
if err == nil {
|
||||
mnt.Close()
|
||||
}
|
||||
if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e {
|
||||
t.Fatalf("wrong error: %v != %v", g, e)
|
||||
}
|
||||
}
|
||||
|
||||
type unwritableFile struct{}
|
||||
|
||||
func (f unwritableFile) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
a.Mode = 0000
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMountOptionDefaultPermissions(t *testing.T) {
|
||||
if runtime.GOOS == "freebsd" {
|
||||
t.Skip("FreeBSD does not support DefaultPermissions")
|
||||
}
|
||||
t.Parallel()
|
||||
mnt, err := fstestutil.MountedT(t,
|
||||
fstestutil.SimpleFS{
|
||||
&fstestutil.ChildMap{"child": unwritableFile{}},
|
||||
},
|
||||
nil,
|
||||
fuse.DefaultPermissions(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
// This will be prevented by kernel-level access checking when
|
||||
// DefaultPermissions is used.
|
||||
f, err := os.OpenFile(mnt.Dir+"/child", os.O_WRONLY, 0000)
|
||||
if err == nil {
|
||||
f.Close()
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
if !os.IsPermission(err) {
|
||||
t.Fatalf("expected a permission error, got %T: %v", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
type createrDir struct {
|
||||
fstestutil.Dir
|
||||
}
|
||||
|
||||
var _ fs.NodeCreater = createrDir{}
|
||||
|
||||
func (createrDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
|
||||
// pick a really distinct error, to identify it later
|
||||
return nil, nil, fuse.Errno(syscall.ENAMETOOLONG)
|
||||
}
|
||||
|
||||
func TestMountOptionReadOnly(t *testing.T) {
|
||||
t.Parallel()
|
||||
mnt, err := fstestutil.MountedT(t,
|
||||
fstestutil.SimpleFS{createrDir{}},
|
||||
nil,
|
||||
fuse.ReadOnly(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer mnt.Close()
|
||||
|
||||
// This will be prevented by kernel-level access checking when
|
||||
// ReadOnly is used.
|
||||
f, err := os.Create(mnt.Dir + "/child")
|
||||
if err == nil {
|
||||
f.Close()
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
perr, ok := err.(*os.PathError)
|
||||
if !ok {
|
||||
t.Fatalf("expected PathError, got %T: %v", err, err)
|
||||
}
|
||||
if perr.Err != syscall.EROFS {
|
||||
t.Fatalf("expected EROFS, got %T: %v", err, err)
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Protocol is a FUSE protocol version number.
|
||||
type Protocol struct {
|
||||
Major uint32
|
||||
Minor uint32
|
||||
}
|
||||
|
||||
func (p Protocol) String() string {
|
||||
return fmt.Sprintf("%d.%d", p.Major, p.Minor)
|
||||
}
|
||||
|
||||
// LT returns whether a is less than b.
|
||||
func (a Protocol) LT(b Protocol) bool {
|
||||
return a.Major < b.Major ||
|
||||
(a.Major == b.Major && a.Minor < b.Minor)
|
||||
}
|
||||
|
||||
// GE returns whether a is greater than or equal to b.
|
||||
func (a Protocol) GE(b Protocol) bool {
|
||||
return a.Major > b.Major ||
|
||||
(a.Major == b.Major && a.Minor >= b.Minor)
|
||||
}
|
||||
|
||||
func (a Protocol) is79() bool {
|
||||
return a.GE(Protocol{7, 9})
|
||||
}
|
||||
|
||||
// HasAttrBlockSize returns whether Attr.BlockSize is respected by the
|
||||
// kernel.
|
||||
func (a Protocol) HasAttrBlockSize() bool {
|
||||
return a.is79()
|
||||
}
|
||||
|
||||
// HasReadWriteFlags returns whether ReadRequest/WriteRequest
|
||||
// fields Flags and FileFlags are valid.
|
||||
func (a Protocol) HasReadWriteFlags() bool {
|
||||
return a.is79()
|
||||
}
|
||||
|
||||
// HasGetattrFlags returns whether GetattrRequest field Flags is
|
||||
// valid.
|
||||
func (a Protocol) HasGetattrFlags() bool {
|
||||
return a.is79()
|
||||
}
|
||||
|
||||
func (a Protocol) is710() bool {
|
||||
return a.GE(Protocol{7, 10})
|
||||
}
|
||||
|
||||
// HasOpenNonSeekable returns whether OpenResponse field Flags flag
|
||||
// OpenNonSeekable is supported.
|
||||
func (a Protocol) HasOpenNonSeekable() bool {
|
||||
return a.is710()
|
||||
}
|
||||
|
||||
func (a Protocol) is712() bool {
|
||||
return a.GE(Protocol{7, 12})
|
||||
}
|
||||
|
||||
// HasUmask returns whether CreateRequest/MkdirRequest/MknodRequest
|
||||
// field Umask is valid.
|
||||
func (a Protocol) HasUmask() bool {
|
||||
return a.is712()
|
||||
}
|
||||
|
||||
// HasInvalidate returns whether InvalidateNode/InvalidateEntry are
|
||||
// supported.
|
||||
func (a Protocol) HasInvalidate() bool {
|
||||
return a.is712()
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// Package syscallx provides wrappers that make syscalls on various
|
||||
// platforms more interoperable.
|
||||
//
|
||||
// The API intentionally omits the OS X-specific position and option
|
||||
// arguments for extended attribute calls.
|
||||
//
|
||||
// Not having position means it might not be useful for accessing the
|
||||
// resource fork. If that's needed by code inside fuse, a function
|
||||
// with a different name may be added on the side.
|
||||
//
|
||||
// Options can be implemented with separate wrappers, in the style of
|
||||
// Linux getxattr/lgetxattr/fgetxattr.
|
||||
package syscallx
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
mksys="$(go env GOROOT)/src/pkg/syscall/mksyscall.pl"
|
||||
|
||||
fix() {
|
||||
sed 's,^package syscall$,&x\nimport "syscall",' \
|
||||
| gofmt -r='BytePtrFromString -> syscall.BytePtrFromString' \
|
||||
| gofmt -r='Syscall6 -> syscall.Syscall6' \
|
||||
| gofmt -r='Syscall -> syscall.Syscall' \
|
||||
| gofmt -r='SYS_GETXATTR -> syscall.SYS_GETXATTR' \
|
||||
| gofmt -r='SYS_LISTXATTR -> syscall.SYS_LISTXATTR' \
|
||||
| gofmt -r='SYS_SETXATTR -> syscall.SYS_SETXATTR' \
|
||||
| gofmt -r='SYS_REMOVEXATTR -> syscall.SYS_REMOVEXATTR' \
|
||||
| gofmt -r='SYS_MSYNC -> syscall.SYS_MSYNC'
|
||||
}
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
$mksys xattr_darwin.go \
|
||||
| fix \
|
||||
>xattr_darwin_amd64.go
|
||||
|
||||
$mksys -l32 xattr_darwin.go \
|
||||
| fix \
|
||||
>xattr_darwin_386.go
|
||||
|
||||
$mksys msync.go \
|
||||
| fix \
|
||||
>msync_amd64.go
|
||||
|
||||
$mksys -l32 msync.go \
|
||||
| fix \
|
||||
>msync_386.go
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package syscallx
|
||||
|
||||
/* This is the source file for msync_*.go, to regenerate run
|
||||
|
||||
./generate
|
||||
|
||||
*/
|
||||
|
||||
//sys Msync(b []byte, flags int) (err error)
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// mksyscall.pl -l32 msync.go
|
||||
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
|
||||
package syscallx
|
||||
|
||||
import "syscall"
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Msync(b []byte, flags int) (err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(b) > 0 {
|
||||
_p0 = unsafe.Pointer(&b[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
_, _, e1 := syscall.Syscall(syscall.SYS_MSYNC, uintptr(_p0), uintptr(len(b)), uintptr(flags))
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// mksyscall.pl msync.go
|
||||
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
|
||||
package syscallx
|
||||
|
||||
import "syscall"
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Msync(b []byte, flags int) (err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(b) > 0 {
|
||||
_p0 = unsafe.Pointer(&b[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
_, _, e1 := syscall.Syscall(syscall.SYS_MSYNC, uintptr(_p0), uintptr(len(b)), uintptr(flags))
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package syscallx
|
||||
|
||||
// make us look more like package syscall, so mksyscall.pl output works
|
||||
var _zero uintptr
|
||||
@@ -0,0 +1,26 @@
|
||||
// +build !darwin
|
||||
|
||||
package syscallx
|
||||
|
||||
// This file just contains wrappers for platforms that already have
|
||||
// the right stuff in golang.org/x/sys/unix.
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func Getxattr(path string, attr string, dest []byte) (sz int, err error) {
|
||||
return unix.Getxattr(path, attr, dest)
|
||||
}
|
||||
|
||||
func Listxattr(path string, dest []byte) (sz int, err error) {
|
||||
return unix.Listxattr(path, dest)
|
||||
}
|
||||
|
||||
func Setxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||
return unix.Setxattr(path, attr, data, flags)
|
||||
}
|
||||
|
||||
func Removexattr(path string, attr string) (err error) {
|
||||
return unix.Removexattr(path, attr)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package syscallx
|
||||
|
||||
/* This is the source file for syscallx_darwin_*.go, to regenerate run
|
||||
|
||||
./generate
|
||||
|
||||
*/
|
||||
|
||||
// cannot use dest []byte here because OS X getxattr really wants a
|
||||
// NULL to trigger size probing, size==0 is not enough
|
||||
//
|
||||
//sys getxattr(path string, attr string, dest *byte, size int, position uint32, options int) (sz int, err error)
|
||||
|
||||
func Getxattr(path string, attr string, dest []byte) (sz int, err error) {
|
||||
var destp *byte
|
||||
if len(dest) > 0 {
|
||||
destp = &dest[0]
|
||||
}
|
||||
return getxattr(path, attr, destp, len(dest), 0, 0)
|
||||
}
|
||||
|
||||
//sys listxattr(path string, dest []byte, options int) (sz int, err error)
|
||||
|
||||
func Listxattr(path string, dest []byte) (sz int, err error) {
|
||||
return listxattr(path, dest, 0)
|
||||
}
|
||||
|
||||
//sys setxattr(path string, attr string, data []byte, position uint32, flags int) (err error)
|
||||
|
||||
func Setxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||
return setxattr(path, attr, data, 0, flags)
|
||||
}
|
||||
|
||||
//sys removexattr(path string, attr string, options int) (err error)
|
||||
|
||||
func Removexattr(path string, attr string) (err error) {
|
||||
return removexattr(path, attr, 0)
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// mksyscall.pl -l32 xattr_darwin.go
|
||||
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
|
||||
package syscallx
|
||||
|
||||
import "syscall"
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func getxattr(path string, attr string, dest *byte, size int, position uint32, options int) (sz int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(position), uintptr(options))
|
||||
sz = int(r0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func listxattr(path string, dest []byte, options int) (sz int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 unsafe.Pointer
|
||||
if len(dest) > 0 {
|
||||
_p1 = unsafe.Pointer(&dest[0])
|
||||
} else {
|
||||
_p1 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)), uintptr(options), 0, 0)
|
||||
sz = int(r0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func setxattr(path string, attr string, data []byte, position uint32, flags int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p2 unsafe.Pointer
|
||||
if len(data) > 0 {
|
||||
_p2 = unsafe.Pointer(&data[0])
|
||||
} else {
|
||||
_p2 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
_, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(position), uintptr(flags))
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func removexattr(path string, attr string, options int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(options))
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// mksyscall.pl xattr_darwin.go
|
||||
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
|
||||
package syscallx
|
||||
|
||||
import "syscall"
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func getxattr(path string, attr string, dest *byte, size int, position uint32, options int) (sz int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(position), uintptr(options))
|
||||
sz = int(r0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func listxattr(path string, dest []byte, options int) (sz int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 unsafe.Pointer
|
||||
if len(dest) > 0 {
|
||||
_p1 = unsafe.Pointer(&dest[0])
|
||||
} else {
|
||||
_p1 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)), uintptr(options), 0, 0)
|
||||
sz = int(r0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func setxattr(path string, attr string, data []byte, position uint32, flags int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p2 unsafe.Pointer
|
||||
if len(data) > 0 {
|
||||
_p2 = unsafe.Pointer(&data[0])
|
||||
} else {
|
||||
_p2 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
_, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(position), uintptr(flags))
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func removexattr(path string, attr string, options int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(options))
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
package fuse
|
||||
|
||||
// Unmount tries to unmount the filesystem mounted at dir.
|
||||
func Unmount(dir string) error {
|
||||
return unmount(dir)
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func unmount(dir string) error {
|
||||
cmd := exec.Command("fusermount", "-u", dir)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if len(output) > 0 {
|
||||
output = bytes.TrimRight(output, "\n")
|
||||
msg := err.Error() + ": " + string(output)
|
||||
err = errors.New(msg)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// +build !linux
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func unmount(dir string) error {
|
||||
err := syscall.Unmount(dir, 0)
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "unmount", Path: dir, Err: err}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
language: go
|
||||
|
||||
install:
|
||||
# go-flags
|
||||
- go get -d -v ./...
|
||||
- go build -v ./...
|
||||
|
||||
# linting
|
||||
- go get golang.org/x/tools/cmd/vet
|
||||
- go get github.com/golang/lint
|
||||
- go install github.com/golang/lint/golint
|
||||
|
||||
# code coverage
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/onsi/ginkgo/ginkgo
|
||||
- go get github.com/modocache/gover
|
||||
- if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then go get github.com/mattn/goveralls; fi
|
||||
|
||||
script:
|
||||
# go-flags
|
||||
- $(exit $(gofmt -l . | wc -l))
|
||||
- go test -v ./...
|
||||
|
||||
# linting
|
||||
- go tool vet -all=true -v=true . || true
|
||||
- $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/golint ./...
|
||||
|
||||
# code coverage
|
||||
- $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/ginkgo -r -cover
|
||||
- $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/gover
|
||||
- if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/goveralls -coverprofile=gover.coverprofile -service=travis-ci -repotoken $COVERALLS_TOKEN; fi
|
||||
|
||||
env:
|
||||
# coveralls.io
|
||||
secure: "RCYbiB4P0RjQRIoUx/vG/AjP3mmYCbzOmr86DCww1Z88yNcy3hYr3Cq8rpPtYU5v0g7wTpu4adaKIcqRE9xknYGbqj3YWZiCoBP1/n4Z+9sHW3Dsd9D/GRGeHUus0laJUGARjWoCTvoEtOgTdGQDoX7mH+pUUY0FBltNYUdOiiU="
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
Copyright (c) 2012 Jesse van den Kieboom. 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.
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
go-flags: a go library for parsing command line arguments
|
||||
=========================================================
|
||||
|
||||
[](https://godoc.org/github.com/jessevdk/go-flags) [](https://travis-ci.org/jessevdk/go-flags) [](https://coveralls.io/r/jessevdk/go-flags?branch=master)
|
||||
|
||||
This library provides similar functionality to the builtin flag library of
|
||||
go, but provides much more functionality and nicer formatting. From the
|
||||
documentation:
|
||||
|
||||
Package flags provides an extensive command line option parser.
|
||||
The flags package is similar in functionality to the go builtin flag package
|
||||
but provides more options and uses reflection to provide a convenient and
|
||||
succinct way of specifying command line options.
|
||||
|
||||
Supported features:
|
||||
* Options with short names (-v)
|
||||
* Options with long names (--verbose)
|
||||
* Options with and without arguments (bool v.s. other type)
|
||||
* Options with optional arguments and default values
|
||||
* Multiple option groups each containing a set of options
|
||||
* Generate and print well-formatted help message
|
||||
* Passing remaining command line arguments after -- (optional)
|
||||
* Ignoring unknown command line options (optional)
|
||||
* Supports -I/usr/include -I=/usr/include -I /usr/include option argument specification
|
||||
* Supports multiple short options -aux
|
||||
* Supports all primitive go types (string, int{8..64}, uint{8..64}, float)
|
||||
* Supports same option multiple times (can store in slice or last option counts)
|
||||
* Supports maps
|
||||
* Supports function callbacks
|
||||
* Supports namespaces for (nested) option groups
|
||||
|
||||
The flags package uses structs, reflection and struct field tags
|
||||
to allow users to specify command line options. This results in very simple
|
||||
and concise specification of your application options. For example:
|
||||
|
||||
type Options struct {
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
|
||||
}
|
||||
|
||||
This specifies one option with a short name -v and a long name --verbose.
|
||||
When either -v or --verbose is found on the command line, a 'true' value
|
||||
will be appended to the Verbose field. e.g. when specifying -vvv, the
|
||||
resulting value of Verbose will be {[true, true, true]}.
|
||||
|
||||
Example:
|
||||
--------
|
||||
var opts struct {
|
||||
// Slice of bool will append 'true' each time the option
|
||||
// is encountered (can be set multiple times, like -vvv)
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
|
||||
|
||||
// Example of automatic marshalling to desired type (uint)
|
||||
Offset uint `long:"offset" description:"Offset"`
|
||||
|
||||
// Example of a callback, called each time the option is found.
|
||||
Call func(string) `short:"c" description:"Call phone number"`
|
||||
|
||||
// Example of a required flag
|
||||
Name string `short:"n" long:"name" description:"A name" required:"true"`
|
||||
|
||||
// Example of a value name
|
||||
File string `short:"f" long:"file" description:"A file" value-name:"FILE"`
|
||||
|
||||
// Example of a pointer
|
||||
Ptr *int `short:"p" description:"A pointer to an integer"`
|
||||
|
||||
// Example of a slice of strings
|
||||
StringSlice []string `short:"s" description:"A slice of strings"`
|
||||
|
||||
// Example of a slice of pointers
|
||||
PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
|
||||
|
||||
// Example of a map
|
||||
IntMap map[string]int `long:"intmap" description:"A map from string to int"`
|
||||
}
|
||||
|
||||
// Callback which will invoke callto:<argument> to call a number.
|
||||
// Note that this works just on OS X (and probably only with
|
||||
// Skype) but it shows the idea.
|
||||
opts.Call = func(num string) {
|
||||
cmd := exec.Command("open", "callto:"+num)
|
||||
cmd.Start()
|
||||
cmd.Process.Release()
|
||||
}
|
||||
|
||||
// Make some fake arguments to parse.
|
||||
args := []string{
|
||||
"-vv",
|
||||
"--offset=5",
|
||||
"-n", "Me",
|
||||
"-p", "3",
|
||||
"-s", "hello",
|
||||
"-s", "world",
|
||||
"--ptrslice", "hello",
|
||||
"--ptrslice", "world",
|
||||
"--intmap", "a:1",
|
||||
"--intmap", "b:5",
|
||||
"arg1",
|
||||
"arg2",
|
||||
"arg3",
|
||||
}
|
||||
|
||||
// Parse flags from `args'. Note that here we use flags.ParseArgs for
|
||||
// the sake of making a working example. Normally, you would simply use
|
||||
// flags.Parse(&opts) which uses os.Args
|
||||
args, err := flags.ParseArgs(&opts, args)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Verbosity: %v\n", opts.Verbose)
|
||||
fmt.Printf("Offset: %d\n", opts.Offset)
|
||||
fmt.Printf("Name: %s\n", opts.Name)
|
||||
fmt.Printf("Ptr: %d\n", *opts.Ptr)
|
||||
fmt.Printf("StringSlice: %v\n", opts.StringSlice)
|
||||
fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1])
|
||||
fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"])
|
||||
fmt.Printf("Remaining args: %s\n", strings.Join(args, " "))
|
||||
|
||||
// Output: Verbosity: [true true]
|
||||
// Offset: 5
|
||||
// Name: Me
|
||||
// Ptr: 3
|
||||
// StringSlice: [hello world]
|
||||
// PtrSlice: [hello world]
|
||||
// IntMap: [a:1 b:5]
|
||||
// Remaining args: arg1 arg2 arg3
|
||||
|
||||
More information can be found in the godocs: <http://godoc.org/github.com/jessevdk/go-flags>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Arg represents a positional argument on the command line.
|
||||
type Arg struct {
|
||||
// The name of the positional argument (used in the help)
|
||||
Name string
|
||||
|
||||
// A description of the positional argument (used in the help)
|
||||
Description string
|
||||
|
||||
value reflect.Value
|
||||
tag multiTag
|
||||
}
|
||||
|
||||
func (a *Arg) isRemaining() bool {
|
||||
return a.value.Type().Kind() == reflect.Slice
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPositional(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Command int
|
||||
Filename string
|
||||
Rest []string
|
||||
} `positional-args:"yes" required:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
ret, err := p.ParseArgs([]string{"10", "arg_test.go", "a", "b"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if opts.Positional.Command != 10 {
|
||||
t.Fatalf("Expected opts.Positional.Command to be 10, but got %v", opts.Positional.Command)
|
||||
}
|
||||
|
||||
if opts.Positional.Filename != "arg_test.go" {
|
||||
t.Fatalf("Expected opts.Positional.Filename to be \"arg_test.go\", but got %v", opts.Positional.Filename)
|
||||
}
|
||||
|
||||
assertStringArray(t, opts.Positional.Rest, []string{"a", "b"})
|
||||
assertStringArray(t, ret, []string{})
|
||||
}
|
||||
|
||||
func TestPositionalRequired(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Command int
|
||||
Filename string
|
||||
Rest []string
|
||||
} `positional-args:"yes" required:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{"10"})
|
||||
|
||||
assertError(t, err, ErrRequired, "the required argument `Filename` was not provided")
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assertCallerInfo() (string, int) {
|
||||
ptr := make([]uintptr, 15)
|
||||
n := runtime.Callers(1, ptr)
|
||||
|
||||
if n == 0 {
|
||||
return "", 0
|
||||
}
|
||||
|
||||
mef := runtime.FuncForPC(ptr[0])
|
||||
mefile, meline := mef.FileLine(ptr[0])
|
||||
|
||||
for i := 2; i < n; i++ {
|
||||
f := runtime.FuncForPC(ptr[i])
|
||||
file, line := f.FileLine(ptr[i])
|
||||
|
||||
if file != mefile {
|
||||
return file, line
|
||||
}
|
||||
}
|
||||
|
||||
return mefile, meline
|
||||
}
|
||||
|
||||
func assertErrorf(t *testing.T, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
|
||||
file, line := assertCallerInfo()
|
||||
|
||||
t.Errorf("%s:%d: %s", path.Base(file), line, msg)
|
||||
}
|
||||
|
||||
func assertFatalf(t *testing.T, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
|
||||
file, line := assertCallerInfo()
|
||||
|
||||
t.Fatalf("%s:%d: %s", path.Base(file), line, msg)
|
||||
}
|
||||
|
||||
func assertString(t *testing.T, a string, b string) {
|
||||
if a != b {
|
||||
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||
}
|
||||
}
|
||||
|
||||
func assertStringArray(t *testing.T, a []string, b []string) {
|
||||
if len(a) != len(b) {
|
||||
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range a {
|
||||
if b[i] != v {
|
||||
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertBoolArray(t *testing.T, a []bool, b []bool) {
|
||||
if len(a) != len(b) {
|
||||
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range a {
|
||||
if b[i] != v {
|
||||
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertParserSuccess(t *testing.T, data interface{}, args ...string) (*Parser, []string) {
|
||||
parser := NewParser(data, Default&^PrintErrors)
|
||||
ret, err := parser.ParseArgs(args)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected parse error: %s", err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return parser, ret
|
||||
}
|
||||
|
||||
func assertParseSuccess(t *testing.T, data interface{}, args ...string) []string {
|
||||
_, ret := assertParserSuccess(t, data, args...)
|
||||
return ret
|
||||
}
|
||||
|
||||
func assertError(t *testing.T, err error, typ ErrorType, msg string) {
|
||||
if err == nil {
|
||||
assertFatalf(t, "Expected error: %s", msg)
|
||||
return
|
||||
}
|
||||
|
||||
if e, ok := err.(*Error); !ok {
|
||||
assertFatalf(t, "Expected Error type, but got %#v", err)
|
||||
} else {
|
||||
if e.Type != typ {
|
||||
assertErrorf(t, "Expected error type {%s}, but got {%s}", typ, e.Type)
|
||||
}
|
||||
|
||||
if e.Message != msg {
|
||||
assertErrorf(t, "Expected error message %#v, but got %#v", msg, e.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertParseFail(t *testing.T, typ ErrorType, msg string, data interface{}, args ...string) []string {
|
||||
parser := NewParser(data, Default&^PrintErrors)
|
||||
ret, err := parser.ParseArgs(args)
|
||||
|
||||
assertError(t, err, typ, msg)
|
||||
return ret
|
||||
}
|
||||
|
||||
func diff(a, b string) (string, error) {
|
||||
atmp, err := ioutil.TempFile("", "help-diff")
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
btmp, err := ioutil.TempFile("", "help-diff")
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := io.WriteString(atmp, a); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := io.WriteString(btmp, b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ret, err := exec.Command("diff", "-u", "-d", "--label", "got", atmp.Name(), "--label", "expected", btmp.Name()).Output()
|
||||
|
||||
os.Remove(atmp.Name())
|
||||
os.Remove(btmp.Name())
|
||||
|
||||
if err.Error() == "exit status 1" {
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
return string(ret), err
|
||||
}
|
||||
|
||||
func assertDiff(t *testing.T, actual, expected, msg string) {
|
||||
if actual == expected {
|
||||
return
|
||||
}
|
||||
|
||||
ret, err := diff(actual, expected)
|
||||
|
||||
if err != nil {
|
||||
assertErrorf(t, "Unexpected diff error: %s", err)
|
||||
assertErrorf(t, "Unexpected %s, expected:\n\n%s\n\nbut got\n\n%s", msg, expected, actual)
|
||||
} else {
|
||||
assertErrorf(t, "Unexpected %s:\n\n%s", msg, ret)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo '# linux arm7'
|
||||
GOARM=7 GOARCH=arm GOOS=linux go build
|
||||
echo '# linux arm5'
|
||||
GOARM=5 GOARCH=arm GOOS=linux go build
|
||||
echo '# windows 386'
|
||||
GOARCH=386 GOOS=windows go build
|
||||
echo '# windows amd64'
|
||||
GOARCH=amd64 GOOS=windows go build
|
||||
echo '# darwin'
|
||||
GOARCH=amd64 GOOS=darwin go build
|
||||
echo '# freebsd'
|
||||
GOARCH=amd64 GOOS=freebsd go build
|
||||
@@ -0,0 +1,59 @@
|
||||
package flags
|
||||
|
||||
func levenshtein(s string, t string) int {
|
||||
if len(s) == 0 {
|
||||
return len(t)
|
||||
}
|
||||
|
||||
if len(t) == 0 {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
dists := make([][]int, len(s)+1)
|
||||
for i := range dists {
|
||||
dists[i] = make([]int, len(t)+1)
|
||||
dists[i][0] = i
|
||||
}
|
||||
|
||||
for j := range t {
|
||||
dists[0][j] = j
|
||||
}
|
||||
|
||||
for i, sc := range s {
|
||||
for j, tc := range t {
|
||||
if sc == tc {
|
||||
dists[i+1][j+1] = dists[i][j]
|
||||
} else {
|
||||
dists[i+1][j+1] = dists[i][j] + 1
|
||||
if dists[i+1][j] < dists[i+1][j+1] {
|
||||
dists[i+1][j+1] = dists[i+1][j] + 1
|
||||
}
|
||||
if dists[i][j+1] < dists[i+1][j+1] {
|
||||
dists[i+1][j+1] = dists[i][j+1] + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dists[len(s)][len(t)]
|
||||
}
|
||||
|
||||
func closestChoice(cmd string, choices []string) (string, int) {
|
||||
if len(choices) == 0 {
|
||||
return "", 0
|
||||
}
|
||||
|
||||
mincmd := -1
|
||||
mindist := -1
|
||||
|
||||
for i, c := range choices {
|
||||
l := levenshtein(cmd, c)
|
||||
|
||||
if mincmd < 0 || l < mindist {
|
||||
mindist = l
|
||||
mincmd = i
|
||||
}
|
||||
}
|
||||
|
||||
return choices[mincmd], mindist
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package flags
|
||||
|
||||
// Command represents an application command. Commands can be added to the
|
||||
// parser (which itself is a command) and are selected/executed when its name
|
||||
// is specified on the command line. The Command type embeds a Group and
|
||||
// therefore also carries a set of command specific options.
|
||||
type Command struct {
|
||||
// Embedded, see Group for more information
|
||||
*Group
|
||||
|
||||
// The name by which the command can be invoked
|
||||
Name string
|
||||
|
||||
// The active sub command (set by parsing) or nil
|
||||
Active *Command
|
||||
|
||||
// Whether subcommands are optional
|
||||
SubcommandsOptional bool
|
||||
|
||||
// Aliases for the command
|
||||
Aliases []string
|
||||
|
||||
// Whether positional arguments are required
|
||||
ArgsRequired bool
|
||||
|
||||
commands []*Command
|
||||
hasBuiltinHelpGroup bool
|
||||
args []*Arg
|
||||
}
|
||||
|
||||
// Commander is an interface which can be implemented by any command added in
|
||||
// the options. When implemented, the Execute method will be called for the last
|
||||
// specified (sub)command providing the remaining command line arguments.
|
||||
type Commander interface {
|
||||
// Execute will be called for the last active (sub)command. The
|
||||
// args argument contains the remaining command line arguments. The
|
||||
// error that Execute returns will be eventually passed out of the
|
||||
// Parse method of the Parser.
|
||||
Execute(args []string) error
|
||||
}
|
||||
|
||||
// Usage is an interface which can be implemented to show a custom usage string
|
||||
// in the help message shown for a command.
|
||||
type Usage interface {
|
||||
// Usage is called for commands to allow customized printing of command
|
||||
// usage in the generated help message.
|
||||
Usage() string
|
||||
}
|
||||
|
||||
// AddCommand adds a new command to the parser with the given name and data. The
|
||||
// data needs to be a pointer to a struct from which the fields indicate which
|
||||
// options are in the command. The provided data can implement the Command and
|
||||
// Usage interfaces.
|
||||
func (c *Command) AddCommand(command string, shortDescription string, longDescription string, data interface{}) (*Command, error) {
|
||||
cmd := newCommand(command, shortDescription, longDescription, data)
|
||||
|
||||
cmd.parent = c
|
||||
|
||||
if err := cmd.scan(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.commands = append(c.commands, cmd)
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// AddGroup adds a new group to the command with the given name and data. The
|
||||
// data needs to be a pointer to a struct from which the fields indicate which
|
||||
// options are in the group.
|
||||
func (c *Command) AddGroup(shortDescription string, longDescription string, data interface{}) (*Group, error) {
|
||||
group := newGroup(shortDescription, longDescription, data)
|
||||
|
||||
group.parent = c
|
||||
|
||||
if err := group.scanType(c.scanSubcommandHandler(group)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.groups = append(c.groups, group)
|
||||
return group, nil
|
||||
}
|
||||
|
||||
// Commands returns a list of subcommands of this command.
|
||||
func (c *Command) Commands() []*Command {
|
||||
return c.commands
|
||||
}
|
||||
|
||||
// Find locates the subcommand with the given name and returns it. If no such
|
||||
// command can be found Find will return nil.
|
||||
func (c *Command) Find(name string) *Command {
|
||||
for _, cc := range c.commands {
|
||||
if cc.match(name) {
|
||||
return cc
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Args returns a list of positional arguments associated with this command.
|
||||
func (c *Command) Args() []*Arg {
|
||||
ret := make([]*Arg, len(c.args))
|
||||
copy(ret, c.args)
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type lookup struct {
|
||||
shortNames map[string]*Option
|
||||
longNames map[string]*Option
|
||||
|
||||
commands map[string]*Command
|
||||
}
|
||||
|
||||
func newCommand(name string, shortDescription string, longDescription string, data interface{}) *Command {
|
||||
return &Command{
|
||||
Group: newGroup(shortDescription, longDescription, data),
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler {
|
||||
f := func(realval reflect.Value, sfield *reflect.StructField) (bool, error) {
|
||||
mtag := newMultiTag(string(sfield.Tag))
|
||||
|
||||
if err := mtag.Parse(); err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
positional := mtag.Get("positional-args")
|
||||
|
||||
if len(positional) != 0 {
|
||||
stype := realval.Type()
|
||||
|
||||
for i := 0; i < stype.NumField(); i++ {
|
||||
field := stype.Field(i)
|
||||
|
||||
m := newMultiTag((string(field.Tag)))
|
||||
|
||||
if err := m.Parse(); err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
name := m.Get("positional-arg-name")
|
||||
|
||||
if len(name) == 0 {
|
||||
name = field.Name
|
||||
}
|
||||
|
||||
arg := &Arg{
|
||||
Name: name,
|
||||
Description: m.Get("description"),
|
||||
|
||||
value: realval.Field(i),
|
||||
tag: m,
|
||||
}
|
||||
|
||||
c.args = append(c.args, arg)
|
||||
|
||||
if len(mtag.Get("required")) != 0 {
|
||||
c.ArgsRequired = true
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
subcommand := mtag.Get("command")
|
||||
|
||||
if len(subcommand) != 0 {
|
||||
ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr()))
|
||||
|
||||
shortDescription := mtag.Get("description")
|
||||
longDescription := mtag.Get("long-description")
|
||||
subcommandsOptional := mtag.Get("subcommands-optional")
|
||||
aliases := mtag.GetMany("alias")
|
||||
|
||||
subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface())
|
||||
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
if len(subcommandsOptional) > 0 {
|
||||
subc.SubcommandsOptional = true
|
||||
}
|
||||
|
||||
if len(aliases) > 0 {
|
||||
subc.Aliases = aliases
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return parentg.scanSubGroupHandler(realval, sfield)
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (c *Command) scan() error {
|
||||
return c.scanType(c.scanSubcommandHandler(c.Group))
|
||||
}
|
||||
|
||||
func (c *Command) eachCommand(f func(*Command), recurse bool) {
|
||||
f(c)
|
||||
|
||||
for _, cc := range c.commands {
|
||||
if recurse {
|
||||
cc.eachCommand(f, true)
|
||||
} else {
|
||||
f(cc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Command) eachActiveGroup(f func(cc *Command, g *Group)) {
|
||||
c.eachGroup(func(g *Group) {
|
||||
f(c, g)
|
||||
})
|
||||
|
||||
if c.Active != nil {
|
||||
c.Active.eachActiveGroup(f)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Command) addHelpGroups(showHelp func() error) {
|
||||
if !c.hasBuiltinHelpGroup {
|
||||
c.addHelpGroup(showHelp)
|
||||
c.hasBuiltinHelpGroup = true
|
||||
}
|
||||
|
||||
for _, cc := range c.commands {
|
||||
cc.addHelpGroups(showHelp)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Command) makeLookup() lookup {
|
||||
ret := lookup{
|
||||
shortNames: make(map[string]*Option),
|
||||
longNames: make(map[string]*Option),
|
||||
commands: make(map[string]*Command),
|
||||
}
|
||||
|
||||
parent := c.parent
|
||||
|
||||
for parent != nil {
|
||||
if cmd, ok := parent.(*Command); ok {
|
||||
cmd.fillLookup(&ret, true)
|
||||
}
|
||||
|
||||
if grp, ok := parent.(*Group); ok {
|
||||
parent = grp
|
||||
} else {
|
||||
parent = nil
|
||||
}
|
||||
}
|
||||
|
||||
c.fillLookup(&ret, false)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *Command) fillLookup(ret *lookup, onlyOptions bool) {
|
||||
c.eachGroup(func(g *Group) {
|
||||
for _, option := range g.options {
|
||||
if option.ShortName != 0 {
|
||||
ret.shortNames[string(option.ShortName)] = option
|
||||
}
|
||||
|
||||
if len(option.LongName) > 0 {
|
||||
ret.longNames[option.LongNameWithNamespace()] = option
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if onlyOptions {
|
||||
return
|
||||
}
|
||||
|
||||
for _, subcommand := range c.commands {
|
||||
ret.commands[subcommand.Name] = subcommand
|
||||
|
||||
for _, a := range subcommand.Aliases {
|
||||
ret.commands[a] = subcommand
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Command) groupByName(name string) *Group {
|
||||
if grp := c.Group.groupByName(name); grp != nil {
|
||||
return grp
|
||||
}
|
||||
|
||||
for _, subc := range c.commands {
|
||||
prefix := subc.Name + "."
|
||||
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
if grp := subc.groupByName(name[len(prefix):]); grp != nil {
|
||||
return grp
|
||||
}
|
||||
} else if name == subc.Name {
|
||||
return subc.Group
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type commandList []*Command
|
||||
|
||||
func (c commandList) Less(i, j int) bool {
|
||||
return c[i].Name < c[j].Name
|
||||
}
|
||||
|
||||
func (c commandList) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c commandList) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
func (c *Command) sortedCommands() []*Command {
|
||||
ret := make(commandList, len(c.commands))
|
||||
copy(ret, c.commands)
|
||||
|
||||
sort.Sort(ret)
|
||||
return []*Command(ret)
|
||||
}
|
||||
|
||||
func (c *Command) match(name string) bool {
|
||||
if c.Name == name {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, v := range c.Aliases {
|
||||
if v == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Command) hasCliOptions() bool {
|
||||
ret := false
|
||||
|
||||
c.eachGroup(func(g *Group) {
|
||||
if g.isBuiltinHelp {
|
||||
return
|
||||
}
|
||||
|
||||
for _, opt := range g.options {
|
||||
if opt.canCli() {
|
||||
ret = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *Command) fillParseState(s *parseState) {
|
||||
s.positional = make([]*Arg, len(c.args))
|
||||
copy(s.positional, c.args)
|
||||
|
||||
s.lookup = c.makeLookup()
|
||||
s.command = c
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommandInline(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
p, ret := assertParserSuccess(t, &opts, "-v", "cmd", "-g")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if p.Active == nil {
|
||||
t.Errorf("Expected active command")
|
||||
}
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Command.G {
|
||||
t.Errorf("Expected Command.G to be true")
|
||||
}
|
||||
|
||||
if p.Command.Find("cmd") != p.Active {
|
||||
t.Errorf("Expected to find command `cmd' to be active")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandInlineMulti(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
C1 struct {
|
||||
} `command:"c1"`
|
||||
|
||||
C2 struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"c2"`
|
||||
}{}
|
||||
|
||||
p, ret := assertParserSuccess(t, &opts, "-v", "c2", "-g")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if p.Active == nil {
|
||||
t.Errorf("Expected active command")
|
||||
}
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.C2.G {
|
||||
t.Errorf("Expected C2.G to be true")
|
||||
}
|
||||
|
||||
if p.Command.Find("c1") == nil {
|
||||
t.Errorf("Expected to find command `c1'")
|
||||
}
|
||||
|
||||
if c2 := p.Command.Find("c2"); c2 == nil {
|
||||
t.Errorf("Expected to find command `c2'")
|
||||
} else if c2 != p.Active {
|
||||
t.Errorf("Expected to find command `c2' to be active")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandFlagOrder1(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrUnknownFlag, "unknown flag `g'", &opts, "-v", "-g", "cmd")
|
||||
}
|
||||
|
||||
func TestCommandFlagOrder2(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cmd", "-v", "-g")
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Command.G {
|
||||
t.Errorf("Expected Command.G to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandFlagOverride1(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
Value bool `short:"v"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "-v", "cmd")
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if opts.Command.Value {
|
||||
t.Errorf("Expected Command.Value to be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandFlagOverride2(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
Value bool `short:"v"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cmd", "-v")
|
||||
|
||||
if opts.Value {
|
||||
t.Errorf("Expected Value to be false")
|
||||
}
|
||||
|
||||
if !opts.Command.Value {
|
||||
t.Errorf("Expected Command.Value to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandEstimate(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Cmd1 struct {
|
||||
} `command:"remove"`
|
||||
|
||||
Cmd2 struct {
|
||||
} `command:"add"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{})
|
||||
|
||||
assertError(t, err, ErrCommandRequired, "Please specify one command of: add or remove")
|
||||
}
|
||||
|
||||
func TestCommandEstimate2(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Cmd1 struct {
|
||||
} `command:"remove"`
|
||||
|
||||
Cmd2 struct {
|
||||
} `command:"add"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{"rmive"})
|
||||
|
||||
assertError(t, err, ErrUnknownCommand, "Unknown command `rmive', did you mean `remove'?")
|
||||
}
|
||||
|
||||
type testCommand struct {
|
||||
G bool `short:"g"`
|
||||
Executed bool
|
||||
EArgs []string
|
||||
}
|
||||
|
||||
func (c *testCommand) Execute(args []string) error {
|
||||
c.Executed = true
|
||||
c.EArgs = args
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCommandExecute(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command testCommand `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "-v", "cmd", "-g", "a", "b")
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Command.Executed {
|
||||
t.Errorf("Did not execute command")
|
||||
}
|
||||
|
||||
if !opts.Command.G {
|
||||
t.Errorf("Expected Command.C to be true")
|
||||
}
|
||||
|
||||
assertStringArray(t, opts.Command.EArgs, []string{"a", "b"})
|
||||
}
|
||||
|
||||
func TestCommandClosest(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Cmd1 struct {
|
||||
} `command:"remove"`
|
||||
|
||||
Cmd2 struct {
|
||||
} `command:"add"`
|
||||
}{}
|
||||
|
||||
args := assertParseFail(t, ErrUnknownCommand, "Unknown command `addd', did you mean `add'?", &opts, "-v", "addd")
|
||||
|
||||
assertStringArray(t, args, []string{"addd"})
|
||||
}
|
||||
|
||||
func TestCommandAdd(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
}{}
|
||||
|
||||
var cmd = struct {
|
||||
G bool `short:"g"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
c, err := p.AddCommand("cmd", "", "", &cmd)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
ret, err := p.ParseArgs([]string{"-v", "cmd", "-g", "rest"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
assertStringArray(t, ret, []string{"rest"})
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !cmd.G {
|
||||
t.Errorf("Expected Command.G to be true")
|
||||
}
|
||||
|
||||
if p.Command.Find("cmd") != c {
|
||||
t.Errorf("Expected to find command `cmd'")
|
||||
}
|
||||
|
||||
if p.Commands()[0] != c {
|
||||
t.Errorf("Expected command %#v, but got %#v", c, p.Commands()[0])
|
||||
}
|
||||
|
||||
if c.Options()[0].ShortName != 'g' {
|
||||
t.Errorf("Expected short name `g' but got %v", c.Options()[0].ShortName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandNestedInline(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
|
||||
Nested struct {
|
||||
N string `long:"n"`
|
||||
} `command:"nested"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
p, ret := assertParserSuccess(t, &opts, "-v", "cmd", "-g", "nested", "--n", "n", "rest")
|
||||
|
||||
assertStringArray(t, ret, []string{"rest"})
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Command.G {
|
||||
t.Errorf("Expected Command.G to be true")
|
||||
}
|
||||
|
||||
assertString(t, opts.Command.Nested.N, "n")
|
||||
|
||||
if c := p.Command.Find("cmd"); c == nil {
|
||||
t.Errorf("Expected to find command `cmd'")
|
||||
} else {
|
||||
if c != p.Active {
|
||||
t.Errorf("Expected `cmd' to be the active parser command")
|
||||
}
|
||||
|
||||
if nested := c.Find("nested"); nested == nil {
|
||||
t.Errorf("Expected to find command `nested'")
|
||||
} else if nested != c.Active {
|
||||
t.Errorf("Expected to find command `nested' to be the active `cmd' command")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiredOnCommand(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v" required:"true"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrRequired, fmt.Sprintf("the required flag `%cv' was not specified", defaultShortOptDelimiter), &opts, "cmd")
|
||||
}
|
||||
|
||||
func TestRequiredAllOnCommand(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v" required:"true"`
|
||||
Missing bool `long:"missing" required:"true"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrRequired, fmt.Sprintf("the required flags `%smissing' and `%cv' were not specified", defaultLongOptDelimiter, defaultShortOptDelimiter), &opts, "cmd")
|
||||
}
|
||||
|
||||
func TestDefaultOnCommand(t *testing.T) {
|
||||
var opts = struct {
|
||||
Command struct {
|
||||
G bool `short:"g" default:"true"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cmd")
|
||||
|
||||
if !opts.Command.G {
|
||||
t.Errorf("Expected G to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubcommandsOptional(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Cmd1 struct {
|
||||
} `command:"remove"`
|
||||
|
||||
Cmd2 struct {
|
||||
} `command:"add"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
p.SubcommandsOptional = true
|
||||
|
||||
_, err := p.ParseArgs([]string{"-v"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandAlias(t *testing.T) {
|
||||
var opts = struct {
|
||||
Command struct {
|
||||
G bool `short:"g" default:"true"`
|
||||
} `command:"cmd" alias:"cm"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cm")
|
||||
|
||||
if !opts.Command.G {
|
||||
t.Errorf("Expected G to be true")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Completion is a type containing information of a completion.
|
||||
type Completion struct {
|
||||
// The completed item
|
||||
Item string
|
||||
|
||||
// A description of the completed item (optional)
|
||||
Description string
|
||||
}
|
||||
|
||||
type completions []Completion
|
||||
|
||||
func (c completions) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c completions) Less(i, j int) bool {
|
||||
return c[i].Item < c[j].Item
|
||||
}
|
||||
|
||||
func (c completions) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
// Completer is an interface which can be implemented by types
|
||||
// to provide custom command line argument completion.
|
||||
type Completer interface {
|
||||
// Complete receives a prefix representing a (partial) value
|
||||
// for its type and should provide a list of possible valid
|
||||
// completions.
|
||||
Complete(match string) []Completion
|
||||
}
|
||||
|
||||
type completion struct {
|
||||
parser *Parser
|
||||
|
||||
ShowDescriptions bool
|
||||
}
|
||||
|
||||
// Filename is a string alias which provides filename completion.
|
||||
type Filename string
|
||||
|
||||
func completionsWithoutDescriptions(items []string) []Completion {
|
||||
ret := make([]Completion, len(items))
|
||||
|
||||
for i, v := range items {
|
||||
ret[i].Item = v
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Complete returns a list of existing files with the given
|
||||
// prefix.
|
||||
func (f *Filename) Complete(match string) []Completion {
|
||||
ret, _ := filepath.Glob(match + "*")
|
||||
return completionsWithoutDescriptions(ret)
|
||||
}
|
||||
|
||||
func (c *completion) skipPositional(s *parseState, n int) {
|
||||
if n >= len(s.positional) {
|
||||
s.positional = nil
|
||||
} else {
|
||||
s.positional = s.positional[n:]
|
||||
}
|
||||
}
|
||||
|
||||
func (c *completion) completeOptionNames(names map[string]*Option, prefix string, match string) []Completion {
|
||||
n := make([]Completion, 0, len(names))
|
||||
|
||||
for k, opt := range names {
|
||||
if strings.HasPrefix(k, match) {
|
||||
n = append(n, Completion{
|
||||
Item: prefix + k,
|
||||
Description: opt.Description,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (c *completion) completeLongNames(s *parseState, prefix string, match string) []Completion {
|
||||
return c.completeOptionNames(s.lookup.longNames, prefix, match)
|
||||
}
|
||||
|
||||
func (c *completion) completeShortNames(s *parseState, prefix string, match string) []Completion {
|
||||
if len(match) != 0 {
|
||||
return []Completion{
|
||||
Completion{
|
||||
Item: prefix + match,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return c.completeOptionNames(s.lookup.shortNames, prefix, match)
|
||||
}
|
||||
|
||||
func (c *completion) completeCommands(s *parseState, match string) []Completion {
|
||||
n := make([]Completion, 0, len(s.command.commands))
|
||||
|
||||
for _, cmd := range s.command.commands {
|
||||
if cmd.data != c && strings.HasPrefix(cmd.Name, match) {
|
||||
n = append(n, Completion{
|
||||
Item: cmd.Name,
|
||||
Description: cmd.ShortDescription,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (c *completion) completeValue(value reflect.Value, prefix string, match string) []Completion {
|
||||
i := value.Interface()
|
||||
|
||||
var ret []Completion
|
||||
|
||||
if cmp, ok := i.(Completer); ok {
|
||||
ret = cmp.Complete(match)
|
||||
} else if value.CanAddr() {
|
||||
if cmp, ok = value.Addr().Interface().(Completer); ok {
|
||||
ret = cmp.Complete(match)
|
||||
}
|
||||
}
|
||||
|
||||
for i, v := range ret {
|
||||
ret[i].Item = prefix + v.Item
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *completion) completeArg(arg *Arg, prefix string, match string) []Completion {
|
||||
if arg.isRemaining() {
|
||||
// For remaining positional args (that are parsed into a slice), complete
|
||||
// based on the element type.
|
||||
return c.completeValue(reflect.New(arg.value.Type().Elem()), prefix, match)
|
||||
}
|
||||
|
||||
return c.completeValue(arg.value, prefix, match)
|
||||
}
|
||||
|
||||
func (c *completion) complete(args []string) []Completion {
|
||||
if len(args) == 0 {
|
||||
args = []string{""}
|
||||
}
|
||||
|
||||
s := &parseState{
|
||||
args: args,
|
||||
}
|
||||
|
||||
c.parser.fillParseState(s)
|
||||
|
||||
var opt *Option
|
||||
|
||||
for len(s.args) > 1 {
|
||||
arg := s.pop()
|
||||
|
||||
if (c.parser.Options&PassDoubleDash) != None && arg == "--" {
|
||||
opt = nil
|
||||
c.skipPositional(s, len(s.args)-1)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if argumentIsOption(arg) {
|
||||
prefix, optname, islong := stripOptionPrefix(arg)
|
||||
optname, _, argument := splitOption(prefix, optname, islong)
|
||||
|
||||
if argument == nil {
|
||||
var o *Option
|
||||
canarg := true
|
||||
|
||||
if islong {
|
||||
o = s.lookup.longNames[optname]
|
||||
} else {
|
||||
for i, r := range optname {
|
||||
sname := string(r)
|
||||
o = s.lookup.shortNames[sname]
|
||||
|
||||
if o == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if i == 0 && o.canArgument() && len(optname) != len(sname) {
|
||||
canarg = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if o == nil && (c.parser.Options&PassAfterNonOption) != None {
|
||||
opt = nil
|
||||
c.skipPositional(s, len(s.args)-1)
|
||||
|
||||
break
|
||||
} else if o != nil && o.canArgument() && !o.OptionalArgument && canarg {
|
||||
if len(s.args) > 1 {
|
||||
s.pop()
|
||||
} else {
|
||||
opt = o
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if len(s.positional) > 0 {
|
||||
if !s.positional[0].isRemaining() {
|
||||
// Don't advance beyond a remaining positional arg (because
|
||||
// it consumes all subsequent args).
|
||||
s.positional = s.positional[1:]
|
||||
}
|
||||
} else if cmd, ok := s.lookup.commands[arg]; ok {
|
||||
cmd.fillParseState(s)
|
||||
}
|
||||
|
||||
opt = nil
|
||||
}
|
||||
}
|
||||
|
||||
lastarg := s.args[len(s.args)-1]
|
||||
var ret []Completion
|
||||
|
||||
if opt != nil {
|
||||
// Completion for the argument of 'opt'
|
||||
ret = c.completeValue(opt.value, "", lastarg)
|
||||
} else if argumentStartsOption(lastarg) {
|
||||
// Complete the option
|
||||
prefix, optname, islong := stripOptionPrefix(lastarg)
|
||||
optname, split, argument := splitOption(prefix, optname, islong)
|
||||
|
||||
if argument == nil && !islong {
|
||||
rname, n := utf8.DecodeRuneInString(optname)
|
||||
sname := string(rname)
|
||||
|
||||
if opt := s.lookup.shortNames[sname]; opt != nil && opt.canArgument() {
|
||||
ret = c.completeValue(opt.value, prefix+sname, optname[n:])
|
||||
} else {
|
||||
ret = c.completeShortNames(s, prefix, optname)
|
||||
}
|
||||
} else if argument != nil {
|
||||
if islong {
|
||||
opt = s.lookup.longNames[optname]
|
||||
} else {
|
||||
opt = s.lookup.shortNames[optname]
|
||||
}
|
||||
|
||||
if opt != nil {
|
||||
ret = c.completeValue(opt.value, prefix+optname+split, *argument)
|
||||
}
|
||||
} else if islong {
|
||||
ret = c.completeLongNames(s, prefix, optname)
|
||||
} else {
|
||||
ret = c.completeShortNames(s, prefix, optname)
|
||||
}
|
||||
} else if len(s.positional) > 0 {
|
||||
// Complete for positional argument
|
||||
ret = c.completeArg(s.positional[0], "", lastarg)
|
||||
} else if len(s.command.commands) > 0 {
|
||||
// Complete for command
|
||||
ret = c.completeCommands(s, lastarg)
|
||||
}
|
||||
|
||||
sort.Sort(completions(ret))
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *completion) execute(args []string) {
|
||||
ret := c.complete(args)
|
||||
|
||||
if c.ShowDescriptions && len(ret) > 1 {
|
||||
maxl := 0
|
||||
|
||||
for _, v := range ret {
|
||||
if len(v.Item) > maxl {
|
||||
maxl = len(v.Item)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range ret {
|
||||
fmt.Printf("%s", v.Item)
|
||||
|
||||
if len(v.Description) > 0 {
|
||||
fmt.Printf("%s # %s", strings.Repeat(" ", maxl-len(v.Item)), v.Description)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
} else {
|
||||
for _, v := range ret {
|
||||
fmt.Println(v.Item)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TestComplete struct {
|
||||
}
|
||||
|
||||
func (t *TestComplete) Complete(match string) []Completion {
|
||||
options := []string{
|
||||
"hello world",
|
||||
"hello universe",
|
||||
"hello multiverse",
|
||||
}
|
||||
|
||||
ret := make([]Completion, 0, len(options))
|
||||
|
||||
for _, o := range options {
|
||||
if strings.HasPrefix(o, match) {
|
||||
ret = append(ret, Completion{
|
||||
Item: o,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
var completionTestOptions struct {
|
||||
Verbose bool `short:"v" long:"verbose" description:"Verbose messages"`
|
||||
Debug bool `short:"d" long:"debug" description:"Enable debug"`
|
||||
Version bool `long:"version" description:"Show version"`
|
||||
Required bool `long:"required" required:"true" description:"This is required"`
|
||||
|
||||
AddCommand struct {
|
||||
Positional struct {
|
||||
Filename Filename
|
||||
} `positional-args:"yes"`
|
||||
} `command:"add" description:"add an item"`
|
||||
|
||||
AddMultiCommand struct {
|
||||
Positional struct {
|
||||
Filename []Filename
|
||||
} `positional-args:"yes"`
|
||||
} `command:"add-multi" description:"add multiple items"`
|
||||
|
||||
RemoveCommand struct {
|
||||
Other bool `short:"o"`
|
||||
File Filename `short:"f" long:"filename"`
|
||||
} `command:"rm" description:"remove an item"`
|
||||
|
||||
RenameCommand struct {
|
||||
Completed TestComplete `short:"c" long:"completed"`
|
||||
} `command:"rename" description:"rename an item"`
|
||||
}
|
||||
|
||||
type completionTest struct {
|
||||
Args []string
|
||||
Completed []string
|
||||
ShowDescriptions bool
|
||||
}
|
||||
|
||||
var completionTests []completionTest
|
||||
|
||||
func init() {
|
||||
_, sourcefile, _, _ := runtime.Caller(0)
|
||||
completionTestSourcedir := filepath.Join(filepath.SplitList(path.Dir(sourcefile))...)
|
||||
|
||||
completionTestFilename := []string{filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion_test.go")}
|
||||
|
||||
completionTests = []completionTest{
|
||||
{
|
||||
// Short names
|
||||
[]string{"-"},
|
||||
[]string{"-d", "-v"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Short names concatenated
|
||||
[]string{"-dv"},
|
||||
[]string{"-dv"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Long names
|
||||
[]string{"--"},
|
||||
[]string{"--debug", "--required", "--verbose", "--version"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Long names with descriptions
|
||||
[]string{"--"},
|
||||
[]string{
|
||||
"--debug # Enable debug",
|
||||
"--required # This is required",
|
||||
"--verbose # Verbose messages",
|
||||
"--version # Show version",
|
||||
},
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
// Long names partial
|
||||
[]string{"--ver"},
|
||||
[]string{"--verbose", "--version"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Commands
|
||||
[]string{""},
|
||||
[]string{"add", "add-multi", "rename", "rm"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Commands with descriptions
|
||||
[]string{""},
|
||||
[]string{
|
||||
"add # add an item",
|
||||
"add-multi # add multiple items",
|
||||
"rename # rename an item",
|
||||
"rm # remove an item",
|
||||
},
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
// Commands partial
|
||||
[]string{"r"},
|
||||
[]string{"rename", "rm"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Positional filename
|
||||
[]string{"add", filepath.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Multiple positional filename (1 arg)
|
||||
[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
{
|
||||
// Multiple positional filename (2 args)
|
||||
[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
{
|
||||
// Multiple positional filename (3 args)
|
||||
[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Flag filename
|
||||
[]string{"rm", "-f", path.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Flag short concat last filename
|
||||
[]string{"rm", "-of", path.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Flag concat filename
|
||||
[]string{"rm", "-f" + path.Join(completionTestSourcedir, "completion")},
|
||||
[]string{"-f" + completionTestFilename[0], "-f" + completionTestFilename[1]},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Flag equal concat filename
|
||||
[]string{"rm", "-f=" + path.Join(completionTestSourcedir, "completion")},
|
||||
[]string{"-f=" + completionTestFilename[0], "-f=" + completionTestFilename[1]},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Flag concat long filename
|
||||
[]string{"rm", "--filename=" + path.Join(completionTestSourcedir, "completion")},
|
||||
[]string{"--filename=" + completionTestFilename[0], "--filename=" + completionTestFilename[1]},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Flag long filename
|
||||
[]string{"rm", "--filename", path.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Custom completed
|
||||
[]string{"rename", "-c", "hello un"},
|
||||
[]string{"hello universe"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompletion(t *testing.T) {
|
||||
p := NewParser(&completionTestOptions, Default)
|
||||
c := &completion{parser: p}
|
||||
|
||||
for _, test := range completionTests {
|
||||
if test.ShowDescriptions {
|
||||
continue
|
||||
}
|
||||
|
||||
ret := c.complete(test.Args)
|
||||
items := make([]string, len(ret))
|
||||
|
||||
for i, v := range ret {
|
||||
items[i] = v.Item
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(items, test.Completed) {
|
||||
t.Errorf("Args: %#v, %#v\n Expected: %#v\n Got: %#v", test.Args, test.ShowDescriptions, test.Completed, items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserCompletion(t *testing.T) {
|
||||
for _, test := range completionTests {
|
||||
if test.ShowDescriptions {
|
||||
os.Setenv("GO_FLAGS_COMPLETION", "verbose")
|
||||
} else {
|
||||
os.Setenv("GO_FLAGS_COMPLETION", "1")
|
||||
}
|
||||
|
||||
tmp := os.Stdout
|
||||
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
out := make(chan string)
|
||||
|
||||
go func() {
|
||||
var buf bytes.Buffer
|
||||
|
||||
io.Copy(&buf, r)
|
||||
|
||||
out <- buf.String()
|
||||
}()
|
||||
|
||||
p := NewParser(&completionTestOptions, None)
|
||||
|
||||
_, err := p.ParseArgs(test.Args)
|
||||
|
||||
w.Close()
|
||||
|
||||
os.Stdout = tmp
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
got := strings.Split(strings.Trim(<-out, "\n"), "\n")
|
||||
|
||||
if !reflect.DeepEqual(got, test.Completed) {
|
||||
t.Errorf("Expected: %#v\nGot: %#v", test.Completed, got)
|
||||
}
|
||||
}
|
||||
|
||||
os.Setenv("GO_FLAGS_COMPLETION", "")
|
||||
}
|
||||
+377
@@ -0,0 +1,377 @@
|
||||
// Copyright 2012 Jesse van den Kieboom. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Marshaler is the interface implemented by types that can marshal themselves
|
||||
// to a string representation of the flag.
|
||||
type Marshaler interface {
|
||||
// MarshalFlag marshals a flag value to its string representation.
|
||||
MarshalFlag() (string, error)
|
||||
}
|
||||
|
||||
// Unmarshaler is the interface implemented by types that can unmarshal a flag
|
||||
// argument to themselves. The provided value is directly passed from the
|
||||
// command line.
|
||||
type Unmarshaler interface {
|
||||
// UnmarshalFlag unmarshals a string value representation to the flag
|
||||
// value (which therefore needs to be a pointer receiver).
|
||||
UnmarshalFlag(value string) error
|
||||
}
|
||||
|
||||
func getBase(options multiTag, base int) (int, error) {
|
||||
sbase := options.Get("base")
|
||||
|
||||
var err error
|
||||
var ivbase int64
|
||||
|
||||
if sbase != "" {
|
||||
ivbase, err = strconv.ParseInt(sbase, 10, 32)
|
||||
base = int(ivbase)
|
||||
}
|
||||
|
||||
return base, err
|
||||
}
|
||||
|
||||
func convertMarshal(val reflect.Value) (bool, string, error) {
|
||||
// Check first for the Marshaler interface
|
||||
if val.Type().NumMethod() > 0 && val.CanInterface() {
|
||||
if marshaler, ok := val.Interface().(Marshaler); ok {
|
||||
ret, err := marshaler.MarshalFlag()
|
||||
return true, ret, err
|
||||
}
|
||||
}
|
||||
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
func convertToString(val reflect.Value, options multiTag) (string, error) {
|
||||
if ok, ret, err := convertMarshal(val); ok {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
tp := val.Type()
|
||||
|
||||
// Support for time.Duration
|
||||
if tp == reflect.TypeOf((*time.Duration)(nil)).Elem() {
|
||||
stringer := val.Interface().(fmt.Stringer)
|
||||
return stringer.String(), nil
|
||||
}
|
||||
|
||||
switch tp.Kind() {
|
||||
case reflect.String:
|
||||
return val.String(), nil
|
||||
case reflect.Bool:
|
||||
if val.Bool() {
|
||||
return "true", nil
|
||||
}
|
||||
|
||||
return "false", nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
base, err := getBase(options, 10)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strconv.FormatInt(val.Int(), base), nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
base, err := getBase(options, 10)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strconv.FormatUint(val.Uint(), base), nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return strconv.FormatFloat(val.Float(), 'g', -1, tp.Bits()), nil
|
||||
case reflect.Slice:
|
||||
if val.Len() == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
ret := "["
|
||||
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
if i != 0 {
|
||||
ret += ", "
|
||||
}
|
||||
|
||||
item, err := convertToString(val.Index(i), options)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ret += item
|
||||
}
|
||||
|
||||
return ret + "]", nil
|
||||
case reflect.Map:
|
||||
ret := "{"
|
||||
|
||||
for i, key := range val.MapKeys() {
|
||||
if i != 0 {
|
||||
ret += ", "
|
||||
}
|
||||
|
||||
keyitem, err := convertToString(key, options)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
item, err := convertToString(val.MapIndex(key), options)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ret += keyitem + ":" + item
|
||||
}
|
||||
|
||||
return ret + "}", nil
|
||||
case reflect.Ptr:
|
||||
return convertToString(reflect.Indirect(val), options)
|
||||
case reflect.Interface:
|
||||
if !val.IsNil() {
|
||||
return convertToString(val.Elem(), options)
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func convertUnmarshal(val string, retval reflect.Value) (bool, error) {
|
||||
if retval.Type().NumMethod() > 0 && retval.CanInterface() {
|
||||
if unmarshaler, ok := retval.Interface().(Unmarshaler); ok {
|
||||
return true, unmarshaler.UnmarshalFlag(val)
|
||||
}
|
||||
}
|
||||
|
||||
if retval.Type().Kind() != reflect.Ptr && retval.CanAddr() {
|
||||
return convertUnmarshal(val, retval.Addr())
|
||||
}
|
||||
|
||||
if retval.Type().Kind() == reflect.Interface && !retval.IsNil() {
|
||||
return convertUnmarshal(val, retval.Elem())
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func convert(val string, retval reflect.Value, options multiTag) error {
|
||||
if ok, err := convertUnmarshal(val, retval); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
tp := retval.Type()
|
||||
|
||||
// Support for time.Duration
|
||||
if tp == reflect.TypeOf((*time.Duration)(nil)).Elem() {
|
||||
parsed, err := time.ParseDuration(val)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
retval.SetInt(int64(parsed))
|
||||
return nil
|
||||
}
|
||||
|
||||
switch tp.Kind() {
|
||||
case reflect.String:
|
||||
retval.SetString(val)
|
||||
case reflect.Bool:
|
||||
if val == "" {
|
||||
retval.SetBool(true)
|
||||
} else {
|
||||
b, err := strconv.ParseBool(val)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
retval.SetBool(b)
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
base, err := getBase(options, 10)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parsed, err := strconv.ParseInt(val, base, tp.Bits())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
retval.SetInt(parsed)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
base, err := getBase(options, 10)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parsed, err := strconv.ParseUint(val, base, tp.Bits())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
retval.SetUint(parsed)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
parsed, err := strconv.ParseFloat(val, tp.Bits())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
retval.SetFloat(parsed)
|
||||
case reflect.Slice:
|
||||
elemtp := tp.Elem()
|
||||
|
||||
elemvalptr := reflect.New(elemtp)
|
||||
elemval := reflect.Indirect(elemvalptr)
|
||||
|
||||
if err := convert(val, elemval, options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
retval.Set(reflect.Append(retval, elemval))
|
||||
case reflect.Map:
|
||||
parts := strings.SplitN(val, ":", 2)
|
||||
|
||||
key := parts[0]
|
||||
var value string
|
||||
|
||||
if len(parts) == 2 {
|
||||
value = parts[1]
|
||||
}
|
||||
|
||||
keytp := tp.Key()
|
||||
keyval := reflect.New(keytp)
|
||||
|
||||
if err := convert(key, keyval, options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
valuetp := tp.Elem()
|
||||
valueval := reflect.New(valuetp)
|
||||
|
||||
if err := convert(value, valueval, options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if retval.IsNil() {
|
||||
retval.Set(reflect.MakeMap(tp))
|
||||
}
|
||||
|
||||
retval.SetMapIndex(reflect.Indirect(keyval), reflect.Indirect(valueval))
|
||||
case reflect.Ptr:
|
||||
if retval.IsNil() {
|
||||
retval.Set(reflect.New(retval.Type().Elem()))
|
||||
}
|
||||
|
||||
return convert(val, reflect.Indirect(retval), options)
|
||||
case reflect.Interface:
|
||||
if !retval.IsNil() {
|
||||
return convert(val, retval.Elem(), options)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isPrint(s string) bool {
|
||||
for _, c := range s {
|
||||
if !strconv.IsPrint(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func quoteIfNeeded(s string) string {
|
||||
if !isPrint(s) {
|
||||
return strconv.Quote(s)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func quoteIfNeededV(s []string) []string {
|
||||
ret := make([]string, len(s))
|
||||
|
||||
for i, v := range s {
|
||||
ret[i] = quoteIfNeeded(v)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func quoteV(s []string) []string {
|
||||
ret := make([]string, len(s))
|
||||
|
||||
for i, v := range s {
|
||||
ret[i] = strconv.Quote(v)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func unquoteIfPossible(s string) (string, error) {
|
||||
if len(s) == 0 || s[0] != '"' {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
return strconv.Unquote(s)
|
||||
}
|
||||
|
||||
func wrapText(s string, l int, prefix string) string {
|
||||
// Basic text wrapping of s at spaces to fit in l
|
||||
var ret string
|
||||
|
||||
s = strings.TrimSpace(s)
|
||||
|
||||
for len(s) > l {
|
||||
// Try to split on space
|
||||
suffix := ""
|
||||
|
||||
pos := strings.LastIndex(s[:l], " ")
|
||||
|
||||
if pos < 0 {
|
||||
pos = l - 1
|
||||
suffix = "-\n"
|
||||
}
|
||||
|
||||
if len(ret) != 0 {
|
||||
ret += "\n" + prefix
|
||||
}
|
||||
|
||||
ret += strings.TrimSpace(s[:pos]) + suffix
|
||||
s = strings.TrimSpace(s[pos:])
|
||||
}
|
||||
|
||||
if len(s) > 0 {
|
||||
if len(ret) != 0 {
|
||||
ret += "\n" + prefix
|
||||
}
|
||||
|
||||
return ret + s
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func expectConvert(t *testing.T, o *Option, expected string) {
|
||||
s, err := convertToString(o.value, o.tag)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
assertString(t, s, expected)
|
||||
}
|
||||
|
||||
func TestConvertToString(t *testing.T) {
|
||||
d, _ := time.ParseDuration("1h2m4s")
|
||||
|
||||
var opts = struct {
|
||||
String string `long:"string"`
|
||||
|
||||
Int int `long:"int"`
|
||||
Int8 int8 `long:"int8"`
|
||||
Int16 int16 `long:"int16"`
|
||||
Int32 int32 `long:"int32"`
|
||||
Int64 int64 `long:"int64"`
|
||||
|
||||
Uint uint `long:"uint"`
|
||||
Uint8 uint8 `long:"uint8"`
|
||||
Uint16 uint16 `long:"uint16"`
|
||||
Uint32 uint32 `long:"uint32"`
|
||||
Uint64 uint64 `long:"uint64"`
|
||||
|
||||
Float32 float32 `long:"float32"`
|
||||
Float64 float64 `long:"float64"`
|
||||
|
||||
Duration time.Duration `long:"duration"`
|
||||
|
||||
Bool bool `long:"bool"`
|
||||
|
||||
IntSlice []int `long:"int-slice"`
|
||||
IntFloatMap map[int]float64 `long:"int-float-map"`
|
||||
|
||||
PtrBool *bool `long:"ptr-bool"`
|
||||
Interface interface{} `long:"interface"`
|
||||
|
||||
Int32Base int32 `long:"int32-base" base:"16"`
|
||||
Uint32Base uint32 `long:"uint32-base" base:"16"`
|
||||
}{
|
||||
"string",
|
||||
|
||||
-2,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
|
||||
1.2,
|
||||
-3.4,
|
||||
|
||||
d,
|
||||
true,
|
||||
|
||||
[]int{-3, 4, -2},
|
||||
map[int]float64{-2: 4.5},
|
||||
|
||||
new(bool),
|
||||
float32(5.2),
|
||||
|
||||
-5823,
|
||||
4232,
|
||||
}
|
||||
|
||||
p := NewNamedParser("test", Default)
|
||||
grp, _ := p.AddGroup("test group", "", &opts)
|
||||
|
||||
expects := []string{
|
||||
"string",
|
||||
"-2",
|
||||
"-1",
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
|
||||
"1.2",
|
||||
"-3.4",
|
||||
|
||||
"1h2m4s",
|
||||
"true",
|
||||
|
||||
"[-3, 4, -2]",
|
||||
"{-2:4.5}",
|
||||
|
||||
"false",
|
||||
"5.2",
|
||||
|
||||
"-16bf",
|
||||
"1088",
|
||||
}
|
||||
|
||||
for i, v := range grp.Options() {
|
||||
expectConvert(t, v, expects[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToStringInvalidIntBase(t *testing.T) {
|
||||
var opts = struct {
|
||||
Int int `long:"int" base:"no"`
|
||||
}{
|
||||
2,
|
||||
}
|
||||
|
||||
p := NewNamedParser("test", Default)
|
||||
grp, _ := p.AddGroup("test group", "", &opts)
|
||||
o := grp.Options()[0]
|
||||
|
||||
_, err := convertToString(o.value, o.tag)
|
||||
|
||||
if err != nil {
|
||||
err = newErrorf(ErrMarshal, "%v", err)
|
||||
}
|
||||
|
||||
assertError(t, err, ErrMarshal, "strconv.ParseInt: parsing \"no\": invalid syntax")
|
||||
}
|
||||
|
||||
func TestConvertToStringInvalidUintBase(t *testing.T) {
|
||||
var opts = struct {
|
||||
Uint uint `long:"uint" base:"no"`
|
||||
}{
|
||||
2,
|
||||
}
|
||||
|
||||
p := NewNamedParser("test", Default)
|
||||
grp, _ := p.AddGroup("test group", "", &opts)
|
||||
o := grp.Options()[0]
|
||||
|
||||
_, err := convertToString(o.value, o.tag)
|
||||
|
||||
if err != nil {
|
||||
err = newErrorf(ErrMarshal, "%v", err)
|
||||
}
|
||||
|
||||
assertError(t, err, ErrMarshal, "strconv.ParseInt: parsing \"no\": invalid syntax")
|
||||
}
|
||||
|
||||
func TestWrapText(t *testing.T) {
|
||||
s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
|
||||
got := wrapText(s, 60, " ")
|
||||
expected := `Lorem ipsum dolor sit amet, consectetur adipisicing elit,
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
||||
ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit
|
||||
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
|
||||
occaecat cupidatat non proident, sunt in culpa qui officia
|
||||
deserunt mollit anim id est laborum.`
|
||||
|
||||
assertDiff(t, got, expected, "wrapped text")
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrorType represents the type of error.
|
||||
type ErrorType uint
|
||||
|
||||
const (
|
||||
// ErrUnknown indicates a generic error.
|
||||
ErrUnknown ErrorType = iota
|
||||
|
||||
// ErrExpectedArgument indicates that an argument was expected.
|
||||
ErrExpectedArgument
|
||||
|
||||
// ErrUnknownFlag indicates an unknown flag.
|
||||
ErrUnknownFlag
|
||||
|
||||
// ErrUnknownGroup indicates an unknown group.
|
||||
ErrUnknownGroup
|
||||
|
||||
// ErrMarshal indicates a marshalling error while converting values.
|
||||
ErrMarshal
|
||||
|
||||
// ErrHelp indicates that the built-in help was shown (the error
|
||||
// contains the help message).
|
||||
ErrHelp
|
||||
|
||||
// ErrNoArgumentForBool indicates that an argument was given for a
|
||||
// boolean flag (which don't not take any arguments).
|
||||
ErrNoArgumentForBool
|
||||
|
||||
// ErrRequired indicates that a required flag was not provided.
|
||||
ErrRequired
|
||||
|
||||
// ErrShortNameTooLong indicates that a short flag name was specified,
|
||||
// longer than one character.
|
||||
ErrShortNameTooLong
|
||||
|
||||
// ErrDuplicatedFlag indicates that a short or long flag has been
|
||||
// defined more than once
|
||||
ErrDuplicatedFlag
|
||||
|
||||
// ErrTag indicates an error while parsing flag tags.
|
||||
ErrTag
|
||||
|
||||
// ErrCommandRequired indicates that a command was required but not
|
||||
// specified
|
||||
ErrCommandRequired
|
||||
|
||||
// ErrUnknownCommand indicates that an unknown command was specified.
|
||||
ErrUnknownCommand
|
||||
)
|
||||
|
||||
func (e ErrorType) String() string {
|
||||
switch e {
|
||||
case ErrUnknown:
|
||||
return "unknown"
|
||||
case ErrExpectedArgument:
|
||||
return "expected argument"
|
||||
case ErrUnknownFlag:
|
||||
return "unknown flag"
|
||||
case ErrUnknownGroup:
|
||||
return "unknown group"
|
||||
case ErrMarshal:
|
||||
return "marshal"
|
||||
case ErrHelp:
|
||||
return "help"
|
||||
case ErrNoArgumentForBool:
|
||||
return "no argument for bool"
|
||||
case ErrRequired:
|
||||
return "required"
|
||||
case ErrShortNameTooLong:
|
||||
return "short name too long"
|
||||
case ErrDuplicatedFlag:
|
||||
return "duplicated flag"
|
||||
case ErrTag:
|
||||
return "tag"
|
||||
case ErrCommandRequired:
|
||||
return "command required"
|
||||
case ErrUnknownCommand:
|
||||
return "unknown command"
|
||||
}
|
||||
|
||||
return "unrecognized error type"
|
||||
}
|
||||
|
||||
// Error represents a parser error. The error returned from Parse is of this
|
||||
// type. The error contains both a Type and Message.
|
||||
type Error struct {
|
||||
// The type of error
|
||||
Type ErrorType
|
||||
|
||||
// The error message
|
||||
Message string
|
||||
}
|
||||
|
||||
// Error returns the error's message
|
||||
func (e *Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func newError(tp ErrorType, message string) *Error {
|
||||
return &Error{
|
||||
Type: tp,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
func newErrorf(tp ErrorType, format string, args ...interface{}) *Error {
|
||||
return newError(tp, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func wrapError(err error) *Error {
|
||||
ret, ok := err.(*Error)
|
||||
|
||||
if !ok {
|
||||
return newError(ErrUnknown, err.Error())
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Example of use of the flags package.
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
var opts struct {
|
||||
// Slice of bool will append 'true' each time the option
|
||||
// is encountered (can be set multiple times, like -vvv)
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
|
||||
|
||||
// Example of automatic marshalling to desired type (uint)
|
||||
Offset uint `long:"offset" description:"Offset"`
|
||||
|
||||
// Example of a callback, called each time the option is found.
|
||||
Call func(string) `short:"c" description:"Call phone number"`
|
||||
|
||||
// Example of a required flag
|
||||
Name string `short:"n" long:"name" description:"A name" required:"true"`
|
||||
|
||||
// Example of a value name
|
||||
File string `short:"f" long:"file" description:"A file" value-name:"FILE"`
|
||||
|
||||
// Example of a pointer
|
||||
Ptr *int `short:"p" description:"A pointer to an integer"`
|
||||
|
||||
// Example of a slice of strings
|
||||
StringSlice []string `short:"s" description:"A slice of strings"`
|
||||
|
||||
// Example of a slice of pointers
|
||||
PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
|
||||
|
||||
// Example of a map
|
||||
IntMap map[string]int `long:"intmap" description:"A map from string to int"`
|
||||
|
||||
// Example of a filename (useful for completion)
|
||||
Filename Filename `long:"filename" description:"A filename"`
|
||||
|
||||
// Example of positional arguments
|
||||
Args struct {
|
||||
Id string
|
||||
Num int
|
||||
Rest []string
|
||||
} `positional-args:"yes" required:"yes"`
|
||||
}
|
||||
|
||||
// Callback which will invoke callto:<argument> to call a number.
|
||||
// Note that this works just on OS X (and probably only with
|
||||
// Skype) but it shows the idea.
|
||||
opts.Call = func(num string) {
|
||||
cmd := exec.Command("open", "callto:"+num)
|
||||
cmd.Start()
|
||||
cmd.Process.Release()
|
||||
}
|
||||
|
||||
// Make some fake arguments to parse.
|
||||
args := []string{
|
||||
"-vv",
|
||||
"--offset=5",
|
||||
"-n", "Me",
|
||||
"-p", "3",
|
||||
"-s", "hello",
|
||||
"-s", "world",
|
||||
"--ptrslice", "hello",
|
||||
"--ptrslice", "world",
|
||||
"--intmap", "a:1",
|
||||
"--intmap", "b:5",
|
||||
"--filename", "hello.go",
|
||||
"id",
|
||||
"10",
|
||||
"remaining1",
|
||||
"remaining2",
|
||||
}
|
||||
|
||||
// Parse flags from `args'. Note that here we use flags.ParseArgs for
|
||||
// the sake of making a working example. Normally, you would simply use
|
||||
// flags.Parse(&opts) which uses os.Args
|
||||
_, err := ParseArgs(&opts, args)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Verbosity: %v\n", opts.Verbose)
|
||||
fmt.Printf("Offset: %d\n", opts.Offset)
|
||||
fmt.Printf("Name: %s\n", opts.Name)
|
||||
fmt.Printf("Ptr: %d\n", *opts.Ptr)
|
||||
fmt.Printf("StringSlice: %v\n", opts.StringSlice)
|
||||
fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1])
|
||||
fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"])
|
||||
fmt.Printf("Filename: %v\n", opts.Filename)
|
||||
fmt.Printf("Args.Id: %s\n", opts.Args.Id)
|
||||
fmt.Printf("Args.Num: %d\n", opts.Args.Num)
|
||||
fmt.Printf("Args.Rest: %v\n", opts.Args.Rest)
|
||||
|
||||
// Output: Verbosity: [true true]
|
||||
// Offset: 5
|
||||
// Name: Me
|
||||
// Ptr: 3
|
||||
// StringSlice: [hello world]
|
||||
// PtrSlice: [hello world]
|
||||
// IntMap: [a:1 b:5]
|
||||
// Filename: hello.go
|
||||
// Args.Id: id
|
||||
// Args.Num: 10
|
||||
// Args.Rest: [remaining1 remaining2]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type AddCommand struct {
|
||||
All bool `short:"a" long:"all" description:"Add all files"`
|
||||
}
|
||||
|
||||
var addCommand AddCommand
|
||||
|
||||
func (x *AddCommand) Execute(args []string) error {
|
||||
fmt.Printf("Adding (all=%v): %#v\n", x.All, args)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
parser.AddCommand("add",
|
||||
"Add a file",
|
||||
"The add command adds a file to the repository. Use -a to add all files.",
|
||||
&addCommand)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
_examples() {
|
||||
args=("${COMP_WORDS[@]:1:$COMP_CWORD}")
|
||||
|
||||
local IFS=$'\n'
|
||||
COMPREPLY=($(GO_FLAGS_COMPLETION=1 ${COMP_WORDS[0]} "${args[@]}"))
|
||||
return 1
|
||||
}
|
||||
|
||||
complete -F _examples examples
|
||||
@@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type EditorOptions struct {
|
||||
Input flags.Filename `short:"i" long:"input" description:"Input file" default:"-"`
|
||||
Output flags.Filename `short:"o" long:"output" description:"Output file" default:"-"`
|
||||
}
|
||||
|
||||
type Point struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
func (p *Point) UnmarshalFlag(value string) error {
|
||||
parts := strings.Split(value, ",")
|
||||
|
||||
if len(parts) != 2 {
|
||||
return errors.New("expected two numbers separated by a ,")
|
||||
}
|
||||
|
||||
x, err := strconv.ParseInt(parts[0], 10, 32)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
y, err := strconv.ParseInt(parts[1], 10, 32)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.X = int(x)
|
||||
p.Y = int(y)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Point) MarshalFlag() (string, error) {
|
||||
return fmt.Sprintf("%d,%d", p.X, p.Y), nil
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
// Example of verbosity with level
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Verbose output"`
|
||||
|
||||
// Example of optional value
|
||||
User string `short:"u" long:"user" description:"User name" optional:"yes" optional-value:"pancake"`
|
||||
|
||||
// Example of map with multiple default values
|
||||
Users map[string]string `long:"users" description:"User e-mail map" default:"system:system@example.org" default:"admin:admin@example.org"`
|
||||
|
||||
// Example of option group
|
||||
Editor EditorOptions `group:"Editor Options"`
|
||||
|
||||
// Example of custom type Marshal/Unmarshal
|
||||
Point Point `long:"point" description:"A x,y point" default:"1,2"`
|
||||
}
|
||||
|
||||
var options Options
|
||||
|
||||
var parser = flags.NewParser(&options, flags.Default)
|
||||
|
||||
func main() {
|
||||
if _, err := parser.Parse(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type RmCommand struct {
|
||||
Force bool `short:"f" long:"force" description:"Force removal of files"`
|
||||
}
|
||||
|
||||
var rmCommand RmCommand
|
||||
|
||||
func (x *RmCommand) Execute(args []string) error {
|
||||
fmt.Printf("Removing (force=%v): %#v\n", x.Force, args)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
parser.AddCommand("rm",
|
||||
"Remove a file",
|
||||
"The rm command removes a file to the repository. Use -f to force removal of files.",
|
||||
&rmCommand)
|
||||
}
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
// Copyright 2012 Jesse van den Kieboom. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package flags provides an extensive command line option parser.
|
||||
The flags package is similar in functionality to the go built-in flag package
|
||||
but provides more options and uses reflection to provide a convenient and
|
||||
succinct way of specifying command line options.
|
||||
|
||||
|
||||
Supported features
|
||||
|
||||
The following features are supported in go-flags:
|
||||
|
||||
Options with short names (-v)
|
||||
Options with long names (--verbose)
|
||||
Options with and without arguments (bool v.s. other type)
|
||||
Options with optional arguments and default values
|
||||
Option default values from ENVIRONMENT_VARIABLES, including slice and map values
|
||||
Multiple option groups each containing a set of options
|
||||
Generate and print well-formatted help message
|
||||
Passing remaining command line arguments after -- (optional)
|
||||
Ignoring unknown command line options (optional)
|
||||
Supports -I/usr/include -I=/usr/include -I /usr/include option argument specification
|
||||
Supports multiple short options -aux
|
||||
Supports all primitive go types (string, int{8..64}, uint{8..64}, float)
|
||||
Supports same option multiple times (can store in slice or last option counts)
|
||||
Supports maps
|
||||
Supports function callbacks
|
||||
Supports namespaces for (nested) option groups
|
||||
|
||||
Additional features specific to Windows:
|
||||
Options with short names (/v)
|
||||
Options with long names (/verbose)
|
||||
Windows-style options with arguments use a colon as the delimiter
|
||||
Modify generated help message with Windows-style / options
|
||||
|
||||
|
||||
Basic usage
|
||||
|
||||
The flags package uses structs, reflection and struct field tags
|
||||
to allow users to specify command line options. This results in very simple
|
||||
and concise specification of your application options. For example:
|
||||
|
||||
type Options struct {
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
|
||||
}
|
||||
|
||||
This specifies one option with a short name -v and a long name --verbose.
|
||||
When either -v or --verbose is found on the command line, a 'true' value
|
||||
will be appended to the Verbose field. e.g. when specifying -vvv, the
|
||||
resulting value of Verbose will be {[true, true, true]}.
|
||||
|
||||
Slice options work exactly the same as primitive type options, except that
|
||||
whenever the option is encountered, a value is appended to the slice.
|
||||
|
||||
Map options from string to primitive type are also supported. On the command
|
||||
line, you specify the value for such an option as key:value. For example
|
||||
|
||||
type Options struct {
|
||||
AuthorInfo string[string] `short:"a"`
|
||||
}
|
||||
|
||||
Then, the AuthorInfo map can be filled with something like
|
||||
-a name:Jesse -a "surname:van den Kieboom".
|
||||
|
||||
Finally, for full control over the conversion between command line argument
|
||||
values and options, user defined types can choose to implement the Marshaler
|
||||
and Unmarshaler interfaces.
|
||||
|
||||
|
||||
Available field tags
|
||||
|
||||
The following is a list of tags for struct fields supported by go-flags:
|
||||
|
||||
short: the short name of the option (single character)
|
||||
long: the long name of the option
|
||||
required: whether an option is required to appear on the command
|
||||
line. If a required option is not present, the parser will
|
||||
return ErrRequired (optional)
|
||||
description: the description of the option (optional)
|
||||
long-description: the long description of the option. Currently only
|
||||
displayed in generated man pages (optional)
|
||||
no-flag: if non-empty this field is ignored as an option (optional)
|
||||
|
||||
optional: whether an argument of the option is optional (optional)
|
||||
optional-value: the value of an optional option when the option occurs
|
||||
without an argument. This tag can be specified multiple
|
||||
times in the case of maps or slices (optional)
|
||||
default: the default value of an option. This tag can be specified
|
||||
multiple times in the case of slices or maps (optional)
|
||||
default-mask: when specified, this value will be displayed in the help
|
||||
instead of the actual default value. This is useful
|
||||
mostly for hiding otherwise sensitive information from
|
||||
showing up in the help. If default-mask takes the special
|
||||
value "-", then no default value will be shown at all
|
||||
(optional)
|
||||
env: the default value of the option is overridden from the
|
||||
specified environment variable, if one has been defined.
|
||||
(optional)
|
||||
env-delim: the 'env' default value from environment is split into
|
||||
multiple values with the given delimiter string, use with
|
||||
slices and maps (optional)
|
||||
value-name: the name of the argument value (to be shown in the help)
|
||||
(optional)
|
||||
|
||||
base: a base (radix) used to convert strings to integer values, the
|
||||
default base is 10 (i.e. decimal) (optional)
|
||||
|
||||
ini-name: the explicit ini option name (optional)
|
||||
no-ini: if non-empty this field is ignored as an ini option
|
||||
(optional)
|
||||
|
||||
group: when specified on a struct field, makes the struct
|
||||
field a separate group with the given name (optional)
|
||||
namespace: when specified on a group struct field, the namespace
|
||||
gets prepended to every option's long name and
|
||||
subgroup's namespace of this group, separated by
|
||||
the parser's namespace delimiter (optional)
|
||||
command: when specified on a struct field, makes the struct
|
||||
field a (sub)command with the given name (optional)
|
||||
subcommands-optional: when specified on a command struct field, makes
|
||||
any subcommands of that command optional (optional)
|
||||
alias: when specified on a command struct field, adds the
|
||||
specified name as an alias for the command. Can be
|
||||
be specified multiple times to add more than one
|
||||
alias (optional)
|
||||
positional-args: when specified on a field with a struct type,
|
||||
uses the fields of that struct to parse remaining
|
||||
positional command line arguments into (in order
|
||||
of the fields). If a field has a slice type,
|
||||
then all remaining arguments will be added to it.
|
||||
Positional arguments are optional by default,
|
||||
unless the "required" tag is specified together
|
||||
with the "positional-args" tag (optional)
|
||||
positional-arg-name: used on a field in a positional argument struct; name
|
||||
of the positional argument placeholder to be shown in
|
||||
the help (optional)
|
||||
|
||||
|
||||
Either the `short:` tag or the `long:` must be specified to make the field eligible as an
|
||||
option.
|
||||
|
||||
|
||||
Option groups
|
||||
|
||||
Option groups are a simple way to semantically separate your options. All
|
||||
options in a particular group are shown together in the help under the name
|
||||
of the group. Namespaces can be used to specify option long names more
|
||||
precisely and emphasize the options affiliation to their group.
|
||||
|
||||
There are currently three ways to specify option groups.
|
||||
|
||||
1. Use NewNamedParser specifying the various option groups.
|
||||
2. Use AddGroup to add a group to an existing parser.
|
||||
3. Add a struct field to the top-level options annotated with the
|
||||
group:"group-name" tag.
|
||||
|
||||
|
||||
|
||||
Commands
|
||||
|
||||
The flags package also has basic support for commands. Commands are often
|
||||
used in monolithic applications that support various commands or actions.
|
||||
Take git for example, all of the add, commit, checkout, etc. are called
|
||||
commands. Using commands you can easily separate multiple functions of your
|
||||
application.
|
||||
|
||||
There are currently two ways to specify a command.
|
||||
|
||||
1. Use AddCommand on an existing parser.
|
||||
2. Add a struct field to your options struct annotated with the
|
||||
command:"command-name" tag.
|
||||
|
||||
The most common, idiomatic way to implement commands is to define a global
|
||||
parser instance and implement each command in a separate file. These
|
||||
command files should define a go init function which calls AddCommand on
|
||||
the global parser.
|
||||
|
||||
When parsing ends and there is an active command and that command implements
|
||||
the Commander interface, then its Execute method will be run with the
|
||||
remaining command line arguments.
|
||||
|
||||
Command structs can have options which become valid to parse after the
|
||||
command has been specified on the command line, in addition to the options
|
||||
of all the parent commands. I.e. considering a -v flag on the parser and an
|
||||
add command, the following are equivalent:
|
||||
|
||||
./app -v add
|
||||
./app add -v
|
||||
|
||||
However, if the -v flag is defined on the add command, then the first of
|
||||
the two examples above would fail since the -v flag is not defined before
|
||||
the add command.
|
||||
|
||||
|
||||
Completion
|
||||
|
||||
go-flags has builtin support to provide bash completion of flags, commands
|
||||
and argument values. To use completion, the binary which uses go-flags
|
||||
can be invoked in a special environment to list completion of the current
|
||||
command line argument. It should be noted that this `executes` your application,
|
||||
and it is up to the user to make sure there are no negative side effects (for
|
||||
example from init functions).
|
||||
|
||||
Setting the environment variable `GO_FLAGS_COMPLETION=1` enables completion
|
||||
by replacing the argument parsing routine with the completion routine which
|
||||
outputs completions for the passed arguments. The basic invocation to
|
||||
complete a set of arguments is therefore:
|
||||
|
||||
GO_FLAGS_COMPLETION=1 ./completion-example arg1 arg2 arg3
|
||||
|
||||
where `completion-example` is the binary, `arg1` and `arg2` are
|
||||
the current arguments, and `arg3` (the last argument) is the argument
|
||||
to be completed. If the GO_FLAGS_COMPLETION is set to "verbose", then
|
||||
descriptions of possible completion items will also be shown, if there
|
||||
are more than 1 completion items.
|
||||
|
||||
To use this with bash completion, a simple file can be written which
|
||||
calls the binary which supports go-flags completion:
|
||||
|
||||
_completion_example() {
|
||||
# All arguments except the first one
|
||||
args=("${COMP_WORDS[@]:1:$COMP_CWORD}")
|
||||
|
||||
# Only split on newlines
|
||||
local IFS=$'\n'
|
||||
|
||||
# Call completion (note that the first element of COMP_WORDS is
|
||||
# the executable itself)
|
||||
COMPREPLY=($(GO_FLAGS_COMPLETION=1 ${COMP_WORDS[0]} "${args[@]}"))
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _completion_example completion-example
|
||||
|
||||
Completion requires the parser option PassDoubleDash and is therefore enforced if the environment variable GO_FLAGS_COMPLETION is set.
|
||||
|
||||
Customized completion for argument values is supported by implementing
|
||||
the flags.Completer interface for the argument value type. An example
|
||||
of a type which does so is the flags.Filename type, an alias of string
|
||||
allowing simple filename completion. A slice or array argument value
|
||||
whose element type implements flags.Completer will also be completed.
|
||||
*/
|
||||
package flags
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2012 Jesse van den Kieboom. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package flags
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrNotPointerToStruct indicates that a provided data container is not
|
||||
// a pointer to a struct. Only pointers to structs are valid data containers
|
||||
// for options.
|
||||
var ErrNotPointerToStruct = errors.New("provided data is not a pointer to struct")
|
||||
|
||||
// Group represents an option group. Option groups can be used to logically
|
||||
// group options together under a description. Groups are only used to provide
|
||||
// more structure to options both for the user (as displayed in the help message)
|
||||
// and for you, since groups can be nested.
|
||||
type Group struct {
|
||||
// A short description of the group. The
|
||||
// short description is primarily used in the built-in generated help
|
||||
// message
|
||||
ShortDescription string
|
||||
|
||||
// A long description of the group. The long
|
||||
// description is primarily used to present information on commands
|
||||
// (Command embeds Group) in the built-in generated help and man pages.
|
||||
LongDescription string
|
||||
|
||||
// The namespace of the group
|
||||
Namespace string
|
||||
|
||||
// The parent of the group or nil if it has no parent
|
||||
parent interface{}
|
||||
|
||||
// All the options in the group
|
||||
options []*Option
|
||||
|
||||
// All the subgroups
|
||||
groups []*Group
|
||||
|
||||
// Whether the group represents the built-in help group
|
||||
isBuiltinHelp bool
|
||||
|
||||
data interface{}
|
||||
}
|
||||
|
||||
// AddGroup adds a new group to the command with the given name and data. The
|
||||
// data needs to be a pointer to a struct from which the fields indicate which
|
||||
// options are in the group.
|
||||
func (g *Group) AddGroup(shortDescription string, longDescription string, data interface{}) (*Group, error) {
|
||||
group := newGroup(shortDescription, longDescription, data)
|
||||
|
||||
group.parent = g
|
||||
|
||||
if err := group.scan(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g.groups = append(g.groups, group)
|
||||
return group, nil
|
||||
}
|
||||
|
||||
// Groups returns the list of groups embedded in this group.
|
||||
func (g *Group) Groups() []*Group {
|
||||
return g.groups
|
||||
}
|
||||
|
||||
// Options returns the list of options in this group.
|
||||
func (g *Group) Options() []*Option {
|
||||
return g.options
|
||||
}
|
||||
|
||||
// Find locates the subgroup with the given short description and returns it.
|
||||
// If no such group can be found Find will return nil. Note that the description
|
||||
// is matched case insensitively.
|
||||
func (g *Group) Find(shortDescription string) *Group {
|
||||
lshortDescription := strings.ToLower(shortDescription)
|
||||
|
||||
var ret *Group
|
||||
|
||||
g.eachGroup(func(gg *Group) {
|
||||
if gg != g && strings.ToLower(gg.ShortDescription) == lshortDescription {
|
||||
ret = gg
|
||||
}
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type scanHandler func(reflect.Value, *reflect.StructField) (bool, error)
|
||||
|
||||
func newGroup(shortDescription string, longDescription string, data interface{}) *Group {
|
||||
return &Group{
|
||||
ShortDescription: shortDescription,
|
||||
LongDescription: longDescription,
|
||||
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Group) optionByName(name string, namematch func(*Option, string) bool) *Option {
|
||||
prio := 0
|
||||
var retopt *Option
|
||||
|
||||
for _, opt := range g.options {
|
||||
if namematch != nil && namematch(opt, name) && prio < 4 {
|
||||
retopt = opt
|
||||
prio = 4
|
||||
}
|
||||
|
||||
if name == opt.field.Name && prio < 3 {
|
||||
retopt = opt
|
||||
prio = 3
|
||||
}
|
||||
|
||||
if name == opt.LongNameWithNamespace() && prio < 2 {
|
||||
retopt = opt
|
||||
prio = 2
|
||||
}
|
||||
|
||||
if opt.ShortName != 0 && name == string(opt.ShortName) && prio < 1 {
|
||||
retopt = opt
|
||||
prio = 1
|
||||
}
|
||||
}
|
||||
|
||||
return retopt
|
||||
}
|
||||
|
||||
func (g *Group) eachGroup(f func(*Group)) {
|
||||
f(g)
|
||||
|
||||
for _, gg := range g.groups {
|
||||
gg.eachGroup(f)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, handler scanHandler) error {
|
||||
stype := realval.Type()
|
||||
|
||||
if sfield != nil {
|
||||
if ok, err := handler(realval, sfield); err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < stype.NumField(); i++ {
|
||||
field := stype.Field(i)
|
||||
|
||||
// PkgName is set only for non-exported fields, which we ignore
|
||||
if field.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
mtag := newMultiTag(string(field.Tag))
|
||||
|
||||
if err := mtag.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip fields with the no-flag tag
|
||||
if mtag.Get("no-flag") != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Dive deep into structs or pointers to structs
|
||||
kind := field.Type.Kind()
|
||||
fld := realval.Field(i)
|
||||
|
||||
if kind == reflect.Struct {
|
||||
if err := g.scanStruct(fld, &field, handler); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if kind == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
|
||||
if fld.IsNil() {
|
||||
fld.Set(reflect.New(fld.Type().Elem()))
|
||||
}
|
||||
|
||||
if err := g.scanStruct(reflect.Indirect(fld), &field, handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
longname := mtag.Get("long")
|
||||
shortname := mtag.Get("short")
|
||||
|
||||
// Need at least either a short or long name
|
||||
if longname == "" && shortname == "" && mtag.Get("ini-name") == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
short := rune(0)
|
||||
rc := utf8.RuneCountInString(shortname)
|
||||
|
||||
if rc > 1 {
|
||||
return newErrorf(ErrShortNameTooLong,
|
||||
"short names can only be 1 character long, not `%s'",
|
||||
shortname)
|
||||
|
||||
} else if rc == 1 {
|
||||
short, _ = utf8.DecodeRuneInString(shortname)
|
||||
}
|
||||
|
||||
description := mtag.Get("description")
|
||||
def := mtag.GetMany("default")
|
||||
|
||||
optionalValue := mtag.GetMany("optional-value")
|
||||
valueName := mtag.Get("value-name")
|
||||
defaultMask := mtag.Get("default-mask")
|
||||
|
||||
optional := (mtag.Get("optional") != "")
|
||||
required := (mtag.Get("required") != "")
|
||||
|
||||
option := &Option{
|
||||
Description: description,
|
||||
ShortName: short,
|
||||
LongName: longname,
|
||||
Default: def,
|
||||
EnvDefaultKey: mtag.Get("env"),
|
||||
EnvDefaultDelim: mtag.Get("env-delim"),
|
||||
OptionalArgument: optional,
|
||||
OptionalValue: optionalValue,
|
||||
Required: required,
|
||||
ValueName: valueName,
|
||||
DefaultMask: defaultMask,
|
||||
|
||||
group: g,
|
||||
|
||||
field: field,
|
||||
value: realval.Field(i),
|
||||
tag: mtag,
|
||||
}
|
||||
|
||||
g.options = append(g.options, option)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Group) checkForDuplicateFlags() *Error {
|
||||
shortNames := make(map[rune]*Option)
|
||||
longNames := make(map[string]*Option)
|
||||
|
||||
var duplicateError *Error
|
||||
|
||||
g.eachGroup(func(g *Group) {
|
||||
for _, option := range g.options {
|
||||
if option.LongName != "" {
|
||||
longName := option.LongNameWithNamespace()
|
||||
|
||||
if otherOption, ok := longNames[longName]; ok {
|
||||
duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same long name as option `%s'", option, otherOption)
|
||||
return
|
||||
}
|
||||
longNames[longName] = option
|
||||
}
|
||||
if option.ShortName != 0 {
|
||||
if otherOption, ok := shortNames[option.ShortName]; ok {
|
||||
duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same short name as option `%s'", option, otherOption)
|
||||
return
|
||||
}
|
||||
shortNames[option.ShortName] = option
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return duplicateError
|
||||
}
|
||||
|
||||
func (g *Group) scanSubGroupHandler(realval reflect.Value, sfield *reflect.StructField) (bool, error) {
|
||||
mtag := newMultiTag(string(sfield.Tag))
|
||||
|
||||
if err := mtag.Parse(); err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
subgroup := mtag.Get("group")
|
||||
|
||||
if len(subgroup) != 0 {
|
||||
ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr()))
|
||||
description := mtag.Get("description")
|
||||
|
||||
group, err := g.AddGroup(subgroup, description, ptrval.Interface())
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
group.Namespace = mtag.Get("namespace")
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (g *Group) scanType(handler scanHandler) error {
|
||||
// Get all the public fields in the data struct
|
||||
ptrval := reflect.ValueOf(g.data)
|
||||
|
||||
if ptrval.Type().Kind() != reflect.Ptr {
|
||||
panic(ErrNotPointerToStruct)
|
||||
}
|
||||
|
||||
stype := ptrval.Type().Elem()
|
||||
|
||||
if stype.Kind() != reflect.Struct {
|
||||
panic(ErrNotPointerToStruct)
|
||||
}
|
||||
|
||||
realval := reflect.Indirect(ptrval)
|
||||
|
||||
if err := g.scanStruct(realval, nil, handler); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := g.checkForDuplicateFlags(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Group) scan() error {
|
||||
return g.scanType(g.scanSubGroupHandler)
|
||||
}
|
||||
|
||||
func (g *Group) groupByName(name string) *Group {
|
||||
if len(name) == 0 {
|
||||
return g
|
||||
}
|
||||
|
||||
return g.Find(name)
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGroupInline(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Group struct {
|
||||
G bool `short:"g"`
|
||||
} `group:"Grouped Options"`
|
||||
}{}
|
||||
|
||||
p, ret := assertParserSuccess(t, &opts, "-v", "-g")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Group.G {
|
||||
t.Errorf("Expected Group.G to be true")
|
||||
}
|
||||
|
||||
if p.Command.Group.Find("Grouped Options") == nil {
|
||||
t.Errorf("Expected to find group `Grouped Options'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupAdd(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
}{}
|
||||
|
||||
var grp = struct {
|
||||
G bool `short:"g"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
g, err := p.AddGroup("Grouped Options", "", &grp)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
ret, err := p.ParseArgs([]string{"-v", "-g", "rest"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
assertStringArray(t, ret, []string{"rest"})
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !grp.G {
|
||||
t.Errorf("Expected Group.G to be true")
|
||||
}
|
||||
|
||||
if p.Command.Group.Find("Grouped Options") != g {
|
||||
t.Errorf("Expected to find group `Grouped Options'")
|
||||
}
|
||||
|
||||
if p.Groups()[1] != g {
|
||||
t.Errorf("Expected group %#v, but got %#v", g, p.Groups()[0])
|
||||
}
|
||||
|
||||
if g.Options()[0].ShortName != 'g' {
|
||||
t.Errorf("Expected short name `g' but got %v", g.Options()[0].ShortName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupNestedInline(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Group struct {
|
||||
G bool `short:"g"`
|
||||
|
||||
Nested struct {
|
||||
N string `long:"n"`
|
||||
} `group:"Nested Options"`
|
||||
} `group:"Grouped Options"`
|
||||
}{}
|
||||
|
||||
p, ret := assertParserSuccess(t, &opts, "-v", "-g", "--n", "n", "rest")
|
||||
|
||||
assertStringArray(t, ret, []string{"rest"})
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Group.G {
|
||||
t.Errorf("Expected Group.G to be true")
|
||||
}
|
||||
|
||||
assertString(t, opts.Group.Nested.N, "n")
|
||||
|
||||
if p.Command.Group.Find("Grouped Options") == nil {
|
||||
t.Errorf("Expected to find group `Grouped Options'")
|
||||
}
|
||||
|
||||
if p.Command.Group.Find("Nested Options") == nil {
|
||||
t.Errorf("Expected to find group `Nested Options'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupNestedInlineNamespace(t *testing.T) {
|
||||
var opts = struct {
|
||||
Opt string `long:"opt"`
|
||||
|
||||
Group struct {
|
||||
Opt string `long:"opt"`
|
||||
Group struct {
|
||||
Opt string `long:"opt"`
|
||||
} `group:"Subsubgroup" namespace:"sap"`
|
||||
} `group:"Subgroup" namespace:"sip"`
|
||||
}{}
|
||||
|
||||
p, ret := assertParserSuccess(t, &opts, "--opt", "a", "--sip.opt", "b", "--sip.sap.opt", "c", "rest")
|
||||
|
||||
assertStringArray(t, ret, []string{"rest"})
|
||||
|
||||
assertString(t, opts.Opt, "a")
|
||||
assertString(t, opts.Group.Opt, "b")
|
||||
assertString(t, opts.Group.Group.Opt, "c")
|
||||
|
||||
for _, name := range []string{"Subgroup", "Subsubgroup"} {
|
||||
if p.Command.Group.Find(name) == nil {
|
||||
t.Errorf("Expected to find group '%s'", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateShortFlags(t *testing.T) {
|
||||
var opts struct {
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
|
||||
Variables []string `short:"v" long:"variable" description:"Set a variable value."`
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"--verbose",
|
||||
"-v", "123",
|
||||
"-v", "456",
|
||||
}
|
||||
|
||||
_, err := ParseArgs(&opts, args)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error with type ErrDuplicatedFlag")
|
||||
} else {
|
||||
err2 := err.(*Error)
|
||||
if err2.Type != ErrDuplicatedFlag {
|
||||
t.Errorf("Expected an error with type ErrDuplicatedFlag")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateLongFlags(t *testing.T) {
|
||||
var opts struct {
|
||||
Test1 []bool `short:"a" long:"testing" description:"Test 1"`
|
||||
Test2 []string `short:"b" long:"testing" description:"Test 2."`
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"--testing",
|
||||
}
|
||||
|
||||
_, err := ParseArgs(&opts, args)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error with type ErrDuplicatedFlag")
|
||||
} else {
|
||||
err2 := err.(*Error)
|
||||
if err2.Type != ErrDuplicatedFlag {
|
||||
t.Errorf("Expected an error with type ErrDuplicatedFlag")
|
||||
}
|
||||
}
|
||||
}
|
||||
+426
@@ -0,0 +1,426 @@
|
||||
// Copyright 2012 Jesse van den Kieboom. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package flags
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type alignmentInfo struct {
|
||||
maxLongLen int
|
||||
hasShort bool
|
||||
hasValueName bool
|
||||
terminalColumns int
|
||||
indent bool
|
||||
}
|
||||
|
||||
const (
|
||||
paddingBeforeOption = 2
|
||||
distanceBetweenOptionAndDescription = 2
|
||||
)
|
||||
|
||||
func (a *alignmentInfo) descriptionStart() int {
|
||||
ret := a.maxLongLen + distanceBetweenOptionAndDescription
|
||||
|
||||
if a.hasShort {
|
||||
ret += 2
|
||||
}
|
||||
|
||||
if a.maxLongLen > 0 {
|
||||
ret += 4
|
||||
}
|
||||
|
||||
if a.hasValueName {
|
||||
ret += 3
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (a *alignmentInfo) updateLen(name string, indent bool) {
|
||||
l := utf8.RuneCountInString(name)
|
||||
|
||||
if indent {
|
||||
l = l + 4
|
||||
}
|
||||
|
||||
if l > a.maxLongLen {
|
||||
a.maxLongLen = l
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) getAlignmentInfo() alignmentInfo {
|
||||
ret := alignmentInfo{
|
||||
maxLongLen: 0,
|
||||
hasShort: false,
|
||||
hasValueName: false,
|
||||
terminalColumns: getTerminalColumns(),
|
||||
}
|
||||
|
||||
if ret.terminalColumns <= 0 {
|
||||
ret.terminalColumns = 80
|
||||
}
|
||||
|
||||
var prevcmd *Command
|
||||
|
||||
p.eachActiveGroup(func(c *Command, grp *Group) {
|
||||
if c != prevcmd {
|
||||
for _, arg := range c.args {
|
||||
ret.updateLen(arg.Name, c != p.Command)
|
||||
}
|
||||
}
|
||||
|
||||
for _, info := range grp.options {
|
||||
if !info.canCli() {
|
||||
continue
|
||||
}
|
||||
|
||||
if info.ShortName != 0 {
|
||||
ret.hasShort = true
|
||||
}
|
||||
|
||||
if len(info.ValueName) > 0 {
|
||||
ret.hasValueName = true
|
||||
}
|
||||
|
||||
ret.updateLen(info.LongNameWithNamespace()+info.ValueName, c != p.Command)
|
||||
}
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alignmentInfo) {
|
||||
line := &bytes.Buffer{}
|
||||
|
||||
prefix := paddingBeforeOption
|
||||
|
||||
if info.indent {
|
||||
prefix += 4
|
||||
}
|
||||
|
||||
line.WriteString(strings.Repeat(" ", prefix))
|
||||
|
||||
if option.ShortName != 0 {
|
||||
line.WriteRune(defaultShortOptDelimiter)
|
||||
line.WriteRune(option.ShortName)
|
||||
} else if info.hasShort {
|
||||
line.WriteString(" ")
|
||||
}
|
||||
|
||||
descstart := info.descriptionStart() + paddingBeforeOption
|
||||
|
||||
if len(option.LongName) > 0 {
|
||||
if option.ShortName != 0 {
|
||||
line.WriteString(", ")
|
||||
} else if info.hasShort {
|
||||
line.WriteString(" ")
|
||||
}
|
||||
|
||||
line.WriteString(defaultLongOptDelimiter)
|
||||
line.WriteString(option.LongNameWithNamespace())
|
||||
}
|
||||
|
||||
if option.canArgument() {
|
||||
line.WriteRune(defaultNameArgDelimiter)
|
||||
|
||||
if len(option.ValueName) > 0 {
|
||||
line.WriteString(option.ValueName)
|
||||
}
|
||||
}
|
||||
|
||||
written := line.Len()
|
||||
line.WriteTo(writer)
|
||||
|
||||
if option.Description != "" {
|
||||
dw := descstart - written
|
||||
writer.WriteString(strings.Repeat(" ", dw))
|
||||
|
||||
def := ""
|
||||
defs := option.Default
|
||||
|
||||
if len(option.DefaultMask) != 0 {
|
||||
if option.DefaultMask != "-" {
|
||||
def = option.DefaultMask
|
||||
}
|
||||
} else if len(defs) == 0 && option.canArgument() {
|
||||
var showdef bool
|
||||
|
||||
switch option.field.Type.Kind() {
|
||||
case reflect.Func, reflect.Ptr:
|
||||
showdef = !option.value.IsNil()
|
||||
case reflect.Slice, reflect.String, reflect.Array:
|
||||
showdef = option.value.Len() > 0
|
||||
case reflect.Map:
|
||||
showdef = !option.value.IsNil() && option.value.Len() > 0
|
||||
default:
|
||||
zeroval := reflect.Zero(option.field.Type)
|
||||
showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface())
|
||||
}
|
||||
|
||||
if showdef {
|
||||
def, _ = convertToString(option.value, option.tag)
|
||||
}
|
||||
} else if len(defs) != 0 {
|
||||
l := len(defs) - 1
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
def += quoteIfNeeded(defs[i]) + ", "
|
||||
}
|
||||
|
||||
def += quoteIfNeeded(defs[l])
|
||||
}
|
||||
|
||||
var envDef string
|
||||
if option.EnvDefaultKey != "" {
|
||||
var envPrintable string
|
||||
if runtime.GOOS == "windows" {
|
||||
envPrintable = "%" + option.EnvDefaultKey + "%"
|
||||
} else {
|
||||
envPrintable = "$" + option.EnvDefaultKey
|
||||
}
|
||||
envDef = fmt.Sprintf(" [%s]", envPrintable)
|
||||
}
|
||||
|
||||
var desc string
|
||||
|
||||
if def != "" {
|
||||
desc = fmt.Sprintf("%s (%v)%s", option.Description, def, envDef)
|
||||
} else {
|
||||
desc = option.Description + envDef
|
||||
}
|
||||
|
||||
writer.WriteString(wrapText(desc,
|
||||
info.terminalColumns-descstart,
|
||||
strings.Repeat(" ", descstart)))
|
||||
}
|
||||
|
||||
writer.WriteString("\n")
|
||||
}
|
||||
|
||||
func maxCommandLength(s []*Command) int {
|
||||
if len(s) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
ret := len(s[0].Name)
|
||||
|
||||
for _, v := range s[1:] {
|
||||
l := len(v.Name)
|
||||
|
||||
if l > ret {
|
||||
ret = l
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// WriteHelp writes a help message containing all the possible options and
|
||||
// their descriptions to the provided writer. Note that the HelpFlag parser
|
||||
// option provides a convenient way to add a -h/--help option group to the
|
||||
// command line parser which will automatically show the help messages using
|
||||
// this method.
|
||||
func (p *Parser) WriteHelp(writer io.Writer) {
|
||||
if writer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
wr := bufio.NewWriter(writer)
|
||||
aligninfo := p.getAlignmentInfo()
|
||||
|
||||
cmd := p.Command
|
||||
|
||||
for cmd.Active != nil {
|
||||
cmd = cmd.Active
|
||||
}
|
||||
|
||||
if p.Name != "" {
|
||||
wr.WriteString("Usage:\n")
|
||||
wr.WriteString(" ")
|
||||
|
||||
allcmd := p.Command
|
||||
|
||||
for allcmd != nil {
|
||||
var usage string
|
||||
|
||||
if allcmd == p.Command {
|
||||
if len(p.Usage) != 0 {
|
||||
usage = p.Usage
|
||||
} else if p.Options&HelpFlag != 0 {
|
||||
usage = "[OPTIONS]"
|
||||
}
|
||||
} else if us, ok := allcmd.data.(Usage); ok {
|
||||
usage = us.Usage()
|
||||
} else if allcmd.hasCliOptions() {
|
||||
usage = fmt.Sprintf("[%s-OPTIONS]", allcmd.Name)
|
||||
}
|
||||
|
||||
if len(usage) != 0 {
|
||||
fmt.Fprintf(wr, " %s %s", allcmd.Name, usage)
|
||||
} else {
|
||||
fmt.Fprintf(wr, " %s", allcmd.Name)
|
||||
}
|
||||
|
||||
if len(allcmd.args) > 0 {
|
||||
fmt.Fprintf(wr, " ")
|
||||
}
|
||||
|
||||
for i, arg := range allcmd.args {
|
||||
if i != 0 {
|
||||
fmt.Fprintf(wr, " ")
|
||||
}
|
||||
|
||||
name := arg.Name
|
||||
|
||||
if arg.isRemaining() {
|
||||
name = name + "..."
|
||||
}
|
||||
|
||||
if !allcmd.ArgsRequired {
|
||||
fmt.Fprintf(wr, "[%s]", name)
|
||||
} else {
|
||||
fmt.Fprintf(wr, "%s", name)
|
||||
}
|
||||
}
|
||||
|
||||
if allcmd.Active == nil && len(allcmd.commands) > 0 {
|
||||
var co, cc string
|
||||
|
||||
if allcmd.SubcommandsOptional {
|
||||
co, cc = "[", "]"
|
||||
} else {
|
||||
co, cc = "<", ">"
|
||||
}
|
||||
|
||||
if len(allcmd.commands) > 3 {
|
||||
fmt.Fprintf(wr, " %scommand%s", co, cc)
|
||||
} else {
|
||||
subcommands := allcmd.sortedCommands()
|
||||
names := make([]string, len(subcommands))
|
||||
|
||||
for i, subc := range subcommands {
|
||||
names[i] = subc.Name
|
||||
}
|
||||
|
||||
fmt.Fprintf(wr, " %s%s%s", co, strings.Join(names, " | "), cc)
|
||||
}
|
||||
}
|
||||
|
||||
allcmd = allcmd.Active
|
||||
}
|
||||
|
||||
fmt.Fprintln(wr)
|
||||
|
||||
if len(cmd.LongDescription) != 0 {
|
||||
fmt.Fprintln(wr)
|
||||
|
||||
t := wrapText(cmd.LongDescription,
|
||||
aligninfo.terminalColumns,
|
||||
"")
|
||||
|
||||
fmt.Fprintln(wr, t)
|
||||
}
|
||||
}
|
||||
|
||||
c := p.Command
|
||||
|
||||
for c != nil {
|
||||
printcmd := c != p.Command
|
||||
|
||||
c.eachGroup(func(grp *Group) {
|
||||
first := true
|
||||
|
||||
// Skip built-in help group for all commands except the top-level
|
||||
// parser
|
||||
if grp.isBuiltinHelp && c != p.Command {
|
||||
return
|
||||
}
|
||||
|
||||
for _, info := range grp.options {
|
||||
if !info.canCli() {
|
||||
continue
|
||||
}
|
||||
|
||||
if printcmd {
|
||||
fmt.Fprintf(wr, "\n[%s command options]\n", c.Name)
|
||||
aligninfo.indent = true
|
||||
printcmd = false
|
||||
}
|
||||
|
||||
if first && cmd.Group != grp {
|
||||
fmt.Fprintln(wr)
|
||||
|
||||
if aligninfo.indent {
|
||||
wr.WriteString(" ")
|
||||
}
|
||||
|
||||
fmt.Fprintf(wr, "%s:\n", grp.ShortDescription)
|
||||
first = false
|
||||
}
|
||||
|
||||
p.writeHelpOption(wr, info, aligninfo)
|
||||
}
|
||||
})
|
||||
|
||||
if len(c.args) > 0 {
|
||||
if c == p.Command {
|
||||
fmt.Fprintf(wr, "\nArguments:\n")
|
||||
} else {
|
||||
fmt.Fprintf(wr, "\n[%s command arguments]\n", c.Name)
|
||||
}
|
||||
|
||||
maxlen := aligninfo.descriptionStart()
|
||||
|
||||
for _, arg := range c.args {
|
||||
prefix := strings.Repeat(" ", paddingBeforeOption)
|
||||
fmt.Fprintf(wr, "%s%s", prefix, arg.Name)
|
||||
|
||||
if len(arg.Description) > 0 {
|
||||
align := strings.Repeat(" ", maxlen-len(arg.Name)-1)
|
||||
fmt.Fprintf(wr, ":%s%s", align, arg.Description)
|
||||
}
|
||||
|
||||
fmt.Fprintln(wr)
|
||||
}
|
||||
}
|
||||
|
||||
c = c.Active
|
||||
}
|
||||
|
||||
scommands := cmd.sortedCommands()
|
||||
|
||||
if len(scommands) > 0 {
|
||||
maxnamelen := maxCommandLength(scommands)
|
||||
|
||||
fmt.Fprintln(wr)
|
||||
fmt.Fprintln(wr, "Available commands:")
|
||||
|
||||
for _, c := range scommands {
|
||||
fmt.Fprintf(wr, " %s", c.Name)
|
||||
|
||||
if len(c.ShortDescription) > 0 {
|
||||
pad := strings.Repeat(" ", maxnamelen-len(c.Name))
|
||||
fmt.Fprintf(wr, "%s %s", pad, c.ShortDescription)
|
||||
|
||||
if len(c.Aliases) > 0 {
|
||||
fmt.Fprintf(wr, " (aliases: %s)", strings.Join(c.Aliases, ", "))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fmt.Fprintln(wr)
|
||||
}
|
||||
}
|
||||
|
||||
wr.Flush()
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type helpOptions struct {
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information" ini-name:"verbose"`
|
||||
Call func(string) `short:"c" description:"Call phone number" ini-name:"call"`
|
||||
PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
|
||||
EmptyDescription bool `long:"empty-description"`
|
||||
|
||||
Default string `long:"default" default:"Some\nvalue" description:"Test default value"`
|
||||
DefaultArray []string `long:"default-array" default:"Some value" default:"Other\tvalue" description:"Test default array value"`
|
||||
DefaultMap map[string]string `long:"default-map" default:"some:value" default:"another:value" description:"Testdefault map value"`
|
||||
EnvDefault1 string `long:"env-default1" default:"Some value" env:"ENV_DEFAULT" description:"Test env-default1 value"`
|
||||
EnvDefault2 string `long:"env-default2" env:"ENV_DEFAULT" description:"Test env-default2 value"`
|
||||
OptionWithArgName string `long:"opt-with-arg-name" value-name:"something" description:"Option with named argument"`
|
||||
|
||||
OnlyIni string `ini-name:"only-ini" description:"Option only available in ini"`
|
||||
|
||||
Other struct {
|
||||
StringSlice []string `short:"s" default:"some" default:"value" description:"A slice of strings"`
|
||||
IntMap map[string]int `long:"intmap" default:"a:1" description:"A map from string to int" ini-name:"int-map"`
|
||||
} `group:"Other Options"`
|
||||
|
||||
Group struct {
|
||||
Opt string `long:"opt" description:"This is a subgroup option"`
|
||||
|
||||
Group struct {
|
||||
Opt string `long:"opt" description:"This is a subsubgroup option"`
|
||||
} `group:"Subsubgroup" namespace:"sap"`
|
||||
} `group:"Subgroup" namespace:"sip"`
|
||||
|
||||
Command struct {
|
||||
ExtraVerbose []bool `long:"extra-verbose" description:"Use for extra verbosity"`
|
||||
} `command:"command" alias:"cm" alias:"cmd" description:"A command"`
|
||||
|
||||
Args struct {
|
||||
Filename string `positional-arg-name:"filename" description:"A filename"`
|
||||
Number int `positional-arg-name:"num" description:"A number"`
|
||||
} `positional-args:"yes"`
|
||||
}
|
||||
|
||||
func TestHelp(t *testing.T) {
|
||||
oldEnv := EnvSnapshot()
|
||||
defer oldEnv.Restore()
|
||||
os.Setenv("ENV_DEFAULT", "env-def")
|
||||
|
||||
var opts helpOptions
|
||||
p := NewNamedParser("TestHelp", HelpFlag)
|
||||
p.AddGroup("Application Options", "The application options", &opts)
|
||||
|
||||
_, err := p.ParseArgs([]string{"--help"})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected help error")
|
||||
}
|
||||
|
||||
if e, ok := err.(*Error); !ok {
|
||||
t.Fatalf("Expected flags.Error, but got %T", err)
|
||||
} else {
|
||||
if e.Type != ErrHelp {
|
||||
t.Errorf("Expected flags.ErrHelp type, but got %s", e.Type)
|
||||
}
|
||||
|
||||
var expected string
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
expected = `Usage:
|
||||
TestHelp [OPTIONS] [filename] [num] <command>
|
||||
|
||||
Application Options:
|
||||
/v, /verbose Show verbose debug information
|
||||
/c: Call phone number
|
||||
/ptrslice: A slice of pointers to string
|
||||
/empty-description
|
||||
/default: Test default value ("Some\nvalue")
|
||||
/default-array: Test default array value (Some value, "Other\tvalue")
|
||||
/default-map: Testdefault map value (some:value, another:value)
|
||||
/env-default1: Test env-default1 value (Some value) [%ENV_DEFAULT%]
|
||||
/env-default2: Test env-default2 value [%ENV_DEFAULT%]
|
||||
/opt-with-arg-name:something Option with named argument
|
||||
|
||||
Other Options:
|
||||
/s: A slice of strings (some, value)
|
||||
/intmap: A map from string to int (a:1)
|
||||
|
||||
Subgroup:
|
||||
/sip.opt: This is a subgroup option
|
||||
|
||||
Subsubgroup:
|
||||
/sip.sap.opt: This is a subsubgroup option
|
||||
|
||||
Help Options:
|
||||
/? Show this help message
|
||||
/h, /help Show this help message
|
||||
|
||||
Arguments:
|
||||
filename: A filename
|
||||
num: A number
|
||||
|
||||
Available commands:
|
||||
command A command (aliases: cm, cmd)
|
||||
`
|
||||
} else {
|
||||
expected = `Usage:
|
||||
TestHelp [OPTIONS] [filename] [num] <command>
|
||||
|
||||
Application Options:
|
||||
-v, --verbose Show verbose debug information
|
||||
-c= Call phone number
|
||||
--ptrslice= A slice of pointers to string
|
||||
--empty-description
|
||||
--default= Test default value ("Some\nvalue")
|
||||
--default-array= Test default array value (Some value,
|
||||
"Other\tvalue")
|
||||
--default-map= Testdefault map value (some:value,
|
||||
another:value)
|
||||
--env-default1= Test env-default1 value (Some value)
|
||||
[$ENV_DEFAULT]
|
||||
--env-default2= Test env-default2 value [$ENV_DEFAULT]
|
||||
--opt-with-arg-name=something Option with named argument
|
||||
|
||||
Other Options:
|
||||
-s= A slice of strings (some, value)
|
||||
--intmap= A map from string to int (a:1)
|
||||
|
||||
Subgroup:
|
||||
--sip.opt= This is a subgroup option
|
||||
|
||||
Subsubgroup:
|
||||
--sip.sap.opt= This is a subsubgroup option
|
||||
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
|
||||
Arguments:
|
||||
filename: A filename
|
||||
num: A number
|
||||
|
||||
Available commands:
|
||||
command A command (aliases: cm, cmd)
|
||||
`
|
||||
}
|
||||
|
||||
assertDiff(t, e.Message, expected, "help message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMan(t *testing.T) {
|
||||
oldEnv := EnvSnapshot()
|
||||
defer oldEnv.Restore()
|
||||
os.Setenv("ENV_DEFAULT", "env-def")
|
||||
|
||||
var opts helpOptions
|
||||
p := NewNamedParser("TestMan", HelpFlag)
|
||||
p.ShortDescription = "Test manpage generation"
|
||||
p.LongDescription = "This is a somewhat `longer' description of what this does"
|
||||
p.AddGroup("Application Options", "The application options", &opts)
|
||||
|
||||
p.Commands()[0].LongDescription = "Longer `command' description"
|
||||
|
||||
var buf bytes.Buffer
|
||||
p.WriteManPage(&buf)
|
||||
|
||||
got := buf.String()
|
||||
|
||||
tt := time.Now()
|
||||
|
||||
var envDefaultName string
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
envDefaultName = "%ENV_DEFAULT%"
|
||||
} else {
|
||||
envDefaultName = "$ENV_DEFAULT"
|
||||
}
|
||||
|
||||
expected := fmt.Sprintf(`.TH TestMan 1 "%s"
|
||||
.SH NAME
|
||||
TestMan \- Test manpage generation
|
||||
.SH SYNOPSIS
|
||||
\fBTestMan\fP [OPTIONS]
|
||||
.SH DESCRIPTION
|
||||
This is a somewhat \fBlonger\fP description of what this does
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\fB\-v\fR, \fB\-\-verbose\fR\fP
|
||||
Show verbose debug information
|
||||
.TP
|
||||
\fB\fB\-c\fR\fP
|
||||
Call phone number
|
||||
.TP
|
||||
\fB\fB\-\-ptrslice\fR\fP
|
||||
A slice of pointers to string
|
||||
.TP
|
||||
\fB\fB\-\-empty-description\fR\fP
|
||||
.TP
|
||||
\fB\fB\-\-default\fR <default: \fI"Some\\nvalue"\fR>\fP
|
||||
Test default value
|
||||
.TP
|
||||
\fB\fB\-\-default-array\fR <default: \fI"Some value", "Other\\tvalue"\fR>\fP
|
||||
Test default array value
|
||||
.TP
|
||||
\fB\fB\-\-default-map\fR <default: \fI"some:value", "another:value"\fR>\fP
|
||||
Testdefault map value
|
||||
.TP
|
||||
\fB\fB\-\-env-default1\fR <default: \fI"Some value"\fR>\fP
|
||||
Test env-default1 value
|
||||
.TP
|
||||
\fB\fB\-\-env-default2\fR <default: \fI%s\fR>\fP
|
||||
Test env-default2 value
|
||||
.TP
|
||||
\fB\fB\-\-opt-with-arg-name\fR \fIsomething\fR\fP
|
||||
Option with named argument
|
||||
.TP
|
||||
\fB\fB\-s\fR <default: \fI"some", "value"\fR>\fP
|
||||
A slice of strings
|
||||
.TP
|
||||
\fB\fB\-\-intmap\fR <default: \fI"a:1"\fR>\fP
|
||||
A map from string to int
|
||||
.TP
|
||||
\fB\fB\-\-sip.opt\fR\fP
|
||||
This is a subgroup option
|
||||
.TP
|
||||
\fB\fB\-\-sip.sap.opt\fR\fP
|
||||
This is a subsubgroup option
|
||||
.SH COMMANDS
|
||||
.SS command
|
||||
A command
|
||||
|
||||
Longer \fBcommand\fP description
|
||||
|
||||
\fBUsage\fP: TestMan [OPTIONS] command [command-OPTIONS]
|
||||
|
||||
|
||||
\fBAliases\fP: cm, cmd
|
||||
|
||||
.TP
|
||||
\fB\fB\-\-extra-verbose\fR\fP
|
||||
Use for extra verbosity
|
||||
`, tt.Format("2 January 2006"), envDefaultName)
|
||||
|
||||
assertDiff(t, got, expected, "man page")
|
||||
}
|
||||
|
||||
type helpCommandNoOptions struct {
|
||||
Command struct {
|
||||
} `command:"command" description:"A command"`
|
||||
}
|
||||
|
||||
func TestHelpCommand(t *testing.T) {
|
||||
oldEnv := EnvSnapshot()
|
||||
defer oldEnv.Restore()
|
||||
os.Setenv("ENV_DEFAULT", "env-def")
|
||||
|
||||
var opts helpCommandNoOptions
|
||||
p := NewNamedParser("TestHelpCommand", HelpFlag)
|
||||
p.AddGroup("Application Options", "The application options", &opts)
|
||||
|
||||
_, err := p.ParseArgs([]string{"command", "--help"})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected help error")
|
||||
}
|
||||
|
||||
if e, ok := err.(*Error); !ok {
|
||||
t.Fatalf("Expected flags.Error, but got %T", err)
|
||||
} else {
|
||||
if e.Type != ErrHelp {
|
||||
t.Errorf("Expected flags.ErrHelp type, but got %s", e.Type)
|
||||
}
|
||||
|
||||
var expected string
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
expected = `Usage:
|
||||
TestHelpCommand [OPTIONS] command
|
||||
|
||||
Help Options:
|
||||
/? Show this help message
|
||||
/h, /help Show this help message
|
||||
`
|
||||
} else {
|
||||
expected = `Usage:
|
||||
TestHelpCommand [OPTIONS] command
|
||||
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
`
|
||||
}
|
||||
|
||||
assertDiff(t, e.Message, expected, "help message")
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user