mirror of
https://github.com/octoleo/restic.git
synced 2024-11-16 18:15:20 +00:00
Merge pull request #631 from restic/switch-to-cobra
Switch to cobra/pflag for CLI
This commit is contained in:
commit
254188f38f
@ -6,101 +6,66 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"restic"
|
||||
"restic/archiver"
|
||||
"restic/debug"
|
||||
"restic/filter"
|
||||
"restic/fs"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"restic/errors"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"restic/archiver"
|
||||
"restic/debug"
|
||||
"restic/errors"
|
||||
"restic/filter"
|
||||
"restic/fs"
|
||||
)
|
||||
|
||||
type CmdBackup struct {
|
||||
Parent string `short:"p" long:"parent" description:"use this parent snapshot (default: last snapshot in repo that has the same target)"`
|
||||
Force bool `short:"f" long:"force" description:"Force re-reading the target. Overrides the \"parent\" flag"`
|
||||
Excludes []string `short:"e" long:"exclude" description:"Exclude a pattern (can be specified multiple times)"`
|
||||
ExcludeOtherFS bool `short:"x" long:"one-file-system" description:"Exclude other file systems"`
|
||||
ExcludeFile string `long:"exclude-file" description:"Read exclude-patterns from file"`
|
||||
Stdin bool `long:"stdin" description:"read backup data from stdin"`
|
||||
StdinFilename string `long:"stdin-filename" default:"stdin" description:"file name to use when reading from stdin"`
|
||||
Tags []string `long:"tag" description:"Add a tag (can be specified multiple times)"`
|
||||
|
||||
global *GlobalOptions
|
||||
var cmdBackup = &cobra.Command{
|
||||
Use: "backup [flags] FILE/DIR [FILE/DIR] ...",
|
||||
Short: "create a new backup of files and/or directories",
|
||||
Long: `
|
||||
The "backup" command creates a new snapshot and saves the files and directories
|
||||
given as the arguments.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if backupOptions.Stdin {
|
||||
return readBackupFromStdin(backupOptions, globalOptions, args)
|
||||
}
|
||||
|
||||
return runBackup(backupOptions, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
// BackupOptions bundles all options for the backup command.
|
||||
type BackupOptions struct {
|
||||
Parent string
|
||||
Force bool
|
||||
Excludes []string
|
||||
ExcludeFile string
|
||||
ExcludeOtherFS bool
|
||||
Stdin bool
|
||||
StdinFilename string
|
||||
Tags []string
|
||||
}
|
||||
|
||||
var backupOptions BackupOptions
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("backup",
|
||||
"save file/directory",
|
||||
"The backup command creates a snapshot of a file or directory",
|
||||
&CmdBackup{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmdRoot.AddCommand(cmdBackup)
|
||||
|
||||
f := cmdBackup.Flags()
|
||||
f.StringVar(&backupOptions.Parent, "parent", "", "use this parent snapshot (default: last snapshot in the repo that has the same target files/directories)")
|
||||
f.BoolVarP(&backupOptions.Force, "force", "f", false, `force re-reading the target files/directories. Overrides the "parent" flag`)
|
||||
f.StringSliceVarP(&backupOptions.Excludes, "exclude", "e", []string{}, "exclude a pattern (can be specified multiple times)")
|
||||
f.StringVar(&backupOptions.ExcludeFile, "exclude-file", "", "read exclude patterns from a file")
|
||||
f.BoolVarP(&backupOptions.ExcludeOtherFS, "one-file-system", "x", false, "Exclude other file systems")
|
||||
f.BoolVar(&backupOptions.Stdin, "stdin", false, "read backup from stdin")
|
||||
f.StringVar(&backupOptions.StdinFilename, "stdin-filename", "", "file name to use when reading from stdin")
|
||||
f.StringSliceVar(&backupOptions.Tags, "tag", []string{}, "add a tag for the new snapshot (can be specified multiple times)")
|
||||
}
|
||||
|
||||
func formatBytes(c uint64) string {
|
||||
b := float64(c)
|
||||
|
||||
switch {
|
||||
case c > 1<<40:
|
||||
return fmt.Sprintf("%.3f TiB", b/(1<<40))
|
||||
case c > 1<<30:
|
||||
return fmt.Sprintf("%.3f GiB", b/(1<<30))
|
||||
case c > 1<<20:
|
||||
return fmt.Sprintf("%.3f MiB", b/(1<<20))
|
||||
case c > 1<<10:
|
||||
return fmt.Sprintf("%.3f KiB", b/(1<<10))
|
||||
default:
|
||||
return fmt.Sprintf("%dB", c)
|
||||
}
|
||||
}
|
||||
|
||||
func formatSeconds(sec uint64) string {
|
||||
hours := sec / 3600
|
||||
sec -= hours * 3600
|
||||
min := sec / 60
|
||||
sec -= min * 60
|
||||
if hours > 0 {
|
||||
return fmt.Sprintf("%d:%02d:%02d", hours, min, sec)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d:%02d", min, sec)
|
||||
}
|
||||
|
||||
func formatPercent(numerator uint64, denominator uint64) string {
|
||||
if denominator == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
percent := 100.0 * float64(numerator) / float64(denominator)
|
||||
|
||||
if percent > 100 {
|
||||
percent = 100
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%3.2f%%", percent)
|
||||
}
|
||||
|
||||
func formatRate(bytes uint64, duration time.Duration) string {
|
||||
sec := float64(duration) / float64(time.Second)
|
||||
rate := float64(bytes) / sec / (1 << 20)
|
||||
return fmt.Sprintf("%.2fMiB/s", rate)
|
||||
}
|
||||
|
||||
func formatDuration(d time.Duration) string {
|
||||
sec := uint64(d / time.Second)
|
||||
return formatSeconds(sec)
|
||||
}
|
||||
|
||||
func (cmd CmdBackup) Usage() string {
|
||||
return "DIR/FILE [DIR/FILE] [...]"
|
||||
}
|
||||
|
||||
func (cmd CmdBackup) newScanProgress() *restic.Progress {
|
||||
if !cmd.global.ShowProgress() {
|
||||
func newScanProgress(gopts GlobalOptions) *restic.Progress {
|
||||
if gopts.Quiet {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -115,8 +80,8 @@ func (cmd CmdBackup) newScanProgress() *restic.Progress {
|
||||
return p
|
||||
}
|
||||
|
||||
func (cmd CmdBackup) newArchiveProgress(todo restic.Stat) *restic.Progress {
|
||||
if !cmd.global.ShowProgress() {
|
||||
func newArchiveProgress(gopts GlobalOptions, todo restic.Stat) *restic.Progress {
|
||||
if gopts.Quiet {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -169,8 +134,8 @@ func (cmd CmdBackup) newArchiveProgress(todo restic.Stat) *restic.Progress {
|
||||
return archiveProgress
|
||||
}
|
||||
|
||||
func (cmd CmdBackup) newArchiveStdinProgress() *restic.Progress {
|
||||
if !cmd.global.ShowProgress() {
|
||||
func newArchiveStdinProgress(gopts GlobalOptions) *restic.Progress {
|
||||
if gopts.Quiet {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -250,12 +215,12 @@ func gatherDevices(items []string) (deviceMap map[uint64]struct{}, err error) {
|
||||
return deviceMap, nil
|
||||
}
|
||||
|
||||
func (cmd CmdBackup) readFromStdin(args []string) error {
|
||||
func readBackupFromStdin(opts BackupOptions, gopts GlobalOptions, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return errors.Fatalf("when reading from stdin, no additional files can be specified")
|
||||
}
|
||||
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -271,7 +236,7 @@ func (cmd CmdBackup) readFromStdin(args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, id, err := archiver.ArchiveReader(repo, cmd.newArchiveStdinProgress(), os.Stdin, cmd.StdinFilename, cmd.Tags)
|
||||
_, id, err := archiver.ArchiveReader(repo, newArchiveStdinProgress(gopts), os.Stdin, opts.StdinFilename, opts.Tags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -280,13 +245,9 @@ func (cmd CmdBackup) readFromStdin(args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd CmdBackup) Execute(args []string) error {
|
||||
if cmd.Stdin {
|
||||
return cmd.readFromStdin(args)
|
||||
}
|
||||
|
||||
func runBackup(opts BackupOptions, gopts GlobalOptions, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.Fatalf("wrong number of parameters, Usage: %s", cmd.Usage())
|
||||
return errors.Fatalf("wrong number of parameters")
|
||||
}
|
||||
|
||||
target := make([]string, 0, len(args))
|
||||
@ -304,7 +265,7 @@ func (cmd CmdBackup) Execute(args []string) error {
|
||||
|
||||
// allowed devices
|
||||
var allowedDevs map[uint64]struct{}
|
||||
if cmd.ExcludeOtherFS {
|
||||
if opts.ExcludeOtherFS {
|
||||
allowedDevs, err = gatherDevices(target)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -312,7 +273,7 @@ func (cmd CmdBackup) Execute(args []string) error {
|
||||
debug.Log("backup.Execute", "allowed devices: %v\n", allowedDevs)
|
||||
}
|
||||
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -331,17 +292,17 @@ func (cmd CmdBackup) Execute(args []string) error {
|
||||
var parentSnapshotID *restic.ID
|
||||
|
||||
// Force using a parent
|
||||
if !cmd.Force && cmd.Parent != "" {
|
||||
id, err := restic.FindSnapshot(repo, cmd.Parent)
|
||||
if !opts.Force && opts.Parent != "" {
|
||||
id, err := restic.FindSnapshot(repo, opts.Parent)
|
||||
if err != nil {
|
||||
return errors.Fatalf("invalid id %q: %v", cmd.Parent, err)
|
||||
return errors.Fatalf("invalid id %q: %v", opts.Parent, err)
|
||||
}
|
||||
|
||||
parentSnapshotID = &id
|
||||
}
|
||||
|
||||
// Find last snapshot to set it as parent, if not already set
|
||||
if !cmd.Force && parentSnapshotID == nil {
|
||||
if !opts.Force && parentSnapshotID == nil {
|
||||
id, err := restic.FindLatestSnapshot(repo, target, "")
|
||||
if err == nil {
|
||||
parentSnapshotID = &id
|
||||
@ -351,16 +312,16 @@ func (cmd CmdBackup) Execute(args []string) error {
|
||||
}
|
||||
|
||||
if parentSnapshotID != nil {
|
||||
cmd.global.Verbosef("using parent snapshot %v\n", parentSnapshotID.Str())
|
||||
Verbosef("using parent snapshot %v\n", parentSnapshotID.Str())
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("scan %v\n", target)
|
||||
Verbosef("scan %v\n", target)
|
||||
|
||||
// add patterns from file
|
||||
if cmd.ExcludeFile != "" {
|
||||
file, err := fs.Open(cmd.ExcludeFile)
|
||||
if opts.ExcludeFile != "" {
|
||||
file, err := fs.Open(opts.ExcludeFile)
|
||||
if err != nil {
|
||||
cmd.global.Warnf("error reading exclude patterns: %v", err)
|
||||
Warnf("error reading exclude patterns: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -369,15 +330,15 @@ func (cmd CmdBackup) Execute(args []string) error {
|
||||
line := scanner.Text()
|
||||
if !strings.HasPrefix(line, "#") {
|
||||
line = os.ExpandEnv(line)
|
||||
cmd.Excludes = append(cmd.Excludes, line)
|
||||
opts.Excludes = append(opts.Excludes, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectFilter := func(item string, fi os.FileInfo) bool {
|
||||
matched, err := filter.List(cmd.Excludes, item)
|
||||
matched, err := filter.List(opts.Excludes, item)
|
||||
if err != nil {
|
||||
cmd.global.Warnf("error for exclude pattern: %v", err)
|
||||
Warnf("error for exclude pattern: %v", err)
|
||||
}
|
||||
|
||||
if matched {
|
||||
@ -385,7 +346,7 @@ func (cmd CmdBackup) Execute(args []string) error {
|
||||
return false
|
||||
}
|
||||
|
||||
if !cmd.ExcludeOtherFS {
|
||||
if !opts.ExcludeOtherFS {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -404,27 +365,27 @@ func (cmd CmdBackup) Execute(args []string) error {
|
||||
return true
|
||||
}
|
||||
|
||||
stat, err := archiver.Scan(target, selectFilter, cmd.newScanProgress())
|
||||
stat, err := archiver.Scan(target, selectFilter, newScanProgress(gopts))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
arch := archiver.New(repo)
|
||||
arch.Excludes = cmd.Excludes
|
||||
arch.Excludes = opts.Excludes
|
||||
arch.SelectFilter = selectFilter
|
||||
|
||||
arch.Error = func(dir string, fi os.FileInfo, err error) error {
|
||||
// TODO: make ignoring errors configurable
|
||||
cmd.global.Warnf("%s\rerror for %s: %v\n", ClearLine(), dir, err)
|
||||
Warnf("%s\rerror for %s: %v\n", ClearLine(), dir, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
_, id, err := arch.Snapshot(cmd.newArchiveProgress(stat), target, cmd.Tags, parentSnapshotID)
|
||||
_, id, err := arch.Snapshot(newArchiveProgress(gopts, stat), target, opts.Tags, parentSnapshotID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("snapshot %s saved\n", id.Str())
|
||||
Verbosef("snapshot %s saved\n", id.Str())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"restic"
|
||||
"restic/backend"
|
||||
"restic/debug"
|
||||
@ -12,30 +14,27 @@ import (
|
||||
"restic/repository"
|
||||
)
|
||||
|
||||
type CmdCat struct {
|
||||
global *GlobalOptions
|
||||
var cmdCat = &cobra.Command{
|
||||
Use: "cat [flags] [pack|blob|tree|snapshot|key|masterkey|config|lock] ID",
|
||||
Short: "print internal objects to stdout",
|
||||
Long: `
|
||||
The "cat" command is used to print internal objects to stdout.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runCat(globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("cat",
|
||||
"dump something",
|
||||
"The cat command dumps data structures or data from a repository",
|
||||
&CmdCat{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmdRoot.AddCommand(cmdCat)
|
||||
}
|
||||
|
||||
func (cmd CmdCat) Usage() string {
|
||||
return "[pack|blob|tree|snapshot|key|masterkey|config|lock] ID"
|
||||
}
|
||||
|
||||
func (cmd CmdCat) Execute(args []string) error {
|
||||
func runCat(gopts GlobalOptions, args []string) error {
|
||||
if len(args) < 1 || (args[0] != "masterkey" && args[0] != "config" && len(args) != 2) {
|
||||
return errors.Fatalf("type or ID not specified, Usage: %s", cmd.Usage())
|
||||
return errors.Fatalf("type or ID not specified")
|
||||
}
|
||||
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -158,7 +157,7 @@ func (cmd CmdCat) Execute(args []string) error {
|
||||
|
||||
hash := restic.Hash(buf)
|
||||
if !hash.Equal(id) {
|
||||
fmt.Fprintf(cmd.global.stderr, "Warning: hash of data does not match ID, want\n %v\ngot:\n %v\n", id.String(), hash.String())
|
||||
fmt.Fprintf(stderr, "Warning: hash of data does not match ID, want\n %v\ngot:\n %v\n", id.String(), hash.String())
|
||||
}
|
||||
|
||||
_, err = os.Stdout.Write(buf)
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"restic"
|
||||
@ -12,29 +14,36 @@ import (
|
||||
"restic/errors"
|
||||
)
|
||||
|
||||
type CmdCheck struct {
|
||||
ReadData bool `long:"read-data" description:"Read data blobs"`
|
||||
CheckUnused bool `long:"check-unused" description:"Check for unused blobs"`
|
||||
|
||||
global *GlobalOptions
|
||||
var cmdCheck = &cobra.Command{
|
||||
Use: "check [flags]",
|
||||
Short: "check the repository for errors",
|
||||
Long: `
|
||||
The "check" command tests the repository for errors and reports any errors it
|
||||
finds. It can also be used to read all data and therefore simulate a restore.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runCheck(checkOptions, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
// CheckOptions bundle all options for the 'check' command.
|
||||
type CheckOptions struct {
|
||||
ReadData bool
|
||||
CheckUnused bool
|
||||
}
|
||||
|
||||
var checkOptions CheckOptions
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("check",
|
||||
"check the repository",
|
||||
"The check command check the integrity and consistency of the repository",
|
||||
&CmdCheck{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmdRoot.AddCommand(cmdCheck)
|
||||
|
||||
f := cmdCheck.Flags()
|
||||
f.BoolVar(&checkOptions.ReadData, "read-data", false, "Read all data blobs")
|
||||
f.BoolVar(&checkOptions.CheckUnused, "check-unused", false, "Find unused blobs")
|
||||
}
|
||||
|
||||
func (cmd CmdCheck) Usage() string {
|
||||
return "[check-options]"
|
||||
}
|
||||
|
||||
func (cmd CmdCheck) newReadProgress(todo restic.Stat) *restic.Progress {
|
||||
if !cmd.global.ShowProgress() {
|
||||
func newReadProgress(gopts GlobalOptions, todo restic.Stat) *restic.Progress {
|
||||
if gopts.Quiet {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -64,18 +73,18 @@ func (cmd CmdCheck) newReadProgress(todo restic.Stat) *restic.Progress {
|
||||
return readProgress
|
||||
}
|
||||
|
||||
func (cmd CmdCheck) Execute(args []string) error {
|
||||
func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return errors.Fatal("check has no arguments")
|
||||
}
|
||||
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cmd.global.NoLock {
|
||||
cmd.global.Verbosef("Create exclusive lock for repository\n")
|
||||
if !gopts.NoLock {
|
||||
Verbosef("Create exclusive lock for repository\n")
|
||||
lock, err := lockRepoExclusive(repo)
|
||||
defer unlockRepo(lock)
|
||||
if err != nil {
|
||||
@ -85,24 +94,24 @@ func (cmd CmdCheck) Execute(args []string) error {
|
||||
|
||||
chkr := checker.New(repo)
|
||||
|
||||
cmd.global.Verbosef("Load indexes\n")
|
||||
Verbosef("Load indexes\n")
|
||||
hints, errs := chkr.LoadIndex()
|
||||
|
||||
dupFound := false
|
||||
for _, hint := range hints {
|
||||
cmd.global.Printf("%v\n", hint)
|
||||
Printf("%v\n", hint)
|
||||
if _, ok := hint.(checker.ErrDuplicatePacks); ok {
|
||||
dupFound = true
|
||||
}
|
||||
}
|
||||
|
||||
if dupFound {
|
||||
cmd.global.Printf("\nrun `restic rebuild-index' to correct this\n")
|
||||
Printf("\nrun `restic rebuild-index' to correct this\n")
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
cmd.global.Warnf("error: %v\n", err)
|
||||
Warnf("error: %v\n", err)
|
||||
}
|
||||
return errors.Fatal("LoadIndex returned errors")
|
||||
}
|
||||
@ -113,7 +122,7 @@ func (cmd CmdCheck) Execute(args []string) error {
|
||||
errorsFound := false
|
||||
errChan := make(chan error)
|
||||
|
||||
cmd.global.Verbosef("Check all packs\n")
|
||||
Verbosef("Check all packs\n")
|
||||
go chkr.Packs(errChan, done)
|
||||
|
||||
for err := range errChan {
|
||||
@ -121,7 +130,7 @@ func (cmd CmdCheck) Execute(args []string) error {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("Check snapshots, trees and blobs\n")
|
||||
Verbosef("Check snapshots, trees and blobs\n")
|
||||
errChan = make(chan error)
|
||||
go chkr.Structure(errChan, done)
|
||||
|
||||
@ -137,17 +146,17 @@ func (cmd CmdCheck) Execute(args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if cmd.CheckUnused {
|
||||
if opts.CheckUnused {
|
||||
for _, id := range chkr.UnusedBlobs() {
|
||||
cmd.global.Verbosef("unused blob %v\n", id.Str())
|
||||
Verbosef("unused blob %v\n", id.Str())
|
||||
errorsFound = true
|
||||
}
|
||||
}
|
||||
|
||||
if cmd.ReadData {
|
||||
cmd.global.Verbosef("Read all data\n")
|
||||
if opts.ReadData {
|
||||
Verbosef("Read all data\n")
|
||||
|
||||
p := cmd.newReadProgress(restic.Stat{Blobs: chkr.CountPacks()})
|
||||
p := newReadProgress(gopts, restic.Stat{Blobs: chkr.CountPacks()})
|
||||
errChan := make(chan error)
|
||||
|
||||
go chkr.ReadData(p, errChan, done)
|
||||
|
@ -8,6 +8,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"restic"
|
||||
"restic/errors"
|
||||
"restic/pack"
|
||||
@ -16,24 +18,19 @@ import (
|
||||
"restic/worker"
|
||||
)
|
||||
|
||||
type CmdDump struct {
|
||||
global *GlobalOptions
|
||||
|
||||
repo *repository.Repository
|
||||
var cmdDump = &cobra.Command{
|
||||
Use: "dump [indexes|snapshots|trees|all|packs]",
|
||||
Short: "dump data structures",
|
||||
Long: `
|
||||
The "dump" command dumps data structures from a repository as JSON objects. It
|
||||
is used for debugging purposes only.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runDump(globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("dump",
|
||||
"dump data structures",
|
||||
"The dump command dumps data structures from a repository as JSON documents",
|
||||
&CmdDump{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd CmdDump) Usage() string {
|
||||
return "[indexes|snapshots|trees|all|packs]"
|
||||
cmdRoot.AddCommand(cmdDump)
|
||||
}
|
||||
|
||||
func prettyPrintJSON(wr io.Writer, item interface{}) error {
|
||||
@ -148,14 +145,14 @@ func printPacks(repo *repository.Repository, wr io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd CmdDump) DumpIndexes() error {
|
||||
func dumpIndexes(repo restic.Repository) error {
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
for id := range cmd.repo.List(restic.IndexFile, done) {
|
||||
for id := range repo.List(restic.IndexFile, done) {
|
||||
fmt.Printf("index_id: %v\n", id)
|
||||
|
||||
idx, err := repository.LoadIndex(cmd.repo, id)
|
||||
idx, err := repository.LoadIndex(repo, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -169,22 +166,23 @@ func (cmd CmdDump) DumpIndexes() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd CmdDump) Execute(args []string) error {
|
||||
func runDump(gopts GlobalOptions, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.Fatalf("type not specified, Usage: %s", cmd.Usage())
|
||||
return errors.Fatalf("type not specified")
|
||||
}
|
||||
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.repo = repo
|
||||
|
||||
if !gopts.NoLock {
|
||||
lock, err := lockRepo(repo)
|
||||
defer unlockRepo(lock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = repo.LoadIndex()
|
||||
if err != nil {
|
||||
@ -195,7 +193,7 @@ func (cmd CmdDump) Execute(args []string) error {
|
||||
|
||||
switch tpe {
|
||||
case "indexes":
|
||||
return cmd.DumpIndexes()
|
||||
return dumpIndexes(repo)
|
||||
case "snapshots":
|
||||
return debugPrintSnapshots(repo, os.Stdout)
|
||||
case "packs":
|
||||
@ -208,7 +206,7 @@ func (cmd CmdDump) Execute(args []string) error {
|
||||
}
|
||||
|
||||
fmt.Printf("\nindexes:\n")
|
||||
err = cmd.DumpIndexes()
|
||||
err = dumpIndexes(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -4,27 +4,53 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"restic"
|
||||
"restic/debug"
|
||||
"restic/errors"
|
||||
"restic/repository"
|
||||
)
|
||||
|
||||
var cmdFind = &cobra.Command{
|
||||
Use: "find [flags] PATTERN",
|
||||
Short: "find a file or directory",
|
||||
Long: `
|
||||
The "find" command searches for files or directories in snapshots stored in the
|
||||
repo. `,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runFind(findOptions, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
// FindOptions bundle all options for the find command.
|
||||
type FindOptions struct {
|
||||
Oldest string
|
||||
Newest string
|
||||
Snapshot string
|
||||
}
|
||||
|
||||
var findOptions FindOptions
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(cmdFind)
|
||||
|
||||
f := cmdFind.Flags()
|
||||
f.StringVarP(&findOptions.Oldest, "oldest", "o", "", "Oldest modification date/time")
|
||||
f.StringVarP(&findOptions.Newest, "newest", "n", "", "Newest modification date/time")
|
||||
f.StringVarP(&findOptions.Snapshot, "snapshot", "s", "", "Snapshot ID to search in")
|
||||
}
|
||||
|
||||
type findPattern struct {
|
||||
oldest, newest time.Time
|
||||
pattern string
|
||||
}
|
||||
|
||||
type findResult struct {
|
||||
node *restic.Node
|
||||
path string
|
||||
}
|
||||
|
||||
type CmdFind struct {
|
||||
Oldest string `short:"o" long:"oldest" description:"Oldest modification date/time"`
|
||||
Newest string `short:"n" long:"newest" description:"Newest modification date/time"`
|
||||
Snapshot string `short:"s" long:"snapshot" description:"Snapshot ID to search in"`
|
||||
|
||||
oldest, newest time.Time
|
||||
pattern string
|
||||
global *GlobalOptions
|
||||
}
|
||||
|
||||
var timeFormats = []string{
|
||||
"2006-01-02",
|
||||
"2006-01-02 15:04",
|
||||
@ -39,16 +65,6 @@ var timeFormats = []string{
|
||||
"Mon Jan 2 15:04:05 -0700 MST 2006",
|
||||
}
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("find",
|
||||
"find a file/directory",
|
||||
"The find command searches for files or directories in snapshots",
|
||||
&CmdFind{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func parseTime(str string) (time.Time, error) {
|
||||
for _, fmt := range timeFormats {
|
||||
if t, err := time.ParseInLocation(fmt, str, time.Local); err == nil {
|
||||
@ -59,7 +75,7 @@ func parseTime(str string) (time.Time, error) {
|
||||
return time.Time{}, errors.Fatalf("unable to parse time: %q", str)
|
||||
}
|
||||
|
||||
func (c CmdFind) findInTree(repo *repository.Repository, id restic.ID, path string) ([]findResult, error) {
|
||||
func findInTree(repo *repository.Repository, pat findPattern, id restic.ID, path string) ([]findResult, error) {
|
||||
debug.Log("restic.find", "checking tree %v\n", id)
|
||||
tree, err := repo.LoadTree(id)
|
||||
if err != nil {
|
||||
@ -70,20 +86,20 @@ func (c CmdFind) findInTree(repo *repository.Repository, id restic.ID, path stri
|
||||
for _, node := range tree.Nodes {
|
||||
debug.Log("restic.find", " testing entry %q\n", node.Name)
|
||||
|
||||
m, err := filepath.Match(c.pattern, node.Name)
|
||||
m, err := filepath.Match(pat.pattern, node.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m {
|
||||
debug.Log("restic.find", " pattern matches\n")
|
||||
if !c.oldest.IsZero() && node.ModTime.Before(c.oldest) {
|
||||
debug.Log("restic.find", " ModTime is older than %s\n", c.oldest)
|
||||
if !pat.oldest.IsZero() && node.ModTime.Before(pat.oldest) {
|
||||
debug.Log("restic.find", " ModTime is older than %s\n", pat.oldest)
|
||||
continue
|
||||
}
|
||||
|
||||
if !c.newest.IsZero() && node.ModTime.After(c.newest) {
|
||||
debug.Log("restic.find", " ModTime is newer than %s\n", c.newest)
|
||||
if !pat.newest.IsZero() && node.ModTime.After(pat.newest) {
|
||||
debug.Log("restic.find", " ModTime is newer than %s\n", pat.newest)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -93,7 +109,7 @@ func (c CmdFind) findInTree(repo *repository.Repository, id restic.ID, path stri
|
||||
}
|
||||
|
||||
if node.Type == "dir" {
|
||||
subdirResults, err := c.findInTree(repo, *node.Subtree, filepath.Join(path, node.Name))
|
||||
subdirResults, err := findInTree(repo, pat, *node.Subtree, filepath.Join(path, node.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -105,15 +121,15 @@ func (c CmdFind) findInTree(repo *repository.Repository, id restic.ID, path stri
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (c CmdFind) findInSnapshot(repo *repository.Repository, id restic.ID) error {
|
||||
debug.Log("restic.find", "searching in snapshot %s\n for entries within [%s %s]", id.Str(), c.oldest, c.newest)
|
||||
func findInSnapshot(repo *repository.Repository, pat findPattern, id restic.ID) error {
|
||||
debug.Log("restic.find", "searching in snapshot %s\n for entries within [%s %s]", id.Str(), pat.oldest, pat.newest)
|
||||
|
||||
sn, err := restic.LoadSnapshot(repo, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
results, err := c.findInTree(repo, *sn.Tree, "")
|
||||
results, err := findInTree(repo, pat, *sn.Tree, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -121,71 +137,72 @@ func (c CmdFind) findInSnapshot(repo *repository.Repository, id restic.ID) error
|
||||
if len(results) == 0 {
|
||||
return nil
|
||||
}
|
||||
c.global.Verbosef("found %d matching entries in snapshot %s\n", len(results), id)
|
||||
Verbosef("found %d matching entries in snapshot %s\n", len(results), id)
|
||||
for _, res := range results {
|
||||
res.node.Name = filepath.Join(res.path, res.node.Name)
|
||||
c.global.Printf(" %s\n", res.node)
|
||||
Printf(" %s\n", res.node)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (CmdFind) Usage() string {
|
||||
return "[find-OPTIONS] PATTERN"
|
||||
}
|
||||
|
||||
func (c CmdFind) Execute(args []string) error {
|
||||
func runFind(opts FindOptions, gopts GlobalOptions, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.Fatalf("wrong number of arguments, Usage: %s", c.Usage())
|
||||
return errors.Fatalf("wrong number of arguments")
|
||||
}
|
||||
|
||||
var err error
|
||||
var (
|
||||
err error
|
||||
pat findPattern
|
||||
)
|
||||
|
||||
if c.Oldest != "" {
|
||||
c.oldest, err = parseTime(c.Oldest)
|
||||
if opts.Oldest != "" {
|
||||
pat.oldest, err = parseTime(opts.Oldest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.Newest != "" {
|
||||
c.newest, err = parseTime(c.Newest)
|
||||
if opts.Newest != "" {
|
||||
pat.newest, err = parseTime(opts.Newest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
repo, err := c.global.OpenRepository()
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !gopts.NoLock {
|
||||
lock, err := lockRepo(repo)
|
||||
defer unlockRepo(lock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = repo.LoadIndex()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.pattern = args[0]
|
||||
pat.pattern = args[0]
|
||||
|
||||
if c.Snapshot != "" {
|
||||
snapshotID, err := restic.FindSnapshot(repo, c.Snapshot)
|
||||
if opts.Snapshot != "" {
|
||||
snapshotID, err := restic.FindSnapshot(repo, opts.Snapshot)
|
||||
if err != nil {
|
||||
return errors.Fatalf("invalid id %q: %v", args[1], err)
|
||||
}
|
||||
|
||||
return c.findInSnapshot(repo, snapshotID)
|
||||
return findInSnapshot(repo, pat, snapshotID)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
for snapshotID := range repo.List(restic.SnapshotFile, done) {
|
||||
err := c.findInSnapshot(repo, snapshotID)
|
||||
err := findInSnapshot(repo, pat, snapshotID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -5,46 +5,58 @@ import (
|
||||
"io"
|
||||
"restic"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CmdForget implements the 'forget' command.
|
||||
type CmdForget struct {
|
||||
Last int `short:"l" long:"keep-last" description:"keep the last n snapshots"`
|
||||
Hourly int `short:"H" long:"keep-hourly" description:"keep the last n hourly snapshots"`
|
||||
Daily int `short:"d" long:"keep-daily" description:"keep the last n daily snapshots"`
|
||||
Weekly int `short:"w" long:"keep-weekly" description:"keep the last n weekly snapshots"`
|
||||
Monthly int `short:"m" long:"keep-monthly"description:"keep the last n monthly snapshots"`
|
||||
Yearly int `short:"y" long:"keep-yearly" description:"keep the last n yearly snapshots"`
|
||||
|
||||
KeepTags []string `long:"keep-tag" description:"alwaps keep snapshots with this tag (can be specified multiple times)"`
|
||||
|
||||
Hostname string `long:"hostname" description:"only forget snapshots for the given hostname"`
|
||||
Tags []string `long:"tag" description:"only forget snapshots with the tag (can be specified multiple times)"`
|
||||
|
||||
DryRun bool `short:"n" long:"dry-run" description:"do not delete anything, just print what would be done"`
|
||||
|
||||
global *GlobalOptions
|
||||
var cmdForget = &cobra.Command{
|
||||
Use: "forget [flags] [snapshot ID] [...]",
|
||||
Short: "forget removes snapshots from the repository",
|
||||
Long: `
|
||||
The "forget" command removes snapshots according to a policy. Please note that
|
||||
this command really only deletes the snapshot object in the repository, which
|
||||
is a reference to data stored there. In order to remove this (now unreferenced)
|
||||
data after 'forget' was run successfully, see the 'prune' command. `,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runForget(forgetOptions, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
// ForgetOptions collects all options for the forget command.
|
||||
type ForgetOptions struct {
|
||||
Last int
|
||||
Hourly int
|
||||
Daily int
|
||||
Weekly int
|
||||
Monthly int
|
||||
Yearly int
|
||||
|
||||
KeepTags []string
|
||||
|
||||
Hostname string
|
||||
Tags []string
|
||||
|
||||
DryRun bool
|
||||
}
|
||||
|
||||
var forgetOptions ForgetOptions
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("forget",
|
||||
"removes snapshots from a repository",
|
||||
`
|
||||
The forget command removes snapshots according to a policy. Please note
|
||||
that this command really only deletes the snapshot object in the repo, which
|
||||
is a reference to data stored there. In order to remove this (now
|
||||
unreferenced) data after 'forget' was run successfully, see the 'prune'
|
||||
command.
|
||||
`,
|
||||
&CmdForget{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
cmdRoot.AddCommand(cmdForget)
|
||||
|
||||
// Usage returns usage information for 'forget'.
|
||||
func (cmd CmdForget) Usage() string {
|
||||
return "[snapshot ID] ..."
|
||||
f := cmdForget.Flags()
|
||||
f.IntVarP(&forgetOptions.Last, "keep-last", "l", 0, "keep the last n snapshots")
|
||||
f.IntVarP(&forgetOptions.Hourly, "keep-hourly", "H", 0, "keep the last n hourly snapshots")
|
||||
f.IntVarP(&forgetOptions.Daily, "keep-daily", "d", 0, "keep the last n daily snapshots")
|
||||
f.IntVarP(&forgetOptions.Weekly, "keep-weekly", "w", 0, "keep the last n weekly snapshots")
|
||||
f.IntVarP(&forgetOptions.Monthly, "keep-monthly", "m", 0, "keep the last n monthly snapshots")
|
||||
f.IntVarP(&forgetOptions.Yearly, "keep-yearly", "y", 0, "keep the last n yearly snapshots")
|
||||
|
||||
f.StringSliceVar(&forgetOptions.KeepTags, "keep-tag", []string{}, "always keep snapshots with this tag (can be specified multiple times)")
|
||||
f.StringVar(&forgetOptions.Hostname, "hostname", "", "only forget snapshots for the given hostname")
|
||||
f.StringSliceVar(&forgetOptions.Tags, "tag", []string{}, "only forget snapshots with the tag (can be specified multiple times)")
|
||||
|
||||
f.BoolVarP(&forgetOptions.DryRun, "dry-run", "n", false, "do not delete anything, just print what would be done")
|
||||
}
|
||||
|
||||
func printSnapshots(w io.Writer, snapshots restic.Snapshots) {
|
||||
@ -87,9 +99,8 @@ func printSnapshots(w io.Writer, snapshots restic.Snapshots) {
|
||||
tab.Write(w)
|
||||
}
|
||||
|
||||
// Execute runs the 'forget' command.
|
||||
func (cmd CmdForget) Execute(args []string) error {
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -112,26 +123,26 @@ func (cmd CmdForget) Execute(args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cmd.DryRun {
|
||||
if !opts.DryRun {
|
||||
err = repo.Backend().Remove(restic.SnapshotFile, id.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("removed snapshot %v\n", id.Str())
|
||||
Verbosef("removed snapshot %v\n", id.Str())
|
||||
} else {
|
||||
cmd.global.Verbosef("would removed snapshot %v\n", id.Str())
|
||||
Verbosef("would removed snapshot %v\n", id.Str())
|
||||
}
|
||||
}
|
||||
|
||||
policy := restic.ExpirePolicy{
|
||||
Last: cmd.Last,
|
||||
Hourly: cmd.Hourly,
|
||||
Daily: cmd.Daily,
|
||||
Weekly: cmd.Weekly,
|
||||
Monthly: cmd.Monthly,
|
||||
Yearly: cmd.Yearly,
|
||||
Tags: cmd.KeepTags,
|
||||
Last: opts.Last,
|
||||
Hourly: opts.Hourly,
|
||||
Daily: opts.Daily,
|
||||
Weekly: opts.Weekly,
|
||||
Monthly: opts.Monthly,
|
||||
Yearly: opts.Yearly,
|
||||
Tags: opts.KeepTags,
|
||||
}
|
||||
|
||||
if policy.Empty() {
|
||||
@ -153,11 +164,11 @@ func (cmd CmdForget) Execute(args []string) error {
|
||||
snapshotGroups := make(map[key]restic.Snapshots)
|
||||
|
||||
for _, sn := range snapshots {
|
||||
if cmd.Hostname != "" && sn.Hostname != cmd.Hostname {
|
||||
if opts.Hostname != "" && sn.Hostname != opts.Hostname {
|
||||
continue
|
||||
}
|
||||
|
||||
if !sn.HasTags(cmd.Tags) {
|
||||
if !sn.HasTags(opts.Tags) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -168,18 +179,18 @@ func (cmd CmdForget) Execute(args []string) error {
|
||||
}
|
||||
|
||||
for key, snapshotGroup := range snapshotGroups {
|
||||
cmd.global.Printf("snapshots for host %v, directories %v:\n\n", key.Hostname, key.Dirs)
|
||||
Printf("snapshots for host %v, directories %v:\n\n", key.Hostname, key.Dirs)
|
||||
keep, remove := restic.ApplyPolicy(snapshotGroup, policy)
|
||||
|
||||
cmd.global.Printf("keep %d snapshots:\n", len(keep))
|
||||
printSnapshots(cmd.global.stdout, keep)
|
||||
cmd.global.Printf("\n")
|
||||
Printf("keep %d snapshots:\n", len(keep))
|
||||
printSnapshots(globalOptions.stdout, keep)
|
||||
Printf("\n")
|
||||
|
||||
cmd.global.Printf("remove %d snapshots:\n", len(remove))
|
||||
printSnapshots(cmd.global.stdout, remove)
|
||||
cmd.global.Printf("\n")
|
||||
Printf("remove %d snapshots:\n", len(remove))
|
||||
printSnapshots(globalOptions.stdout, remove)
|
||||
Printf("\n")
|
||||
|
||||
if !cmd.DryRun {
|
||||
if !opts.DryRun {
|
||||
for _, sn := range remove {
|
||||
err = repo.Backend().Remove(restic.SnapshotFile, sn.ID().String())
|
||||
if err != nil {
|
||||
|
@ -3,24 +3,37 @@ package main
|
||||
import (
|
||||
"restic/errors"
|
||||
"restic/repository"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type CmdInit struct {
|
||||
global *GlobalOptions
|
||||
var cmdInit = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "initialize a new repository",
|
||||
Long: `
|
||||
The "init" command initializes a new repository.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runInit(globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
func (cmd CmdInit) Execute(args []string) error {
|
||||
if cmd.global.Repo == "" {
|
||||
func init() {
|
||||
cmdRoot.AddCommand(cmdInit)
|
||||
}
|
||||
|
||||
func runInit(gopts GlobalOptions, args []string) error {
|
||||
if gopts.Repo == "" {
|
||||
return errors.Fatal("Please specify repository location (-r)")
|
||||
}
|
||||
|
||||
be, err := create(cmd.global.Repo)
|
||||
be, err := create(gopts.Repo)
|
||||
if err != nil {
|
||||
cmd.global.Exitf(1, "creating backend at %s failed: %v\n", cmd.global.Repo, err)
|
||||
return errors.Fatalf("create backend at %s failed: %v\n", gopts.Repo, err)
|
||||
}
|
||||
|
||||
if cmd.global.password == "" {
|
||||
cmd.global.password, err = cmd.global.ReadPasswordTwice(
|
||||
if gopts.password == "" {
|
||||
gopts.password, err = ReadPasswordTwice(gopts,
|
||||
"enter password for new backend: ",
|
||||
"enter password again: ")
|
||||
if err != nil {
|
||||
@ -30,26 +43,16 @@ func (cmd CmdInit) Execute(args []string) error {
|
||||
|
||||
s := repository.New(be)
|
||||
|
||||
err = s.Init(cmd.global.password)
|
||||
err = s.Init(gopts.password)
|
||||
if err != nil {
|
||||
cmd.global.Exitf(1, "creating key in backend at %s failed: %v\n", cmd.global.Repo, err)
|
||||
return errors.Fatalf("create key in backend at %s failed: %v\n", gopts.Repo, err)
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("created restic backend %v at %s\n", s.Config().ID[:10], cmd.global.Repo)
|
||||
cmd.global.Verbosef("\n")
|
||||
cmd.global.Verbosef("Please note that knowledge of your password is required to access\n")
|
||||
cmd.global.Verbosef("the repository. Losing your password means that your data is\n")
|
||||
cmd.global.Verbosef("irrecoverably lost.\n")
|
||||
Verbosef("created restic backend %v at %s\n", s.Config().ID[:10], gopts.Repo)
|
||||
Verbosef("\n")
|
||||
Verbosef("Please note that knowledge of your password is required to access\n")
|
||||
Verbosef("the repository. Losing your password means that your data is\n")
|
||||
Verbosef("irrecoverably lost.\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("init",
|
||||
"create repository",
|
||||
"The init command creates a new repository",
|
||||
&CmdInit{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -4,42 +4,39 @@ import (
|
||||
"fmt"
|
||||
"restic"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"restic/errors"
|
||||
"restic/repository"
|
||||
)
|
||||
|
||||
type CmdKey struct {
|
||||
global *GlobalOptions
|
||||
newPassword string
|
||||
var cmdKey = &cobra.Command{
|
||||
Use: "key [list|add|rm|passwd] [ID]",
|
||||
Short: "manage keys (passwords)",
|
||||
Long: `
|
||||
The "key" command manages keys (passwords) for accessing a repository.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runKey(globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("key",
|
||||
"manage keys",
|
||||
"The key command manages keys (passwords) of a repository",
|
||||
&CmdKey{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmdRoot.AddCommand(cmdKey)
|
||||
}
|
||||
|
||||
func (cmd CmdKey) listKeys(s *repository.Repository) error {
|
||||
func listKeys(s *repository.Repository) error {
|
||||
tab := NewTable()
|
||||
tab.Header = fmt.Sprintf(" %-10s %-10s %-10s %s", "ID", "User", "Host", "Created")
|
||||
tab.RowFormat = "%s%-10s %-10s %-10s %s"
|
||||
|
||||
plen, err := s.PrefixLength(restic.KeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
for id := range s.List(restic.KeyFile, done) {
|
||||
k, err := repository.LoadKey(s, id.String())
|
||||
if err != nil {
|
||||
cmd.global.Warnf("LoadKey() failed: %v\n", err)
|
||||
Warnf("LoadKey() failed: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -49,25 +46,28 @@ func (cmd CmdKey) listKeys(s *repository.Repository) error {
|
||||
} else {
|
||||
current = " "
|
||||
}
|
||||
tab.Rows = append(tab.Rows, []interface{}{current, id.String()[:plen],
|
||||
tab.Rows = append(tab.Rows, []interface{}{current, id.Str(),
|
||||
k.Username, k.Hostname, k.Created.Format(TimeFormat)})
|
||||
}
|
||||
|
||||
return tab.Write(cmd.global.stdout)
|
||||
return tab.Write(globalOptions.stdout)
|
||||
}
|
||||
|
||||
func (cmd CmdKey) getNewPassword() (string, error) {
|
||||
if cmd.newPassword != "" {
|
||||
return cmd.newPassword, nil
|
||||
// testKeyNewPassword is used to set a new password during integration testing.
|
||||
var testKeyNewPassword string
|
||||
|
||||
func getNewPassword(gopts GlobalOptions) (string, error) {
|
||||
if testKeyNewPassword != "" {
|
||||
return testKeyNewPassword, nil
|
||||
}
|
||||
|
||||
return cmd.global.ReadPasswordTwice(
|
||||
return ReadPasswordTwice(gopts,
|
||||
"enter password for new key: ",
|
||||
"enter password again: ")
|
||||
}
|
||||
|
||||
func (cmd CmdKey) addKey(repo *repository.Repository) error {
|
||||
pw, err := cmd.getNewPassword()
|
||||
func addKey(gopts GlobalOptions, repo *repository.Repository) error {
|
||||
pw, err := getNewPassword(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -77,12 +77,12 @@ func (cmd CmdKey) addKey(repo *repository.Repository) error {
|
||||
return errors.Fatalf("creating new key failed: %v\n", err)
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("saved new key as %s\n", id)
|
||||
Verbosef("saved new key as %s\n", id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd CmdKey) deleteKey(repo *repository.Repository, name string) error {
|
||||
func deleteKey(repo *repository.Repository, name string) error {
|
||||
if name == repo.KeyName() {
|
||||
return errors.Fatal("refusing to remove key currently used to access repository")
|
||||
}
|
||||
@ -92,12 +92,12 @@ func (cmd CmdKey) deleteKey(repo *repository.Repository, name string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("removed key %v\n", name)
|
||||
Verbosef("removed key %v\n", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd CmdKey) changePassword(repo *repository.Repository) error {
|
||||
pw, err := cmd.getNewPassword()
|
||||
func changePassword(gopts GlobalOptions, repo *repository.Repository) error {
|
||||
pw, err := getNewPassword(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -112,21 +112,17 @@ func (cmd CmdKey) changePassword(repo *repository.Repository) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("saved new key as %s\n", id)
|
||||
Verbosef("saved new key as %s\n", id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd CmdKey) Usage() string {
|
||||
return "[list|add|rm|passwd] [ID]"
|
||||
}
|
||||
|
||||
func (cmd CmdKey) Execute(args []string) error {
|
||||
func runKey(gopts GlobalOptions, args []string) error {
|
||||
if len(args) < 1 || (args[0] == "rm" && len(args) != 2) {
|
||||
return errors.Fatalf("wrong number of arguments, Usage: %s", cmd.Usage())
|
||||
return errors.Fatalf("wrong number of arguments")
|
||||
}
|
||||
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -139,7 +135,7 @@ func (cmd CmdKey) Execute(args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return cmd.listKeys(repo)
|
||||
return listKeys(repo)
|
||||
case "add":
|
||||
lock, err := lockRepo(repo)
|
||||
defer unlockRepo(lock)
|
||||
@ -147,7 +143,7 @@ func (cmd CmdKey) Execute(args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return cmd.addKey(repo)
|
||||
return addKey(gopts, repo)
|
||||
case "rm":
|
||||
lock, err := lockRepoExclusive(repo)
|
||||
defer unlockRepo(lock)
|
||||
@ -160,7 +156,7 @@ func (cmd CmdKey) Execute(args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return cmd.deleteKey(repo, id)
|
||||
return deleteKey(repo, id)
|
||||
case "passwd":
|
||||
lock, err := lockRepoExclusive(repo)
|
||||
defer unlockRepo(lock)
|
||||
@ -168,7 +164,7 @@ func (cmd CmdKey) Execute(args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return cmd.changePassword(repo)
|
||||
return changePassword(gopts, repo)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -3,37 +3,36 @@ package main
|
||||
import (
|
||||
"restic"
|
||||
"restic/errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type CmdList struct {
|
||||
global *GlobalOptions
|
||||
var cmdList = &cobra.Command{
|
||||
Use: "list [blobs|packs|index|snapshots|keys|locks]",
|
||||
Short: "list items in the repository",
|
||||
Long: `
|
||||
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runList(globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("list",
|
||||
"lists data",
|
||||
"The list command lists structures or data of a repository",
|
||||
&CmdList{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmdRoot.AddCommand(cmdList)
|
||||
}
|
||||
|
||||
func (cmd CmdList) Usage() string {
|
||||
return "[blobs|packs|index|snapshots|keys|locks]"
|
||||
}
|
||||
|
||||
func (cmd CmdList) Execute(args []string) error {
|
||||
func runList(opts GlobalOptions, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.Fatalf("type not specified, Usage: %s", cmd.Usage())
|
||||
return errors.Fatalf("type not specified")
|
||||
}
|
||||
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
repo, err := OpenRepository(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cmd.global.NoLock {
|
||||
if !opts.NoLock {
|
||||
lock, err := lockRepo(repo)
|
||||
defer unlockRepo(lock)
|
||||
if err != nil {
|
||||
@ -58,7 +57,7 @@ func (cmd CmdList) Execute(args []string) error {
|
||||
}
|
||||
|
||||
for id := range repo.List(t, nil) {
|
||||
cmd.global.Printf("%s\n", id)
|
||||
Printf("%s\n", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -5,29 +5,34 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"restic"
|
||||
"restic/errors"
|
||||
"restic/repository"
|
||||
)
|
||||
|
||||
type CmdLs struct {
|
||||
Long bool `short:"l" long:"long" description:"Use a long listing format showing size and mode"`
|
||||
|
||||
global *GlobalOptions
|
||||
var cmdLs = &cobra.Command{
|
||||
Use: "ls [flags] snapshot-ID",
|
||||
Short: "list files in a snapshot",
|
||||
Long: `
|
||||
The "ls" command allows listing files and directories in a snapshot.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runLs(globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
var listLong bool
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("ls",
|
||||
"list files",
|
||||
"The ls command lists all files and directories in a snapshot",
|
||||
&CmdLs{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmdRoot.AddCommand(cmdLs)
|
||||
|
||||
cmdLs.Flags().BoolVarP(&listLong, "long", "l", false, "use a long listing format showing size and mode")
|
||||
}
|
||||
|
||||
func (cmd CmdLs) printNode(prefix string, n *restic.Node) string {
|
||||
if !cmd.Long {
|
||||
func printNode(prefix string, n *restic.Node) string {
|
||||
if !listLong {
|
||||
return filepath.Join(prefix, n.Name)
|
||||
}
|
||||
|
||||
@ -46,17 +51,17 @@ func (cmd CmdLs) printNode(prefix string, n *restic.Node) string {
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd CmdLs) printTree(prefix string, repo *repository.Repository, id restic.ID) error {
|
||||
func printTree(prefix string, repo *repository.Repository, id restic.ID) error {
|
||||
tree, err := repo.LoadTree(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range tree.Nodes {
|
||||
cmd.global.Printf(cmd.printNode(prefix, entry) + "\n")
|
||||
Printf(printNode(prefix, entry) + "\n")
|
||||
|
||||
if entry.Type == "dir" && entry.Subtree != nil {
|
||||
err = cmd.printTree(filepath.Join(prefix, entry.Name), repo, *entry.Subtree)
|
||||
err = printTree(filepath.Join(prefix, entry.Name), repo, *entry.Subtree)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -66,16 +71,12 @@ func (cmd CmdLs) printTree(prefix string, repo *repository.Repository, id restic
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd CmdLs) Usage() string {
|
||||
return "snapshot-ID [DIR]"
|
||||
}
|
||||
|
||||
func (cmd CmdLs) Execute(args []string) error {
|
||||
func runLs(gopts GlobalOptions, args []string) error {
|
||||
if len(args) < 1 || len(args) > 2 {
|
||||
return errors.Fatalf("wrong number of arguments, Usage: %s", cmd.Usage())
|
||||
return errors.Fatalf("no snapshot ID given")
|
||||
}
|
||||
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -95,7 +96,7 @@ func (cmd CmdLs) Execute(args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("snapshot of %v at %s:\n", sn.Paths, sn.Time)
|
||||
Verbosef("snapshot of %v at %s:\n", sn.Paths, sn.Time)
|
||||
|
||||
return cmd.printTree("", repo, *sn.Tree)
|
||||
return printTree("", repo, *sn.Tree)
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ package main
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"restic/debug"
|
||||
"restic/errors"
|
||||
|
||||
@ -16,33 +18,36 @@ import (
|
||||
"bazil.org/fuse/fs"
|
||||
)
|
||||
|
||||
type CmdMount struct {
|
||||
Root bool `long:"owner-root" description:"use 'root' as the owner of files and dirs"`
|
||||
|
||||
global *GlobalOptions
|
||||
var cmdMount = &cobra.Command{
|
||||
Use: "mount [flags] mountpoint",
|
||||
Short: "mount the repository",
|
||||
Long: `
|
||||
The "mount" command mounts the repository via fuse to a directory. This is a
|
||||
read-only mount.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runMount(mountOptions, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
// MountOptions collects all options for the mount command.
|
||||
type MountOptions struct {
|
||||
OwnerRoot bool
|
||||
}
|
||||
|
||||
var mountOptions MountOptions
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("mount",
|
||||
"mount a repository",
|
||||
"The mount command mounts a repository read-only to a given directory",
|
||||
&CmdMount{
|
||||
global: &globalOpts,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmdRoot.AddCommand(cmdMount)
|
||||
|
||||
cmdMount.Flags().BoolVar(&mountOptions.OwnerRoot, "owner-root", false, "use 'root' as the owner of files and dirs")
|
||||
}
|
||||
|
||||
func (cmd CmdMount) Usage() string {
|
||||
return "MOUNTPOINT"
|
||||
}
|
||||
|
||||
func (cmd CmdMount) Mount(mountpoint string) error {
|
||||
func mount(opts MountOptions, gopts GlobalOptions, mountpoint string) error {
|
||||
debug.Log("mount", "start mount")
|
||||
defer debug.Log("mount", "finish mount")
|
||||
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -53,7 +58,7 @@ func (cmd CmdMount) Mount(mountpoint string) error {
|
||||
}
|
||||
|
||||
if _, err := resticfs.Stat(mountpoint); os.IsNotExist(errors.Cause(err)) {
|
||||
cmd.global.Verbosef("Mountpoint %s doesn't exist, creating it\n", mountpoint)
|
||||
Verbosef("Mountpoint %s doesn't exist, creating it\n", mountpoint)
|
||||
err = resticfs.Mkdir(mountpoint, os.ModeDir|0700)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -68,8 +73,11 @@ func (cmd CmdMount) Mount(mountpoint string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
Printf("Now serving the repository at %s\n", mountpoint)
|
||||
Printf("Don't forget to umount after quitting!\n")
|
||||
|
||||
root := fs.Tree{}
|
||||
root.Add("snapshots", fuse.NewSnapshotsDir(repo, cmd.Root))
|
||||
root.Add("snapshots", fuse.NewSnapshotsDir(repo, opts.OwnerRoot))
|
||||
|
||||
debug.Log("mount", "serving mount at %v", mountpoint)
|
||||
err = fs.Serve(c, &root)
|
||||
@ -81,28 +89,25 @@ func (cmd CmdMount) Mount(mountpoint string) error {
|
||||
return c.MountError
|
||||
}
|
||||
|
||||
func (cmd CmdMount) Umount(mountpoint string) error {
|
||||
func umount(mountpoint string) error {
|
||||
return systemFuse.Unmount(mountpoint)
|
||||
}
|
||||
|
||||
func (cmd CmdMount) Execute(args []string) error {
|
||||
func runMount(opts MountOptions, gopts GlobalOptions, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.Fatalf("wrong number of parameters, Usage: %s", cmd.Usage())
|
||||
return errors.Fatalf("wrong number of parameters")
|
||||
}
|
||||
|
||||
mountpoint := args[0]
|
||||
|
||||
AddCleanupHandler(func() error {
|
||||
debug.Log("mount", "running umount cleanup handler for mount at %v", mountpoint)
|
||||
err := cmd.Umount(mountpoint)
|
||||
err := umount(mountpoint)
|
||||
if err != nil {
|
||||
cmd.global.Warnf("unable to umount (maybe already umounted?): %v\n", err)
|
||||
Warnf("unable to umount (maybe already umounted?): %v\n", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
cmd.global.Printf("Now serving the repository at %s\n", mountpoint)
|
||||
cmd.global.Printf("Don't forget to umount after quitting!\n")
|
||||
|
||||
return cmd.Mount(mountpoint)
|
||||
return mount(opts, gopts, mountpoint)
|
||||
}
|
||||
|
@ -10,26 +10,25 @@ import (
|
||||
"restic/repository"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// CmdPrune implements the 'prune' command.
|
||||
type CmdPrune struct {
|
||||
global *GlobalOptions
|
||||
var cmdPrune = &cobra.Command{
|
||||
Use: "prune [flags]",
|
||||
Short: "remove unneeded data from the repository",
|
||||
Long: `
|
||||
The "prune" command checks the repository and removes data that is not
|
||||
referenced and therefore not needed any more.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runPrune(globalOptions)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("prune",
|
||||
"removes content from a repository",
|
||||
`
|
||||
The prune command removes rendundant and unneeded data from the repository.
|
||||
For removing snapshots, please see the 'forget' command, then afterwards run
|
||||
'prune'.
|
||||
`,
|
||||
&CmdPrune{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmdRoot.AddCommand(cmdPrune)
|
||||
}
|
||||
|
||||
// newProgressMax returns a progress that counts blobs.
|
||||
@ -64,9 +63,8 @@ func newProgressMax(show bool, max uint64, description string) *restic.Progress
|
||||
return p
|
||||
}
|
||||
|
||||
// Execute runs the 'prune' command.
|
||||
func (cmd CmdPrune) Execute(args []string) error {
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
func runPrune(gopts GlobalOptions) error {
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -92,14 +90,14 @@ func (cmd CmdPrune) Execute(args []string) error {
|
||||
bytes int64
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("counting files in repo\n")
|
||||
Verbosef("counting files in repo\n")
|
||||
for _ = range repo.List(restic.DataFile, done) {
|
||||
stats.packs++
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("building new index for repo\n")
|
||||
Verbosef("building new index for repo\n")
|
||||
|
||||
bar := newProgressMax(cmd.global.ShowProgress(), uint64(stats.packs), "packs")
|
||||
bar := newProgressMax(!gopts.Quiet, uint64(stats.packs), "packs")
|
||||
idx, err := index.New(repo, bar)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -108,7 +106,7 @@ func (cmd CmdPrune) Execute(args []string) error {
|
||||
for _, pack := range idx.Packs {
|
||||
stats.bytes += pack.Size
|
||||
}
|
||||
cmd.global.Verbosef("repository contains %v packs (%v blobs) with %v bytes\n",
|
||||
Verbosef("repository contains %v packs (%v blobs) with %v bytes\n",
|
||||
len(idx.Packs), len(idx.Blobs), formatBytes(uint64(stats.bytes)))
|
||||
|
||||
blobCount := make(map[restic.BlobHandle]int)
|
||||
@ -129,9 +127,9 @@ func (cmd CmdPrune) Execute(args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("processed %d blobs: %d duplicate blobs, %v duplicate\n",
|
||||
Verbosef("processed %d blobs: %d duplicate blobs, %v duplicate\n",
|
||||
stats.blobs, duplicateBlobs, formatBytes(uint64(duplicateBytes)))
|
||||
cmd.global.Verbosef("load all snapshots\n")
|
||||
Verbosef("load all snapshots\n")
|
||||
|
||||
// find referenced blobs
|
||||
snapshots, err := restic.LoadAllSnapshots(repo)
|
||||
@ -141,12 +139,12 @@ func (cmd CmdPrune) Execute(args []string) error {
|
||||
|
||||
stats.snapshots = len(snapshots)
|
||||
|
||||
cmd.global.Verbosef("find data that is still in use for %d snapshots\n", stats.snapshots)
|
||||
Verbosef("find data that is still in use for %d snapshots\n", stats.snapshots)
|
||||
|
||||
usedBlobs := restic.NewBlobSet()
|
||||
seenBlobs := restic.NewBlobSet()
|
||||
|
||||
bar = newProgressMax(cmd.global.ShowProgress(), uint64(len(snapshots)), "snapshots")
|
||||
bar = newProgressMax(!gopts.Quiet, uint64(len(snapshots)), "snapshots")
|
||||
bar.Start()
|
||||
for _, sn := range snapshots {
|
||||
debug.Log("CmdPrune.Execute", "process snapshot %v", sn.ID().Str())
|
||||
@ -161,7 +159,7 @@ func (cmd CmdPrune) Execute(args []string) error {
|
||||
}
|
||||
bar.Done()
|
||||
|
||||
cmd.global.Verbosef("found %d of %d data blobs still in use, removing %d blobs\n",
|
||||
Verbosef("found %d of %d data blobs still in use, removing %d blobs\n",
|
||||
len(usedBlobs), stats.blobs, stats.blobs-len(usedBlobs))
|
||||
|
||||
// find packs that need a rewrite
|
||||
@ -207,7 +205,7 @@ func (cmd CmdPrune) Execute(args []string) error {
|
||||
rewritePacks.Delete(packID)
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("will delete %d packs and rewrite %d packs, this frees %s\n",
|
||||
Verbosef("will delete %d packs and rewrite %d packs, this frees %s\n",
|
||||
len(removePacks), len(rewritePacks), formatBytes(uint64(removeBytes)))
|
||||
|
||||
err = repository.Repack(repo, rewritePacks, usedBlobs)
|
||||
@ -218,17 +216,17 @@ func (cmd CmdPrune) Execute(args []string) error {
|
||||
for packID := range removePacks {
|
||||
err = repo.Backend().Remove(restic.DataFile, packID.String())
|
||||
if err != nil {
|
||||
cmd.global.Warnf("unable to remove file %v from the repository\n", packID.Str())
|
||||
Warnf("unable to remove file %v from the repository\n", packID.Str())
|
||||
}
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("creating new index\n")
|
||||
Verbosef("creating new index\n")
|
||||
|
||||
stats.packs = 0
|
||||
for _ = range repo.List(restic.DataFile, done) {
|
||||
stats.packs++
|
||||
}
|
||||
bar = newProgressMax(cmd.global.ShowProgress(), uint64(stats.packs), "packs")
|
||||
bar = newProgressMax(!gopts.Quiet, uint64(stats.packs), "packs")
|
||||
idx, err = index.New(repo, bar)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -248,8 +246,8 @@ func (cmd CmdPrune) Execute(args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.global.Verbosef("saved new index as %v\n", id.Str())
|
||||
Verbosef("saved new index as %v\n", id.Str())
|
||||
|
||||
cmd.global.Verbosef("done\n")
|
||||
Verbosef("done\n")
|
||||
return nil
|
||||
}
|
||||
|
@ -1,29 +1,32 @@
|
||||
package main
|
||||
|
||||
import "restic/repository"
|
||||
import (
|
||||
"restic/repository"
|
||||
|
||||
type CmdRebuildIndex struct {
|
||||
global *GlobalOptions
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
repo *repository.Repository
|
||||
var cmdRebuildIndex = &cobra.Command{
|
||||
Use: "rebuild-index [flags]",
|
||||
Short: "build a new index file",
|
||||
Long: `
|
||||
The "rebuild-index" command creates a new index by combining the index files
|
||||
into a new one.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runRebuildIndex(globalOptions)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("rebuild-index",
|
||||
"rebuild the index",
|
||||
"The rebuild-index command builds a new index",
|
||||
&CmdRebuildIndex{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmdRoot.AddCommand(cmdRebuildIndex)
|
||||
}
|
||||
|
||||
func (cmd CmdRebuildIndex) Execute(args []string) error {
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
func runRebuildIndex(gopts GlobalOptions) error {
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.repo = repo
|
||||
|
||||
lock, err := lockRepoExclusive(repo)
|
||||
defer unlockRepo(lock)
|
||||
|
@ -5,55 +5,71 @@ import (
|
||||
"restic/debug"
|
||||
"restic/errors"
|
||||
"restic/filter"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type CmdRestore struct {
|
||||
Exclude []string `short:"e" long:"exclude" description:"Exclude a pattern (can be specified multiple times)"`
|
||||
Include []string `short:"i" long:"include" description:"Include a pattern, exclude everything else (can be specified multiple times)"`
|
||||
Target string `short:"t" long:"target" description:"Directory to restore to"`
|
||||
Host string `short:"h" long:"host" description:"Source Filter (for id=latest)"`
|
||||
Paths []string `short:"p" long:"path" description:"Path Filter (absolute path;for id=latest) (can be specified multiple times)"`
|
||||
var cmdRestore = &cobra.Command{
|
||||
Use: "restore [flags] snapshotID",
|
||||
Short: "extract the data from a snapshot",
|
||||
Long: `
|
||||
The "restore" command extracts the data from a snapshot from the repository to
|
||||
a directory.
|
||||
|
||||
global *GlobalOptions
|
||||
The special snapshot "latest" can be used to restore the latest snapshot in the
|
||||
repository.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runRestore(restoreOptions, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
// RestoreOptions collects all options for the restore command.
|
||||
type RestoreOptions struct {
|
||||
Exclude []string
|
||||
Include []string
|
||||
Target string
|
||||
Host string
|
||||
Paths []string
|
||||
}
|
||||
|
||||
var restoreOptions RestoreOptions
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("restore",
|
||||
"restore a snapshot",
|
||||
"The restore command restores a snapshot to a directory",
|
||||
&CmdRestore{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmdRoot.AddCommand(cmdRestore)
|
||||
|
||||
flags := cmdRestore.Flags()
|
||||
flags.StringSliceVarP(&restoreOptions.Exclude, "exclude", "e", nil, "exclude a pattern (can be specified multiple times)")
|
||||
flags.StringSliceVarP(&restoreOptions.Include, "include", "i", nil, "include a pattern, exclude everything else (can be specified multiple times)")
|
||||
flags.StringVarP(&restoreOptions.Target, "target", "t", "", "directory to extract data to")
|
||||
|
||||
flags.StringVarP(&restoreOptions.Host, "host", "h", "", `only consider snapshots for this host when the snapshot ID is "latest"`)
|
||||
flags.StringSliceVarP(&restoreOptions.Paths, "path", "p", nil, `only consider snapshots which include this (absolute) path for snapshot ID "latest"`)
|
||||
}
|
||||
|
||||
func (cmd CmdRestore) Usage() string {
|
||||
return "snapshot-ID"
|
||||
}
|
||||
|
||||
func (cmd CmdRestore) Execute(args []string) error {
|
||||
func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.Fatalf("wrong number of arguments, Usage: %s", cmd.Usage())
|
||||
return errors.Fatalf("no snapshot ID specified")
|
||||
}
|
||||
|
||||
if cmd.Target == "" {
|
||||
if opts.Target == "" {
|
||||
return errors.Fatal("please specify a directory to restore to (--target)")
|
||||
}
|
||||
|
||||
if len(cmd.Exclude) > 0 && len(cmd.Include) > 0 {
|
||||
if len(opts.Exclude) > 0 && len(opts.Include) > 0 {
|
||||
return errors.Fatal("exclude and include patterns are mutually exclusive")
|
||||
}
|
||||
|
||||
snapshotIDString := args[0]
|
||||
|
||||
debug.Log("restore", "restore %v to %v", snapshotIDString, cmd.Target)
|
||||
debug.Log("restore", "restore %v to %v", snapshotIDString, opts.Target)
|
||||
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cmd.global.NoLock {
|
||||
if !gopts.NoLock {
|
||||
lock, err := lockRepo(repo)
|
||||
defer unlockRepo(lock)
|
||||
if err != nil {
|
||||
@ -69,57 +85,52 @@ func (cmd CmdRestore) Execute(args []string) error {
|
||||
var id restic.ID
|
||||
|
||||
if snapshotIDString == "latest" {
|
||||
id, err = restic.FindLatestSnapshot(repo, cmd.Paths, cmd.Host)
|
||||
id, err = restic.FindLatestSnapshot(repo, opts.Paths, opts.Host)
|
||||
if err != nil {
|
||||
cmd.global.Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Host:%v", err, cmd.Paths, cmd.Host)
|
||||
Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Host:%v", err, opts.Paths, opts.Host)
|
||||
}
|
||||
} else {
|
||||
id, err = restic.FindSnapshot(repo, snapshotIDString)
|
||||
if err != nil {
|
||||
cmd.global.Exitf(1, "invalid id %q: %v", snapshotIDString, err)
|
||||
Exitf(1, "invalid id %q: %v", snapshotIDString, err)
|
||||
}
|
||||
}
|
||||
|
||||
res, err := restic.NewRestorer(repo, id)
|
||||
if err != nil {
|
||||
cmd.global.Exitf(2, "creating restorer failed: %v\n", err)
|
||||
Exitf(2, "creating restorer failed: %v\n", err)
|
||||
}
|
||||
|
||||
res.Error = func(dir string, node *restic.Node, err error) error {
|
||||
cmd.global.Warnf("error for %s: %+v\n", dir, err)
|
||||
Warnf("error for %s: %+v\n", dir, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
selectExcludeFilter := func(item string, dstpath string, node *restic.Node) bool {
|
||||
matched, err := filter.List(cmd.Exclude, item)
|
||||
matched, err := filter.List(opts.Exclude, item)
|
||||
if err != nil {
|
||||
cmd.global.Warnf("error for exclude pattern: %v", err)
|
||||
Warnf("error for exclude pattern: %v", err)
|
||||
}
|
||||
|
||||
return !matched
|
||||
}
|
||||
|
||||
selectIncludeFilter := func(item string, dstpath string, node *restic.Node) bool {
|
||||
matched, err := filter.List(cmd.Include, item)
|
||||
matched, err := filter.List(opts.Include, item)
|
||||
if err != nil {
|
||||
cmd.global.Warnf("error for include pattern: %v", err)
|
||||
Warnf("error for include pattern: %v", err)
|
||||
}
|
||||
|
||||
return matched
|
||||
}
|
||||
|
||||
if len(cmd.Exclude) > 0 {
|
||||
if len(opts.Exclude) > 0 {
|
||||
res.SelectFilter = selectExcludeFilter
|
||||
} else if len(cmd.Include) > 0 {
|
||||
} else if len(opts.Include) > 0 {
|
||||
res.SelectFilter = selectIncludeFilter
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("restoring %s to %s\n", res.Snapshot(), cmd.Target)
|
||||
Verbosef("restoring %s to %s\n", res.Snapshot(), opts.Target)
|
||||
|
||||
err = res.RestoreTo(cmd.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return res.RestoreTo(opts.Target)
|
||||
}
|
||||
|
@ -2,86 +2,59 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"restic/errors"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"restic"
|
||||
)
|
||||
|
||||
type Table struct {
|
||||
Header string
|
||||
Rows [][]interface{}
|
||||
|
||||
RowFormat string
|
||||
var cmdSnapshots = &cobra.Command{
|
||||
Use: "snapshots",
|
||||
Short: "list all snapshots",
|
||||
Long: `
|
||||
The "snapshots" command lists all snapshots stored in a repository.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runSnapshots(snapshotOptions, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
func NewTable() Table {
|
||||
return Table{
|
||||
Rows: [][]interface{}{},
|
||||
}
|
||||
// SnapshotOptions bundle all options for the snapshots command.
|
||||
type SnapshotOptions struct {
|
||||
Host string
|
||||
Paths []string
|
||||
}
|
||||
|
||||
func (t Table) Write(w io.Writer) error {
|
||||
_, err := fmt.Fprintln(w, t.Header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintln(w, strings.Repeat("-", 70))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, row := range t.Rows {
|
||||
_, err = fmt.Fprintf(w, t.RowFormat+"\n", row...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const TimeFormat = "2006-01-02 15:04:05"
|
||||
|
||||
type CmdSnapshots struct {
|
||||
Host string `short:"h" long:"host" description:"Host Filter"`
|
||||
Paths []string `short:"p" long:"path" description:"Path Filter (absolute path) (can be specified multiple times)"`
|
||||
|
||||
global *GlobalOptions
|
||||
}
|
||||
var snapshotOptions SnapshotOptions
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("snapshots",
|
||||
"show snapshots",
|
||||
"The snapshots command lists all snapshots stored in a repository",
|
||||
&CmdSnapshots{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmdRoot.AddCommand(cmdSnapshots)
|
||||
|
||||
f := cmdSnapshots.Flags()
|
||||
f.StringVar(&snapshotOptions.Host, "host", "", "only print snapshots for this host")
|
||||
f.StringSliceVar(&snapshotOptions.Paths, "path", []string{}, "only print snapshots for this path (can be specified multiple times)")
|
||||
}
|
||||
|
||||
func (cmd CmdSnapshots) Usage() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (cmd CmdSnapshots) Execute(args []string) error {
|
||||
func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return errors.Fatalf("wrong number of arguments, usage: %s", cmd.Usage())
|
||||
return errors.Fatalf("wrong number of arguments")
|
||||
}
|
||||
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !gopts.NoLock {
|
||||
lock, err := lockRepo(repo)
|
||||
defer unlockRepo(lock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tab := NewTable()
|
||||
tab.Header = fmt.Sprintf("%-8s %-19s %-10s %-10s %s", "ID", "Date", "Host", "Tags", "Directory")
|
||||
@ -98,7 +71,7 @@ func (cmd CmdSnapshots) Execute(args []string) error {
|
||||
continue
|
||||
}
|
||||
|
||||
if restic.SamePaths(sn.Paths, cmd.Paths) && (cmd.Host == "" || cmd.Host == sn.Hostname) {
|
||||
if restic.SamePaths(sn.Paths, opts.Paths) && (opts.Host == "" || opts.Host == sn.Hostname) {
|
||||
pos := sort.Search(len(list), func(i int) bool {
|
||||
return list[i].Time.After(sn.Time)
|
||||
})
|
||||
|
@ -1,35 +1,43 @@
|
||||
package main
|
||||
|
||||
import "restic"
|
||||
import (
|
||||
"restic"
|
||||
|
||||
type CmdUnlock struct {
|
||||
RemoveAll bool `long:"remove-all" description:"Remove all locks, even stale ones"`
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
global *GlobalOptions
|
||||
var unlockCmd = &cobra.Command{
|
||||
Use: "unlock",
|
||||
Short: "remove locks other processes created",
|
||||
Long: `
|
||||
The "unlock" command removes stale locks that have been created by other restic processes.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runUnlock(unlockOptions, globalOptions)
|
||||
},
|
||||
}
|
||||
|
||||
// UnlockOptions collects all options for the unlock command.
|
||||
type UnlockOptions struct {
|
||||
RemoveAll bool
|
||||
}
|
||||
|
||||
var unlockOptions UnlockOptions
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("unlock",
|
||||
"remove locks",
|
||||
"The unlock command checks for stale locks and removes them",
|
||||
&CmdUnlock{global: &globalOpts})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmdRoot.AddCommand(unlockCmd)
|
||||
|
||||
unlockCmd.Flags().BoolVar(&unlockOptions.RemoveAll, "remove-all", false, "Remove all locks, even non-stale ones")
|
||||
}
|
||||
|
||||
func (cmd CmdUnlock) Usage() string {
|
||||
return "[unlock-options]"
|
||||
}
|
||||
|
||||
func (cmd CmdUnlock) Execute(args []string) error {
|
||||
repo, err := cmd.global.OpenRepository()
|
||||
func runUnlock(opts UnlockOptions, gopts GlobalOptions) error {
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fn := restic.RemoveStaleLocks
|
||||
if cmd.RemoveAll {
|
||||
if opts.RemoveAll {
|
||||
fn = restic.RemoveAllLocks
|
||||
}
|
||||
|
||||
@ -38,6 +46,6 @@ func (cmd CmdUnlock) Execute(args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.global.Verbosef("successfully removed locks\n")
|
||||
Verbosef("successfully removed locks\n")
|
||||
return nil
|
||||
}
|
||||
|
@ -3,23 +3,23 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type CmdVersion struct{}
|
||||
|
||||
func init() {
|
||||
_, err := parser.AddCommand("version",
|
||||
"display version",
|
||||
"The version command displays detailed information about the version",
|
||||
&CmdVersion{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd CmdVersion) Execute(args []string) error {
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print version information",
|
||||
Long: `
|
||||
The "version" command prints detailed information about the build environment
|
||||
and the version of this software.
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("restic %s\ncompiled at %s with %v on %v/%v\n",
|
||||
version, compiledAt, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(versionCmd)
|
||||
}
|
||||
|
60
src/cmds/restic/format.go
Normal file
60
src/cmds/restic/format.go
Normal file
@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func formatBytes(c uint64) string {
|
||||
b := float64(c)
|
||||
|
||||
switch {
|
||||
case c > 1<<40:
|
||||
return fmt.Sprintf("%.3f TiB", b/(1<<40))
|
||||
case c > 1<<30:
|
||||
return fmt.Sprintf("%.3f GiB", b/(1<<30))
|
||||
case c > 1<<20:
|
||||
return fmt.Sprintf("%.3f MiB", b/(1<<20))
|
||||
case c > 1<<10:
|
||||
return fmt.Sprintf("%.3f KiB", b/(1<<10))
|
||||
default:
|
||||
return fmt.Sprintf("%dB", c)
|
||||
}
|
||||
}
|
||||
|
||||
func formatSeconds(sec uint64) string {
|
||||
hours := sec / 3600
|
||||
sec -= hours * 3600
|
||||
min := sec / 60
|
||||
sec -= min * 60
|
||||
if hours > 0 {
|
||||
return fmt.Sprintf("%d:%02d:%02d", hours, min, sec)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d:%02d", min, sec)
|
||||
}
|
||||
|
||||
func formatPercent(numerator uint64, denominator uint64) string {
|
||||
if denominator == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
percent := 100.0 * float64(numerator) / float64(denominator)
|
||||
|
||||
if percent > 100 {
|
||||
percent = 100
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%3.2f%%", percent)
|
||||
}
|
||||
|
||||
func formatRate(bytes uint64, duration time.Duration) string {
|
||||
sec := float64(duration) / float64(time.Second)
|
||||
rate := float64(bytes) / sec / (1 << 20)
|
||||
return fmt.Sprintf("%.2fMiB/s", rate)
|
||||
}
|
||||
|
||||
func formatDuration(d time.Duration) string {
|
||||
sec := uint64(d / time.Second)
|
||||
return formatSeconds(sec)
|
||||
}
|
@ -10,6 +10,8 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"restic/backend/local"
|
||||
"restic/backend/rest"
|
||||
"restic/backend/s3"
|
||||
@ -20,28 +22,48 @@ import (
|
||||
|
||||
"restic/errors"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
var version = "compiled manually"
|
||||
var compiledAt = "unknown time"
|
||||
|
||||
// GlobalOptions holds all those options that can be set for every command.
|
||||
func parseEnvironment(cmd *cobra.Command, args []string) {
|
||||
repo := os.Getenv("RESTIC_REPOSITORY")
|
||||
if repo != "" {
|
||||
globalOptions.Repo = repo
|
||||
}
|
||||
|
||||
pw := os.Getenv("RESTIC_PASSWORD")
|
||||
if pw != "" {
|
||||
globalOptions.password = pw
|
||||
}
|
||||
}
|
||||
|
||||
// GlobalOptions hold all global options for restic.
|
||||
type GlobalOptions struct {
|
||||
Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restore from"`
|
||||
PasswordFile string `short:"p" long:"password-file" description:"Read the repository password from a file"`
|
||||
CacheDir string ` long:"cache-dir" description:"Directory to use as a local cache"`
|
||||
Quiet bool `short:"q" long:"quiet" description:"Do not output comprehensive progress report"`
|
||||
NoLock bool ` long:"no-lock" description:"Do not lock the repo, this allows some operations on read-only repos."`
|
||||
Options []string `short:"o" long:"option" description:"Specify options in the form 'foo.key=value'"`
|
||||
Repo string
|
||||
PasswordFile string
|
||||
Quiet bool
|
||||
NoLock bool
|
||||
|
||||
password string
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
}
|
||||
|
||||
var globalOptions = GlobalOptions{
|
||||
stdout: os.Stdout,
|
||||
stderr: os.Stderr,
|
||||
}
|
||||
|
||||
func init() {
|
||||
f := cmdRoot.PersistentFlags()
|
||||
f.StringVarP(&globalOptions.Repo, "repo", "r", "", "repository to backup to or restore from (default: $RESTIC_REPOSITORY)")
|
||||
f.StringVarP(&globalOptions.PasswordFile, "password-file", "p", "", "read the repository password from a file")
|
||||
f.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "do not outputcomprehensive progress report")
|
||||
f.BoolVar(&globalOptions.NoLock, "no-lock", false, "do not lock the repo, this allows some operations on read-only repos")
|
||||
|
||||
restoreTerminal()
|
||||
}
|
||||
|
||||
@ -91,9 +113,6 @@ func restoreTerminal() {
|
||||
})
|
||||
}
|
||||
|
||||
var globalOpts = GlobalOptions{stdout: os.Stdout, stderr: os.Stderr}
|
||||
var parser = flags.NewParser(&globalOpts, flags.HelpFlag|flags.PassDoubleDash)
|
||||
|
||||
// ClearLine creates a platform dependent string to clear the current
|
||||
// line, so it can be overwritten. ANSI sequences are not supported on
|
||||
// current windows cmd shell.
|
||||
@ -109,8 +128,8 @@ func ClearLine() string {
|
||||
}
|
||||
|
||||
// Printf writes the message to the configured stdout stream.
|
||||
func (o GlobalOptions) Printf(format string, args ...interface{}) {
|
||||
_, err := fmt.Fprintf(o.stdout, format, args...)
|
||||
func Printf(format string, args ...interface{}) {
|
||||
_, err := fmt.Fprintf(globalOptions.stdout, format, args...)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unable to write to stdout: %v\n", err)
|
||||
os.Exit(100)
|
||||
@ -118,22 +137,12 @@ func (o GlobalOptions) Printf(format string, args ...interface{}) {
|
||||
}
|
||||
|
||||
// Verbosef calls Printf to write the message when the verbose flag is set.
|
||||
func (o GlobalOptions) Verbosef(format string, args ...interface{}) {
|
||||
if o.Quiet {
|
||||
func Verbosef(format string, args ...interface{}) {
|
||||
if globalOptions.Quiet {
|
||||
return
|
||||
}
|
||||
|
||||
o.Printf(format, args...)
|
||||
}
|
||||
|
||||
// ShowProgress returns true iff the progress status should be written, i.e.
|
||||
// the quiet flag is not set.
|
||||
func (o GlobalOptions) ShowProgress() bool {
|
||||
if o.Quiet {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
Printf(format, args...)
|
||||
}
|
||||
|
||||
// PrintProgress wraps fmt.Printf to handle the difference in writing progress
|
||||
@ -162,8 +171,8 @@ func PrintProgress(format string, args ...interface{}) {
|
||||
}
|
||||
|
||||
// Warnf writes the message to the configured stderr stream.
|
||||
func (o GlobalOptions) Warnf(format string, args ...interface{}) {
|
||||
_, err := fmt.Fprintf(o.stderr, format, args...)
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
_, err := fmt.Fprintf(globalOptions.stderr, format, args...)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unable to write to stderr: %v\n", err)
|
||||
os.Exit(100)
|
||||
@ -171,12 +180,12 @@ func (o GlobalOptions) Warnf(format string, args ...interface{}) {
|
||||
}
|
||||
|
||||
// Exitf uses Warnf to write the message and then calls os.Exit(exitcode).
|
||||
func (o GlobalOptions) Exitf(exitcode int, format string, args ...interface{}) {
|
||||
func Exitf(exitcode int, format string, args ...interface{}) {
|
||||
if format[len(format)-1] != '\n' {
|
||||
format += "\n"
|
||||
}
|
||||
|
||||
o.Warnf(format, args...)
|
||||
Warnf(format, args...)
|
||||
os.Exit(exitcode)
|
||||
}
|
||||
|
||||
@ -210,9 +219,9 @@ func readPasswordTerminal(in *os.File, out io.Writer, prompt string) (password s
|
||||
|
||||
// ReadPassword reads the password from a password file, the environment
|
||||
// variable RESTIC_PASSWORD or prompts the user.
|
||||
func (o GlobalOptions) ReadPassword(prompt string) (string, error) {
|
||||
if o.PasswordFile != "" {
|
||||
s, err := ioutil.ReadFile(o.PasswordFile)
|
||||
func ReadPassword(opts GlobalOptions, prompt string) (string, error) {
|
||||
if opts.PasswordFile != "" {
|
||||
s, err := ioutil.ReadFile(opts.PasswordFile)
|
||||
return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile")
|
||||
}
|
||||
|
||||
@ -244,12 +253,12 @@ func (o GlobalOptions) ReadPassword(prompt string) (string, error) {
|
||||
|
||||
// ReadPasswordTwice calls ReadPassword two times and returns an error when the
|
||||
// passwords don't match.
|
||||
func (o GlobalOptions) ReadPasswordTwice(prompt1, prompt2 string) (string, error) {
|
||||
pw1, err := o.ReadPassword(prompt1)
|
||||
func ReadPasswordTwice(gopts GlobalOptions, prompt1, prompt2 string) (string, error) {
|
||||
pw1, err := ReadPassword(gopts, prompt1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pw2, err := o.ReadPassword(prompt2)
|
||||
pw2, err := ReadPassword(gopts, prompt2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -264,26 +273,26 @@ func (o GlobalOptions) ReadPasswordTwice(prompt1, prompt2 string) (string, error
|
||||
const maxKeys = 20
|
||||
|
||||
// OpenRepository reads the password and opens the repository.
|
||||
func (o GlobalOptions) OpenRepository() (*repository.Repository, error) {
|
||||
if o.Repo == "" {
|
||||
func OpenRepository(opts GlobalOptions) (*repository.Repository, error) {
|
||||
if opts.Repo == "" {
|
||||
return nil, errors.Fatal("Please specify repository location (-r)")
|
||||
}
|
||||
|
||||
be, err := open(o.Repo)
|
||||
be, err := open(opts.Repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := repository.New(be)
|
||||
|
||||
if o.password == "" {
|
||||
o.password, err = o.ReadPassword("enter password for repository: ")
|
||||
if opts.password == "" {
|
||||
opts.password, err = ReadPassword(opts, "enter password for repository: ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = s.SearchKey(o.password, maxKeys)
|
||||
err = s.SearchKey(opts.password, maxKeys)
|
||||
if err != nil {
|
||||
return nil, errors.Fatalf("unable to open repo: %v", err)
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
// +build ignore
|
||||
// +build !openbsd
|
||||
// +build !windows
|
||||
|
||||
|
@ -166,18 +166,6 @@ type testEnvironment struct {
|
||||
base, cache, repo, testdata string
|
||||
}
|
||||
|
||||
func configureRestic(t testing.TB, cache, repo string) GlobalOptions {
|
||||
return GlobalOptions{
|
||||
CacheDir: cache,
|
||||
Repo: repo,
|
||||
Quiet: true,
|
||||
|
||||
password: TestPassword,
|
||||
stdout: os.Stdout,
|
||||
stderr: os.Stderr,
|
||||
}
|
||||
}
|
||||
|
||||
// withTestEnvironment creates a test environment and calls f with it. After f has
|
||||
// returned, the temporary directory is removed.
|
||||
func withTestEnvironment(t testing.TB, f func(*testEnvironment, GlobalOptions)) {
|
||||
@ -201,7 +189,18 @@ func withTestEnvironment(t testing.TB, f func(*testEnvironment, GlobalOptions))
|
||||
OK(t, os.MkdirAll(env.cache, 0700))
|
||||
OK(t, os.MkdirAll(env.repo, 0700))
|
||||
|
||||
f(&env, configureRestic(t, env.cache, env.repo))
|
||||
gopts := GlobalOptions{
|
||||
Repo: env.repo,
|
||||
Quiet: true,
|
||||
password: TestPassword,
|
||||
stdout: os.Stdout,
|
||||
stderr: os.Stderr,
|
||||
}
|
||||
|
||||
// always overwrite global options
|
||||
globalOptions = gopts
|
||||
|
||||
f(&env, gopts)
|
||||
|
||||
if !TestCleanupTempDirs {
|
||||
t.Logf("leaving temporary directory %v used for test", tempdir)
|
||||
|
@ -41,107 +41,126 @@ func parseIDsFromReader(t testing.TB, rd io.Reader) restic.IDs {
|
||||
return IDs
|
||||
}
|
||||
|
||||
func cmdInit(t testing.TB, global GlobalOptions) {
|
||||
func testRunInit(t testing.TB, opts GlobalOptions) {
|
||||
repository.TestUseLowSecurityKDFParameters(t)
|
||||
restic.TestSetLockTimeout(t, 0)
|
||||
|
||||
cmd := &CmdInit{global: &global}
|
||||
OK(t, cmd.Execute(nil))
|
||||
|
||||
t.Logf("repository initialized at %v", global.Repo)
|
||||
}
|
||||
|
||||
func cmdBackup(t testing.TB, global GlobalOptions, target []string, parentID *restic.ID) {
|
||||
cmdBackupExcludes(t, global, target, parentID, nil)
|
||||
}
|
||||
|
||||
func cmdBackupExcludes(t testing.TB, global GlobalOptions, target []string, parentID *restic.ID, excludes []string) {
|
||||
cmd := &CmdBackup{global: &global, Excludes: excludes}
|
||||
if parentID != nil {
|
||||
cmd.Parent = parentID.String()
|
||||
OK(t, runInit(opts, nil))
|
||||
t.Logf("repository initialized at %v", opts.Repo)
|
||||
}
|
||||
|
||||
func testRunBackup(t testing.TB, target []string, opts BackupOptions, gopts GlobalOptions) {
|
||||
t.Logf("backing up %v", target)
|
||||
|
||||
OK(t, cmd.Execute(target))
|
||||
OK(t, runBackup(opts, gopts, target))
|
||||
}
|
||||
|
||||
func cmdList(t testing.TB, global GlobalOptions, tpe string) restic.IDs {
|
||||
cmd := &CmdList{global: &global}
|
||||
return executeAndParseIDs(t, cmd, tpe)
|
||||
}
|
||||
|
||||
func executeAndParseIDs(t testing.TB, cmd *CmdList, args ...string) restic.IDs {
|
||||
func testRunList(t testing.TB, tpe string, opts GlobalOptions) restic.IDs {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
cmd.global.stdout = buf
|
||||
OK(t, cmd.Execute(args))
|
||||
globalOptions.stdout = buf
|
||||
defer func() {
|
||||
globalOptions.stdout = os.Stdout
|
||||
}()
|
||||
|
||||
OK(t, runList(opts, []string{tpe}))
|
||||
return parseIDsFromReader(t, buf)
|
||||
}
|
||||
|
||||
func cmdRestore(t testing.TB, global GlobalOptions, dir string, snapshotID restic.ID) {
|
||||
cmdRestoreExcludes(t, global, dir, snapshotID, nil)
|
||||
func testRunRestore(t testing.TB, opts GlobalOptions, dir string, snapshotID restic.ID) {
|
||||
testRunRestoreExcludes(t, opts, dir, snapshotID, nil)
|
||||
}
|
||||
|
||||
func cmdRestoreLatest(t testing.TB, global GlobalOptions, dir string, paths []string, host string) {
|
||||
cmd := &CmdRestore{global: &global, Target: dir, Host: host, Paths: paths}
|
||||
OK(t, cmd.Execute([]string{"latest"}))
|
||||
func testRunRestoreLatest(t testing.TB, gopts GlobalOptions, dir string, paths []string, host string) {
|
||||
opts := RestoreOptions{
|
||||
Target: dir,
|
||||
Host: host,
|
||||
Paths: paths,
|
||||
}
|
||||
|
||||
func cmdRestoreExcludes(t testing.TB, global GlobalOptions, dir string, snapshotID restic.ID, excludes []string) {
|
||||
cmd := &CmdRestore{global: &global, Target: dir, Exclude: excludes}
|
||||
OK(t, cmd.Execute([]string{snapshotID.String()}))
|
||||
OK(t, runRestore(opts, gopts, []string{"latest"}))
|
||||
}
|
||||
|
||||
func cmdRestoreIncludes(t testing.TB, global GlobalOptions, dir string, snapshotID restic.ID, includes []string) {
|
||||
cmd := &CmdRestore{global: &global, Target: dir, Include: includes}
|
||||
OK(t, cmd.Execute([]string{snapshotID.String()}))
|
||||
func testRunRestoreExcludes(t testing.TB, gopts GlobalOptions, dir string, snapshotID restic.ID, excludes []string) {
|
||||
opts := RestoreOptions{
|
||||
Target: dir,
|
||||
Exclude: excludes,
|
||||
}
|
||||
|
||||
func cmdCheck(t testing.TB, global GlobalOptions) {
|
||||
cmd := &CmdCheck{
|
||||
global: &global,
|
||||
OK(t, runRestore(opts, gopts, []string{snapshotID.String()}))
|
||||
}
|
||||
|
||||
func testRunRestoreIncludes(t testing.TB, gopts GlobalOptions, dir string, snapshotID restic.ID, includes []string) {
|
||||
opts := RestoreOptions{
|
||||
Target: dir,
|
||||
Include: includes,
|
||||
}
|
||||
|
||||
OK(t, runRestore(opts, gopts, []string{snapshotID.String()}))
|
||||
}
|
||||
|
||||
func testRunCheck(t testing.TB, gopts GlobalOptions) {
|
||||
opts := CheckOptions{
|
||||
ReadData: true,
|
||||
CheckUnused: true,
|
||||
}
|
||||
OK(t, cmd.Execute(nil))
|
||||
OK(t, runCheck(opts, gopts, nil))
|
||||
}
|
||||
|
||||
func cmdCheckOutput(t testing.TB, global GlobalOptions) string {
|
||||
func testRunCheckOutput(gopts GlobalOptions) (string, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
global.stdout = buf
|
||||
cmd := &CmdCheck{global: &global, ReadData: true}
|
||||
OK(t, cmd.Execute(nil))
|
||||
return string(buf.Bytes())
|
||||
|
||||
globalOptions.stdout = buf
|
||||
defer func() {
|
||||
globalOptions.stdout = os.Stdout
|
||||
}()
|
||||
|
||||
opts := CheckOptions{
|
||||
ReadData: true,
|
||||
}
|
||||
|
||||
func cmdRebuildIndex(t testing.TB, global GlobalOptions) {
|
||||
global.stdout = ioutil.Discard
|
||||
cmd := &CmdRebuildIndex{global: &global}
|
||||
OK(t, cmd.Execute(nil))
|
||||
err := runCheck(opts, gopts, nil)
|
||||
return string(buf.Bytes()), err
|
||||
}
|
||||
|
||||
func cmdLs(t testing.TB, global GlobalOptions, snapshotID string) []string {
|
||||
var buf bytes.Buffer
|
||||
global.stdout = &buf
|
||||
func testRunRebuildIndex(t testing.TB, gopts GlobalOptions) {
|
||||
globalOptions.stdout = ioutil.Discard
|
||||
defer func() {
|
||||
globalOptions.stdout = os.Stdout
|
||||
}()
|
||||
|
||||
cmd := &CmdLs{global: &global}
|
||||
OK(t, cmd.Execute([]string{snapshotID}))
|
||||
OK(t, runRebuildIndex(gopts))
|
||||
}
|
||||
|
||||
func testRunLs(t testing.TB, gopts GlobalOptions, snapshotID string) []string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
globalOptions.stdout = buf
|
||||
quiet := globalOptions.Quiet
|
||||
globalOptions.Quiet = true
|
||||
defer func() {
|
||||
globalOptions.stdout = os.Stdout
|
||||
globalOptions.Quiet = quiet
|
||||
}()
|
||||
|
||||
OK(t, runLs(gopts, []string{snapshotID}))
|
||||
|
||||
return strings.Split(string(buf.Bytes()), "\n")
|
||||
}
|
||||
|
||||
func cmdFind(t testing.TB, global GlobalOptions, pattern string) []string {
|
||||
var buf bytes.Buffer
|
||||
global.stdout = &buf
|
||||
func testRunFind(t testing.TB, gopts GlobalOptions, pattern string) []string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
globalOptions.stdout = buf
|
||||
defer func() {
|
||||
globalOptions.stdout = os.Stdout
|
||||
}()
|
||||
|
||||
cmd := &CmdFind{global: &global}
|
||||
OK(t, cmd.Execute([]string{pattern}))
|
||||
opts := FindOptions{}
|
||||
|
||||
OK(t, runFind(opts, gopts, []string{pattern}))
|
||||
|
||||
return strings.Split(string(buf.Bytes()), "\n")
|
||||
}
|
||||
|
||||
func TestBackup(t *testing.T) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
datafile := filepath.Join("testdata", "backup-data.tar.gz")
|
||||
fd, err := os.Open(datafile)
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
@ -151,22 +170,23 @@ func TestBackup(t *testing.T) {
|
||||
OK(t, err)
|
||||
OK(t, fd.Close())
|
||||
|
||||
cmdInit(t, global)
|
||||
testRunInit(t, gopts)
|
||||
|
||||
SetupTarTestFixture(t, env.testdata, datafile)
|
||||
opts := BackupOptions{}
|
||||
|
||||
// first backup
|
||||
cmdBackup(t, global, []string{env.testdata}, nil)
|
||||
snapshotIDs := cmdList(t, global, "snapshots")
|
||||
testRunBackup(t, []string{env.testdata}, opts, gopts)
|
||||
snapshotIDs := testRunList(t, "snapshots", gopts)
|
||||
Assert(t, len(snapshotIDs) == 1,
|
||||
"expected one snapshot, got %v", snapshotIDs)
|
||||
|
||||
cmdCheck(t, global)
|
||||
testRunCheck(t, gopts)
|
||||
stat1 := dirStats(env.repo)
|
||||
|
||||
// second backup, implicit incremental
|
||||
cmdBackup(t, global, []string{env.testdata}, nil)
|
||||
snapshotIDs = cmdList(t, global, "snapshots")
|
||||
testRunBackup(t, []string{env.testdata}, opts, gopts)
|
||||
snapshotIDs = testRunList(t, "snapshots", gopts)
|
||||
Assert(t, len(snapshotIDs) == 2,
|
||||
"expected two snapshots, got %v", snapshotIDs)
|
||||
|
||||
@ -176,10 +196,11 @@ func TestBackup(t *testing.T) {
|
||||
}
|
||||
t.Logf("repository grown by %d bytes", stat2.size-stat1.size)
|
||||
|
||||
cmdCheck(t, global)
|
||||
testRunCheck(t, gopts)
|
||||
// third backup, explicit incremental
|
||||
cmdBackup(t, global, []string{env.testdata}, &snapshotIDs[0])
|
||||
snapshotIDs = cmdList(t, global, "snapshots")
|
||||
opts.Parent = snapshotIDs[0].String()
|
||||
testRunBackup(t, []string{env.testdata}, opts, gopts)
|
||||
snapshotIDs = testRunList(t, "snapshots", gopts)
|
||||
Assert(t, len(snapshotIDs) == 3,
|
||||
"expected three snapshots, got %v", snapshotIDs)
|
||||
|
||||
@ -193,17 +214,17 @@ func TestBackup(t *testing.T) {
|
||||
for i, snapshotID := range snapshotIDs {
|
||||
restoredir := filepath.Join(env.base, fmt.Sprintf("restore%d", i))
|
||||
t.Logf("restoring snapshot %v to %v", snapshotID.Str(), restoredir)
|
||||
cmdRestore(t, global, restoredir, snapshotIDs[0])
|
||||
testRunRestore(t, gopts, restoredir, snapshotIDs[0])
|
||||
Assert(t, directoriesEqualContents(env.testdata, filepath.Join(restoredir, "testdata")),
|
||||
"directories are not equal")
|
||||
}
|
||||
|
||||
cmdCheck(t, global)
|
||||
testRunCheck(t, gopts)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackupNonExistingFile(t *testing.T) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
datafile := filepath.Join("testdata", "backup-data.tar.gz")
|
||||
fd, err := os.Open(datafile)
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
@ -215,9 +236,11 @@ func TestBackupNonExistingFile(t *testing.T) {
|
||||
|
||||
SetupTarTestFixture(t, env.testdata, datafile)
|
||||
|
||||
cmdInit(t, global)
|
||||
|
||||
global.stderr = ioutil.Discard
|
||||
testRunInit(t, gopts)
|
||||
globalOptions.stderr = ioutil.Discard
|
||||
defer func() {
|
||||
globalOptions.stderr = os.Stderr
|
||||
}()
|
||||
|
||||
p := filepath.Join(env.testdata, "0", "0")
|
||||
dirs := []string{
|
||||
@ -226,12 +249,15 @@ func TestBackupNonExistingFile(t *testing.T) {
|
||||
filepath.Join(p, "nonexisting"),
|
||||
filepath.Join(p, "5"),
|
||||
}
|
||||
cmdBackup(t, global, dirs, nil)
|
||||
|
||||
opts := BackupOptions{}
|
||||
|
||||
testRunBackup(t, dirs, opts, gopts)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackupMissingFile1(t *testing.T) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
datafile := filepath.Join("testdata", "backup-data.tar.gz")
|
||||
fd, err := os.Open(datafile)
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
@ -243,9 +269,12 @@ func TestBackupMissingFile1(t *testing.T) {
|
||||
|
||||
SetupTarTestFixture(t, env.testdata, datafile)
|
||||
|
||||
cmdInit(t, global)
|
||||
testRunInit(t, gopts)
|
||||
globalOptions.stderr = ioutil.Discard
|
||||
defer func() {
|
||||
globalOptions.stderr = os.Stderr
|
||||
}()
|
||||
|
||||
global.stderr = ioutil.Discard
|
||||
ranHook := false
|
||||
debug.Hook("pipe.walk1", func(context interface{}) {
|
||||
pathname := context.(string)
|
||||
@ -260,8 +289,10 @@ func TestBackupMissingFile1(t *testing.T) {
|
||||
OK(t, os.Remove(filepath.Join(env.testdata, "0", "0", "9", "37")))
|
||||
})
|
||||
|
||||
cmdBackup(t, global, []string{env.testdata}, nil)
|
||||
cmdCheck(t, global)
|
||||
opts := BackupOptions{}
|
||||
|
||||
testRunBackup(t, []string{env.testdata}, opts, gopts)
|
||||
testRunCheck(t, gopts)
|
||||
|
||||
Assert(t, ranHook, "hook did not run")
|
||||
debug.RemoveHook("pipe.walk1")
|
||||
@ -269,7 +300,7 @@ func TestBackupMissingFile1(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBackupMissingFile2(t *testing.T) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
datafile := filepath.Join("testdata", "backup-data.tar.gz")
|
||||
fd, err := os.Open(datafile)
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
@ -281,9 +312,13 @@ func TestBackupMissingFile2(t *testing.T) {
|
||||
|
||||
SetupTarTestFixture(t, env.testdata, datafile)
|
||||
|
||||
cmdInit(t, global)
|
||||
testRunInit(t, gopts)
|
||||
|
||||
globalOptions.stderr = ioutil.Discard
|
||||
defer func() {
|
||||
globalOptions.stderr = os.Stderr
|
||||
}()
|
||||
|
||||
global.stderr = ioutil.Discard
|
||||
ranHook := false
|
||||
debug.Hook("pipe.walk2", func(context interface{}) {
|
||||
pathname := context.(string)
|
||||
@ -298,8 +333,10 @@ func TestBackupMissingFile2(t *testing.T) {
|
||||
OK(t, os.Remove(filepath.Join(env.testdata, "0", "0", "9", "37")))
|
||||
})
|
||||
|
||||
cmdBackup(t, global, []string{env.testdata}, nil)
|
||||
cmdCheck(t, global)
|
||||
opts := BackupOptions{}
|
||||
|
||||
testRunBackup(t, []string{env.testdata}, opts, gopts)
|
||||
testRunCheck(t, gopts)
|
||||
|
||||
Assert(t, ranHook, "hook did not run")
|
||||
debug.RemoveHook("pipe.walk2")
|
||||
@ -307,7 +344,7 @@ func TestBackupMissingFile2(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBackupDirectoryError(t *testing.T) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
datafile := filepath.Join("testdata", "backup-data.tar.gz")
|
||||
fd, err := os.Open(datafile)
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
@ -319,9 +356,13 @@ func TestBackupDirectoryError(t *testing.T) {
|
||||
|
||||
SetupTarTestFixture(t, env.testdata, datafile)
|
||||
|
||||
cmdInit(t, global)
|
||||
testRunInit(t, gopts)
|
||||
|
||||
globalOptions.stderr = ioutil.Discard
|
||||
defer func() {
|
||||
globalOptions.stderr = os.Stderr
|
||||
}()
|
||||
|
||||
global.stderr = ioutil.Discard
|
||||
ranHook := false
|
||||
|
||||
testdir := filepath.Join(env.testdata, "0", "0", "9")
|
||||
@ -340,17 +381,17 @@ func TestBackupDirectoryError(t *testing.T) {
|
||||
OK(t, os.RemoveAll(testdir))
|
||||
})
|
||||
|
||||
cmdBackup(t, global, []string{filepath.Join(env.testdata, "0", "0")}, nil)
|
||||
cmdCheck(t, global)
|
||||
testRunBackup(t, []string{filepath.Join(env.testdata, "0", "0")}, BackupOptions{}, gopts)
|
||||
testRunCheck(t, gopts)
|
||||
|
||||
Assert(t, ranHook, "hook did not run")
|
||||
debug.RemoveHook("pipe.walk2")
|
||||
|
||||
snapshots := cmdList(t, global, "snapshots")
|
||||
snapshots := testRunList(t, "snapshots", gopts)
|
||||
Assert(t, len(snapshots) > 0,
|
||||
"no snapshots found in repo (%v)", datafile)
|
||||
|
||||
files := cmdLs(t, global, snapshots[0].String())
|
||||
files := testRunLs(t, gopts, snapshots[0].String())
|
||||
|
||||
Assert(t, len(files) > 1, "snapshot is empty")
|
||||
})
|
||||
@ -366,8 +407,8 @@ func includes(haystack []string, needle string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func loadSnapshotMap(t testing.TB, global GlobalOptions) map[string]struct{} {
|
||||
snapshotIDs := cmdList(t, global, "snapshots")
|
||||
func loadSnapshotMap(t testing.TB, gopts GlobalOptions) map[string]struct{} {
|
||||
snapshotIDs := testRunList(t, "snapshots", gopts)
|
||||
|
||||
m := make(map[string]struct{})
|
||||
for _, id := range snapshotIDs {
|
||||
@ -396,8 +437,8 @@ var backupExcludeFilenames = []string{
|
||||
}
|
||||
|
||||
func TestBackupExclude(t *testing.T) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
cmdInit(t, global)
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
testRunInit(t, gopts)
|
||||
|
||||
datadir := filepath.Join(env.base, "testdata")
|
||||
|
||||
@ -414,21 +455,25 @@ func TestBackupExclude(t *testing.T) {
|
||||
|
||||
snapshots := make(map[string]struct{})
|
||||
|
||||
cmdBackup(t, global, []string{datadir}, nil)
|
||||
snapshots, snapshotID := lastSnapshot(snapshots, loadSnapshotMap(t, global))
|
||||
files := cmdLs(t, global, snapshotID)
|
||||
opts := BackupOptions{}
|
||||
|
||||
testRunBackup(t, []string{datadir}, opts, gopts)
|
||||
snapshots, snapshotID := lastSnapshot(snapshots, loadSnapshotMap(t, gopts))
|
||||
files := testRunLs(t, gopts, snapshotID)
|
||||
Assert(t, includes(files, filepath.Join("testdata", "foo.tar.gz")),
|
||||
"expected file %q in first snapshot, but it's not included", "foo.tar.gz")
|
||||
|
||||
cmdBackupExcludes(t, global, []string{datadir}, nil, []string{"*.tar.gz"})
|
||||
snapshots, snapshotID = lastSnapshot(snapshots, loadSnapshotMap(t, global))
|
||||
files = cmdLs(t, global, snapshotID)
|
||||
opts.Excludes = []string{"*.tar.gz"}
|
||||
testRunBackup(t, []string{datadir}, opts, gopts)
|
||||
snapshots, snapshotID = lastSnapshot(snapshots, loadSnapshotMap(t, gopts))
|
||||
files = testRunLs(t, gopts, snapshotID)
|
||||
Assert(t, !includes(files, filepath.Join("testdata", "foo.tar.gz")),
|
||||
"expected file %q not in first snapshot, but it's included", "foo.tar.gz")
|
||||
|
||||
cmdBackupExcludes(t, global, []string{datadir}, nil, []string{"*.tar.gz", "private/secret"})
|
||||
snapshots, snapshotID = lastSnapshot(snapshots, loadSnapshotMap(t, global))
|
||||
files = cmdLs(t, global, snapshotID)
|
||||
opts.Excludes = []string{"*.tar.gz", "private/secret"}
|
||||
testRunBackup(t, []string{datadir}, opts, gopts)
|
||||
snapshots, snapshotID = lastSnapshot(snapshots, loadSnapshotMap(t, gopts))
|
||||
files = testRunLs(t, gopts, snapshotID)
|
||||
Assert(t, !includes(files, filepath.Join("testdata", "foo.tar.gz")),
|
||||
"expected file %q not in first snapshot, but it's included", "foo.tar.gz")
|
||||
Assert(t, !includes(files, filepath.Join("testdata", "private", "secret", "passwords.txt")),
|
||||
@ -465,22 +510,24 @@ func appendRandomData(filename string, bytes uint) error {
|
||||
}
|
||||
|
||||
func TestIncrementalBackup(t *testing.T) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
cmdInit(t, global)
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
testRunInit(t, gopts)
|
||||
|
||||
datadir := filepath.Join(env.base, "testdata")
|
||||
testfile := filepath.Join(datadir, "testfile")
|
||||
|
||||
OK(t, appendRandomData(testfile, incrementalFirstWrite))
|
||||
|
||||
cmdBackup(t, global, []string{datadir}, nil)
|
||||
cmdCheck(t, global)
|
||||
opts := BackupOptions{}
|
||||
|
||||
testRunBackup(t, []string{datadir}, opts, gopts)
|
||||
testRunCheck(t, gopts)
|
||||
stat1 := dirStats(env.repo)
|
||||
|
||||
OK(t, appendRandomData(testfile, incrementalSecondWrite))
|
||||
|
||||
cmdBackup(t, global, []string{datadir}, nil)
|
||||
cmdCheck(t, global)
|
||||
testRunBackup(t, []string{datadir}, opts, gopts)
|
||||
testRunCheck(t, gopts)
|
||||
stat2 := dirStats(env.repo)
|
||||
if stat2.size-stat1.size > incrementalFirstWrite {
|
||||
t.Errorf("repository size has grown by more than %d bytes", incrementalFirstWrite)
|
||||
@ -489,8 +536,8 @@ func TestIncrementalBackup(t *testing.T) {
|
||||
|
||||
OK(t, appendRandomData(testfile, incrementalThirdWrite))
|
||||
|
||||
cmdBackup(t, global, []string{datadir}, nil)
|
||||
cmdCheck(t, global)
|
||||
testRunBackup(t, []string{datadir}, opts, gopts)
|
||||
testRunCheck(t, gopts)
|
||||
stat3 := dirStats(env.repo)
|
||||
if stat3.size-stat2.size > incrementalFirstWrite {
|
||||
t.Errorf("repository size has grown by more than %d bytes", incrementalFirstWrite)
|
||||
@ -499,24 +546,17 @@ func TestIncrementalBackup(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func cmdKey(t testing.TB, global GlobalOptions, args ...string) string {
|
||||
var buf bytes.Buffer
|
||||
func testRunKeyListOtherIDs(t testing.TB, gopts GlobalOptions) []string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
global.stdout = &buf
|
||||
cmd := &CmdKey{global: &global}
|
||||
OK(t, cmd.Execute(args))
|
||||
globalOptions.stdout = buf
|
||||
defer func() {
|
||||
globalOptions.stdout = os.Stdout
|
||||
}()
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
OK(t, runKey(gopts, []string{"list"}))
|
||||
|
||||
func cmdKeyListOtherIDs(t testing.TB, global GlobalOptions) []string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
global.stdout = &buf
|
||||
cmd := &CmdKey{global: &global}
|
||||
OK(t, cmd.Execute([]string{"list"}))
|
||||
|
||||
scanner := bufio.NewScanner(&buf)
|
||||
scanner := bufio.NewScanner(buf)
|
||||
exp := regexp.MustCompile(`^ ([a-f0-9]+) `)
|
||||
|
||||
IDs := []string{}
|
||||
@ -529,21 +569,28 @@ func cmdKeyListOtherIDs(t testing.TB, global GlobalOptions) []string {
|
||||
return IDs
|
||||
}
|
||||
|
||||
func cmdKeyAddNewKey(t testing.TB, global GlobalOptions, newPassword string) {
|
||||
cmd := &CmdKey{global: &global, newPassword: newPassword}
|
||||
OK(t, cmd.Execute([]string{"add"}))
|
||||
func testRunKeyAddNewKey(t testing.TB, newPassword string, gopts GlobalOptions) {
|
||||
testKeyNewPassword = newPassword
|
||||
defer func() {
|
||||
testKeyNewPassword = ""
|
||||
}()
|
||||
|
||||
OK(t, runKey(gopts, []string{"add"}))
|
||||
}
|
||||
|
||||
func cmdKeyPasswd(t testing.TB, global GlobalOptions, newPassword string) {
|
||||
cmd := &CmdKey{global: &global, newPassword: newPassword}
|
||||
OK(t, cmd.Execute([]string{"passwd"}))
|
||||
func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) {
|
||||
testKeyNewPassword = newPassword
|
||||
defer func() {
|
||||
testKeyNewPassword = ""
|
||||
}()
|
||||
|
||||
OK(t, runKey(gopts, []string{"passwd"}))
|
||||
}
|
||||
|
||||
func cmdKeyRemove(t testing.TB, global GlobalOptions, IDs []string) {
|
||||
cmd := &CmdKey{global: &global}
|
||||
func testRunKeyRemove(t testing.TB, gopts GlobalOptions, IDs []string) {
|
||||
t.Logf("remove %d keys: %q\n", len(IDs), IDs)
|
||||
for _, id := range IDs {
|
||||
OK(t, cmd.Execute([]string{"rm", id}))
|
||||
OK(t, runKey(gopts, []string{"rm", id}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -553,25 +600,24 @@ func TestKeyAddRemove(t *testing.T) {
|
||||
"raicneirvOjEfEigonOmLasOd",
|
||||
}
|
||||
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
cmdInit(t, global)
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
testRunInit(t, gopts)
|
||||
|
||||
cmdKeyPasswd(t, global, "geheim2")
|
||||
global.password = "geheim2"
|
||||
t.Logf("changed password to %q", global.password)
|
||||
testRunKeyPasswd(t, "geheim2", gopts)
|
||||
gopts.password = "geheim2"
|
||||
t.Logf("changed password to %q", gopts.password)
|
||||
|
||||
for _, newPassword := range passwordList {
|
||||
cmdKeyAddNewKey(t, global, newPassword)
|
||||
testRunKeyAddNewKey(t, newPassword, gopts)
|
||||
t.Logf("added new password %q", newPassword)
|
||||
global.password = newPassword
|
||||
cmdKeyRemove(t, global, cmdKeyListOtherIDs(t, global))
|
||||
gopts.password = newPassword
|
||||
testRunKeyRemove(t, gopts, testRunKeyListOtherIDs(t, gopts))
|
||||
}
|
||||
|
||||
global.password = passwordList[len(passwordList)-1]
|
||||
t.Logf("testing access with last password %q\n", global.password)
|
||||
cmdKey(t, global, "list")
|
||||
|
||||
cmdCheck(t, global)
|
||||
gopts.password = passwordList[len(passwordList)-1]
|
||||
t.Logf("testing access with last password %q\n", gopts.password)
|
||||
OK(t, runKey(gopts, []string{"list"}))
|
||||
testRunCheck(t, gopts)
|
||||
})
|
||||
}
|
||||
|
||||
@ -599,8 +645,8 @@ func TestRestoreFilter(t *testing.T) {
|
||||
{"subdir1/subdir2/testfile4.c", 102},
|
||||
}
|
||||
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
cmdInit(t, global)
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
testRunInit(t, gopts)
|
||||
|
||||
for _, test := range testfiles {
|
||||
p := filepath.Join(env.testdata, test.name)
|
||||
@ -608,20 +654,22 @@ func TestRestoreFilter(t *testing.T) {
|
||||
OK(t, appendRandomData(p, test.size))
|
||||
}
|
||||
|
||||
cmdBackup(t, global, []string{env.testdata}, nil)
|
||||
cmdCheck(t, global)
|
||||
opts := BackupOptions{}
|
||||
|
||||
snapshotID := cmdList(t, global, "snapshots")[0]
|
||||
testRunBackup(t, []string{env.testdata}, opts, gopts)
|
||||
testRunCheck(t, gopts)
|
||||
|
||||
snapshotID := testRunList(t, "snapshots", gopts)[0]
|
||||
|
||||
// no restore filter should restore all files
|
||||
cmdRestore(t, global, filepath.Join(env.base, "restore0"), snapshotID)
|
||||
testRunRestore(t, gopts, filepath.Join(env.base, "restore0"), snapshotID)
|
||||
for _, test := range testfiles {
|
||||
OK(t, testFileSize(filepath.Join(env.base, "restore0", "testdata", test.name), int64(test.size)))
|
||||
}
|
||||
|
||||
for i, pat := range []string{"*.c", "*.exe", "*", "*file3*"} {
|
||||
base := filepath.Join(env.base, fmt.Sprintf("restore%d", i+1))
|
||||
cmdRestoreExcludes(t, global, base, snapshotID, []string{pat})
|
||||
testRunRestoreExcludes(t, gopts, base, snapshotID, []string{pat})
|
||||
for _, test := range testfiles {
|
||||
err := testFileSize(filepath.Join(base, "testdata", test.name), int64(test.size))
|
||||
if ok, _ := filter.Match(pat, filepath.Base(test.name)); !ok {
|
||||
@ -638,49 +686,51 @@ func TestRestoreFilter(t *testing.T) {
|
||||
|
||||
func TestRestoreLatest(t *testing.T) {
|
||||
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
cmdInit(t, global)
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
testRunInit(t, gopts)
|
||||
|
||||
p := filepath.Join(env.testdata, "testfile.c")
|
||||
OK(t, os.MkdirAll(filepath.Dir(p), 0755))
|
||||
OK(t, appendRandomData(p, 100))
|
||||
|
||||
cmdBackup(t, global, []string{env.testdata}, nil)
|
||||
cmdCheck(t, global)
|
||||
opts := BackupOptions{}
|
||||
|
||||
testRunBackup(t, []string{env.testdata}, opts, gopts)
|
||||
testRunCheck(t, gopts)
|
||||
|
||||
os.Remove(p)
|
||||
OK(t, appendRandomData(p, 101))
|
||||
cmdBackup(t, global, []string{env.testdata}, nil)
|
||||
cmdCheck(t, global)
|
||||
testRunBackup(t, []string{env.testdata}, opts, gopts)
|
||||
testRunCheck(t, gopts)
|
||||
|
||||
// Restore latest without any filters
|
||||
cmdRestoreLatest(t, global, filepath.Join(env.base, "restore0"), nil, "")
|
||||
testRunRestoreLatest(t, gopts, filepath.Join(env.base, "restore0"), nil, "")
|
||||
OK(t, testFileSize(filepath.Join(env.base, "restore0", "testdata", "testfile.c"), int64(101)))
|
||||
|
||||
// Setup test files in different directories backed up in different snapshots
|
||||
p1 := filepath.Join(env.testdata, "p1/testfile.c")
|
||||
OK(t, os.MkdirAll(filepath.Dir(p1), 0755))
|
||||
OK(t, appendRandomData(p1, 102))
|
||||
cmdBackup(t, global, []string{filepath.Dir(p1)}, nil)
|
||||
cmdCheck(t, global)
|
||||
testRunBackup(t, []string{filepath.Dir(p1)}, opts, gopts)
|
||||
testRunCheck(t, gopts)
|
||||
|
||||
p2 := filepath.Join(env.testdata, "p2/testfile.c")
|
||||
OK(t, os.MkdirAll(filepath.Dir(p2), 0755))
|
||||
OK(t, appendRandomData(p2, 103))
|
||||
cmdBackup(t, global, []string{filepath.Dir(p2)}, nil)
|
||||
cmdCheck(t, global)
|
||||
testRunBackup(t, []string{filepath.Dir(p2)}, opts, gopts)
|
||||
testRunCheck(t, gopts)
|
||||
|
||||
p1rAbs := filepath.Join(env.base, "restore1", "p1/testfile.c")
|
||||
p2rAbs := filepath.Join(env.base, "restore2", "p2/testfile.c")
|
||||
|
||||
cmdRestoreLatest(t, global, filepath.Join(env.base, "restore1"), []string{filepath.Dir(p1)}, "")
|
||||
testRunRestoreLatest(t, gopts, filepath.Join(env.base, "restore1"), []string{filepath.Dir(p1)}, "")
|
||||
OK(t, testFileSize(p1rAbs, int64(102)))
|
||||
if _, err := os.Stat(p2rAbs); os.IsNotExist(errors.Cause(err)) {
|
||||
Assert(t, os.IsNotExist(errors.Cause(err)),
|
||||
"expected %v to not exist in restore, but it exists, err %v", p2rAbs, err)
|
||||
}
|
||||
|
||||
cmdRestoreLatest(t, global, filepath.Join(env.base, "restore2"), []string{filepath.Dir(p2)}, "")
|
||||
testRunRestoreLatest(t, gopts, filepath.Join(env.base, "restore2"), []string{filepath.Dir(p2)}, "")
|
||||
OK(t, testFileSize(p2rAbs, int64(103)))
|
||||
if _, err := os.Stat(p1rAbs); os.IsNotExist(errors.Cause(err)) {
|
||||
Assert(t, os.IsNotExist(errors.Cause(err)),
|
||||
@ -691,20 +741,24 @@ func TestRestoreLatest(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRestoreWithPermissionFailure(t *testing.T) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
datafile := filepath.Join("testdata", "repo-restore-permissions-test.tar.gz")
|
||||
SetupTarTestFixture(t, env.base, datafile)
|
||||
|
||||
snapshots := cmdList(t, global, "snapshots")
|
||||
snapshots := testRunList(t, "snapshots", gopts)
|
||||
Assert(t, len(snapshots) > 0,
|
||||
"no snapshots found in repo (%v)", datafile)
|
||||
|
||||
global.stderr = ioutil.Discard
|
||||
cmdRestore(t, global, filepath.Join(env.base, "restore"), snapshots[0])
|
||||
globalOptions.stderr = ioutil.Discard
|
||||
defer func() {
|
||||
globalOptions.stderr = os.Stderr
|
||||
}()
|
||||
|
||||
testRunRestore(t, gopts, filepath.Join(env.base, "restore"), snapshots[0])
|
||||
|
||||
// make sure that all files have been restored, regardeless of any
|
||||
// permission errors
|
||||
files := cmdLs(t, global, snapshots[0].String())
|
||||
files := testRunLs(t, gopts, snapshots[0].String())
|
||||
for _, filename := range files {
|
||||
fi, err := os.Lstat(filepath.Join(env.base, "restore", filename))
|
||||
OK(t, err)
|
||||
@ -725,23 +779,25 @@ func setZeroModTime(filename string) error {
|
||||
}
|
||||
|
||||
func TestRestoreNoMetadataOnIgnoredIntermediateDirs(t *testing.T) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
cmdInit(t, global)
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
testRunInit(t, gopts)
|
||||
|
||||
p := filepath.Join(env.testdata, "subdir1", "subdir2", "subdir3", "file.ext")
|
||||
OK(t, os.MkdirAll(filepath.Dir(p), 0755))
|
||||
OK(t, appendRandomData(p, 200))
|
||||
OK(t, setZeroModTime(filepath.Join(env.testdata, "subdir1", "subdir2")))
|
||||
|
||||
cmdBackup(t, global, []string{env.testdata}, nil)
|
||||
cmdCheck(t, global)
|
||||
opts := BackupOptions{}
|
||||
|
||||
snapshotID := cmdList(t, global, "snapshots")[0]
|
||||
testRunBackup(t, []string{env.testdata}, opts, gopts)
|
||||
testRunCheck(t, gopts)
|
||||
|
||||
snapshotID := testRunList(t, "snapshots", gopts)[0]
|
||||
|
||||
// restore with filter "*.ext", this should restore "file.ext", but
|
||||
// since the directories are ignored and only created because of
|
||||
// "file.ext", no meta data should be restored for them.
|
||||
cmdRestoreIncludes(t, global, filepath.Join(env.base, "restore0"), snapshotID, []string{"*.ext"})
|
||||
testRunRestoreIncludes(t, gopts, filepath.Join(env.base, "restore0"), snapshotID, []string{"*.ext"})
|
||||
|
||||
f1 := filepath.Join(env.base, "restore0", "testdata", "subdir1", "subdir2")
|
||||
fi, err := os.Stat(f1)
|
||||
@ -751,7 +807,7 @@ func TestRestoreNoMetadataOnIgnoredIntermediateDirs(t *testing.T) {
|
||||
"meta data of intermediate directory has been restore although it was ignored")
|
||||
|
||||
// restore with filter "*", this should restore meta data on everything.
|
||||
cmdRestoreIncludes(t, global, filepath.Join(env.base, "restore1"), snapshotID, []string{"*"})
|
||||
testRunRestoreIncludes(t, gopts, filepath.Join(env.base, "restore1"), snapshotID, []string{"*"})
|
||||
|
||||
f2 := filepath.Join(env.base, "restore1", "testdata", "subdir1", "subdir2")
|
||||
fi, err = os.Stat(f2)
|
||||
@ -763,44 +819,55 @@ func TestRestoreNoMetadataOnIgnoredIntermediateDirs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFind(t *testing.T) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
datafile := filepath.Join("testdata", "backup-data.tar.gz")
|
||||
cmdInit(t, global)
|
||||
testRunInit(t, gopts)
|
||||
SetupTarTestFixture(t, env.testdata, datafile)
|
||||
cmdBackup(t, global, []string{env.testdata}, nil)
|
||||
cmdCheck(t, global)
|
||||
|
||||
results := cmdFind(t, global, "unexistingfile")
|
||||
opts := BackupOptions{}
|
||||
|
||||
testRunBackup(t, []string{env.testdata}, opts, gopts)
|
||||
testRunCheck(t, gopts)
|
||||
|
||||
results := testRunFind(t, gopts, "unexistingfile")
|
||||
Assert(t, len(results) != 0, "unexisting file found in repo (%v)", datafile)
|
||||
|
||||
results = cmdFind(t, global, "testfile")
|
||||
results = testRunFind(t, gopts, "testfile")
|
||||
Assert(t, len(results) != 1, "file not found in repo (%v)", datafile)
|
||||
|
||||
results = cmdFind(t, global, "test")
|
||||
results = testRunFind(t, gopts, "test")
|
||||
Assert(t, len(results) < 2, "less than two file found in repo (%v)", datafile)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRebuildIndex(t *testing.T) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
datafile := filepath.Join("..", "..", "restic", "checker", "testdata", "duplicate-packs-in-index-test-repo.tar.gz")
|
||||
SetupTarTestFixture(t, env.base, datafile)
|
||||
|
||||
out := cmdCheckOutput(t, global)
|
||||
out, err := testRunCheckOutput(gopts)
|
||||
if !strings.Contains(out, "contained in several indexes") {
|
||||
t.Fatalf("did not find checker hint for packs in several indexes")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error from checker for test repository, got %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(out, "restic rebuild-index") {
|
||||
t.Fatalf("did not find hint for rebuild-index comman")
|
||||
}
|
||||
|
||||
cmdRebuildIndex(t, global)
|
||||
testRunRebuildIndex(t, gopts)
|
||||
|
||||
out = cmdCheckOutput(t, global)
|
||||
out, err = testRunCheckOutput(gopts)
|
||||
if len(out) != 0 {
|
||||
t.Fatalf("expected no output from the checker, got: %v", out)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error from checker after rebuild-index, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -810,7 +877,7 @@ func TestRebuildIndexAlwaysFull(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCheckRestoreNoLock(t *testing.T) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
datafile := filepath.Join("testdata", "small-repo.tar.gz")
|
||||
SetupTarTestFixture(t, env.base, datafile)
|
||||
|
||||
@ -822,14 +889,15 @@ func TestCheckRestoreNoLock(t *testing.T) {
|
||||
})
|
||||
OK(t, err)
|
||||
|
||||
global.NoLock = true
|
||||
cmdCheck(t, global)
|
||||
gopts.NoLock = true
|
||||
|
||||
snapshotIDs := cmdList(t, global, "snapshots")
|
||||
testRunCheck(t, gopts)
|
||||
|
||||
snapshotIDs := testRunList(t, "snapshots", gopts)
|
||||
if len(snapshotIDs) == 0 {
|
||||
t.Fatalf("found no snapshots")
|
||||
}
|
||||
|
||||
cmdRestore(t, global, filepath.Join(env.base, "restore"), snapshotIDs[0])
|
||||
testRunRestore(t, gopts, filepath.Join(env.base, "restore"), snapshotIDs[0])
|
||||
})
|
||||
}
|
||||
|
@ -7,11 +7,24 @@ import (
|
||||
"restic/debug"
|
||||
"runtime"
|
||||
|
||||
"restic/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
"restic/errors"
|
||||
)
|
||||
|
||||
// cmdRoot is the base command when no other command has been specified.
|
||||
var cmdRoot = &cobra.Command{
|
||||
Use: "restic",
|
||||
Short: "backup and restore files",
|
||||
Long: `
|
||||
restic is a backup program which allows saving multiple revisions of files and
|
||||
directories in an encrypted repository stored on different backends.
|
||||
`,
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
PersistentPreRun: parseEnvironment,
|
||||
}
|
||||
|
||||
func init() {
|
||||
// set GOMAXPROCS to number of CPUs
|
||||
if runtime.Version() < "go1.5" {
|
||||
@ -21,23 +34,11 @@ func init() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
// defer profile.Start(profile.MemProfileRate(100000), profile.ProfilePath(".")).Stop()
|
||||
// defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop()
|
||||
globalOpts.Repo = os.Getenv("RESTIC_REPOSITORY")
|
||||
|
||||
debug.Log("restic", "main %#v", os.Args)
|
||||
|
||||
_, err := parser.Parse()
|
||||
if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp {
|
||||
parser.WriteHelp(os.Stdout)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
debug.Log("main", "command returned error: %#v", err)
|
||||
err := cmdRoot.Execute()
|
||||
|
||||
switch {
|
||||
case restic.IsAlreadyLocked(errors.Cause(err)):
|
||||
|
46
src/cmds/restic/table.go
Normal file
46
src/cmds/restic/table.go
Normal file
@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Table contains data for a table to be printed.
|
||||
type Table struct {
|
||||
Header string
|
||||
Rows [][]interface{}
|
||||
|
||||
RowFormat string
|
||||
}
|
||||
|
||||
// NewTable initializes a new Table.
|
||||
func NewTable() Table {
|
||||
return Table{
|
||||
Rows: [][]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// Write prints the table to w.
|
||||
func (t Table) Write(w io.Writer) error {
|
||||
_, err := fmt.Fprintln(w, t.Header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintln(w, strings.Repeat("-", 70))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, row := range t.Rows {
|
||||
_, err = fmt.Fprintf(w, t.RowFormat+"\n", row...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TimeFormat is the format used for all timestamps printed by restic.
|
||||
const TimeFormat = "2006-01-02 15:04:05"
|
@ -27,9 +27,9 @@ func IsFatal(err error) bool {
|
||||
return ok && e.Fatal()
|
||||
}
|
||||
|
||||
// Fatal returns an error which implements the Fataler interface.
|
||||
// Fatal returns a wrapped error which implements the Fataler interface.
|
||||
func Fatal(s string) error {
|
||||
return fatalError(s)
|
||||
return Wrap(fatalError(s), "Fatal")
|
||||
}
|
||||
|
||||
// Fatalf returns an error which implements the Fataler interface.
|
||||
|
@ -524,7 +524,7 @@ func DecodeOldIndex(rd io.Reader) (idx *Index, err error) {
|
||||
|
||||
// LoadIndexWithDecoder loads the index and decodes it with fn.
|
||||
func LoadIndexWithDecoder(repo restic.Repository, id restic.ID, fn func(io.Reader) (*Index, error)) (idx *Index, err error) {
|
||||
debug.Log("LoadIndexWithDecoder", "Loading index %v", id[:8])
|
||||
debug.Log("LoadIndexWithDecoder", "Loading index %v", id.Str())
|
||||
|
||||
buf, err := repo.LoadAndDecrypt(restic.IndexFile, id)
|
||||
if err != nil {
|
||||
|
@ -122,7 +122,7 @@ nextTag:
|
||||
|
||||
// SamePaths compares the Snapshot's paths and provided paths are exactly the same
|
||||
func SamePaths(expected, actual []string) bool {
|
||||
if expected == nil || actual == nil {
|
||||
if len(expected) == 0 || len(actual) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ func (e ExpirePolicy) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
empty := ExpirePolicy{}
|
||||
empty := ExpirePolicy{Tags: e.Tags}
|
||||
return reflect.DeepEqual(e, empty)
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ func OKs(tb testing.TB, errs []error) {
|
||||
if err != nil {
|
||||
errFound = true
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error())
|
||||
fmt.Printf("\033[31m%s:%d: unexpected error: %+v\033[39m\n\n", filepath.Base(file), line, err.Error())
|
||||
}
|
||||
}
|
||||
if errFound {
|
||||
|
18
vendor/manifest
vendored
18
vendor/manifest
vendored
@ -14,9 +14,9 @@
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/jessevdk/go-flags",
|
||||
"repository": "https://github.com/jessevdk/go-flags",
|
||||
"revision": "4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa",
|
||||
"importpath": "github.com/inconshreveable/mousetrap",
|
||||
"repository": "https://github.com/inconshreveable/mousetrap",
|
||||
"revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
@ -49,6 +49,18 @@
|
||||
"revision": "49e9b5212b022a1ab373faf981ed4f2fc807502a",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/spf13/cobra",
|
||||
"repository": "https://github.com/spf13/cobra",
|
||||
"revision": "9c28e4bbd74e5c3ed7aacbc552b2cab7cfdfe744",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/spf13/pflag",
|
||||
"repository": "https://github.com/spf13/pflag",
|
||||
"revision": "c7e63cf4530bcd3ba943729cee0efeff2ebea63f",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "golang.org/x/crypto/curve25519",
|
||||
"repository": "https://go.googlesource.com/crypto",
|
||||
|
13
vendor/src/github.com/inconshreveable/mousetrap/LICENSE
vendored
Normal file
13
vendor/src/github.com/inconshreveable/mousetrap/LICENSE
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
Copyright 2014 Alan Shreve
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
23
vendor/src/github.com/inconshreveable/mousetrap/README.md
vendored
Normal file
23
vendor/src/github.com/inconshreveable/mousetrap/README.md
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# mousetrap
|
||||
|
||||
mousetrap is a tiny library that answers a single question.
|
||||
|
||||
On a Windows machine, was the process invoked by someone double clicking on
|
||||
the executable file while browsing in explorer?
|
||||
|
||||
### Motivation
|
||||
|
||||
Windows developers unfamiliar with command line tools will often "double-click"
|
||||
the executable for a tool. Because most CLI tools print the help and then exit
|
||||
when invoked without arguments, this is often very frustrating for those users.
|
||||
|
||||
mousetrap provides a way to detect these invocations so that you can provide
|
||||
more helpful behavior and instructions on how to run the CLI tool. To see what
|
||||
this looks like, both from an organizational and a technical perspective, see
|
||||
https://inconshreveable.com/09-09-2014/sweat-the-small-stuff/
|
||||
|
||||
### The interface
|
||||
|
||||
The library exposes a single interface:
|
||||
|
||||
func StartedByExplorer() (bool)
|
15
vendor/src/github.com/inconshreveable/mousetrap/trap_others.go
vendored
Normal file
15
vendor/src/github.com/inconshreveable/mousetrap/trap_others.go
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// +build !windows
|
||||
|
||||
package mousetrap
|
||||
|
||||
// StartedByExplorer returns true if the program was invoked by the user
|
||||
// double-clicking on the executable from explorer.exe
|
||||
//
|
||||
// It is conservative and returns false if any of the internal calls fail.
|
||||
// It does not guarantee that the program was run from a terminal. It only can tell you
|
||||
// whether it was launched from explorer.exe
|
||||
//
|
||||
// On non-Windows platforms, it always returns false.
|
||||
func StartedByExplorer() bool {
|
||||
return false
|
||||
}
|
98
vendor/src/github.com/inconshreveable/mousetrap/trap_windows.go
vendored
Normal file
98
vendor/src/github.com/inconshreveable/mousetrap/trap_windows.go
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
// +build windows
|
||||
// +build !go1.4
|
||||
|
||||
package mousetrap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// defined by the Win32 API
|
||||
th32cs_snapprocess uintptr = 0x2
|
||||
)
|
||||
|
||||
var (
|
||||
kernel = syscall.MustLoadDLL("kernel32.dll")
|
||||
CreateToolhelp32Snapshot = kernel.MustFindProc("CreateToolhelp32Snapshot")
|
||||
Process32First = kernel.MustFindProc("Process32FirstW")
|
||||
Process32Next = kernel.MustFindProc("Process32NextW")
|
||||
)
|
||||
|
||||
// ProcessEntry32 structure defined by the Win32 API
|
||||
type processEntry32 struct {
|
||||
dwSize uint32
|
||||
cntUsage uint32
|
||||
th32ProcessID uint32
|
||||
th32DefaultHeapID int
|
||||
th32ModuleID uint32
|
||||
cntThreads uint32
|
||||
th32ParentProcessID uint32
|
||||
pcPriClassBase int32
|
||||
dwFlags uint32
|
||||
szExeFile [syscall.MAX_PATH]uint16
|
||||
}
|
||||
|
||||
func getProcessEntry(pid int) (pe *processEntry32, err error) {
|
||||
snapshot, _, e1 := CreateToolhelp32Snapshot.Call(th32cs_snapprocess, uintptr(0))
|
||||
if snapshot == uintptr(syscall.InvalidHandle) {
|
||||
err = fmt.Errorf("CreateToolhelp32Snapshot: %v", e1)
|
||||
return
|
||||
}
|
||||
defer syscall.CloseHandle(syscall.Handle(snapshot))
|
||||
|
||||
var processEntry processEntry32
|
||||
processEntry.dwSize = uint32(unsafe.Sizeof(processEntry))
|
||||
ok, _, e1 := Process32First.Call(snapshot, uintptr(unsafe.Pointer(&processEntry)))
|
||||
if ok == 0 {
|
||||
err = fmt.Errorf("Process32First: %v", e1)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
if processEntry.th32ProcessID == uint32(pid) {
|
||||
pe = &processEntry
|
||||
return
|
||||
}
|
||||
|
||||
ok, _, e1 = Process32Next.Call(snapshot, uintptr(unsafe.Pointer(&processEntry)))
|
||||
if ok == 0 {
|
||||
err = fmt.Errorf("Process32Next: %v", e1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getppid() (pid int, err error) {
|
||||
pe, err := getProcessEntry(os.Getpid())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pid = int(pe.th32ParentProcessID)
|
||||
return
|
||||
}
|
||||
|
||||
// StartedByExplorer returns true if the program was invoked by the user double-clicking
|
||||
// on the executable from explorer.exe
|
||||
//
|
||||
// It is conservative and returns false if any of the internal calls fail.
|
||||
// It does not guarantee that the program was run from a terminal. It only can tell you
|
||||
// whether it was launched from explorer.exe
|
||||
func StartedByExplorer() bool {
|
||||
ppid, err := getppid()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
pe, err := getProcessEntry(ppid)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
name := syscall.UTF16ToString(pe.szExeFile[:])
|
||||
return name == "explorer.exe"
|
||||
}
|
46
vendor/src/github.com/inconshreveable/mousetrap/trap_windows_1.4.go
vendored
Normal file
46
vendor/src/github.com/inconshreveable/mousetrap/trap_windows_1.4.go
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
// +build windows
|
||||
// +build go1.4
|
||||
|
||||
package mousetrap
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func getProcessEntry(pid int) (*syscall.ProcessEntry32, error) {
|
||||
snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer syscall.CloseHandle(snapshot)
|
||||
var procEntry syscall.ProcessEntry32
|
||||
procEntry.Size = uint32(unsafe.Sizeof(procEntry))
|
||||
if err = syscall.Process32First(snapshot, &procEntry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
if procEntry.ProcessID == uint32(pid) {
|
||||
return &procEntry, nil
|
||||
}
|
||||
err = syscall.Process32Next(snapshot, &procEntry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StartedByExplorer returns true if the program was invoked by the user double-clicking
|
||||
// on the executable from explorer.exe
|
||||
//
|
||||
// It is conservative and returns false if any of the internal calls fail.
|
||||
// It does not guarantee that the program was run from a terminal. It only can tell you
|
||||
// whether it was launched from explorer.exe
|
||||
func StartedByExplorer() bool {
|
||||
pe, err := getProcessEntry(os.Getppid())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return "explorer.exe" == syscall.UTF16ToString(pe.ExeFile[:])
|
||||
}
|
135
vendor/src/github.com/jessevdk/go-flags/README.md
vendored
135
vendor/src/github.com/jessevdk/go-flags/README.md
vendored
@ -1,135 +0,0 @@
|
||||
go-flags: a go library for parsing command line arguments
|
||||
=========================================================
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/jessevdk/go-flags?status.png)](https://godoc.org/github.com/jessevdk/go-flags) [![Build Status](https://travis-ci.org/jessevdk/go-flags.svg?branch=master)](https://travis-ci.org/jessevdk/go-flags) [![Coverage Status](https://img.shields.io/coveralls/jessevdk/go-flags.svg)](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:
|
||||
|
||||
```go
|
||||
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:
|
||||
--------
|
||||
```go
|
||||
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>
|
27
vendor/src/github.com/jessevdk/go-flags/arg.go
vendored
27
vendor/src/github.com/jessevdk/go-flags/arg.go
vendored
@ -1,27 +0,0 @@
|
||||
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
|
||||
|
||||
// The minimal number of required positional arguments
|
||||
Required int
|
||||
|
||||
// The maximum number of required positional arguments
|
||||
RequiredMaximum int
|
||||
|
||||
value reflect.Value
|
||||
tag multiTag
|
||||
}
|
||||
|
||||
func (a *Arg) isRemaining() bool {
|
||||
return a.value.Type().Kind() == reflect.Slice
|
||||
}
|
163
vendor/src/github.com/jessevdk/go-flags/arg_test.go
vendored
163
vendor/src/github.com/jessevdk/go-flags/arg_test.go
vendored
@ -1,163 +0,0 @@
|
||||
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")
|
||||
}
|
||||
|
||||
func TestPositionalRequiredRest1Fail(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Rest []string `required:"yes"`
|
||||
} `positional-args:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{})
|
||||
|
||||
assertError(t, err, ErrRequired, "the required argument `Rest (at least 1 argument)` was not provided")
|
||||
}
|
||||
|
||||
func TestPositionalRequiredRest1Pass(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Rest []string `required:"yes"`
|
||||
} `positional-args:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{"rest1"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(opts.Positional.Rest) != 1 {
|
||||
t.Fatalf("Expected 1 positional rest argument")
|
||||
}
|
||||
|
||||
assertString(t, opts.Positional.Rest[0], "rest1")
|
||||
}
|
||||
|
||||
func TestPositionalRequiredRest2Fail(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Rest []string `required:"2"`
|
||||
} `positional-args:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{"rest1"})
|
||||
|
||||
assertError(t, err, ErrRequired, "the required argument `Rest (at least 2 arguments, but got only 1)` was not provided")
|
||||
}
|
||||
|
||||
func TestPositionalRequiredRest2Pass(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Rest []string `required:"2"`
|
||||
} `positional-args:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{"rest1", "rest2", "rest3"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(opts.Positional.Rest) != 3 {
|
||||
t.Fatalf("Expected 3 positional rest argument")
|
||||
}
|
||||
|
||||
assertString(t, opts.Positional.Rest[0], "rest1")
|
||||
assertString(t, opts.Positional.Rest[1], "rest2")
|
||||
assertString(t, opts.Positional.Rest[2], "rest3")
|
||||
}
|
||||
|
||||
func TestPositionalRequiredRestRangeFail(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Rest []string `required:"1-2"`
|
||||
} `positional-args:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{"rest1", "rest2", "rest3"})
|
||||
|
||||
assertError(t, err, ErrRequired, "the required argument `Rest (at most 2 arguments, but got 3)` was not provided")
|
||||
}
|
||||
|
||||
func TestPositionalRequiredRestRangeEmptyFail(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Rest []string `required:"0-0"`
|
||||
} `positional-args:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{"some", "thing"})
|
||||
|
||||
assertError(t, err, ErrRequired, "the required argument `Rest (zero arguments)` was not provided")
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
#!/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
|
@ -1,59 +0,0 @@
|
||||
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
|
||||
}
|
455
vendor/src/github.com/jessevdk/go-flags/command.go
vendored
455
vendor/src/github.com/jessevdk/go-flags/command.go
vendored
@ -1,455 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
type lookup struct {
|
||||
shortNames map[string]*Option
|
||||
longNames map[string]*Option
|
||||
|
||||
commands map[string]*Command
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// FindOptionByLongName finds an option that is part of the command, or any of
|
||||
// its parent commands, by matching its long name (including the option
|
||||
// namespace).
|
||||
func (c *Command) FindOptionByLongName(longName string) (option *Option) {
|
||||
for option == nil && c != nil {
|
||||
option = c.Group.FindOptionByLongName(longName)
|
||||
|
||||
c, _ = c.parent.(*Command)
|
||||
}
|
||||
|
||||
return option
|
||||
}
|
||||
|
||||
// FindOptionByShortName finds an option that is part of the command, or any of
|
||||
// its parent commands, by matching its long name (including the option
|
||||
// namespace).
|
||||
func (c *Command) FindOptionByShortName(shortName rune) (option *Option) {
|
||||
for option == nil && c != nil {
|
||||
option = c.Group.FindOptionByShortName(shortName)
|
||||
|
||||
c, _ = c.parent.(*Command)
|
||||
}
|
||||
|
||||
return option
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
required := -1
|
||||
requiredMaximum := -1
|
||||
|
||||
sreq := m.Get("required")
|
||||
|
||||
if sreq != "" {
|
||||
required = 1
|
||||
|
||||
rng := strings.SplitN(sreq, "-", 2)
|
||||
|
||||
if len(rng) > 1 {
|
||||
if preq, err := strconv.ParseInt(rng[0], 10, 32); err == nil {
|
||||
required = int(preq)
|
||||
}
|
||||
|
||||
if preq, err := strconv.ParseInt(rng[1], 10, 32); err == nil {
|
||||
requiredMaximum = int(preq)
|
||||
}
|
||||
} else {
|
||||
if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil {
|
||||
required = int(preq)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arg := &Arg{
|
||||
Name: name,
|
||||
Description: m.Get("description"),
|
||||
Required: required,
|
||||
RequiredMaximum: requiredMaximum,
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
subc.Hidden = mtag.Get("hidden") != ""
|
||||
|
||||
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) eachOption(f func(*Command, *Group, *Option)) {
|
||||
c.eachCommand(func(c *Command) {
|
||||
c.eachGroup(func(g *Group) {
|
||||
for _, option := range g.options {
|
||||
f(c, g, option)
|
||||
}
|
||||
})
|
||||
}, true)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
var parents []*Command
|
||||
|
||||
for parent != nil {
|
||||
if cmd, ok := parent.(*Command); ok {
|
||||
parents = append(parents, cmd)
|
||||
parent = cmd.parent
|
||||
} else {
|
||||
parent = nil
|
||||
}
|
||||
}
|
||||
|
||||
for i := len(parents) - 1; i >= 0; i-- {
|
||||
parents[i].fillLookup(&ret, true)
|
||||
}
|
||||
|
||||
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) sortedVisibleCommands() []*Command {
|
||||
ret := commandList(c.visibleCommands())
|
||||
sort.Sort(ret)
|
||||
|
||||
return []*Command(ret)
|
||||
}
|
||||
|
||||
func (c *Command) visibleCommands() []*Command {
|
||||
ret := make([]*Command, 0, len(c.commands))
|
||||
|
||||
for _, cmd := range c.commands {
|
||||
if !cmd.Hidden {
|
||||
ret = append(ret, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
return 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
|
||||
}
|
@ -1,582 +0,0 @@
|
||||
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 TestCommandFlagOrderSub(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
|
||||
SubCommand struct {
|
||||
B bool `short:"b"`
|
||||
} `command:"sub"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cmd", "sub", "-v", "-g", "-b")
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Command.G {
|
||||
t.Errorf("Expected Command.G to be true")
|
||||
}
|
||||
|
||||
if !opts.Command.SubCommand.B {
|
||||
t.Errorf("Expected Command.SubCommand.B 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 TestCommandFlagOverrideSub(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
SubCommand struct {
|
||||
Value bool `short:"v"`
|
||||
} `command:"sub"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cmd", "sub", "-v")
|
||||
|
||||
if opts.Value {
|
||||
t.Errorf("Expected Value to be false")
|
||||
}
|
||||
|
||||
if opts.Command.Value {
|
||||
t.Errorf("Expected Command.Value to be false")
|
||||
}
|
||||
|
||||
if !opts.Command.SubCommand.Value {
|
||||
t.Errorf("Expected Command.Value to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandFlagOverrideSub2(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
SubCommand struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"sub"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cmd", "sub", "-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 string `short:"g" default:"value"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cmd")
|
||||
|
||||
if opts.Command.G != "value" {
|
||||
t.Errorf("Expected G to be \"value\"")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAfterNonCommand(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Cmd1 struct {
|
||||
} `command:"remove"`
|
||||
|
||||
Cmd2 struct {
|
||||
} `command:"add"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrUnknownCommand, "Unknown command `nocmd'. Please specify one command of: add or remove", &opts, "nocmd", "remove")
|
||||
}
|
||||
|
||||
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 TestSubcommandsOptionalAfterNonCommand(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
|
||||
|
||||
retargs, err := p.ParseArgs([]string{"nocmd", "remove"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
assertStringArray(t, retargs, []string{"nocmd", "remove"})
|
||||
}
|
||||
|
||||
func TestCommandAlias(t *testing.T) {
|
||||
var opts = struct {
|
||||
Command struct {
|
||||
G string `short:"g" default:"value"`
|
||||
} `command:"cmd" alias:"cm"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cm")
|
||||
|
||||
if opts.Command.G != "value" {
|
||||
t.Errorf("Expected G to be \"value\"")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubCommandFindOptionByLongFlag(t *testing.T) {
|
||||
var opts struct {
|
||||
Testing bool `long:"testing" description:"Testing"`
|
||||
}
|
||||
|
||||
var cmd struct {
|
||||
Other bool `long:"other" description:"Other"`
|
||||
}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
c, _ := p.AddCommand("command", "Short", "Long", &cmd)
|
||||
|
||||
opt := c.FindOptionByLongName("other")
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
assertString(t, opt.LongName, "other")
|
||||
|
||||
opt = c.FindOptionByLongName("testing")
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
assertString(t, opt.LongName, "testing")
|
||||
}
|
||||
|
||||
func TestSubCommandFindOptionByShortFlag(t *testing.T) {
|
||||
var opts struct {
|
||||
Testing bool `short:"t" description:"Testing"`
|
||||
}
|
||||
|
||||
var cmd struct {
|
||||
Other bool `short:"o" description:"Other"`
|
||||
}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
c, _ := p.AddCommand("command", "Short", "Long", &cmd)
|
||||
|
||||
opt := c.FindOptionByShortName('o')
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
if opt.ShortName != 'o' {
|
||||
t.Errorf("Expected 'o', but got %v", opt.ShortName)
|
||||
}
|
||||
|
||||
opt = c.FindOptionByShortName('t')
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
if opt.ShortName != 't' {
|
||||
t.Errorf("Expected 'o', but got %v", opt.ShortName)
|
||||
}
|
||||
}
|
@ -1,300 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 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) && !opt.Hidden {
|
||||
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) print(items []Completion, showDescriptions bool) {
|
||||
if showDescriptions && len(items) > 1 {
|
||||
maxl := 0
|
||||
|
||||
for _, v := range items {
|
||||
if len(v.Item) > maxl {
|
||||
maxl = len(v.Item)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range items {
|
||||
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 items {
|
||||
fmt.Println(v.Item)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,295 +0,0 @@
|
||||
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"`
|
||||
Hidden bool `long:"hidden" hidden:"true" description:"This is hidden"`
|
||||
|
||||
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)
|
||||
|
||||
p.CompletionHandler = func(items []Completion) {
|
||||
comp := &completion{parser: p}
|
||||
comp.print(items, test.ShowDescriptions)
|
||||
}
|
||||
|
||||
_, 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", "")
|
||||
}
|
341
vendor/src/github.com/jessevdk/go-flags/convert.go
vendored
341
vendor/src/github.com/jessevdk/go-flags/convert.go
vendored
@ -1,341 +0,0 @@
|
||||
// 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)
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
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")
|
||||
}
|
134
vendor/src/github.com/jessevdk/go-flags/error.go
vendored
134
vendor/src/github.com/jessevdk/go-flags/error.go
vendored
@ -1,134 +0,0 @@
|
||||
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
|
||||
|
||||
// ErrInvalidChoice indicates an invalid option value which only allows
|
||||
// a certain number of choices.
|
||||
ErrInvalidChoice
|
||||
|
||||
// ErrInvalidTag indicates an invalid tag or invalid use of an existing tag
|
||||
ErrInvalidTag
|
||||
)
|
||||
|
||||
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"
|
||||
case ErrInvalidChoice:
|
||||
return "invalid choice"
|
||||
case ErrInvalidTag:
|
||||
return "invalid tag"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
// 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]
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
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)
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
_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
|
@ -1,75 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
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)
|
||||
}
|
258
vendor/src/github.com/jessevdk/go-flags/flags.go
vendored
258
vendor/src/github.com/jessevdk/go-flags/flags.go
vendored
@ -1,258 +0,0 @@
|
||||
// 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
|
||||
Windows style options can be disabled at build time using the "forceposix"
|
||||
build tag
|
||||
|
||||
|
||||
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. When an
|
||||
argument is optional it can only be specified using
|
||||
--option=argument (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)
|
||||
choice: limits the values for an option to a set of values.
|
||||
This tag can be specified mltiple times (optional)
|
||||
hidden: the option is not visible in the help or man page.
|
||||
|
||||
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. The "required" tag
|
||||
can also be set on the individual rest argument
|
||||
fields, to require only the first N positional
|
||||
arguments. If the "required" tag is set on the
|
||||
rest arguments slice, then its value determines
|
||||
the minimum amount of rest arguments that needs to
|
||||
be provided (e.g. `required:"2"`) (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
|
385
vendor/src/github.com/jessevdk/go-flags/group.go
vendored
385
vendor/src/github.com/jessevdk/go-flags/group.go
vendored
@ -1,385 +0,0 @@
|
||||
// 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"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// If true, the group is not displayed in the help or man page
|
||||
Hidden bool
|
||||
|
||||
// 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{}
|
||||
}
|
||||
|
||||
type scanHandler func(reflect.Value, *reflect.StructField) (bool, error)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (g *Group) findOption(matcher func(*Option) bool) (option *Option) {
|
||||
g.eachGroup(func(g *Group) {
|
||||
for _, opt := range g.options {
|
||||
if option == nil && matcher(opt) {
|
||||
option = opt
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return option
|
||||
}
|
||||
|
||||
// FindOptionByLongName finds an option that is part of the group, or any of its
|
||||
// subgroups, by matching its long name (including the option namespace).
|
||||
func (g *Group) FindOptionByLongName(longName string) *Option {
|
||||
return g.findOption(func(option *Option) bool {
|
||||
return option.LongNameWithNamespace() == longName
|
||||
})
|
||||
}
|
||||
|
||||
// FindOptionByShortName finds an option that is part of the group, or any of
|
||||
// its subgroups, by matching its short name.
|
||||
func (g *Group) FindOptionByShortName(shortName rune) *Option {
|
||||
return g.findOption(func(option *Option) bool {
|
||||
return option.ShortName == shortName
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
g.eachGroup(func(g *Group) {
|
||||
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 != "" && !field.Anonymous {
|
||||
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") != "")
|
||||
choices := mtag.GetMany("choice")
|
||||
hidden := (mtag.Get("hidden") != "")
|
||||
|
||||
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,
|
||||
Choices: choices,
|
||||
Hidden: hidden,
|
||||
|
||||
group: g,
|
||||
|
||||
field: field,
|
||||
value: realval.Field(i),
|
||||
tag: mtag,
|
||||
}
|
||||
|
||||
if option.isBool() && option.Default != nil {
|
||||
return newErrorf(ErrInvalidTag,
|
||||
"boolean flag `%s' may not have default values, they always default to `false' and can only be turned on",
|
||||
option.shortAndLongName())
|
||||
}
|
||||
|
||||
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")
|
||||
group.Hidden = mtag.Get("hidden") != ""
|
||||
|
||||
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)
|
||||
}
|
@ -1,255 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindOptionByLongFlag(t *testing.T) {
|
||||
var opts struct {
|
||||
Testing bool `long:"testing" description:"Testing"`
|
||||
}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
opt := p.FindOptionByLongName("testing")
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
assertString(t, opt.LongName, "testing")
|
||||
}
|
||||
|
||||
func TestFindOptionByShortFlag(t *testing.T) {
|
||||
var opts struct {
|
||||
Testing bool `short:"t" description:"Testing"`
|
||||
}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
opt := p.FindOptionByShortName('t')
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
if opt.ShortName != 't' {
|
||||
t.Errorf("Expected 't', but got %v", opt.ShortName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindOptionByLongFlagInSubGroup(t *testing.T) {
|
||||
var opts struct {
|
||||
Group struct {
|
||||
Testing bool `long:"testing" description:"Testing"`
|
||||
} `group:"sub-group"`
|
||||
}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
opt := p.FindOptionByLongName("testing")
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
assertString(t, opt.LongName, "testing")
|
||||
}
|
||||
|
||||
func TestFindOptionByShortFlagInSubGroup(t *testing.T) {
|
||||
var opts struct {
|
||||
Group struct {
|
||||
Testing bool `short:"t" description:"Testing"`
|
||||
} `group:"sub-group"`
|
||||
}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
opt := p.FindOptionByShortName('t')
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
if opt.ShortName != 't' {
|
||||
t.Errorf("Expected 't', but got %v", opt.ShortName)
|
||||
}
|
||||
}
|
485
vendor/src/github.com/jessevdk/go-flags/help.go
vendored
485
vendor/src/github.com/jessevdk/go-flags/help.go
vendored
@ -1,485 +0,0 @@
|
||||
// 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"
|
||||
"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
|
||||
}
|
||||
|
||||
l := info.LongNameWithNamespace() + info.ValueName
|
||||
|
||||
if len(info.Choices) != 0 {
|
||||
l += "[" + strings.Join(info.Choices, "|") + "]"
|
||||
}
|
||||
|
||||
ret.updateLen(l, c != p.Command)
|
||||
}
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func wrapText(s string, l int, prefix string) string {
|
||||
var ret string
|
||||
|
||||
// Basic text wrapping of s at spaces to fit in l
|
||||
lines := strings.Split(s, "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
var retline string
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
for len(line) > l {
|
||||
// Try to split on space
|
||||
suffix := ""
|
||||
|
||||
pos := strings.LastIndex(line[:l], " ")
|
||||
|
||||
if pos < 0 {
|
||||
pos = l - 1
|
||||
suffix = "-\n"
|
||||
}
|
||||
|
||||
if len(retline) != 0 {
|
||||
retline += "\n" + prefix
|
||||
}
|
||||
|
||||
retline += strings.TrimSpace(line[:pos]) + suffix
|
||||
line = strings.TrimSpace(line[pos:])
|
||||
}
|
||||
|
||||
if len(line) > 0 {
|
||||
if len(retline) != 0 {
|
||||
retline += "\n" + prefix
|
||||
}
|
||||
|
||||
retline += line
|
||||
}
|
||||
|
||||
if len(ret) > 0 {
|
||||
ret += "\n"
|
||||
|
||||
if len(retline) > 0 {
|
||||
ret += prefix
|
||||
}
|
||||
}
|
||||
|
||||
ret += retline
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alignmentInfo) {
|
||||
line := &bytes.Buffer{}
|
||||
|
||||
prefix := paddingBeforeOption
|
||||
|
||||
if info.indent {
|
||||
prefix += 4
|
||||
}
|
||||
|
||||
if option.Hidden {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if len(option.Choices) > 0 {
|
||||
line.WriteString("[" + strings.Join(option.Choices, "|") + "]")
|
||||
}
|
||||
}
|
||||
|
||||
written := line.Len()
|
||||
line.WriteTo(writer)
|
||||
|
||||
if option.Description != "" {
|
||||
dw := descstart - written
|
||||
writer.WriteString(strings.Repeat(" ", dw))
|
||||
|
||||
var def string
|
||||
|
||||
if len(option.DefaultMask) != 0 && option.DefaultMask != "-" {
|
||||
def = option.DefaultMask
|
||||
} else {
|
||||
def = option.defaultLiteral
|
||||
}
|
||||
|
||||
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 (default: %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 = "<", ">"
|
||||
}
|
||||
|
||||
visibleCommands := allcmd.visibleCommands()
|
||||
|
||||
if len(visibleCommands) > 3 {
|
||||
fmt.Fprintf(wr, " %scommand%s", co, cc)
|
||||
} else {
|
||||
subcommands := allcmd.sortedVisibleCommands()
|
||||
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.Hidden || (grp.isBuiltinHelp && c != p.Command) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, info := range grp.options {
|
||||
if !info.canCli() || info.Hidden {
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
var args []*Arg
|
||||
for _, arg := range c.args {
|
||||
if arg.Description != "" {
|
||||
args = append(args, arg)
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
if c == p.Command {
|
||||
fmt.Fprintf(wr, "\nArguments:\n")
|
||||
} else {
|
||||
fmt.Fprintf(wr, "\n[%s command arguments]\n", c.Name)
|
||||
}
|
||||
|
||||
descStart := aligninfo.descriptionStart() + paddingBeforeOption
|
||||
|
||||
for _, arg := range args {
|
||||
argPrefix := strings.Repeat(" ", paddingBeforeOption)
|
||||
argPrefix += arg.Name
|
||||
|
||||
if len(arg.Description) > 0 {
|
||||
argPrefix += ":"
|
||||
wr.WriteString(argPrefix)
|
||||
|
||||
// Space between "arg:" and the description start
|
||||
descPadding := strings.Repeat(" ", descStart-len(argPrefix))
|
||||
// How much space the description gets before wrapping
|
||||
descWidth := aligninfo.terminalColumns - 1 - descStart
|
||||
// Whitespace to which we can indent new description lines
|
||||
descPrefix := strings.Repeat(" ", descStart)
|
||||
|
||||
wr.WriteString(descPadding)
|
||||
wr.WriteString(wrapText(arg.Description, descWidth, descPrefix))
|
||||
} else {
|
||||
wr.WriteString(argPrefix)
|
||||
}
|
||||
|
||||
fmt.Fprintln(wr)
|
||||
}
|
||||
}
|
||||
|
||||
c = c.Active
|
||||
}
|
||||
|
||||
scommands := cmd.sortedVisibleCommands()
|
||||
|
||||
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()
|
||||
}
|
468
vendor/src/github.com/jessevdk/go-flags/help_test.go
vendored
468
vendor/src/github.com/jessevdk/go-flags/help_test.go
vendored
@ -1,468 +0,0 @@
|
||||
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"`
|
||||
OptionWithChoices string `long:"opt-with-choices" value-name:"choice" choice:"dog" choice:"cat" description:"Option with choices"`
|
||||
Hidden string `long:"hidden" description:"Hidden option" hidden:"yes"`
|
||||
|
||||
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"`
|
||||
|
||||
HiddenGroup struct {
|
||||
InsideHiddenGroup string `long:"inside-hidden-group" description:"Inside hidden group"`
|
||||
} `group:"Hidden group" hidden:"yes"`
|
||||
|
||||
Group struct {
|
||||
Opt string `long:"opt" description:"This is a subgroup option"`
|
||||
HiddenInsideGroup string `long:"hidden-inside-group" description:"Hidden inside group" hidden:"yes"`
|
||||
|
||||
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"`
|
||||
|
||||
HiddenCommand struct {
|
||||
ExtraVerbose []bool `long:"extra-verbose" description:"Use for extra verbosity"`
|
||||
} `command:"hidden-command" description:"A hidden command" hidden:"yes"`
|
||||
|
||||
Args struct {
|
||||
Filename string `positional-arg-name:"filename" description:"A filename with a long description to trigger line wrapping"`
|
||||
Number int `positional-arg-name:"num" description:"A number"`
|
||||
HiddenInHelp float32 `positional-arg-name:"hidden-in-help" required:"yes"`
|
||||
} `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] [hidden-in-help] <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 (default:
|
||||
"Some\nvalue")
|
||||
/default-array: Test default array value (default:
|
||||
Some value, "Other\tvalue")
|
||||
/default-map: Testdefault map value (default:
|
||||
some:value, another:value)
|
||||
/env-default1: Test env-default1 value (default:
|
||||
Some value) [%ENV_DEFAULT%]
|
||||
/env-default2: Test env-default2 value
|
||||
[%ENV_DEFAULT%]
|
||||
/opt-with-arg-name:something Option with named argument
|
||||
/opt-with-choices:choice[dog|cat] Option with choices
|
||||
|
||||
Other Options:
|
||||
/s: A slice of strings (default: some,
|
||||
value)
|
||||
/intmap: A map from string to int (default:
|
||||
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] [hidden-in-help] <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 (default:
|
||||
"Some\nvalue")
|
||||
--default-array= Test default array value (default:
|
||||
Some value, "Other\tvalue")
|
||||
--default-map= Testdefault map value (default:
|
||||
some:value, another:value)
|
||||
--env-default1= Test env-default1 value (default:
|
||||
Some value) [$ENV_DEFAULT]
|
||||
--env-default2= Test env-default2 value
|
||||
[$ENV_DEFAULT]
|
||||
--opt-with-arg-name=something Option with named argument
|
||||
--opt-with-choices=choice[dog|cat] Option with choices
|
||||
|
||||
Other Options:
|
||||
-s= A slice of strings (default: some,
|
||||
value)
|
||||
--intmap= A map from string to int (default:
|
||||
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 with a long description
|
||||
to trigger line wrapping
|
||||
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
|
||||
.SS Application Options
|
||||
The application 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\-\-opt-with-choices\fR \fIchoice\fR\fP
|
||||
Option with choices
|
||||
.SS Other Options
|
||||
.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
|
||||
.SS Subgroup
|
||||
.TP
|
||||
\fB\fB\-\-sip.opt\fR\fP
|
||||
This is a subgroup option
|
||||
.SS Subsubgroup
|
||||
.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]
|
||||
.TP
|
||||
|
||||
\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")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelpDefaults(t *testing.T) {
|
||||
var expected string
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
expected = `Usage:
|
||||
TestHelpDefaults [OPTIONS]
|
||||
|
||||
Application Options:
|
||||
/with-default: With default (default: default-value)
|
||||
/without-default: Without default
|
||||
/with-programmatic-default: With programmatic default (default:
|
||||
default-value)
|
||||
|
||||
Help Options:
|
||||
/? Show this help message
|
||||
/h, /help Show this help message
|
||||
`
|
||||
} else {
|
||||
expected = `Usage:
|
||||
TestHelpDefaults [OPTIONS]
|
||||
|
||||
Application Options:
|
||||
--with-default= With default (default: default-value)
|
||||
--without-default= Without default
|
||||
--with-programmatic-default= With programmatic default (default:
|
||||
default-value)
|
||||
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
`
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
Args []string
|
||||
Output string
|
||||
}{
|
||||
{
|
||||
Args: []string{"-h"},
|
||||
Output: expected,
|
||||
},
|
||||
{
|
||||
Args: []string{"--with-default", "other-value", "--with-programmatic-default", "other-value", "-h"},
|
||||
Output: expected,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
var opts struct {
|
||||
WithDefault string `long:"with-default" default:"default-value" description:"With default"`
|
||||
WithoutDefault string `long:"without-default" description:"Without default"`
|
||||
WithProgrammaticDefault string `long:"with-programmatic-default" description:"With programmatic default"`
|
||||
}
|
||||
|
||||
opts.WithProgrammaticDefault = "default-value"
|
||||
|
||||
p := NewNamedParser("TestHelpDefaults", HelpFlag)
|
||||
p.AddGroup("Application Options", "The application options", &opts)
|
||||
|
||||
_, err := p.ParseArgs(test.Args)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
assertDiff(t, e.Message, test.Output, "help message")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelpRestArgs(t *testing.T) {
|
||||
opts := struct {
|
||||
Verbose bool `short:"v"`
|
||||
}{}
|
||||
|
||||
p := NewNamedParser("TestHelpDefaults", HelpFlag)
|
||||
p.AddGroup("Application Options", "The application options", &opts)
|
||||
|
||||
retargs, err := p.ParseArgs([]string{"-h", "-v", "rest"})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected help error")
|
||||
}
|
||||
|
||||
assertStringArray(t, retargs, []string{"-v", "rest"})
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
func TestWrapParagraph(t *testing.T) {
|
||||
s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n\n"
|
||||
s += "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\n"
|
||||
s += "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n\n"
|
||||
s += "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n"
|
||||
|
||||
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 paragraph")
|
||||
}
|
601
vendor/src/github.com/jessevdk/go-flags/ini.go
vendored
601
vendor/src/github.com/jessevdk/go-flags/ini.go
vendored
@ -1,601 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IniError contains location information on where an error occured.
|
||||
type IniError struct {
|
||||
// The error message.
|
||||
Message string
|
||||
|
||||
// The filename of the file in which the error occurred.
|
||||
File string
|
||||
|
||||
// The line number at which the error occurred.
|
||||
LineNumber uint
|
||||
}
|
||||
|
||||
// Error provides a "file:line: message" formatted message of the ini error.
|
||||
func (x *IniError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"%s:%d: %s",
|
||||
x.File,
|
||||
x.LineNumber,
|
||||
x.Message,
|
||||
)
|
||||
}
|
||||
|
||||
// IniOptions for writing
|
||||
type IniOptions uint
|
||||
|
||||
const (
|
||||
// IniNone indicates no options.
|
||||
IniNone IniOptions = 0
|
||||
|
||||
// IniIncludeDefaults indicates that default values should be written.
|
||||
IniIncludeDefaults = 1 << iota
|
||||
|
||||
// IniCommentDefaults indicates that if IniIncludeDefaults is used
|
||||
// options with default values are written but commented out.
|
||||
IniCommentDefaults
|
||||
|
||||
// IniIncludeComments indicates that comments containing the description
|
||||
// of an option should be written.
|
||||
IniIncludeComments
|
||||
|
||||
// IniDefault provides a default set of options.
|
||||
IniDefault = IniIncludeComments
|
||||
)
|
||||
|
||||
// IniParser is a utility to read and write flags options from and to ini
|
||||
// formatted strings.
|
||||
type IniParser struct {
|
||||
ParseAsDefaults bool // override default flags
|
||||
|
||||
parser *Parser
|
||||
}
|
||||
|
||||
type iniValue struct {
|
||||
Name string
|
||||
Value string
|
||||
Quoted bool
|
||||
LineNumber uint
|
||||
}
|
||||
|
||||
type iniSection []iniValue
|
||||
|
||||
type ini struct {
|
||||
File string
|
||||
Sections map[string]iniSection
|
||||
}
|
||||
|
||||
// NewIniParser creates a new ini parser for a given Parser.
|
||||
func NewIniParser(p *Parser) *IniParser {
|
||||
return &IniParser{
|
||||
parser: p,
|
||||
}
|
||||
}
|
||||
|
||||
// IniParse is a convenience function to parse command line options with default
|
||||
// settings from an ini formatted file. The provided data is a pointer to a struct
|
||||
// representing the default option group (named "Application Options"). For
|
||||
// more control, use flags.NewParser.
|
||||
func IniParse(filename string, data interface{}) error {
|
||||
p := NewParser(data, Default)
|
||||
|
||||
return NewIniParser(p).ParseFile(filename)
|
||||
}
|
||||
|
||||
// ParseFile parses flags from an ini formatted file. See Parse for more
|
||||
// information on the ini file format. The returned errors can be of the type
|
||||
// flags.Error or flags.IniError.
|
||||
func (i *IniParser) ParseFile(filename string) error {
|
||||
i.parser.clearIsSet()
|
||||
|
||||
ini, err := readIniFromFile(filename)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return i.parse(ini)
|
||||
}
|
||||
|
||||
// Parse parses flags from an ini format. You can use ParseFile as a
|
||||
// convenience function to parse from a filename instead of a general
|
||||
// io.Reader.
|
||||
//
|
||||
// The format of the ini file is as follows:
|
||||
//
|
||||
// [Option group name]
|
||||
// option = value
|
||||
//
|
||||
// Each section in the ini file represents an option group or command in the
|
||||
// flags parser. The default flags parser option group (i.e. when using
|
||||
// flags.Parse) is named 'Application Options'. The ini option name is matched
|
||||
// in the following order:
|
||||
//
|
||||
// 1. Compared to the ini-name tag on the option struct field (if present)
|
||||
// 2. Compared to the struct field name
|
||||
// 3. Compared to the option long name (if present)
|
||||
// 4. Compared to the option short name (if present)
|
||||
//
|
||||
// Sections for nested groups and commands can be addressed using a dot `.'
|
||||
// namespacing notation (i.e [subcommand.Options]). Group section names are
|
||||
// matched case insensitive.
|
||||
//
|
||||
// The returned errors can be of the type flags.Error or flags.IniError.
|
||||
func (i *IniParser) Parse(reader io.Reader) error {
|
||||
i.parser.clearIsSet()
|
||||
|
||||
ini, err := readIni(reader, "")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return i.parse(ini)
|
||||
}
|
||||
|
||||
// WriteFile writes the flags as ini format into a file. See Write
|
||||
// for more information. The returned error occurs when the specified file
|
||||
// could not be opened for writing.
|
||||
func (i *IniParser) WriteFile(filename string, options IniOptions) error {
|
||||
return writeIniToFile(i, filename, options)
|
||||
}
|
||||
|
||||
// Write writes the current values of all the flags to an ini format.
|
||||
// See Parse for more information on the ini file format. You typically
|
||||
// call this only after settings have been parsed since the default values of each
|
||||
// option are stored just before parsing the flags (this is only relevant when
|
||||
// IniIncludeDefaults is _not_ set in options).
|
||||
func (i *IniParser) Write(writer io.Writer, options IniOptions) {
|
||||
writeIni(i, writer, options)
|
||||
}
|
||||
|
||||
func readFullLine(reader *bufio.Reader) (string, error) {
|
||||
var line []byte
|
||||
|
||||
for {
|
||||
l, more, err := reader.ReadLine()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if line == nil && !more {
|
||||
return string(l), nil
|
||||
}
|
||||
|
||||
line = append(line, l...)
|
||||
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return string(line), nil
|
||||
}
|
||||
|
||||
func optionIniName(option *Option) string {
|
||||
name := option.tag.Get("_read-ini-name")
|
||||
|
||||
if len(name) != 0 {
|
||||
return name
|
||||
}
|
||||
|
||||
name = option.tag.Get("ini-name")
|
||||
|
||||
if len(name) != 0 {
|
||||
return name
|
||||
}
|
||||
|
||||
return option.field.Name
|
||||
}
|
||||
|
||||
func writeGroupIni(cmd *Command, group *Group, namespace string, writer io.Writer, options IniOptions) {
|
||||
var sname string
|
||||
|
||||
if len(namespace) != 0 {
|
||||
sname = namespace
|
||||
}
|
||||
|
||||
if cmd.Group != group && len(group.ShortDescription) != 0 {
|
||||
if len(sname) != 0 {
|
||||
sname += "."
|
||||
}
|
||||
|
||||
sname += group.ShortDescription
|
||||
}
|
||||
|
||||
sectionwritten := false
|
||||
comments := (options & IniIncludeComments) != IniNone
|
||||
|
||||
for _, option := range group.options {
|
||||
if option.isFunc() || option.Hidden {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(option.tag.Get("no-ini")) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
val := option.value
|
||||
|
||||
if (options&IniIncludeDefaults) == IniNone && option.valueIsDefault() {
|
||||
continue
|
||||
}
|
||||
|
||||
if !sectionwritten {
|
||||
fmt.Fprintf(writer, "[%s]\n", sname)
|
||||
sectionwritten = true
|
||||
}
|
||||
|
||||
if comments && len(option.Description) != 0 {
|
||||
fmt.Fprintf(writer, "; %s\n", option.Description)
|
||||
}
|
||||
|
||||
oname := optionIniName(option)
|
||||
|
||||
commentOption := (options&(IniIncludeDefaults|IniCommentDefaults)) == IniIncludeDefaults|IniCommentDefaults && option.valueIsDefault()
|
||||
|
||||
kind := val.Type().Kind()
|
||||
switch kind {
|
||||
case reflect.Slice:
|
||||
kind = val.Type().Elem().Kind()
|
||||
|
||||
if val.Len() == 0 {
|
||||
writeOption(writer, oname, kind, "", "", true, option.iniQuote)
|
||||
} else {
|
||||
for idx := 0; idx < val.Len(); idx++ {
|
||||
v, _ := convertToString(val.Index(idx), option.tag)
|
||||
|
||||
writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
kind = val.Type().Elem().Kind()
|
||||
|
||||
if val.Len() == 0 {
|
||||
writeOption(writer, oname, kind, "", "", true, option.iniQuote)
|
||||
} else {
|
||||
mkeys := val.MapKeys()
|
||||
keys := make([]string, len(val.MapKeys()))
|
||||
kkmap := make(map[string]reflect.Value)
|
||||
|
||||
for i, k := range mkeys {
|
||||
keys[i], _ = convertToString(k, option.tag)
|
||||
kkmap[keys[i]] = k
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
v, _ := convertToString(val.MapIndex(kkmap[k]), option.tag)
|
||||
|
||||
writeOption(writer, oname, kind, k, v, commentOption, option.iniQuote)
|
||||
}
|
||||
}
|
||||
default:
|
||||
v, _ := convertToString(val, option.tag)
|
||||
|
||||
writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
|
||||
}
|
||||
|
||||
if comments {
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
|
||||
if sectionwritten && !comments {
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
|
||||
func writeOption(writer io.Writer, optionName string, optionType reflect.Kind, optionKey string, optionValue string, commentOption bool, forceQuote bool) {
|
||||
if forceQuote || (optionType == reflect.String && !isPrint(optionValue)) {
|
||||
optionValue = strconv.Quote(optionValue)
|
||||
}
|
||||
|
||||
comment := ""
|
||||
if commentOption {
|
||||
comment = "; "
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "%s%s =", comment, optionName)
|
||||
|
||||
if optionKey != "" {
|
||||
fmt.Fprintf(writer, " %s:%s", optionKey, optionValue)
|
||||
} else if optionValue != "" {
|
||||
fmt.Fprintf(writer, " %s", optionValue)
|
||||
}
|
||||
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
|
||||
func writeCommandIni(command *Command, namespace string, writer io.Writer, options IniOptions) {
|
||||
command.eachGroup(func(group *Group) {
|
||||
if !group.Hidden {
|
||||
writeGroupIni(command, group, namespace, writer, options)
|
||||
}
|
||||
})
|
||||
|
||||
for _, c := range command.commands {
|
||||
var nns string
|
||||
|
||||
if c.Hidden {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(namespace) != 0 {
|
||||
nns = c.Name + "." + nns
|
||||
} else {
|
||||
nns = c.Name
|
||||
}
|
||||
|
||||
writeCommandIni(c, nns, writer, options)
|
||||
}
|
||||
}
|
||||
|
||||
func writeIni(parser *IniParser, writer io.Writer, options IniOptions) {
|
||||
writeCommandIni(parser.parser.Command, "", writer, options)
|
||||
}
|
||||
|
||||
func writeIniToFile(parser *IniParser, filename string, options IniOptions) error {
|
||||
file, err := os.Create(filename)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
writeIni(parser, file, options)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readIniFromFile(filename string) (*ini, error) {
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
return readIni(file, filename)
|
||||
}
|
||||
|
||||
func readIni(contents io.Reader, filename string) (*ini, error) {
|
||||
ret := &ini{
|
||||
File: filename,
|
||||
Sections: make(map[string]iniSection),
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(contents)
|
||||
|
||||
// Empty global section
|
||||
section := make(iniSection, 0, 10)
|
||||
sectionname := ""
|
||||
|
||||
ret.Sections[sectionname] = section
|
||||
|
||||
var lineno uint
|
||||
|
||||
for {
|
||||
line, err := readFullLine(reader)
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lineno++
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
// Skip empty lines and lines starting with ; (comments)
|
||||
if len(line) == 0 || line[0] == ';' || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
|
||||
if line[0] == '[' {
|
||||
if line[0] != '[' || line[len(line)-1] != ']' {
|
||||
return nil, &IniError{
|
||||
Message: "malformed section header",
|
||||
File: filename,
|
||||
LineNumber: lineno,
|
||||
}
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(line[1 : len(line)-1])
|
||||
|
||||
if len(name) == 0 {
|
||||
return nil, &IniError{
|
||||
Message: "empty section name",
|
||||
File: filename,
|
||||
LineNumber: lineno,
|
||||
}
|
||||
}
|
||||
|
||||
sectionname = name
|
||||
section = ret.Sections[name]
|
||||
|
||||
if section == nil {
|
||||
section = make(iniSection, 0, 10)
|
||||
ret.Sections[name] = section
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse option here
|
||||
keyval := strings.SplitN(line, "=", 2)
|
||||
|
||||
if len(keyval) != 2 {
|
||||
return nil, &IniError{
|
||||
Message: fmt.Sprintf("malformed key=value (%s)", line),
|
||||
File: filename,
|
||||
LineNumber: lineno,
|
||||
}
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(keyval[0])
|
||||
value := strings.TrimSpace(keyval[1])
|
||||
quoted := false
|
||||
|
||||
if len(value) != 0 && value[0] == '"' {
|
||||
if v, err := strconv.Unquote(value); err == nil {
|
||||
value = v
|
||||
|
||||
quoted = true
|
||||
} else {
|
||||
return nil, &IniError{
|
||||
Message: err.Error(),
|
||||
File: filename,
|
||||
LineNumber: lineno,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section = append(section, iniValue{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Quoted: quoted,
|
||||
LineNumber: lineno,
|
||||
})
|
||||
|
||||
ret.Sections[sectionname] = section
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (i *IniParser) matchingGroups(name string) []*Group {
|
||||
if len(name) == 0 {
|
||||
var ret []*Group
|
||||
|
||||
i.parser.eachGroup(func(g *Group) {
|
||||
ret = append(ret, g)
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
g := i.parser.groupByName(name)
|
||||
|
||||
if g != nil {
|
||||
return []*Group{g}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *IniParser) parse(ini *ini) error {
|
||||
p := i.parser
|
||||
|
||||
var quotesLookup = make(map[*Option]bool)
|
||||
|
||||
for name, section := range ini.Sections {
|
||||
groups := i.matchingGroups(name)
|
||||
|
||||
if len(groups) == 0 {
|
||||
return newErrorf(ErrUnknownGroup, "could not find option group `%s'", name)
|
||||
}
|
||||
|
||||
for _, inival := range section {
|
||||
var opt *Option
|
||||
|
||||
for _, group := range groups {
|
||||
opt = group.optionByName(inival.Name, func(o *Option, n string) bool {
|
||||
return strings.ToLower(o.tag.Get("ini-name")) == strings.ToLower(n)
|
||||
})
|
||||
|
||||
if opt != nil && len(opt.tag.Get("no-ini")) != 0 {
|
||||
opt = nil
|
||||
}
|
||||
|
||||
if opt != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if opt == nil {
|
||||
if (p.Options & IgnoreUnknown) == None {
|
||||
return &IniError{
|
||||
Message: fmt.Sprintf("unknown option: %s", inival.Name),
|
||||
File: ini.File,
|
||||
LineNumber: inival.LineNumber,
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// ini value is ignored if override is set and
|
||||
// value was previously set from non default
|
||||
if i.ParseAsDefaults && !opt.isSetDefault {
|
||||
continue
|
||||
}
|
||||
|
||||
pval := &inival.Value
|
||||
|
||||
if !opt.canArgument() && len(inival.Value) == 0 {
|
||||
pval = nil
|
||||
} else {
|
||||
if opt.value.Type().Kind() == reflect.Map {
|
||||
parts := strings.SplitN(inival.Value, ":", 2)
|
||||
|
||||
// only handle unquoting
|
||||
if len(parts) == 2 && parts[1][0] == '"' {
|
||||
if v, err := strconv.Unquote(parts[1]); err == nil {
|
||||
parts[1] = v
|
||||
|
||||
inival.Quoted = true
|
||||
} else {
|
||||
return &IniError{
|
||||
Message: err.Error(),
|
||||
File: ini.File,
|
||||
LineNumber: inival.LineNumber,
|
||||
}
|
||||
}
|
||||
|
||||
s := parts[0] + ":" + parts[1]
|
||||
|
||||
pval = &s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := opt.set(pval); err != nil {
|
||||
return &IniError{
|
||||
Message: err.Error(),
|
||||
File: ini.File,
|
||||
LineNumber: inival.LineNumber,
|
||||
}
|
||||
}
|
||||
|
||||
// either all INI values are quoted or only values who need quoting
|
||||
if _, ok := quotesLookup[opt]; !inival.Quoted || !ok {
|
||||
quotesLookup[opt] = inival.Quoted
|
||||
}
|
||||
|
||||
opt.tag.Set("_read-ini-name", inival.Name)
|
||||
}
|
||||
}
|
||||
|
||||
for opt, quoted := range quotesLookup {
|
||||
opt.iniQuote = quoted
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
1021
vendor/src/github.com/jessevdk/go-flags/ini_test.go
vendored
1021
vendor/src/github.com/jessevdk/go-flags/ini_test.go
vendored
File diff suppressed because it is too large
Load Diff
@ -1,85 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLong(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `long:"value"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "--value")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLongArg(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value string `long:"value"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "--value", "value")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
assertString(t, opts.Value, "value")
|
||||
}
|
||||
|
||||
func TestLongArgEqual(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value string `long:"value"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "--value=value")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
assertString(t, opts.Value, "value")
|
||||
}
|
||||
|
||||
func TestLongDefault(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value string `long:"value" default:"value"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts)
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
assertString(t, opts.Value, "value")
|
||||
}
|
||||
|
||||
func TestLongOptional(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value string `long:"value" optional:"yes" optional-value:"value"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "--value")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
assertString(t, opts.Value, "value")
|
||||
}
|
||||
|
||||
func TestLongOptionalArg(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value string `long:"value" optional:"yes" optional-value:"value"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "--value", "no")
|
||||
|
||||
assertStringArray(t, ret, []string{"no"})
|
||||
assertString(t, opts.Value, "value")
|
||||
}
|
||||
|
||||
func TestLongOptionalArgEqual(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value string `long:"value" optional:"yes" optional-value:"value"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "--value=value", "no")
|
||||
|
||||
assertStringArray(t, ret, []string{"no"})
|
||||
assertString(t, opts.Value, "value")
|
||||
}
|
205
vendor/src/github.com/jessevdk/go-flags/man.go
vendored
205
vendor/src/github.com/jessevdk/go-flags/man.go
vendored
@ -1,205 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func manQuote(s string) string {
|
||||
return strings.Replace(s, "\\", "\\\\", -1)
|
||||
}
|
||||
|
||||
func formatForMan(wr io.Writer, s string) {
|
||||
for {
|
||||
idx := strings.IndexRune(s, '`')
|
||||
|
||||
if idx < 0 {
|
||||
fmt.Fprintf(wr, "%s", manQuote(s))
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Fprintf(wr, "%s", manQuote(s[:idx]))
|
||||
|
||||
s = s[idx+1:]
|
||||
idx = strings.IndexRune(s, '\'')
|
||||
|
||||
if idx < 0 {
|
||||
fmt.Fprintf(wr, "%s", manQuote(s))
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Fprintf(wr, "\\fB%s\\fP", manQuote(s[:idx]))
|
||||
s = s[idx+1:]
|
||||
}
|
||||
}
|
||||
|
||||
func writeManPageOptions(wr io.Writer, grp *Group) {
|
||||
grp.eachGroup(func(group *Group) {
|
||||
if group.Hidden || len(group.options) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// If the parent (grp) has any subgroups, display their descriptions as
|
||||
// subsection headers similar to the output of --help.
|
||||
if group.ShortDescription != "" && len(grp.groups) > 0 {
|
||||
fmt.Fprintf(wr, ".SS %s\n", group.ShortDescription)
|
||||
|
||||
if group.LongDescription != "" {
|
||||
formatForMan(wr, group.LongDescription)
|
||||
fmt.Fprintln(wr, "")
|
||||
}
|
||||
}
|
||||
|
||||
for _, opt := range group.options {
|
||||
if !opt.canCli() || opt.Hidden {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Fprintln(wr, ".TP")
|
||||
fmt.Fprintf(wr, "\\fB")
|
||||
|
||||
if opt.ShortName != 0 {
|
||||
fmt.Fprintf(wr, "\\fB\\-%c\\fR", opt.ShortName)
|
||||
}
|
||||
|
||||
if len(opt.LongName) != 0 {
|
||||
if opt.ShortName != 0 {
|
||||
fmt.Fprintf(wr, ", ")
|
||||
}
|
||||
|
||||
fmt.Fprintf(wr, "\\fB\\-\\-%s\\fR", manQuote(opt.LongNameWithNamespace()))
|
||||
}
|
||||
|
||||
if len(opt.ValueName) != 0 || opt.OptionalArgument {
|
||||
if opt.OptionalArgument {
|
||||
fmt.Fprintf(wr, " [\\fI%s=%s\\fR]", manQuote(opt.ValueName), manQuote(strings.Join(quoteV(opt.OptionalValue), ", ")))
|
||||
} else {
|
||||
fmt.Fprintf(wr, " \\fI%s\\fR", manQuote(opt.ValueName))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opt.Default) != 0 {
|
||||
fmt.Fprintf(wr, " <default: \\fI%s\\fR>", manQuote(strings.Join(quoteV(opt.Default), ", ")))
|
||||
} else if len(opt.EnvDefaultKey) != 0 {
|
||||
if runtime.GOOS == "windows" {
|
||||
fmt.Fprintf(wr, " <default: \\fI%%%s%%\\fR>", manQuote(opt.EnvDefaultKey))
|
||||
} else {
|
||||
fmt.Fprintf(wr, " <default: \\fI$%s\\fR>", manQuote(opt.EnvDefaultKey))
|
||||
}
|
||||
}
|
||||
|
||||
if opt.Required {
|
||||
fmt.Fprintf(wr, " (\\fIrequired\\fR)")
|
||||
}
|
||||
|
||||
fmt.Fprintln(wr, "\\fP")
|
||||
|
||||
if len(opt.Description) != 0 {
|
||||
formatForMan(wr, opt.Description)
|
||||
fmt.Fprintln(wr, "")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func writeManPageSubcommands(wr io.Writer, name string, root *Command) {
|
||||
commands := root.sortedVisibleCommands()
|
||||
|
||||
for _, c := range commands {
|
||||
var nn string
|
||||
|
||||
if c.Hidden {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(name) != 0 {
|
||||
nn = name + " " + c.Name
|
||||
} else {
|
||||
nn = c.Name
|
||||
}
|
||||
|
||||
writeManPageCommand(wr, nn, root, c)
|
||||
}
|
||||
}
|
||||
|
||||
func writeManPageCommand(wr io.Writer, name string, root *Command, command *Command) {
|
||||
fmt.Fprintf(wr, ".SS %s\n", name)
|
||||
fmt.Fprintln(wr, command.ShortDescription)
|
||||
|
||||
if len(command.LongDescription) > 0 {
|
||||
fmt.Fprintln(wr, "")
|
||||
|
||||
cmdstart := fmt.Sprintf("The %s command", manQuote(command.Name))
|
||||
|
||||
if strings.HasPrefix(command.LongDescription, cmdstart) {
|
||||
fmt.Fprintf(wr, "The \\fI%s\\fP command", manQuote(command.Name))
|
||||
|
||||
formatForMan(wr, command.LongDescription[len(cmdstart):])
|
||||
fmt.Fprintln(wr, "")
|
||||
} else {
|
||||
formatForMan(wr, command.LongDescription)
|
||||
fmt.Fprintln(wr, "")
|
||||
}
|
||||
}
|
||||
|
||||
var usage string
|
||||
if us, ok := command.data.(Usage); ok {
|
||||
usage = us.Usage()
|
||||
} else if command.hasCliOptions() {
|
||||
usage = fmt.Sprintf("[%s-OPTIONS]", command.Name)
|
||||
}
|
||||
|
||||
var pre string
|
||||
if root.hasCliOptions() {
|
||||
pre = fmt.Sprintf("%s [OPTIONS] %s", root.Name, command.Name)
|
||||
} else {
|
||||
pre = fmt.Sprintf("%s %s", root.Name, command.Name)
|
||||
}
|
||||
|
||||
if len(usage) > 0 {
|
||||
fmt.Fprintf(wr, "\n\\fBUsage\\fP: %s %s\n.TP\n", manQuote(pre), manQuote(usage))
|
||||
}
|
||||
|
||||
if len(command.Aliases) > 0 {
|
||||
fmt.Fprintf(wr, "\n\\fBAliases\\fP: %s\n\n", manQuote(strings.Join(command.Aliases, ", ")))
|
||||
}
|
||||
|
||||
writeManPageOptions(wr, command.Group)
|
||||
writeManPageSubcommands(wr, name, command)
|
||||
}
|
||||
|
||||
// WriteManPage writes a basic man page in groff format to the specified
|
||||
// writer.
|
||||
func (p *Parser) WriteManPage(wr io.Writer) {
|
||||
t := time.Now()
|
||||
|
||||
fmt.Fprintf(wr, ".TH %s 1 \"%s\"\n", manQuote(p.Name), t.Format("2 January 2006"))
|
||||
fmt.Fprintln(wr, ".SH NAME")
|
||||
fmt.Fprintf(wr, "%s \\- %s\n", manQuote(p.Name), manQuote(p.ShortDescription))
|
||||
fmt.Fprintln(wr, ".SH SYNOPSIS")
|
||||
|
||||
usage := p.Usage
|
||||
|
||||
if len(usage) == 0 {
|
||||
usage = "[OPTIONS]"
|
||||
}
|
||||
|
||||
fmt.Fprintf(wr, "\\fB%s\\fP %s\n", manQuote(p.Name), manQuote(usage))
|
||||
fmt.Fprintln(wr, ".SH DESCRIPTION")
|
||||
|
||||
formatForMan(wr, p.LongDescription)
|
||||
fmt.Fprintln(wr, "")
|
||||
|
||||
fmt.Fprintln(wr, ".SH OPTIONS")
|
||||
|
||||
writeManPageOptions(wr, p.Command.Group)
|
||||
|
||||
if len(p.visibleCommands()) > 0 {
|
||||
fmt.Fprintln(wr, ".SH COMMANDS")
|
||||
|
||||
writeManPageSubcommands(wr, "", p.Command)
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type marshalled string
|
||||
|
||||
func (m *marshalled) UnmarshalFlag(value string) error {
|
||||
if value == "yes" {
|
||||
*m = "true"
|
||||
} else if value == "no" {
|
||||
*m = "false"
|
||||
} else {
|
||||
return fmt.Errorf("`%s' is not a valid value, please specify `yes' or `no'", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m marshalled) MarshalFlag() (string, error) {
|
||||
if m == "true" {
|
||||
return "yes", nil
|
||||
}
|
||||
|
||||
return "no", nil
|
||||
}
|
||||
|
||||
type marshalledError bool
|
||||
|
||||
func (m marshalledError) MarshalFlag() (string, error) {
|
||||
return "", newErrorf(ErrMarshal, "Failed to marshal")
|
||||
}
|
||||
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value marshalled `short:"v"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-v=yes")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if opts.Value != "true" {
|
||||
t.Errorf("Expected Value to be \"true\"")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalDefault(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value marshalled `short:"v" default:"yes"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts)
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if opts.Value != "true" {
|
||||
t.Errorf("Expected Value to be \"true\"")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalOptional(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value marshalled `short:"v" optional:"yes" optional-value:"yes"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-v")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if opts.Value != "true" {
|
||||
t.Errorf("Expected Value to be \"true\"")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalError(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value marshalled `short:"v"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrMarshal, fmt.Sprintf("invalid argument for flag `%cv' (expected flags.marshalled): `invalid' is not a valid value, please specify `yes' or `no'", defaultShortOptDelimiter), &opts, "-vinvalid")
|
||||
}
|
||||
|
||||
func TestUnmarshalPositionalError(t *testing.T) {
|
||||
var opts = struct {
|
||||
Args struct {
|
||||
Value marshalled
|
||||
} `positional-args:"yes"`
|
||||
}{}
|
||||
|
||||
parser := NewParser(&opts, Default&^PrintErrors)
|
||||
_, err := parser.ParseArgs([]string{"invalid"})
|
||||
|
||||
msg := "`invalid' is not a valid value, please specify `yes' or `no'"
|
||||
|
||||
if err == nil {
|
||||
assertFatalf(t, "Expected error: %s", msg)
|
||||
return
|
||||
}
|
||||
|
||||
if err.Error() != msg {
|
||||
assertErrorf(t, "Expected error message %#v, but got %#v", msg, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalError(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value marshalledError `short:"v"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
o := p.Command.Groups()[0].Options()[0]
|
||||
|
||||
_, err := convertToString(o.value, o.tag)
|
||||
|
||||
assertError(t, err, ErrMarshal, "Failed to marshal")
|
||||
}
|
140
vendor/src/github.com/jessevdk/go-flags/multitag.go
vendored
140
vendor/src/github.com/jessevdk/go-flags/multitag.go
vendored
@ -1,140 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type multiTag struct {
|
||||
value string
|
||||
cache map[string][]string
|
||||
}
|
||||
|
||||
func newMultiTag(v string) multiTag {
|
||||
return multiTag{
|
||||
value: v,
|
||||
}
|
||||
}
|
||||
|
||||
func (x *multiTag) scan() (map[string][]string, error) {
|
||||
v := x.value
|
||||
|
||||
ret := make(map[string][]string)
|
||||
|
||||
// This is mostly copied from reflect.StructTag.Get
|
||||
for v != "" {
|
||||
i := 0
|
||||
|
||||
// Skip whitespace
|
||||
for i < len(v) && v[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
|
||||
v = v[i:]
|
||||
|
||||
if v == "" {
|
||||
break
|
||||
}
|
||||
|
||||
// Scan to colon to find key
|
||||
i = 0
|
||||
|
||||
for i < len(v) && v[i] != ' ' && v[i] != ':' && v[i] != '"' {
|
||||
i++
|
||||
}
|
||||
|
||||
if i >= len(v) {
|
||||
return nil, newErrorf(ErrTag, "expected `:' after key name, but got end of tag (in `%v`)", x.value)
|
||||
}
|
||||
|
||||
if v[i] != ':' {
|
||||
return nil, newErrorf(ErrTag, "expected `:' after key name, but got `%v' (in `%v`)", v[i], x.value)
|
||||
}
|
||||
|
||||
if i+1 >= len(v) {
|
||||
return nil, newErrorf(ErrTag, "expected `\"' to start tag value at end of tag (in `%v`)", x.value)
|
||||
}
|
||||
|
||||
if v[i+1] != '"' {
|
||||
return nil, newErrorf(ErrTag, "expected `\"' to start tag value, but got `%v' (in `%v`)", v[i+1], x.value)
|
||||
}
|
||||
|
||||
name := v[:i]
|
||||
v = v[i+1:]
|
||||
|
||||
// Scan quoted string to find value
|
||||
i = 1
|
||||
|
||||
for i < len(v) && v[i] != '"' {
|
||||
if v[i] == '\n' {
|
||||
return nil, newErrorf(ErrTag, "unexpected newline in tag value `%v' (in `%v`)", name, x.value)
|
||||
}
|
||||
|
||||
if v[i] == '\\' {
|
||||
i++
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
if i >= len(v) {
|
||||
return nil, newErrorf(ErrTag, "expected end of tag value `\"' at end of tag (in `%v`)", x.value)
|
||||
}
|
||||
|
||||
val, err := strconv.Unquote(v[:i+1])
|
||||
|
||||
if err != nil {
|
||||
return nil, newErrorf(ErrTag, "Malformed value of tag `%v:%v` => %v (in `%v`)", name, v[:i+1], err, x.value)
|
||||
}
|
||||
|
||||
v = v[i+1:]
|
||||
|
||||
ret[name] = append(ret[name], val)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (x *multiTag) Parse() error {
|
||||
vals, err := x.scan()
|
||||
x.cache = vals
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (x *multiTag) cached() map[string][]string {
|
||||
if x.cache == nil {
|
||||
cache, _ := x.scan()
|
||||
|
||||
if cache == nil {
|
||||
cache = make(map[string][]string)
|
||||
}
|
||||
|
||||
x.cache = cache
|
||||
}
|
||||
|
||||
return x.cache
|
||||
}
|
||||
|
||||
func (x *multiTag) Get(key string) string {
|
||||
c := x.cached()
|
||||
|
||||
if v, ok := c[key]; ok {
|
||||
return v[len(v)-1]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *multiTag) GetMany(key string) []string {
|
||||
c := x.cached()
|
||||
return c[key]
|
||||
}
|
||||
|
||||
func (x *multiTag) Set(key string, value string) {
|
||||
c := x.cached()
|
||||
c[key] = []string{value}
|
||||
}
|
||||
|
||||
func (x *multiTag) SetMany(key string, value []string) {
|
||||
c := x.cached()
|
||||
c[key] = value
|
||||
}
|
456
vendor/src/github.com/jessevdk/go-flags/option.go
vendored
456
vendor/src/github.com/jessevdk/go-flags/option.go
vendored
@ -1,456 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Option flag information. Contains a description of the option, short and
|
||||
// long name as well as a default value and whether an argument for this
|
||||
// flag is optional.
|
||||
type Option struct {
|
||||
// The description of the option flag. This description is shown
|
||||
// automatically in the built-in help.
|
||||
Description string
|
||||
|
||||
// The short name of the option (a single character). If not 0, the
|
||||
// option flag can be 'activated' using -<ShortName>. Either ShortName
|
||||
// or LongName needs to be non-empty.
|
||||
ShortName rune
|
||||
|
||||
// The long name of the option. If not "", the option flag can be
|
||||
// activated using --<LongName>. Either ShortName or LongName needs
|
||||
// to be non-empty.
|
||||
LongName string
|
||||
|
||||
// The default value of the option.
|
||||
Default []string
|
||||
|
||||
// The optional environment default value key name.
|
||||
EnvDefaultKey string
|
||||
|
||||
// The optional delimiter string for EnvDefaultKey values.
|
||||
EnvDefaultDelim string
|
||||
|
||||
// If true, specifies that the argument to an option flag is optional.
|
||||
// When no argument to the flag is specified on the command line, the
|
||||
// value of OptionalValue will be set in the field this option represents.
|
||||
// This is only valid for non-boolean options.
|
||||
OptionalArgument bool
|
||||
|
||||
// The optional value of the option. The optional value is used when
|
||||
// the option flag is marked as having an OptionalArgument. This means
|
||||
// that when the flag is specified, but no option argument is given,
|
||||
// the value of the field this option represents will be set to
|
||||
// OptionalValue. This is only valid for non-boolean options.
|
||||
OptionalValue []string
|
||||
|
||||
// If true, the option _must_ be specified on the command line. If the
|
||||
// option is not specified, the parser will generate an ErrRequired type
|
||||
// error.
|
||||
Required bool
|
||||
|
||||
// A name for the value of an option shown in the Help as --flag [ValueName]
|
||||
ValueName string
|
||||
|
||||
// A mask value to show in the help instead of the default value. This
|
||||
// is useful for hiding sensitive information in the help, such as
|
||||
// passwords.
|
||||
DefaultMask string
|
||||
|
||||
// If non empty, only a certain set of values is allowed for an option.
|
||||
Choices []string
|
||||
|
||||
// If true, the option is not displayed in the help or man page
|
||||
Hidden bool
|
||||
|
||||
// The group which the option belongs to
|
||||
group *Group
|
||||
|
||||
// The struct field which the option represents.
|
||||
field reflect.StructField
|
||||
|
||||
// The struct field value which the option represents.
|
||||
value reflect.Value
|
||||
|
||||
// Determines if the option will be always quoted in the INI output
|
||||
iniQuote bool
|
||||
|
||||
tag multiTag
|
||||
isSet bool
|
||||
isSetDefault bool
|
||||
preventDefault bool
|
||||
|
||||
defaultLiteral string
|
||||
}
|
||||
|
||||
// LongNameWithNamespace returns the option's long name with the group namespaces
|
||||
// prepended by walking up the option's group tree. Namespaces and the long name
|
||||
// itself are separated by the parser's namespace delimiter. If the long name is
|
||||
// empty an empty string is returned.
|
||||
func (option *Option) LongNameWithNamespace() string {
|
||||
if len(option.LongName) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// fetch the namespace delimiter from the parser which is always at the
|
||||
// end of the group hierarchy
|
||||
namespaceDelimiter := ""
|
||||
g := option.group
|
||||
|
||||
for {
|
||||
if p, ok := g.parent.(*Parser); ok {
|
||||
namespaceDelimiter = p.NamespaceDelimiter
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
switch i := g.parent.(type) {
|
||||
case *Command:
|
||||
g = i.Group
|
||||
case *Group:
|
||||
g = i
|
||||
}
|
||||
}
|
||||
|
||||
// concatenate long name with namespace
|
||||
longName := option.LongName
|
||||
g = option.group
|
||||
|
||||
for g != nil {
|
||||
if g.Namespace != "" {
|
||||
longName = g.Namespace + namespaceDelimiter + longName
|
||||
}
|
||||
|
||||
switch i := g.parent.(type) {
|
||||
case *Command:
|
||||
g = i.Group
|
||||
case *Group:
|
||||
g = i
|
||||
case *Parser:
|
||||
g = nil
|
||||
}
|
||||
}
|
||||
|
||||
return longName
|
||||
}
|
||||
|
||||
// String converts an option to a human friendly readable string describing the
|
||||
// option.
|
||||
func (option *Option) String() string {
|
||||
var s string
|
||||
var short string
|
||||
|
||||
if option.ShortName != 0 {
|
||||
data := make([]byte, utf8.RuneLen(option.ShortName))
|
||||
utf8.EncodeRune(data, option.ShortName)
|
||||
short = string(data)
|
||||
|
||||
if len(option.LongName) != 0 {
|
||||
s = fmt.Sprintf("%s%s, %s%s",
|
||||
string(defaultShortOptDelimiter), short,
|
||||
defaultLongOptDelimiter, option.LongNameWithNamespace())
|
||||
} else {
|
||||
s = fmt.Sprintf("%s%s", string(defaultShortOptDelimiter), short)
|
||||
}
|
||||
} else if len(option.LongName) != 0 {
|
||||
s = fmt.Sprintf("%s%s", defaultLongOptDelimiter, option.LongNameWithNamespace())
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Value returns the option value as an interface{}.
|
||||
func (option *Option) Value() interface{} {
|
||||
return option.value.Interface()
|
||||
}
|
||||
|
||||
// Field returns the reflect struct field of the option.
|
||||
func (option *Option) Field() reflect.StructField {
|
||||
return option.field
|
||||
}
|
||||
|
||||
// IsSet returns true if option has been set
|
||||
func (option *Option) IsSet() bool {
|
||||
return option.isSet
|
||||
}
|
||||
|
||||
// Set the value of an option to the specified value. An error will be returned
|
||||
// if the specified value could not be converted to the corresponding option
|
||||
// value type.
|
||||
func (option *Option) set(value *string) error {
|
||||
kind := option.value.Type().Kind()
|
||||
|
||||
if (kind == reflect.Map || kind == reflect.Slice) && !option.isSet {
|
||||
option.empty()
|
||||
}
|
||||
|
||||
option.isSet = true
|
||||
option.preventDefault = true
|
||||
|
||||
if len(option.Choices) != 0 {
|
||||
found := false
|
||||
|
||||
for _, choice := range option.Choices {
|
||||
if choice == *value {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
allowed := strings.Join(option.Choices[0:len(option.Choices)-1], ", ")
|
||||
|
||||
if len(option.Choices) > 1 {
|
||||
allowed += " or " + option.Choices[len(option.Choices)-1]
|
||||
}
|
||||
|
||||
return newErrorf(ErrInvalidChoice,
|
||||
"Invalid value `%s' for option `%s'. Allowed values are: %s",
|
||||
*value, option, allowed)
|
||||
}
|
||||
}
|
||||
|
||||
if option.isFunc() {
|
||||
return option.call(value)
|
||||
} else if value != nil {
|
||||
return convert(*value, option.value, option.tag)
|
||||
}
|
||||
|
||||
return convert("", option.value, option.tag)
|
||||
}
|
||||
|
||||
func (option *Option) canCli() bool {
|
||||
return option.ShortName != 0 || len(option.LongName) != 0
|
||||
}
|
||||
|
||||
func (option *Option) canArgument() bool {
|
||||
if u := option.isUnmarshaler(); u != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return !option.isBool()
|
||||
}
|
||||
|
||||
func (option *Option) emptyValue() reflect.Value {
|
||||
tp := option.value.Type()
|
||||
|
||||
if tp.Kind() == reflect.Map {
|
||||
return reflect.MakeMap(tp)
|
||||
}
|
||||
|
||||
return reflect.Zero(tp)
|
||||
}
|
||||
|
||||
func (option *Option) empty() {
|
||||
if !option.isFunc() {
|
||||
option.value.Set(option.emptyValue())
|
||||
}
|
||||
}
|
||||
|
||||
func (option *Option) clearDefault() {
|
||||
usedDefault := option.Default
|
||||
|
||||
if envKey := option.EnvDefaultKey; envKey != "" {
|
||||
// os.Getenv() makes no distinction between undefined and
|
||||
// empty values, so we use syscall.Getenv()
|
||||
if value, ok := syscall.Getenv(envKey); ok {
|
||||
if option.EnvDefaultDelim != "" {
|
||||
usedDefault = strings.Split(value,
|
||||
option.EnvDefaultDelim)
|
||||
} else {
|
||||
usedDefault = []string{value}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
option.isSetDefault = true
|
||||
|
||||
if len(usedDefault) > 0 {
|
||||
option.empty()
|
||||
|
||||
for _, d := range usedDefault {
|
||||
option.set(&d)
|
||||
option.isSetDefault = true
|
||||
}
|
||||
} else {
|
||||
tp := option.value.Type()
|
||||
|
||||
switch tp.Kind() {
|
||||
case reflect.Map:
|
||||
if option.value.IsNil() {
|
||||
option.empty()
|
||||
}
|
||||
case reflect.Slice:
|
||||
if option.value.IsNil() {
|
||||
option.empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (option *Option) valueIsDefault() bool {
|
||||
// Check if the value of the option corresponds to its
|
||||
// default value
|
||||
emptyval := option.emptyValue()
|
||||
|
||||
checkvalptr := reflect.New(emptyval.Type())
|
||||
checkval := reflect.Indirect(checkvalptr)
|
||||
|
||||
checkval.Set(emptyval)
|
||||
|
||||
if len(option.Default) != 0 {
|
||||
for _, v := range option.Default {
|
||||
convert(v, checkval, option.tag)
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.DeepEqual(option.value.Interface(), checkval.Interface())
|
||||
}
|
||||
|
||||
func (option *Option) isUnmarshaler() Unmarshaler {
|
||||
v := option.value
|
||||
|
||||
for {
|
||||
if !v.CanInterface() {
|
||||
break
|
||||
}
|
||||
|
||||
i := v.Interface()
|
||||
|
||||
if u, ok := i.(Unmarshaler); ok {
|
||||
return u
|
||||
}
|
||||
|
||||
if !v.CanAddr() {
|
||||
break
|
||||
}
|
||||
|
||||
v = v.Addr()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (option *Option) isBool() bool {
|
||||
tp := option.value.Type()
|
||||
|
||||
for {
|
||||
switch tp.Kind() {
|
||||
case reflect.Slice, reflect.Ptr:
|
||||
tp = tp.Elem()
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.Func:
|
||||
return tp.NumIn() == 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (option *Option) isSignedNumber() bool {
|
||||
tp := option.value.Type()
|
||||
|
||||
for {
|
||||
switch tp.Kind() {
|
||||
case reflect.Slice, reflect.Ptr:
|
||||
tp = tp.Elem()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (option *Option) isFunc() bool {
|
||||
return option.value.Type().Kind() == reflect.Func
|
||||
}
|
||||
|
||||
func (option *Option) call(value *string) error {
|
||||
var retval []reflect.Value
|
||||
|
||||
if value == nil {
|
||||
retval = option.value.Call(nil)
|
||||
} else {
|
||||
tp := option.value.Type().In(0)
|
||||
|
||||
val := reflect.New(tp)
|
||||
val = reflect.Indirect(val)
|
||||
|
||||
if err := convert(*value, val, option.tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
retval = option.value.Call([]reflect.Value{val})
|
||||
}
|
||||
|
||||
if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() {
|
||||
if retval[0].Interface() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return retval[0].Interface().(error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (option *Option) updateDefaultLiteral() {
|
||||
defs := option.Default
|
||||
def := ""
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
option.defaultLiteral = def
|
||||
}
|
||||
|
||||
func (option *Option) shortAndLongName() string {
|
||||
ret := &bytes.Buffer{}
|
||||
|
||||
if option.ShortName != 0 {
|
||||
ret.WriteRune(defaultShortOptDelimiter)
|
||||
ret.WriteRune(option.ShortName)
|
||||
}
|
||||
|
||||
if len(option.LongName) != 0 {
|
||||
if option.ShortName != 0 {
|
||||
ret.WriteRune('/')
|
||||
}
|
||||
|
||||
ret.WriteString(option.LongName)
|
||||
}
|
||||
|
||||
return ret.String()
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPassDoubleDash(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, PassDoubleDash)
|
||||
ret, err := p.ParseArgs([]string{"-v", "--", "-v", "-g"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
assertStringArray(t, ret, []string{"-v", "-g"})
|
||||
}
|
||||
|
||||
func TestPassAfterNonOption(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, PassAfterNonOption)
|
||||
ret, err := p.ParseArgs([]string{"-v", "arg", "-v", "-g"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
assertStringArray(t, ret, []string{"arg", "-v", "-g"})
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
// +build !windows forceposix
|
||||
|
||||
package flags
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultShortOptDelimiter = '-'
|
||||
defaultLongOptDelimiter = "--"
|
||||
defaultNameArgDelimiter = '='
|
||||
)
|
||||
|
||||
func argumentStartsOption(arg string) bool {
|
||||
return len(arg) > 0 && arg[0] == '-'
|
||||
}
|
||||
|
||||
func argumentIsOption(arg string) bool {
|
||||
if len(arg) > 1 && arg[0] == '-' && arg[1] != '-' {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(arg) > 2 && arg[0] == '-' && arg[1] == '-' && arg[2] != '-' {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// stripOptionPrefix returns the option without the prefix and whether or
|
||||
// not the option is a long option or not.
|
||||
func stripOptionPrefix(optname string) (prefix string, name string, islong bool) {
|
||||
if strings.HasPrefix(optname, "--") {
|
||||
return "--", optname[2:], true
|
||||
} else if strings.HasPrefix(optname, "-") {
|
||||
return "-", optname[1:], false
|
||||
}
|
||||
|
||||
return "", optname, false
|
||||
}
|
||||
|
||||
// splitOption attempts to split the passed option into a name and an argument.
|
||||
// When there is no argument specified, nil will be returned for it.
|
||||
func splitOption(prefix string, option string, islong bool) (string, string, *string) {
|
||||
pos := strings.Index(option, "=")
|
||||
|
||||
if (islong && pos >= 0) || (!islong && pos == 1) {
|
||||
rest := option[pos+1:]
|
||||
return option[:pos], "=", &rest
|
||||
}
|
||||
|
||||
return option, "", nil
|
||||
}
|
||||
|
||||
// addHelpGroup adds a new group that contains default help parameters.
|
||||
func (c *Command) addHelpGroup(showHelp func() error) *Group {
|
||||
var help struct {
|
||||
ShowHelp func() error `short:"h" long:"help" description:"Show this help message"`
|
||||
}
|
||||
|
||||
help.ShowHelp = showHelp
|
||||
ret, _ := c.AddGroup("Help Options", "", &help)
|
||||
ret.isBuiltinHelp = true
|
||||
|
||||
return ret
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
// +build !forceposix
|
||||
|
||||
package flags
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Windows uses a front slash for both short and long options. Also it uses
|
||||
// a colon for name/argument delimter.
|
||||
const (
|
||||
defaultShortOptDelimiter = '/'
|
||||
defaultLongOptDelimiter = "/"
|
||||
defaultNameArgDelimiter = ':'
|
||||
)
|
||||
|
||||
func argumentStartsOption(arg string) bool {
|
||||
return len(arg) > 0 && (arg[0] == '-' || arg[0] == '/')
|
||||
}
|
||||
|
||||
func argumentIsOption(arg string) bool {
|
||||
// Windows-style options allow front slash for the option
|
||||
// delimiter.
|
||||
if len(arg) > 1 && arg[0] == '/' {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(arg) > 1 && arg[0] == '-' && arg[1] != '-' {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(arg) > 2 && arg[0] == '-' && arg[1] == '-' && arg[2] != '-' {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// stripOptionPrefix returns the option without the prefix and whether or
|
||||
// not the option is a long option or not.
|
||||
func stripOptionPrefix(optname string) (prefix string, name string, islong bool) {
|
||||
// Determine if the argument is a long option or not. Windows
|
||||
// typically supports both long and short options with a single
|
||||
// front slash as the option delimiter, so handle this situation
|
||||
// nicely.
|
||||
possplit := 0
|
||||
|
||||
if strings.HasPrefix(optname, "--") {
|
||||
possplit = 2
|
||||
islong = true
|
||||
} else if strings.HasPrefix(optname, "-") {
|
||||
possplit = 1
|
||||
islong = false
|
||||
} else if strings.HasPrefix(optname, "/") {
|
||||
possplit = 1
|
||||
islong = len(optname) > 2
|
||||
}
|
||||
|
||||
return optname[:possplit], optname[possplit:], islong
|
||||
}
|
||||
|
||||
// splitOption attempts to split the passed option into a name and an argument.
|
||||
// When there is no argument specified, nil will be returned for it.
|
||||
func splitOption(prefix string, option string, islong bool) (string, string, *string) {
|
||||
if len(option) == 0 {
|
||||
return option, "", nil
|
||||
}
|
||||
|
||||
// Windows typically uses a colon for the option name and argument
|
||||
// delimiter while POSIX typically uses an equals. Support both styles,
|
||||
// but don't allow the two to be mixed. That is to say /foo:bar and
|
||||
// --foo=bar are acceptable, but /foo=bar and --foo:bar are not.
|
||||
var pos int
|
||||
var sp string
|
||||
|
||||
if prefix == "/" {
|
||||
sp = ":"
|
||||
pos = strings.Index(option, sp)
|
||||
} else if len(prefix) > 0 {
|
||||
sp = "="
|
||||
pos = strings.Index(option, sp)
|
||||
}
|
||||
|
||||
if (islong && pos >= 0) || (!islong && pos == 1) {
|
||||
rest := option[pos+1:]
|
||||
return option[:pos], sp, &rest
|
||||
}
|
||||
|
||||
return option, "", nil
|
||||
}
|
||||
|
||||
// addHelpGroup adds a new group that contains default help parameters.
|
||||
func (c *Command) addHelpGroup(showHelp func() error) *Group {
|
||||
// Windows CLI applications typically use /? for help, so make both
|
||||
// that available as well as the POSIX style h and help.
|
||||
var help struct {
|
||||
ShowHelpWindows func() error `short:"?" description:"Show this help message"`
|
||||
ShowHelpPosix func() error `short:"h" long:"help" description:"Show this help message"`
|
||||
}
|
||||
|
||||
help.ShowHelpWindows = showHelp
|
||||
help.ShowHelpPosix = showHelp
|
||||
|
||||
ret, _ := c.AddGroup("Help Options", "", &help)
|
||||
ret.isBuiltinHelp = true
|
||||
|
||||
return ret
|
||||
}
|
693
vendor/src/github.com/jessevdk/go-flags/parser.go
vendored
693
vendor/src/github.com/jessevdk/go-flags/parser.go
vendored
@ -1,693 +0,0 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// A Parser provides command line option parsing. It can contain several
|
||||
// option groups each with their own set of options.
|
||||
type Parser struct {
|
||||
// Embedded, see Command for more information
|
||||
*Command
|
||||
|
||||
// A usage string to be displayed in the help message.
|
||||
Usage string
|
||||
|
||||
// Option flags changing the behavior of the parser.
|
||||
Options Options
|
||||
|
||||
// NamespaceDelimiter separates group namespaces and option long names
|
||||
NamespaceDelimiter string
|
||||
|
||||
// UnknownOptionsHandler is a function which gets called when the parser
|
||||
// encounters an unknown option. The function receives the unknown option
|
||||
// name, a SplitArgument which specifies its value if set with an argument
|
||||
// separator, and the remaining command line arguments.
|
||||
// It should return a new list of remaining arguments to continue parsing,
|
||||
// or an error to indicate a parse failure.
|
||||
UnknownOptionHandler func(option string, arg SplitArgument, args []string) ([]string, error)
|
||||
|
||||
// CompletionHandler is a function gets called to handle the completion of
|
||||
// items. By default, the items are printed and the application is exited.
|
||||
// You can override this default behavior by specifying a custom CompletionHandler.
|
||||
CompletionHandler func(items []Completion)
|
||||
|
||||
// CommandHandler is a function that gets called to handle execution of a
|
||||
// command. By default, the command will simply be executed. This can be
|
||||
// overridden to perform certain actions (such as applying global flags)
|
||||
// just before the command is executed. Note that if you override the
|
||||
// handler it is your responsibility to call the command.Execute function.
|
||||
//
|
||||
// The command passed into CommandHandler may be nil in case there is no
|
||||
// command to be executed when parsing has finished.
|
||||
CommandHandler func(command Commander, args []string) error
|
||||
|
||||
internalError error
|
||||
}
|
||||
|
||||
// SplitArgument represents the argument value of an option that was passed using
|
||||
// an argument separator.
|
||||
type SplitArgument interface {
|
||||
// String returns the option's value as a string, and a boolean indicating
|
||||
// if the option was present.
|
||||
Value() (string, bool)
|
||||
}
|
||||
|
||||
type strArgument struct {
|
||||
value *string
|
||||
}
|
||||
|
||||
func (s strArgument) Value() (string, bool) {
|
||||
if s.value == nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return *s.value, true
|
||||
}
|
||||
|
||||
// Options provides parser options that change the behavior of the option
|
||||
// parser.
|
||||
type Options uint
|
||||
|
||||
const (
|
||||
// None indicates no options.
|
||||
None Options = 0
|
||||
|
||||
// HelpFlag adds a default Help Options group to the parser containing
|
||||
// -h and --help options. When either -h or --help is specified on the
|
||||
// command line, the parser will return the special error of type
|
||||
// ErrHelp. When PrintErrors is also specified, then the help message
|
||||
// will also be automatically printed to os.Stderr.
|
||||
HelpFlag = 1 << iota
|
||||
|
||||
// PassDoubleDash passes all arguments after a double dash, --, as
|
||||
// remaining command line arguments (i.e. they will not be parsed for
|
||||
// flags).
|
||||
PassDoubleDash
|
||||
|
||||
// IgnoreUnknown ignores any unknown options and passes them as
|
||||
// remaining command line arguments instead of generating an error.
|
||||
IgnoreUnknown
|
||||
|
||||
// PrintErrors prints any errors which occurred during parsing to
|
||||
// os.Stderr.
|
||||
PrintErrors
|
||||
|
||||
// PassAfterNonOption passes all arguments after the first non option
|
||||
// as remaining command line arguments. This is equivalent to strict
|
||||
// POSIX processing.
|
||||
PassAfterNonOption
|
||||
|
||||
// Default is a convenient default set of options which should cover
|
||||
// most of the uses of the flags package.
|
||||
Default = HelpFlag | PrintErrors | PassDoubleDash
|
||||
)
|
||||
|
||||
type parseState struct {
|
||||
arg string
|
||||
args []string
|
||||
retargs []string
|
||||
positional []*Arg
|
||||
err error
|
||||
|
||||
command *Command
|
||||
lookup lookup
|
||||
}
|
||||
|
||||
// Parse is a convenience function to parse command line options with default
|
||||
// settings. The provided data is a pointer to a struct representing the
|
||||
// default option group (named "Application Options"). For more control, use
|
||||
// flags.NewParser.
|
||||
func Parse(data interface{}) ([]string, error) {
|
||||
return NewParser(data, Default).Parse()
|
||||
}
|
||||
|
||||
// ParseArgs is a convenience function to parse command line options with default
|
||||
// settings. The provided data is a pointer to a struct representing the
|
||||
// default option group (named "Application Options"). The args argument is
|
||||
// the list of command line arguments to parse. If you just want to parse the
|
||||
// default program command line arguments (i.e. os.Args), then use flags.Parse
|
||||
// instead. For more control, use flags.NewParser.
|
||||
func ParseArgs(data interface{}, args []string) ([]string, error) {
|
||||
return NewParser(data, Default).ParseArgs(args)
|
||||
}
|
||||
|
||||
// NewParser creates a new parser. It uses os.Args[0] as the application
|
||||
// name and then calls Parser.NewNamedParser (see Parser.NewNamedParser for
|
||||
// more details). The provided data is a pointer to a struct representing the
|
||||
// default option group (named "Application Options"), or nil if the default
|
||||
// group should not be added. The options parameter specifies a set of options
|
||||
// for the parser.
|
||||
func NewParser(data interface{}, options Options) *Parser {
|
||||
p := NewNamedParser(path.Base(os.Args[0]), options)
|
||||
|
||||
if data != nil {
|
||||
g, err := p.AddGroup("Application Options", "", data)
|
||||
|
||||
if err == nil {
|
||||
g.parent = p
|
||||
}
|
||||
|
||||
p.internalError = err
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// NewNamedParser creates a new parser. The appname is used to display the
|
||||
// executable name in the built-in help message. Option groups and commands can
|
||||
// be added to this parser by using AddGroup and AddCommand.
|
||||
func NewNamedParser(appname string, options Options) *Parser {
|
||||
p := &Parser{
|
||||
Command: newCommand(appname, "", "", nil),
|
||||
Options: options,
|
||||
NamespaceDelimiter: ".",
|
||||
}
|
||||
|
||||
p.Command.parent = p
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Parse parses the command line arguments from os.Args using Parser.ParseArgs.
|
||||
// For more detailed information see ParseArgs.
|
||||
func (p *Parser) Parse() ([]string, error) {
|
||||
return p.ParseArgs(os.Args[1:])
|
||||
}
|
||||
|
||||
// ParseArgs parses the command line arguments according to the option groups that
|
||||
// were added to the parser. On successful parsing of the arguments, the
|
||||
// remaining, non-option, arguments (if any) are returned. The returned error
|
||||
// indicates a parsing error and can be used with PrintError to display
|
||||
// contextual information on where the error occurred exactly.
|
||||
//
|
||||
// When the common help group has been added (AddHelp) and either -h or --help
|
||||
// was specified in the command line arguments, a help message will be
|
||||
// automatically printed if the PrintErrors option is enabled.
|
||||
// Furthermore, the special error type ErrHelp is returned.
|
||||
// It is up to the caller to exit the program if so desired.
|
||||
func (p *Parser) ParseArgs(args []string) ([]string, error) {
|
||||
if p.internalError != nil {
|
||||
return nil, p.internalError
|
||||
}
|
||||
|
||||
p.eachOption(func(c *Command, g *Group, option *Option) {
|
||||
option.isSet = false
|
||||
option.isSetDefault = false
|
||||
option.updateDefaultLiteral()
|
||||
})
|
||||
|
||||
// Add built-in help group to all commands if necessary
|
||||
if (p.Options & HelpFlag) != None {
|
||||
p.addHelpGroups(p.showBuiltinHelp)
|
||||
}
|
||||
|
||||
compval := os.Getenv("GO_FLAGS_COMPLETION")
|
||||
|
||||
if len(compval) != 0 {
|
||||
comp := &completion{parser: p}
|
||||
items := comp.complete(args)
|
||||
|
||||
if p.CompletionHandler != nil {
|
||||
p.CompletionHandler(items)
|
||||
} else {
|
||||
comp.print(items, compval == "verbose")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
s := &parseState{
|
||||
args: args,
|
||||
retargs: make([]string, 0, len(args)),
|
||||
}
|
||||
|
||||
p.fillParseState(s)
|
||||
|
||||
for !s.eof() {
|
||||
arg := s.pop()
|
||||
|
||||
// When PassDoubleDash is set and we encounter a --, then
|
||||
// simply append all the rest as arguments and break out
|
||||
if (p.Options&PassDoubleDash) != None && arg == "--" {
|
||||
s.addArgs(s.args...)
|
||||
break
|
||||
}
|
||||
|
||||
if !argumentIsOption(arg) {
|
||||
// Note: this also sets s.err, so we can just check for
|
||||
// nil here and use s.err later
|
||||
if p.parseNonOption(s) != nil {
|
||||
break
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
prefix, optname, islong := stripOptionPrefix(arg)
|
||||
optname, _, argument := splitOption(prefix, optname, islong)
|
||||
|
||||
if islong {
|
||||
err = p.parseLong(s, optname, argument)
|
||||
} else {
|
||||
err = p.parseShort(s, optname, argument)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ignoreUnknown := (p.Options & IgnoreUnknown) != None
|
||||
parseErr := wrapError(err)
|
||||
|
||||
if parseErr.Type != ErrUnknownFlag || (!ignoreUnknown && p.UnknownOptionHandler == nil) {
|
||||
s.err = parseErr
|
||||
break
|
||||
}
|
||||
|
||||
if ignoreUnknown {
|
||||
s.addArgs(arg)
|
||||
} else if p.UnknownOptionHandler != nil {
|
||||
modifiedArgs, err := p.UnknownOptionHandler(optname, strArgument{argument}, s.args)
|
||||
|
||||
if err != nil {
|
||||
s.err = err
|
||||
break
|
||||
}
|
||||
|
||||
s.args = modifiedArgs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.err == nil {
|
||||
p.eachOption(func(c *Command, g *Group, option *Option) {
|
||||
if option.preventDefault {
|
||||
return
|
||||
}
|
||||
|
||||
option.clearDefault()
|
||||
})
|
||||
|
||||
s.checkRequired(p)
|
||||
}
|
||||
|
||||
var reterr error
|
||||
|
||||
if s.err != nil {
|
||||
reterr = s.err
|
||||
} else if len(s.command.commands) != 0 && !s.command.SubcommandsOptional {
|
||||
reterr = s.estimateCommand()
|
||||
} else if cmd, ok := s.command.data.(Commander); ok {
|
||||
if p.CommandHandler != nil {
|
||||
reterr = p.CommandHandler(cmd, s.retargs)
|
||||
} else {
|
||||
reterr = cmd.Execute(s.retargs)
|
||||
}
|
||||
} else if p.CommandHandler != nil {
|
||||
reterr = p.CommandHandler(nil, s.retargs)
|
||||
}
|
||||
|
||||
if reterr != nil {
|
||||
var retargs []string
|
||||
|
||||
if ourErr, ok := reterr.(*Error); !ok || ourErr.Type != ErrHelp {
|
||||
retargs = append([]string{s.arg}, s.args...)
|
||||
} else {
|
||||
retargs = s.args
|
||||
}
|
||||
|
||||
return retargs, p.printError(reterr)
|
||||
}
|
||||
|
||||
return s.retargs, nil
|
||||
}
|
||||
|
||||
func (p *parseState) eof() bool {
|
||||
return len(p.args) == 0
|
||||
}
|
||||
|
||||
func (p *parseState) pop() string {
|
||||
if p.eof() {
|
||||
return ""
|
||||
}
|
||||
|
||||
p.arg = p.args[0]
|
||||
p.args = p.args[1:]
|
||||
|
||||
return p.arg
|
||||
}
|
||||
|
||||
func (p *parseState) peek() string {
|
||||
if p.eof() {
|
||||
return ""
|
||||
}
|
||||
|
||||
return p.args[0]
|
||||
}
|
||||
|
||||
func (p *parseState) checkRequired(parser *Parser) error {
|
||||
c := parser.Command
|
||||
|
||||
var required []*Option
|
||||
|
||||
for c != nil {
|
||||
c.eachGroup(func(g *Group) {
|
||||
for _, option := range g.options {
|
||||
if !option.isSet && option.Required {
|
||||
required = append(required, option)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
c = c.Active
|
||||
}
|
||||
|
||||
if len(required) == 0 {
|
||||
if len(p.positional) > 0 {
|
||||
var reqnames []string
|
||||
|
||||
for _, arg := range p.positional {
|
||||
argRequired := (!arg.isRemaining() && p.command.ArgsRequired) || arg.Required != -1 || arg.RequiredMaximum != -1
|
||||
|
||||
if !argRequired {
|
||||
continue
|
||||
}
|
||||
|
||||
if arg.isRemaining() {
|
||||
if arg.value.Len() < arg.Required {
|
||||
var arguments string
|
||||
|
||||
if arg.Required > 1 {
|
||||
arguments = "arguments, but got only " + fmt.Sprintf("%d", arg.value.Len())
|
||||
} else {
|
||||
arguments = "argument"
|
||||
}
|
||||
|
||||
reqnames = append(reqnames, "`"+arg.Name+" (at least "+fmt.Sprintf("%d", arg.Required)+" "+arguments+")`")
|
||||
} else if arg.RequiredMaximum != -1 && arg.value.Len() > arg.RequiredMaximum {
|
||||
if arg.RequiredMaximum == 0 {
|
||||
reqnames = append(reqnames, "`"+arg.Name+" (zero arguments)`")
|
||||
} else {
|
||||
var arguments string
|
||||
|
||||
if arg.RequiredMaximum > 1 {
|
||||
arguments = "arguments, but got " + fmt.Sprintf("%d", arg.value.Len())
|
||||
} else {
|
||||
arguments = "argument"
|
||||
}
|
||||
|
||||
reqnames = append(reqnames, "`"+arg.Name+" (at most "+fmt.Sprintf("%d", arg.RequiredMaximum)+" "+arguments+")`")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reqnames = append(reqnames, "`"+arg.Name+"`")
|
||||
}
|
||||
}
|
||||
|
||||
if len(reqnames) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var msg string
|
||||
|
||||
if len(reqnames) == 1 {
|
||||
msg = fmt.Sprintf("the required argument %s was not provided", reqnames[0])
|
||||
} else {
|
||||
msg = fmt.Sprintf("the required arguments %s and %s were not provided",
|
||||
strings.Join(reqnames[:len(reqnames)-1], ", "), reqnames[len(reqnames)-1])
|
||||
}
|
||||
|
||||
p.err = newError(ErrRequired, msg)
|
||||
return p.err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(required))
|
||||
|
||||
for _, k := range required {
|
||||
names = append(names, "`"+k.String()+"'")
|
||||
}
|
||||
|
||||
sort.Strings(names)
|
||||
|
||||
var msg string
|
||||
|
||||
if len(names) == 1 {
|
||||
msg = fmt.Sprintf("the required flag %s was not specified", names[0])
|
||||
} else {
|
||||
msg = fmt.Sprintf("the required flags %s and %s were not specified",
|
||||
strings.Join(names[:len(names)-1], ", "), names[len(names)-1])
|
||||
}
|
||||
|
||||
p.err = newError(ErrRequired, msg)
|
||||
return p.err
|
||||
}
|
||||
|
||||
func (p *parseState) estimateCommand() error {
|
||||
commands := p.command.sortedVisibleCommands()
|
||||
cmdnames := make([]string, len(commands))
|
||||
|
||||
for i, v := range commands {
|
||||
cmdnames[i] = v.Name
|
||||
}
|
||||
|
||||
var msg string
|
||||
var errtype ErrorType
|
||||
|
||||
if len(p.retargs) != 0 {
|
||||
c, l := closestChoice(p.retargs[0], cmdnames)
|
||||
msg = fmt.Sprintf("Unknown command `%s'", p.retargs[0])
|
||||
errtype = ErrUnknownCommand
|
||||
|
||||
if float32(l)/float32(len(c)) < 0.5 {
|
||||
msg = fmt.Sprintf("%s, did you mean `%s'?", msg, c)
|
||||
} else if len(cmdnames) == 1 {
|
||||
msg = fmt.Sprintf("%s. You should use the %s command",
|
||||
msg,
|
||||
cmdnames[0])
|
||||
} else {
|
||||
msg = fmt.Sprintf("%s. Please specify one command of: %s or %s",
|
||||
msg,
|
||||
strings.Join(cmdnames[:len(cmdnames)-1], ", "),
|
||||
cmdnames[len(cmdnames)-1])
|
||||
}
|
||||
} else {
|
||||
errtype = ErrCommandRequired
|
||||
|
||||
if len(cmdnames) == 1 {
|
||||
msg = fmt.Sprintf("Please specify the %s command", cmdnames[0])
|
||||
} else {
|
||||
msg = fmt.Sprintf("Please specify one command of: %s or %s",
|
||||
strings.Join(cmdnames[:len(cmdnames)-1], ", "),
|
||||
cmdnames[len(cmdnames)-1])
|
||||
}
|
||||
}
|
||||
|
||||
return newError(errtype, msg)
|
||||
}
|
||||
|
||||
func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (err error) {
|
||||
if !option.canArgument() {
|
||||
if argument != nil {
|
||||
return newErrorf(ErrNoArgumentForBool, "bool flag `%s' cannot have an argument", option)
|
||||
}
|
||||
|
||||
err = option.set(nil)
|
||||
} else if argument != nil || (canarg && !s.eof()) {
|
||||
var arg string
|
||||
|
||||
if argument != nil {
|
||||
arg = *argument
|
||||
} else {
|
||||
arg = s.pop()
|
||||
|
||||
if argumentIsOption(arg) && !(option.isSignedNumber() && len(arg) > 1 && arg[0] == '-' && arg[1] >= '0' && arg[1] <= '9') {
|
||||
return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got option `%s'", option, arg)
|
||||
} else if p.Options&PassDoubleDash != 0 && arg == "--" {
|
||||
return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got double dash `--'", option)
|
||||
}
|
||||
}
|
||||
|
||||
if option.tag.Get("unquote") != "false" {
|
||||
arg, err = unquoteIfPossible(arg)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = option.set(&arg)
|
||||
}
|
||||
} else if option.OptionalArgument {
|
||||
option.empty()
|
||||
|
||||
for _, v := range option.OptionalValue {
|
||||
err = option.set(&v)
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = newErrorf(ErrExpectedArgument, "expected argument for flag `%s'", option)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if _, ok := err.(*Error); !ok {
|
||||
err = newErrorf(ErrMarshal, "invalid argument for flag `%s' (expected %s): %s",
|
||||
option,
|
||||
option.value.Type(),
|
||||
err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Parser) parseLong(s *parseState, name string, argument *string) error {
|
||||
if option := s.lookup.longNames[name]; option != nil {
|
||||
// Only long options that are required can consume an argument
|
||||
// from the argument list
|
||||
canarg := !option.OptionalArgument
|
||||
|
||||
return p.parseOption(s, name, option, canarg, argument)
|
||||
}
|
||||
|
||||
return newErrorf(ErrUnknownFlag, "unknown flag `%s'", name)
|
||||
}
|
||||
|
||||
func (p *Parser) splitShortConcatArg(s *parseState, optname string) (string, *string) {
|
||||
c, n := utf8.DecodeRuneInString(optname)
|
||||
|
||||
if n == len(optname) {
|
||||
return optname, nil
|
||||
}
|
||||
|
||||
first := string(c)
|
||||
|
||||
if option := s.lookup.shortNames[first]; option != nil && option.canArgument() {
|
||||
arg := optname[n:]
|
||||
return first, &arg
|
||||
}
|
||||
|
||||
return optname, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseShort(s *parseState, optname string, argument *string) error {
|
||||
if argument == nil {
|
||||
optname, argument = p.splitShortConcatArg(s, optname)
|
||||
}
|
||||
|
||||
for i, c := range optname {
|
||||
shortname := string(c)
|
||||
|
||||
if option := s.lookup.shortNames[shortname]; option != nil {
|
||||
// Only the last short argument can consume an argument from
|
||||
// the arguments list, and only if it's non optional
|
||||
canarg := (i+utf8.RuneLen(c) == len(optname)) && !option.OptionalArgument
|
||||
|
||||
if err := p.parseOption(s, shortname, option, canarg, argument); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return newErrorf(ErrUnknownFlag, "unknown flag `%s'", shortname)
|
||||
}
|
||||
|
||||
// Only the first option can have a concatted argument, so just
|
||||
// clear argument here
|
||||
argument = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parseState) addArgs(args ...string) error {
|
||||
for len(p.positional) > 0 && len(args) > 0 {
|
||||
arg := p.positional[0]
|
||||
|
||||
if err := convert(args[0], arg.value, arg.tag); err != nil {
|
||||
p.err = err
|
||||
return err
|
||||
}
|
||||
|
||||
if !arg.isRemaining() {
|
||||
p.positional = p.positional[1:]
|
||||
}
|
||||
|
||||
args = args[1:]
|
||||
}
|
||||
|
||||
p.retargs = append(p.retargs, args...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseNonOption(s *parseState) error {
|
||||
if len(s.positional) > 0 {
|
||||
return s.addArgs(s.arg)
|
||||
}
|
||||
|
||||
if len(s.command.commands) > 0 && len(s.retargs) == 0 {
|
||||
if cmd := s.lookup.commands[s.arg]; cmd != nil {
|
||||
s.command.Active = cmd
|
||||
cmd.fillParseState(s)
|
||||
|
||||
return nil
|
||||
} else if !s.command.SubcommandsOptional {
|
||||
s.addArgs(s.arg)
|
||||
return newErrorf(ErrUnknownCommand, "Unknown command `%s'", s.arg)
|
||||
}
|
||||
}
|
||||
|
||||
if (p.Options & PassAfterNonOption) != None {
|
||||
// If PassAfterNonOption is set then all remaining arguments
|
||||
// are considered positional
|
||||
if err := s.addArgs(s.arg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.addArgs(s.args...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.args = []string{}
|
||||
} else {
|
||||
return s.addArgs(s.arg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) showBuiltinHelp() error {
|
||||
var b bytes.Buffer
|
||||
|
||||
p.WriteHelp(&b)
|
||||
return newError(ErrHelp, b.String())
|
||||
}
|
||||
|
||||
func (p *Parser) printError(err error) error {
|
||||
if err != nil && (p.Options&PrintErrors) != None {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Parser) clearIsSet() {
|
||||
p.eachCommand(func(c *Command) {
|
||||
c.eachGroup(func(g *Group) {
|
||||
for _, option := range g.options {
|
||||
option.isSet = false
|
||||
}
|
||||
})
|
||||
}, true)
|
||||
}
|
@ -1,612 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type defaultOptions struct {
|
||||
Int int `long:"i"`
|
||||
IntDefault int `long:"id" default:"1"`
|
||||
|
||||
Float64 float64 `long:"f"`
|
||||
Float64Default float64 `long:"fd" default:"-3.14"`
|
||||
|
||||
NumericFlag bool `short:"3"`
|
||||
|
||||
String string `long:"str"`
|
||||
StringDefault string `long:"strd" default:"abc"`
|
||||
StringNotUnquoted string `long:"strnot" unquote:"false"`
|
||||
|
||||
Time time.Duration `long:"t"`
|
||||
TimeDefault time.Duration `long:"td" default:"1m"`
|
||||
|
||||
Map map[string]int `long:"m"`
|
||||
MapDefault map[string]int `long:"md" default:"a:1"`
|
||||
|
||||
Slice []int `long:"s"`
|
||||
SliceDefault []int `long:"sd" default:"1" default:"2"`
|
||||
}
|
||||
|
||||
func TestDefaults(t *testing.T) {
|
||||
var tests = []struct {
|
||||
msg string
|
||||
args []string
|
||||
expected defaultOptions
|
||||
}{
|
||||
{
|
||||
msg: "no arguments, expecting default values",
|
||||
args: []string{},
|
||||
expected: defaultOptions{
|
||||
Int: 0,
|
||||
IntDefault: 1,
|
||||
|
||||
Float64: 0.0,
|
||||
Float64Default: -3.14,
|
||||
|
||||
NumericFlag: false,
|
||||
|
||||
String: "",
|
||||
StringDefault: "abc",
|
||||
|
||||
Time: 0,
|
||||
TimeDefault: time.Minute,
|
||||
|
||||
Map: map[string]int{},
|
||||
MapDefault: map[string]int{"a": 1},
|
||||
|
||||
Slice: []int{},
|
||||
SliceDefault: []int{1, 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "non-zero value arguments, expecting overwritten arguments",
|
||||
args: []string{"--i=3", "--id=3", "--f=-2.71", "--fd=2.71", "-3", "--str=def", "--strd=def", "--t=3ms", "--td=3ms", "--m=c:3", "--md=c:3", "--s=3", "--sd=3"},
|
||||
expected: defaultOptions{
|
||||
Int: 3,
|
||||
IntDefault: 3,
|
||||
|
||||
Float64: -2.71,
|
||||
Float64Default: 2.71,
|
||||
|
||||
NumericFlag: true,
|
||||
|
||||
String: "def",
|
||||
StringDefault: "def",
|
||||
|
||||
Time: 3 * time.Millisecond,
|
||||
TimeDefault: 3 * time.Millisecond,
|
||||
|
||||
Map: map[string]int{"c": 3},
|
||||
MapDefault: map[string]int{"c": 3},
|
||||
|
||||
Slice: []int{3},
|
||||
SliceDefault: []int{3},
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "zero value arguments, expecting overwritten arguments",
|
||||
args: []string{"--i=0", "--id=0", "--f=0", "--fd=0", "--str", "", "--strd=\"\"", "--t=0ms", "--td=0s", "--m=:0", "--md=:0", "--s=0", "--sd=0"},
|
||||
expected: defaultOptions{
|
||||
Int: 0,
|
||||
IntDefault: 0,
|
||||
|
||||
Float64: 0,
|
||||
Float64Default: 0,
|
||||
|
||||
String: "",
|
||||
StringDefault: "",
|
||||
|
||||
Time: 0,
|
||||
TimeDefault: 0,
|
||||
|
||||
Map: map[string]int{"": 0},
|
||||
MapDefault: map[string]int{"": 0},
|
||||
|
||||
Slice: []int{0},
|
||||
SliceDefault: []int{0},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
var opts defaultOptions
|
||||
|
||||
_, err := ParseArgs(&opts, test.args)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:\nUnexpected error: %v", test.msg, err)
|
||||
}
|
||||
|
||||
if opts.Slice == nil {
|
||||
opts.Slice = []int{}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(opts, test.expected) {
|
||||
t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoDefaultsForBools(t *testing.T) {
|
||||
var opts struct {
|
||||
DefaultBool bool `short:"d" default:"true"`
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
assertParseFail(t, ErrInvalidTag, "boolean flag `/d' may not have default values, they always default to `false' and can only be turned on", &opts)
|
||||
} else {
|
||||
assertParseFail(t, ErrInvalidTag, "boolean flag `-d' may not have default values, they always default to `false' and can only be turned on", &opts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnquoting(t *testing.T) {
|
||||
var tests = []struct {
|
||||
arg string
|
||||
err error
|
||||
value string
|
||||
}{
|
||||
{
|
||||
arg: "\"abc",
|
||||
err: strconv.ErrSyntax,
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
arg: "\"\"abc\"",
|
||||
err: strconv.ErrSyntax,
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
arg: "\"abc\"",
|
||||
err: nil,
|
||||
value: "abc",
|
||||
},
|
||||
{
|
||||
arg: "\"\\\"abc\\\"\"",
|
||||
err: nil,
|
||||
value: "\"abc\"",
|
||||
},
|
||||
{
|
||||
arg: "\"\\\"abc\"",
|
||||
err: nil,
|
||||
value: "\"abc",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
var opts defaultOptions
|
||||
|
||||
for _, delimiter := range []bool{false, true} {
|
||||
p := NewParser(&opts, None)
|
||||
|
||||
var err error
|
||||
if delimiter {
|
||||
_, err = p.ParseArgs([]string{"--str=" + test.arg, "--strnot=" + test.arg})
|
||||
} else {
|
||||
_, err = p.ParseArgs([]string{"--str", test.arg, "--strnot", test.arg})
|
||||
}
|
||||
|
||||
if test.err == nil {
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if test.value != opts.String {
|
||||
t.Fatalf("Expected String to be %q but got %q", test.value, opts.String)
|
||||
}
|
||||
if q := strconv.Quote(test.value); q != opts.StringNotUnquoted {
|
||||
t.Fatalf("Expected StringDefault to be %q but got %q", q, opts.StringNotUnquoted)
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error")
|
||||
} else if e, ok := err.(*Error); ok {
|
||||
if strings.HasPrefix(e.Message, test.err.Error()) {
|
||||
t.Fatalf("Expected error message to end with %q but got %v", test.err.Error(), e.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EnvRestorer keeps a copy of a set of env variables and can restore the env from them
|
||||
type EnvRestorer struct {
|
||||
env map[string]string
|
||||
}
|
||||
|
||||
func (r *EnvRestorer) Restore() {
|
||||
os.Clearenv()
|
||||
|
||||
for k, v := range r.env {
|
||||
os.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// EnvSnapshot returns a snapshot of the currently set env variables
|
||||
func EnvSnapshot() *EnvRestorer {
|
||||
r := EnvRestorer{make(map[string]string)}
|
||||
|
||||
for _, kv := range os.Environ() {
|
||||
parts := strings.SplitN(kv, "=", 2)
|
||||
|
||||
if len(parts) != 2 {
|
||||
panic("got a weird env variable: " + kv)
|
||||
}
|
||||
|
||||
r.env[parts[0]] = parts[1]
|
||||
}
|
||||
|
||||
return &r
|
||||
}
|
||||
|
||||
type envDefaultOptions struct {
|
||||
Int int `long:"i" default:"1" env:"TEST_I"`
|
||||
Time time.Duration `long:"t" default:"1m" env:"TEST_T"`
|
||||
Map map[string]int `long:"m" default:"a:1" env:"TEST_M" env-delim:";"`
|
||||
Slice []int `long:"s" default:"1" default:"2" env:"TEST_S" env-delim:","`
|
||||
}
|
||||
|
||||
func TestEnvDefaults(t *testing.T) {
|
||||
var tests = []struct {
|
||||
msg string
|
||||
args []string
|
||||
expected envDefaultOptions
|
||||
env map[string]string
|
||||
}{
|
||||
{
|
||||
msg: "no arguments, no env, expecting default values",
|
||||
args: []string{},
|
||||
expected: envDefaultOptions{
|
||||
Int: 1,
|
||||
Time: time.Minute,
|
||||
Map: map[string]int{"a": 1},
|
||||
Slice: []int{1, 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "no arguments, env defaults, expecting env default values",
|
||||
args: []string{},
|
||||
expected: envDefaultOptions{
|
||||
Int: 2,
|
||||
Time: 2 * time.Minute,
|
||||
Map: map[string]int{"a": 2, "b": 3},
|
||||
Slice: []int{4, 5, 6},
|
||||
},
|
||||
env: map[string]string{
|
||||
"TEST_I": "2",
|
||||
"TEST_T": "2m",
|
||||
"TEST_M": "a:2;b:3",
|
||||
"TEST_S": "4,5,6",
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "non-zero value arguments, expecting overwritten arguments",
|
||||
args: []string{"--i=3", "--t=3ms", "--m=c:3", "--s=3"},
|
||||
expected: envDefaultOptions{
|
||||
Int: 3,
|
||||
Time: 3 * time.Millisecond,
|
||||
Map: map[string]int{"c": 3},
|
||||
Slice: []int{3},
|
||||
},
|
||||
env: map[string]string{
|
||||
"TEST_I": "2",
|
||||
"TEST_T": "2m",
|
||||
"TEST_M": "a:2;b:3",
|
||||
"TEST_S": "4,5,6",
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "zero value arguments, expecting overwritten arguments",
|
||||
args: []string{"--i=0", "--t=0ms", "--m=:0", "--s=0"},
|
||||
expected: envDefaultOptions{
|
||||
Int: 0,
|
||||
Time: 0,
|
||||
Map: map[string]int{"": 0},
|
||||
Slice: []int{0},
|
||||
},
|
||||
env: map[string]string{
|
||||
"TEST_I": "2",
|
||||
"TEST_T": "2m",
|
||||
"TEST_M": "a:2;b:3",
|
||||
"TEST_S": "4,5,6",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
oldEnv := EnvSnapshot()
|
||||
defer oldEnv.Restore()
|
||||
|
||||
for _, test := range tests {
|
||||
var opts envDefaultOptions
|
||||
oldEnv.Restore()
|
||||
for envKey, envValue := range test.env {
|
||||
os.Setenv(envKey, envValue)
|
||||
}
|
||||
_, err := ParseArgs(&opts, test.args)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:\nUnexpected error: %v", test.msg, err)
|
||||
}
|
||||
|
||||
if opts.Slice == nil {
|
||||
opts.Slice = []int{}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(opts, test.expected) {
|
||||
t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionAsArgument(t *testing.T) {
|
||||
var tests = []struct {
|
||||
args []string
|
||||
expectError bool
|
||||
errType ErrorType
|
||||
errMsg string
|
||||
rest []string
|
||||
}{
|
||||
{
|
||||
// short option must not be accepted as argument
|
||||
args: []string{"--string-slice", "foobar", "--string-slice", "-o"},
|
||||
expectError: true,
|
||||
errType: ErrExpectedArgument,
|
||||
errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-o'",
|
||||
},
|
||||
{
|
||||
// long option must not be accepted as argument
|
||||
args: []string{"--string-slice", "foobar", "--string-slice", "--other-option"},
|
||||
expectError: true,
|
||||
errType: ErrExpectedArgument,
|
||||
errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `--other-option'",
|
||||
},
|
||||
{
|
||||
// long option must not be accepted as argument
|
||||
args: []string{"--string-slice", "--"},
|
||||
expectError: true,
|
||||
errType: ErrExpectedArgument,
|
||||
errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got double dash `--'",
|
||||
},
|
||||
{
|
||||
// quoted and appended option should be accepted as argument (even if it looks like an option)
|
||||
args: []string{"--string-slice", "foobar", "--string-slice=\"--other-option\""},
|
||||
},
|
||||
{
|
||||
// Accept any single character arguments including '-'
|
||||
args: []string{"--string-slice", "-"},
|
||||
},
|
||||
{
|
||||
// Do not accept arguments which start with '-' even if the next character is a digit
|
||||
args: []string{"--string-slice", "-3.14"},
|
||||
expectError: true,
|
||||
errType: ErrExpectedArgument,
|
||||
errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-3.14'",
|
||||
},
|
||||
{
|
||||
// Do not accept arguments which start with '-' if the next character is not a digit
|
||||
args: []string{"--string-slice", "-character"},
|
||||
expectError: true,
|
||||
errType: ErrExpectedArgument,
|
||||
errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-character'",
|
||||
},
|
||||
{
|
||||
args: []string{"-o", "-", "-"},
|
||||
rest: []string{"-", "-"},
|
||||
},
|
||||
{
|
||||
// Accept arguments which start with '-' if the next character is a digit, for number options only
|
||||
args: []string{"--int-slice", "-3"},
|
||||
},
|
||||
{
|
||||
// Accept arguments which start with '-' if the next character is a digit, for number options only
|
||||
args: []string{"--int16", "-3"},
|
||||
},
|
||||
{
|
||||
// Accept arguments which start with '-' if the next character is a digit, for number options only
|
||||
args: []string{"--float32", "-3.2"},
|
||||
},
|
||||
{
|
||||
// Accept arguments which start with '-' if the next character is a digit, for number options only
|
||||
args: []string{"--float32ptr", "-3.2"},
|
||||
},
|
||||
}
|
||||
|
||||
var opts struct {
|
||||
StringSlice []string `long:"string-slice"`
|
||||
IntSlice []int `long:"int-slice"`
|
||||
Int16 int16 `long:"int16"`
|
||||
Float32 float32 `long:"float32"`
|
||||
Float32Ptr *float32 `long:"float32ptr"`
|
||||
OtherOption bool `long:"other-option" short:"o"`
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if test.expectError {
|
||||
assertParseFail(t, test.errType, test.errMsg, &opts, test.args...)
|
||||
} else {
|
||||
args := assertParseSuccess(t, &opts, test.args...)
|
||||
|
||||
assertStringArray(t, args, test.rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownFlagHandler(t *testing.T) {
|
||||
|
||||
var opts struct {
|
||||
Flag1 string `long:"flag1"`
|
||||
Flag2 string `long:"flag2"`
|
||||
}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
|
||||
var unknownFlag1 string
|
||||
var unknownFlag2 bool
|
||||
var unknownFlag3 string
|
||||
|
||||
// Set up a callback to intercept unknown options during parsing
|
||||
p.UnknownOptionHandler = func(option string, arg SplitArgument, args []string) ([]string, error) {
|
||||
if option == "unknownFlag1" {
|
||||
if argValue, ok := arg.Value(); ok {
|
||||
unknownFlag1 = argValue
|
||||
return args, nil
|
||||
}
|
||||
// consume a value from remaining args list
|
||||
unknownFlag1 = args[0]
|
||||
return args[1:], nil
|
||||
} else if option == "unknownFlag2" {
|
||||
// treat this one as a bool switch, don't consume any args
|
||||
unknownFlag2 = true
|
||||
return args, nil
|
||||
} else if option == "unknownFlag3" {
|
||||
if argValue, ok := arg.Value(); ok {
|
||||
unknownFlag3 = argValue
|
||||
return args, nil
|
||||
}
|
||||
// consume a value from remaining args list
|
||||
unknownFlag3 = args[0]
|
||||
return args[1:], nil
|
||||
}
|
||||
|
||||
return args, fmt.Errorf("Unknown flag: %v", option)
|
||||
}
|
||||
|
||||
// Parse args containing some unknown flags, verify that
|
||||
// our callback can handle all of them
|
||||
_, err := p.ParseArgs([]string{"--flag1=stuff", "--unknownFlag1", "blah", "--unknownFlag2", "--unknownFlag3=baz", "--flag2=foo"})
|
||||
|
||||
if err != nil {
|
||||
assertErrorf(t, "Parser returned unexpected error %v", err)
|
||||
}
|
||||
|
||||
assertString(t, opts.Flag1, "stuff")
|
||||
assertString(t, opts.Flag2, "foo")
|
||||
assertString(t, unknownFlag1, "blah")
|
||||
assertString(t, unknownFlag3, "baz")
|
||||
|
||||
if !unknownFlag2 {
|
||||
assertErrorf(t, "Flag should have been set by unknown handler, but had value: %v", unknownFlag2)
|
||||
}
|
||||
|
||||
// Parse args with unknown flags that callback doesn't handle, verify it returns error
|
||||
_, err = p.ParseArgs([]string{"--flag1=stuff", "--unknownFlagX", "blah", "--flag2=foo"})
|
||||
|
||||
if err == nil {
|
||||
assertErrorf(t, "Parser should have returned error, but returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChoices(t *testing.T) {
|
||||
var opts struct {
|
||||
Choice string `long:"choose" choice:"v1" choice:"v2"`
|
||||
}
|
||||
|
||||
assertParseFail(t, ErrInvalidChoice, "Invalid value `invalid' for option `"+defaultLongOptDelimiter+"choose'. Allowed values are: v1 or v2", &opts, "--choose", "invalid")
|
||||
assertParseSuccess(t, &opts, "--choose", "v2")
|
||||
assertString(t, opts.Choice, "v2")
|
||||
}
|
||||
|
||||
func TestEmbedded(t *testing.T) {
|
||||
type embedded struct {
|
||||
V bool `short:"v"`
|
||||
}
|
||||
var opts struct {
|
||||
embedded
|
||||
}
|
||||
|
||||
assertParseSuccess(t, &opts, "-v")
|
||||
|
||||
if !opts.V {
|
||||
t.Errorf("Expected V to be true")
|
||||
}
|
||||
}
|
||||
|
||||
type command struct {
|
||||
}
|
||||
|
||||
func (c *command) Execute(args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCommandHandlerNoCommand(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
}{}
|
||||
|
||||
parser := NewParser(&opts, Default&^PrintErrors)
|
||||
|
||||
var executedCommand Commander
|
||||
var executedArgs []string
|
||||
|
||||
executed := false
|
||||
|
||||
parser.CommandHandler = func(command Commander, args []string) error {
|
||||
executed = true
|
||||
|
||||
executedCommand = command
|
||||
executedArgs = args
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := parser.ParseArgs([]string{"arg1", "arg2"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected parse error: %s", err)
|
||||
}
|
||||
|
||||
if !executed {
|
||||
t.Errorf("Expected command handler to be executed")
|
||||
}
|
||||
|
||||
if executedCommand != nil {
|
||||
t.Errorf("Did not exect an executed command")
|
||||
}
|
||||
|
||||
assertStringArray(t, executedArgs, []string{"arg1", "arg2"})
|
||||
}
|
||||
|
||||
func TestCommandHandler(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command command `command:"cmd"`
|
||||
}{}
|
||||
|
||||
parser := NewParser(&opts, Default&^PrintErrors)
|
||||
|
||||
var executedCommand Commander
|
||||
var executedArgs []string
|
||||
|
||||
executed := false
|
||||
|
||||
parser.CommandHandler = func(command Commander, args []string) error {
|
||||
executed = true
|
||||
|
||||
executedCommand = command
|
||||
executedArgs = args
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := parser.ParseArgs([]string{"cmd", "arg1", "arg2"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected parse error: %s", err)
|
||||
}
|
||||
|
||||
if !executed {
|
||||
t.Errorf("Expected command handler to be executed")
|
||||
}
|
||||
|
||||
if executedCommand == nil {
|
||||
t.Errorf("Expected command handler to be executed")
|
||||
}
|
||||
|
||||
assertStringArray(t, executedArgs, []string{"arg1", "arg2"})
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPointerBool(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value *bool `short:"v"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-v")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if !*opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPointerString(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value *string `short:"v"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-v", "value")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
assertString(t, *opts.Value, "value")
|
||||
}
|
||||
|
||||
func TestPointerSlice(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value *[]string `short:"v"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-v", "value1", "-v", "value2")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
assertStringArray(t, *opts.Value, []string{"value1", "value2"})
|
||||
}
|
||||
|
||||
func TestPointerMap(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value *map[string]int `short:"v"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-v", "k1:2", "-v", "k2:-5")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if v, ok := (*opts.Value)["k1"]; !ok {
|
||||
t.Errorf("Expected key \"k1\" to exist")
|
||||
} else if v != 2 {
|
||||
t.Errorf("Expected \"k1\" to be 2, but got %#v", v)
|
||||
}
|
||||
|
||||
if v, ok := (*opts.Value)["k2"]; !ok {
|
||||
t.Errorf("Expected key \"k2\" to exist")
|
||||
} else if v != -5 {
|
||||
t.Errorf("Expected \"k2\" to be -5, but got %#v", v)
|
||||
}
|
||||
}
|
||||
|
||||
type PointerGroup struct {
|
||||
Value bool `short:"v"`
|
||||
}
|
||||
|
||||
func TestPointerGroup(t *testing.T) {
|
||||
var opts = struct {
|
||||
Group *PointerGroup `group:"Group Options"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-v")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if !opts.Group.Value {
|
||||
t.Errorf("Expected Group.Value to be true")
|
||||
}
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShort(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-v")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortTooLong(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"vv"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrShortNameTooLong, "short names can only be 1 character long, not `vv'", &opts)
|
||||
}
|
||||
|
||||
func TestShortRequired(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v" required:"true"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrRequired, fmt.Sprintf("the required flag `%cv' was not specified", defaultShortOptDelimiter), &opts)
|
||||
}
|
||||
|
||||
func TestShortMultiConcat(t *testing.T) {
|
||||
var opts = struct {
|
||||
V bool `short:"v"`
|
||||
O bool `short:"o"`
|
||||
F bool `short:"f"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-vo", "-f")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if !opts.V {
|
||||
t.Errorf("Expected V to be true")
|
||||
}
|
||||
|
||||
if !opts.O {
|
||||
t.Errorf("Expected O to be true")
|
||||
}
|
||||
|
||||
if !opts.F {
|
||||
t.Errorf("Expected F to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortMultiRequiredConcat(t *testing.T) {
|
||||
var opts = struct {
|
||||
V bool `short:"v" required:"true"`
|
||||
O bool `short:"o" required:"true"`
|
||||
F bool `short:"f" required:"true"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-vo", "-f")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if !opts.V {
|
||||
t.Errorf("Expected V to be true")
|
||||
}
|
||||
|
||||
if !opts.O {
|
||||
t.Errorf("Expected O to be true")
|
||||
}
|
||||
|
||||
if !opts.F {
|
||||
t.Errorf("Expected F to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortMultiSlice(t *testing.T) {
|
||||
var opts = struct {
|
||||
Values []bool `short:"v"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-v", "-v")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
assertBoolArray(t, opts.Values, []bool{true, true})
|
||||
}
|
||||
|
||||
func TestShortMultiSliceConcat(t *testing.T) {
|
||||
var opts = struct {
|
||||
Values []bool `short:"v"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-vvv")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
assertBoolArray(t, opts.Values, []bool{true, true, true})
|
||||
}
|
||||
|
||||
func TestShortWithEqualArg(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value string `short:"v"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-v=value")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
assertString(t, opts.Value, "value")
|
||||
}
|
||||
|
||||
func TestShortWithArg(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value string `short:"v"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-vvalue")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
assertString(t, opts.Value, "value")
|
||||
}
|
||||
|
||||
func TestShortArg(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value string `short:"v"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-v", "value")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
assertString(t, opts.Value, "value")
|
||||
}
|
||||
|
||||
func TestShortMultiWithEqualArg(t *testing.T) {
|
||||
var opts = struct {
|
||||
F []bool `short:"f"`
|
||||
Value string `short:"v"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrExpectedArgument, fmt.Sprintf("expected argument for flag `%cv'", defaultShortOptDelimiter), &opts, "-ffv=value")
|
||||
}
|
||||
|
||||
func TestShortMultiArg(t *testing.T) {
|
||||
var opts = struct {
|
||||
F []bool `short:"f"`
|
||||
Value string `short:"v"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-ffv", "value")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
assertBoolArray(t, opts.F, []bool{true, true})
|
||||
assertString(t, opts.Value, "value")
|
||||
}
|
||||
|
||||
func TestShortMultiArgConcatFail(t *testing.T) {
|
||||
var opts = struct {
|
||||
F []bool `short:"f"`
|
||||
Value string `short:"v"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrExpectedArgument, fmt.Sprintf("expected argument for flag `%cv'", defaultShortOptDelimiter), &opts, "-ffvvalue")
|
||||
}
|
||||
|
||||
func TestShortMultiArgConcat(t *testing.T) {
|
||||
var opts = struct {
|
||||
F []bool `short:"f"`
|
||||
Value string `short:"v"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-vff")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
assertString(t, opts.Value, "ff")
|
||||
}
|
||||
|
||||
func TestShortOptional(t *testing.T) {
|
||||
var opts = struct {
|
||||
F []bool `short:"f"`
|
||||
Value string `short:"v" optional:"yes" optional-value:"value"`
|
||||
}{}
|
||||
|
||||
ret := assertParseSuccess(t, &opts, "-fv", "f")
|
||||
|
||||
assertStringArray(t, ret, []string{"f"})
|
||||
assertString(t, opts.Value, "value")
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTagMissingColon(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrTag, "expected `:' after key name, but got end of tag (in `short`)", &opts, "")
|
||||
}
|
||||
|
||||
func TestTagMissingValue(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrTag, "expected `\"' to start tag value at end of tag (in `short:`)", &opts, "")
|
||||
}
|
||||
|
||||
func TestTagMissingQuote(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrTag, "expected end of tag value `\"' at end of tag (in `short:\"v`)", &opts, "")
|
||||
}
|
||||
|
||||
func TestTagNewline(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `long:"verbose" description:"verbose
|
||||
something"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrTag, "unexpected newline in tag value `description' (in `long:\"verbose\" description:\"verbose\nsomething\"`)", &opts, "")
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
// +build !windows,!plan9,!solaris
|
||||
|
||||
package flags
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type winsize struct {
|
||||
row, col uint16
|
||||
xpixel, ypixel uint16
|
||||
}
|
||||
|
||||
func getTerminalColumns() int {
|
||||
ws := winsize{}
|
||||
|
||||
if tIOCGWINSZ != 0 {
|
||||
syscall.Syscall(syscall.SYS_IOCTL,
|
||||
uintptr(0),
|
||||
uintptr(tIOCGWINSZ),
|
||||
uintptr(unsafe.Pointer(&ws)))
|
||||
|
||||
return int(ws.col)
|
||||
}
|
||||
|
||||
return 80
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
// +build linux
|
||||
|
||||
package flags
|
||||
|
||||
const (
|
||||
tIOCGWINSZ = 0x5413
|
||||
)
|
@ -1,7 +0,0 @@
|
||||
// +build windows plan9 solaris
|
||||
|
||||
package flags
|
||||
|
||||
func getTerminalColumns() int {
|
||||
return 80
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
// +build !darwin,!freebsd,!netbsd,!openbsd,!linux
|
||||
|
||||
package flags
|
||||
|
||||
const (
|
||||
tIOCGWINSZ = 0
|
||||
)
|
@ -1,7 +0,0 @@
|
||||
// +build darwin freebsd netbsd openbsd
|
||||
|
||||
package flags
|
||||
|
||||
const (
|
||||
tIOCGWINSZ = 0x40087468
|
||||
)
|
@ -1,66 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnknownFlags(t *testing.T) {
|
||||
var opts = struct {
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Verbose output"`
|
||||
}{}
|
||||
|
||||
args := []string{
|
||||
"-f",
|
||||
}
|
||||
|
||||
p := NewParser(&opts, 0)
|
||||
args, err := p.ParseArgs(args)
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for unknown argument")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreUnknownFlags(t *testing.T) {
|
||||
var opts = struct {
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Verbose output"`
|
||||
}{}
|
||||
|
||||
args := []string{
|
||||
"hello",
|
||||
"world",
|
||||
"-v",
|
||||
"--foo=bar",
|
||||
"--verbose",
|
||||
"-f",
|
||||
}
|
||||
|
||||
p := NewParser(&opts, IgnoreUnknown)
|
||||
args, err := p.ParseArgs(args)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
exargs := []string{
|
||||
"hello",
|
||||
"world",
|
||||
"--foo=bar",
|
||||
"-f",
|
||||
}
|
||||
|
||||
issame := (len(args) == len(exargs))
|
||||
|
||||
if issame {
|
||||
for i := 0; i < len(args); i++ {
|
||||
if args[i] != exargs[i] {
|
||||
issame = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !issame {
|
||||
t.Fatalf("Expected %v but got %v", exargs, args)
|
||||
}
|
||||
}
|
174
vendor/src/github.com/spf13/cobra/LICENSE.txt
vendored
Normal file
174
vendor/src/github.com/spf13/cobra/LICENSE.txt
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
898
vendor/src/github.com/spf13/cobra/README.md
vendored
Normal file
898
vendor/src/github.com/spf13/cobra/README.md
vendored
Normal file
@ -0,0 +1,898 @@
|
||||
![cobra logo](https://cloud.githubusercontent.com/assets/173412/10886352/ad566232-814f-11e5-9cd0-aa101788c117.png)
|
||||
|
||||
Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files.
|
||||
|
||||
Many of the most widely used Go projects are built using Cobra including:
|
||||
|
||||
* [Kubernetes](http://kubernetes.io/)
|
||||
* [Hugo](http://gohugo.io)
|
||||
* [rkt](https://github.com/coreos/rkt)
|
||||
* [etcd](https://github.com/coreos/etcd)
|
||||
* [Docker (distribution)](https://github.com/docker/distribution)
|
||||
* [OpenShift](https://www.openshift.com/)
|
||||
* [Delve](https://github.com/derekparker/delve)
|
||||
* [GopherJS](http://www.gopherjs.org/)
|
||||
* [CockroachDB](http://www.cockroachlabs.com/)
|
||||
* [Bleve](http://www.blevesearch.com/)
|
||||
* [ProjectAtomic (enterprise)](http://www.projectatomic.io/)
|
||||
* [Parse (CLI)](https://parse.com/)
|
||||
* [GiantSwarm's swarm](https://github.com/giantswarm/cli)
|
||||
* [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack)
|
||||
|
||||
|
||||
[![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra)
|
||||
[![CircleCI status](https://circleci.com/gh/spf13/cobra.png?circle-token=:circle-token "CircleCI status")](https://circleci.com/gh/spf13/cobra)
|
||||
[![GoDoc](https://godoc.org/github.com/spf13/cobra?status.svg)](https://godoc.org/github.com/spf13/cobra)
|
||||
|
||||
![cobra](https://cloud.githubusercontent.com/assets/173412/10911369/84832a8e-8212-11e5-9f82-cc96660a4794.gif)
|
||||
|
||||
# Overview
|
||||
|
||||
Cobra is a library providing a simple interface to create powerful modern CLI
|
||||
interfaces similar to git & go tools.
|
||||
|
||||
Cobra is also an application that will generate your application scaffolding to rapidly
|
||||
develop a Cobra-based application.
|
||||
|
||||
Cobra provides:
|
||||
* Easy subcommand-based CLIs: `app server`, `app fetch`, etc.
|
||||
* Fully POSIX-compliant flags (including short & long versions)
|
||||
* Nested subcommands
|
||||
* Global, local and cascading flags
|
||||
* Easy generation of applications & commands with `cobra create appname` & `cobra add cmdname`
|
||||
* Intelligent suggestions (`app srver`... did you mean `app server`?)
|
||||
* Automatic help generation for commands and flags
|
||||
* Automatic detailed help for `app help [command]`
|
||||
* Automatic help flag recognition of `-h`, `--help`, etc.
|
||||
* Automatically generated bash autocomplete for your application
|
||||
* Automatically generated man pages for your application
|
||||
* Command aliases so you can change things without breaking them
|
||||
* The flexibilty to define your own help, usage, etc.
|
||||
* Optional tight integration with [viper](http://github.com/spf13/viper) for 12-factor apps
|
||||
|
||||
Cobra has an exceptionally clean interface and simple design without needless
|
||||
constructors or initialization methods.
|
||||
|
||||
Applications built with Cobra commands are designed to be as user-friendly as
|
||||
possible. Flags can be placed before or after the command (as long as a
|
||||
confusing space isn’t provided). Both short and long flags can be used. A
|
||||
command need not even be fully typed. Help is automatically generated and
|
||||
available for the application or for a specific command using either the help
|
||||
command or the `--help` flag.
|
||||
|
||||
# Concepts
|
||||
|
||||
Cobra is built on a structure of commands, arguments & flags.
|
||||
|
||||
**Commands** represent actions, **Args** are things and **Flags** are modifiers for those actions.
|
||||
|
||||
The best applications will read like sentences when used. Users will know how
|
||||
to use the application because they will natively understand how to use it.
|
||||
|
||||
The pattern to follow is
|
||||
`APPNAME VERB NOUN --ADJECTIVE.`
|
||||
or
|
||||
`APPNAME COMMAND ARG --FLAG`
|
||||
|
||||
A few good real world examples may better illustrate this point.
|
||||
|
||||
In the following example, 'server' is a command, and 'port' is a flag:
|
||||
|
||||
> hugo server --port=1313
|
||||
|
||||
In this command we are telling Git to clone the url bare.
|
||||
|
||||
> git clone URL --bare
|
||||
|
||||
## Commands
|
||||
|
||||
Command is the central point of the application. Each interaction that
|
||||
the application supports will be contained in a Command. A command can
|
||||
have children commands and optionally run an action.
|
||||
|
||||
In the example above, 'server' is the command.
|
||||
|
||||
A Command has the following structure:
|
||||
|
||||
```go
|
||||
type Command struct {
|
||||
Use string // The one-line usage message.
|
||||
Short string // The short description shown in the 'help' output.
|
||||
Long string // The long message shown in the 'help <this-command>' output.
|
||||
Run func(cmd *Command, args []string) // Run runs the command.
|
||||
}
|
||||
```
|
||||
|
||||
## Flags
|
||||
|
||||
A Flag is a way to modify the behavior of a command. Cobra supports
|
||||
fully POSIX-compliant flags as well as the Go [flag package](https://golang.org/pkg/flag/).
|
||||
A Cobra command can define flags that persist through to children commands
|
||||
and flags that are only available to that command.
|
||||
|
||||
In the example above, 'port' is the flag.
|
||||
|
||||
Flag functionality is provided by the [pflag
|
||||
library](https://github.com/ogier/pflag), a fork of the flag standard library
|
||||
which maintains the same interface while adding POSIX compliance.
|
||||
|
||||
## Usage
|
||||
|
||||
Cobra works by creating a set of commands and then organizing them into a tree.
|
||||
The tree defines the structure of the application.
|
||||
|
||||
Once each command is defined with its corresponding flags, then the
|
||||
tree is assigned to the commander which is finally executed.
|
||||
|
||||
# Installing
|
||||
Using Cobra is easy. First, use `go get` to install the latest version
|
||||
of the library. This command will install the `cobra` generator executible
|
||||
along with the library:
|
||||
|
||||
> go get -v github.com/spf13/cobra/cobra
|
||||
|
||||
Next, include Cobra in your application:
|
||||
|
||||
```go
|
||||
import "github.com/spf13/cobra"
|
||||
```
|
||||
|
||||
# Getting Started
|
||||
|
||||
While you are welcome to provide your own organization, typically a Cobra based
|
||||
application will follow the following organizational structure.
|
||||
|
||||
```
|
||||
▾ appName/
|
||||
▾ cmd/
|
||||
add.go
|
||||
your.go
|
||||
commands.go
|
||||
here.go
|
||||
main.go
|
||||
```
|
||||
|
||||
In a Cobra app, typically the main.go file is very bare. It serves, one purpose, to initialize Cobra.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "{pathToYourApp}/cmd"
|
||||
|
||||
func main() {
|
||||
if err := cmd.RootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using the Cobra Generator
|
||||
|
||||
Cobra provides its own program that will create your application and add any
|
||||
commands you want. It's the easiest way to incorporate Cobra into your application.
|
||||
|
||||
In order to use the cobra command, compile it using the following command:
|
||||
|
||||
> go install github.com/spf13/cobra/cobra
|
||||
|
||||
This will create the cobra executable under your go path bin directory!
|
||||
|
||||
### cobra init
|
||||
|
||||
The `cobra init [yourApp]` command will create your initial application code
|
||||
for you. It is a very powerful application that will populate your program with
|
||||
the right structure so you can immediately enjoy all the benefits of Cobra. It
|
||||
will also automatically apply the license you specify to your application.
|
||||
|
||||
Cobra init is pretty smart. You can provide it a full path, or simply a path
|
||||
similar to what is expected in the import.
|
||||
|
||||
```
|
||||
cobra init github.com/spf13/newAppName
|
||||
```
|
||||
|
||||
### cobra add
|
||||
|
||||
Once an application is initialized Cobra can create additional commands for you.
|
||||
Let's say you created an app and you wanted the following commands for it:
|
||||
|
||||
* app serve
|
||||
* app config
|
||||
* app config create
|
||||
|
||||
In your project directory (where your main.go file is) you would run the following:
|
||||
|
||||
```
|
||||
cobra add serve
|
||||
cobra add config
|
||||
cobra add create -p 'configCmd'
|
||||
```
|
||||
|
||||
Once you have run these three commands you would have an app structure that would look like:
|
||||
|
||||
```
|
||||
▾ app/
|
||||
▾ cmd/
|
||||
serve.go
|
||||
config.go
|
||||
create.go
|
||||
main.go
|
||||
```
|
||||
|
||||
at this point you can run `go run main.go` and it would run your app. `go run
|
||||
main.go serve`, `go run main.go config`, `go run main.go config create` along
|
||||
with `go run main.go help serve`, etc would all work.
|
||||
|
||||
Obviously you haven't added your own code to these yet, the commands are ready
|
||||
for you to give them their tasks. Have fun.
|
||||
|
||||
### Configuring the cobra generator
|
||||
|
||||
The cobra generator will be easier to use if you provide a simple configuration
|
||||
file which will help you eliminate providing a bunch of repeated information in
|
||||
flags over and over.
|
||||
|
||||
An example ~/.cobra.yaml file:
|
||||
|
||||
```yaml
|
||||
author: Steve Francia <spf@spf13.com>
|
||||
license: MIT
|
||||
```
|
||||
|
||||
You can specify no license by setting `license` to `none` or you can specify
|
||||
a custom license:
|
||||
|
||||
```yaml
|
||||
license:
|
||||
header: This file is part of {{ .appName }}.
|
||||
text: |
|
||||
{{ .copyright }}
|
||||
|
||||
This is my license. There are many like it, but this one is mine.
|
||||
My license is my best friend. It is my life. I must master it as I must
|
||||
master my life.
|
||||
```
|
||||
|
||||
## Manually implementing Cobra
|
||||
|
||||
To manually implement cobra you need to create a bare main.go file and a RootCmd file.
|
||||
You will optionally provide additional commands as you see fit.
|
||||
|
||||
### Create the root command
|
||||
|
||||
The root command represents your binary itself.
|
||||
|
||||
|
||||
#### Manually create rootCmd
|
||||
|
||||
Cobra doesn't require any special constructors. Simply create your commands.
|
||||
|
||||
Ideally you place this in app/cmd/root.go:
|
||||
|
||||
```go
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "hugo",
|
||||
Short: "Hugo is a very fast static site generator",
|
||||
Long: `A Fast and Flexible Static Site Generator built with
|
||||
love by spf13 and friends in Go.
|
||||
Complete documentation is available at http://hugo.spf13.com`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Do Stuff Here
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
You will additionally define flags and handle configuration in your init() function.
|
||||
|
||||
for example cmd/root.go:
|
||||
|
||||
```go
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
|
||||
RootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/")
|
||||
RootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution")
|
||||
RootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)")
|
||||
RootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration")
|
||||
viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author"))
|
||||
viper.BindPFlag("projectbase", RootCmd.PersistentFlags().Lookup("projectbase"))
|
||||
viper.BindPFlag("useViper", RootCmd.PersistentFlags().Lookup("viper"))
|
||||
viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
|
||||
viper.SetDefault("license", "apache")
|
||||
}
|
||||
```
|
||||
|
||||
### Create your main.go
|
||||
|
||||
With the root command you need to have your main function execute it.
|
||||
Execute should be run on the root for clarity, though it can be called on any command.
|
||||
|
||||
In a Cobra app, typically the main.go file is very bare. It serves, one purpose, to initialize Cobra.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "{pathToYourApp}/cmd"
|
||||
|
||||
func main() {
|
||||
if err := cmd.RootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Create additional commands
|
||||
|
||||
Additional commands can be defined and typically are each given their own file
|
||||
inside of the cmd/ directory.
|
||||
|
||||
If you wanted to create a version command you would create cmd/version.go and
|
||||
populate it with the following:
|
||||
|
||||
```go
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version number of Hugo",
|
||||
Long: `All software has versions. This is Hugo's`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Attach command to its parent
|
||||
|
||||
|
||||
If you notice in the above example we attach the command to its parent. In
|
||||
this case the parent is the rootCmd. In this example we are attaching it to the
|
||||
root, but commands can be attached at any level.
|
||||
|
||||
```go
|
||||
RootCmd.AddCommand(versionCmd)
|
||||
```
|
||||
|
||||
### Remove a command from its parent
|
||||
|
||||
Removing a command is not a common action in simple programs, but it allows 3rd
|
||||
parties to customize an existing command tree.
|
||||
|
||||
In this example, we remove the existing `VersionCmd` command of an existing
|
||||
root command, and we replace it with our own version:
|
||||
|
||||
```go
|
||||
mainlib.RootCmd.RemoveCommand(mainlib.VersionCmd)
|
||||
mainlib.RootCmd.AddCommand(versionCmd)
|
||||
```
|
||||
|
||||
## Working with Flags
|
||||
|
||||
Flags provide modifiers to control how the action command operates.
|
||||
|
||||
### Assign flags to a command
|
||||
|
||||
Since the flags are defined and used in different locations, we need to
|
||||
define a variable outside with the correct scope to assign the flag to
|
||||
work with.
|
||||
|
||||
```go
|
||||
var Verbose bool
|
||||
var Source string
|
||||
```
|
||||
|
||||
There are two different approaches to assign a flag.
|
||||
|
||||
### Persistent Flags
|
||||
|
||||
A flag can be 'persistent' meaning that this flag will be available to the
|
||||
command it's assigned to as well as every command under that command. For
|
||||
global flags, assign a flag as a persistent flag on the root.
|
||||
|
||||
```go
|
||||
RootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
|
||||
```
|
||||
|
||||
### Local Flags
|
||||
|
||||
A flag can also be assigned locally which will only apply to that specific command.
|
||||
|
||||
```go
|
||||
RootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
|
||||
```
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
In the example below, we have defined three commands. Two are at the top level
|
||||
and one (cmdTimes) is a child of one of the top commands. In this case the root
|
||||
is not executable meaning that a subcommand is required. This is accomplished
|
||||
by not providing a 'Run' for the 'rootCmd'.
|
||||
|
||||
We have only defined one flag for a single command.
|
||||
|
||||
More documentation about flags is available at https://github.com/spf13/pflag
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
var echoTimes int
|
||||
|
||||
var cmdPrint = &cobra.Command{
|
||||
Use: "print [string to print]",
|
||||
Short: "Print anything to the screen",
|
||||
Long: `print is for printing anything back to the screen.
|
||||
For many years people have printed back to the screen.
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("Print: " + strings.Join(args, " "))
|
||||
},
|
||||
}
|
||||
|
||||
var cmdEcho = &cobra.Command{
|
||||
Use: "echo [string to echo]",
|
||||
Short: "Echo anything to the screen",
|
||||
Long: `echo is for echoing anything back.
|
||||
Echo works a lot like print, except it has a child command.
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("Print: " + strings.Join(args, " "))
|
||||
},
|
||||
}
|
||||
|
||||
var cmdTimes = &cobra.Command{
|
||||
Use: "times [# times] [string to echo]",
|
||||
Short: "Echo anything to the screen more times",
|
||||
Long: `echo things multiple times back to the user by providing
|
||||
a count and a string.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
for i := 0; i < echoTimes; i++ {
|
||||
fmt.Println("Echo: " + strings.Join(args, " "))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")
|
||||
|
||||
var rootCmd = &cobra.Command{Use: "app"}
|
||||
rootCmd.AddCommand(cmdPrint, cmdEcho)
|
||||
cmdEcho.AddCommand(cmdTimes)
|
||||
rootCmd.Execute()
|
||||
}
|
||||
```
|
||||
|
||||
For a more complete example of a larger application, please checkout [Hugo](http://gohugo.io/).
|
||||
|
||||
## The Help Command
|
||||
|
||||
Cobra automatically adds a help command to your application when you have subcommands.
|
||||
This will be called when a user runs 'app help'. Additionally, help will also
|
||||
support all other commands as input. Say, for instance, you have a command called
|
||||
'create' without any additional configuration; Cobra will work when 'app help
|
||||
create' is called. Every command will automatically have the '--help' flag added.
|
||||
|
||||
### Example
|
||||
|
||||
The following output is automatically generated by Cobra. Nothing beyond the
|
||||
command and flag definitions are needed.
|
||||
|
||||
> hugo help
|
||||
|
||||
hugo is the main command, used to build your Hugo site.
|
||||
|
||||
Hugo is a Fast and Flexible Static Site Generator
|
||||
built with love by spf13 and friends in Go.
|
||||
|
||||
Complete documentation is available at http://gohugo.io/.
|
||||
|
||||
Usage:
|
||||
hugo [flags]
|
||||
hugo [command]
|
||||
|
||||
Available Commands:
|
||||
server Hugo runs its own webserver to render the files
|
||||
version Print the version number of Hugo
|
||||
config Print the site configuration
|
||||
check Check content in the source directory
|
||||
benchmark Benchmark hugo by building a site a number of times.
|
||||
convert Convert your content to different formats
|
||||
new Create new content for your site
|
||||
list Listing out various types of content
|
||||
undraft Undraft changes the content's draft status from 'True' to 'False'
|
||||
genautocomplete Generate shell autocompletion script for Hugo
|
||||
gendoc Generate Markdown documentation for the Hugo CLI.
|
||||
genman Generate man page for Hugo
|
||||
import Import your site from others.
|
||||
|
||||
Flags:
|
||||
-b, --baseURL="": hostname (and path) to the root, e.g. http://spf13.com/
|
||||
-D, --buildDrafts[=false]: include content marked as draft
|
||||
-F, --buildFuture[=false]: include content with publishdate in the future
|
||||
--cacheDir="": filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/
|
||||
--canonifyURLs[=false]: if true, all relative URLs will be canonicalized using baseURL
|
||||
--config="": config file (default is path/config.yaml|json|toml)
|
||||
-d, --destination="": filesystem path to write files to
|
||||
--disableRSS[=false]: Do not build RSS files
|
||||
--disableSitemap[=false]: Do not build Sitemap file
|
||||
--editor="": edit new content with this editor, if provided
|
||||
--ignoreCache[=false]: Ignores the cache directory for reading but still writes to it
|
||||
--log[=false]: Enable Logging
|
||||
--logFile="": Log File path (if set, logging enabled automatically)
|
||||
--noTimes[=false]: Don't sync modification time of files
|
||||
--pluralizeListTitles[=true]: Pluralize titles in lists using inflect
|
||||
--preserveTaxonomyNames[=false]: Preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu")
|
||||
-s, --source="": filesystem path to read files relative from
|
||||
--stepAnalysis[=false]: display memory and timing of different steps of the program
|
||||
-t, --theme="": theme to use (located in /themes/THEMENAME/)
|
||||
--uglyURLs[=false]: if true, use /filename.html instead of /filename/
|
||||
-v, --verbose[=false]: verbose output
|
||||
--verboseLog[=false]: verbose logging
|
||||
-w, --watch[=false]: watch filesystem for changes and recreate as needed
|
||||
|
||||
Use "hugo [command] --help" for more information about a command.
|
||||
|
||||
|
||||
Help is just a command like any other. There is no special logic or behavior
|
||||
around it. In fact, you can provide your own if you want.
|
||||
|
||||
### Defining your own help
|
||||
|
||||
You can provide your own Help command or your own template for the default command to use.
|
||||
|
||||
The default help command is
|
||||
|
||||
```go
|
||||
func (c *Command) initHelp() {
|
||||
if c.helpCommand == nil {
|
||||
c.helpCommand = &Command{
|
||||
Use: "help [command]",
|
||||
Short: "Help about any command",
|
||||
Long: `Help provides help for any command in the application.
|
||||
Simply type ` + c.Name() + ` help [path to command] for full details.`,
|
||||
Run: c.HelpFunc(),
|
||||
}
|
||||
}
|
||||
c.AddCommand(c.helpCommand)
|
||||
}
|
||||
```
|
||||
|
||||
You can provide your own command, function or template through the following methods:
|
||||
|
||||
```go
|
||||
command.SetHelpCommand(cmd *Command)
|
||||
|
||||
command.SetHelpFunc(f func(*Command, []string))
|
||||
|
||||
command.SetHelpTemplate(s string)
|
||||
```
|
||||
|
||||
The latter two will also apply to any children commands.
|
||||
|
||||
## Usage
|
||||
|
||||
When the user provides an invalid flag or invalid command, Cobra responds by
|
||||
showing the user the 'usage'.
|
||||
|
||||
### Example
|
||||
You may recognize this from the help above. That's because the default help
|
||||
embeds the usage as part of its output.
|
||||
|
||||
Usage:
|
||||
hugo [flags]
|
||||
hugo [command]
|
||||
|
||||
Available Commands:
|
||||
server Hugo runs its own webserver to render the files
|
||||
version Print the version number of Hugo
|
||||
config Print the site configuration
|
||||
check Check content in the source directory
|
||||
benchmark Benchmark hugo by building a site a number of times.
|
||||
convert Convert your content to different formats
|
||||
new Create new content for your site
|
||||
list Listing out various types of content
|
||||
undraft Undraft changes the content's draft status from 'True' to 'False'
|
||||
genautocomplete Generate shell autocompletion script for Hugo
|
||||
gendoc Generate Markdown documentation for the Hugo CLI.
|
||||
genman Generate man page for Hugo
|
||||
import Import your site from others.
|
||||
|
||||
Flags:
|
||||
-b, --baseURL="": hostname (and path) to the root, e.g. http://spf13.com/
|
||||
-D, --buildDrafts[=false]: include content marked as draft
|
||||
-F, --buildFuture[=false]: include content with publishdate in the future
|
||||
--cacheDir="": filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/
|
||||
--canonifyURLs[=false]: if true, all relative URLs will be canonicalized using baseURL
|
||||
--config="": config file (default is path/config.yaml|json|toml)
|
||||
-d, --destination="": filesystem path to write files to
|
||||
--disableRSS[=false]: Do not build RSS files
|
||||
--disableSitemap[=false]: Do not build Sitemap file
|
||||
--editor="": edit new content with this editor, if provided
|
||||
--ignoreCache[=false]: Ignores the cache directory for reading but still writes to it
|
||||
--log[=false]: Enable Logging
|
||||
--logFile="": Log File path (if set, logging enabled automatically)
|
||||
--noTimes[=false]: Don't sync modification time of files
|
||||
--pluralizeListTitles[=true]: Pluralize titles in lists using inflect
|
||||
--preserveTaxonomyNames[=false]: Preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu")
|
||||
-s, --source="": filesystem path to read files relative from
|
||||
--stepAnalysis[=false]: display memory and timing of different steps of the program
|
||||
-t, --theme="": theme to use (located in /themes/THEMENAME/)
|
||||
--uglyURLs[=false]: if true, use /filename.html instead of /filename/
|
||||
-v, --verbose[=false]: verbose output
|
||||
--verboseLog[=false]: verbose logging
|
||||
-w, --watch[=false]: watch filesystem for changes and recreate as needed
|
||||
|
||||
### Defining your own usage
|
||||
You can provide your own usage function or template for Cobra to use.
|
||||
|
||||
The default usage function is:
|
||||
|
||||
```go
|
||||
return func(c *Command) error {
|
||||
err := tmpl(c.Out(), c.UsageTemplate(), c)
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
Like help, the function and template are overridable through public methods:
|
||||
|
||||
```go
|
||||
command.SetUsageFunc(f func(*Command) error)
|
||||
|
||||
command.SetUsageTemplate(s string)
|
||||
```
|
||||
|
||||
## PreRun or PostRun Hooks
|
||||
|
||||
It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order:
|
||||
|
||||
- `PersistentPreRun`
|
||||
- `PreRun`
|
||||
- `Run`
|
||||
- `PostRun`
|
||||
- `PersistentPostRun`
|
||||
|
||||
An example of two commands which use all of these features is below. When the subcommand is executed, it will run the root command's `PersistentPreRun` but not the root command's `PersistentPostRun`:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "root [sub]",
|
||||
Short: "My root command",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args)
|
||||
},
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Inside rootCmd PreRun with args: %v\n", args)
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Inside rootCmd Run with args: %v\n", args)
|
||||
},
|
||||
PostRun: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Inside rootCmd PostRun with args: %v\n", args)
|
||||
},
|
||||
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args)
|
||||
},
|
||||
}
|
||||
|
||||
var subCmd = &cobra.Command{
|
||||
Use: "sub [no options!]",
|
||||
Short: "My subcommand",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Inside subCmd PreRun with args: %v\n", args)
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Inside subCmd Run with args: %v\n", args)
|
||||
},
|
||||
PostRun: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Inside subCmd PostRun with args: %v\n", args)
|
||||
},
|
||||
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n", args)
|
||||
},
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(subCmd)
|
||||
|
||||
rootCmd.SetArgs([]string{""})
|
||||
_ = rootCmd.Execute()
|
||||
fmt.Print("\n")
|
||||
rootCmd.SetArgs([]string{"sub", "arg1", "arg2"})
|
||||
_ = rootCmd.Execute()
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Alternative Error Handling
|
||||
|
||||
Cobra also has functions where the return signature is an error. This allows for errors to bubble up to the top,
|
||||
providing a way to handle the errors in one location. The current list of functions that return an error is:
|
||||
|
||||
* PersistentPreRunE
|
||||
* PreRunE
|
||||
* RunE
|
||||
* PostRunE
|
||||
* PersistentPostRunE
|
||||
|
||||
If you would like to silence the default `error` and `usage` output in favor of your own, you can set `SilenceUsage`
|
||||
and `SilenceErrors` to `false` on the command. A child command respects these flags if they are set on the parent
|
||||
command.
|
||||
|
||||
**Example Usage using RunE:**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "hugo",
|
||||
Short: "Hugo is a very fast static site generator",
|
||||
Long: `A Fast and Flexible Static Site Generator built with
|
||||
love by spf13 and friends in Go.
|
||||
Complete documentation is available at http://hugo.spf13.com`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Do Stuff Here
|
||||
return errors.New("some random error")
|
||||
},
|
||||
}
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Suggestions when "unknown command" happens
|
||||
|
||||
Cobra will print automatic suggestions when "unknown command" errors happen. This allows Cobra to behave similarly to the `git` command when a typo happens. For example:
|
||||
|
||||
```
|
||||
$ hugo srever
|
||||
Error: unknown command "srever" for "hugo"
|
||||
|
||||
Did you mean this?
|
||||
server
|
||||
|
||||
Run 'hugo --help' for usage.
|
||||
```
|
||||
|
||||
Suggestions are automatic based on every subcommand registered and use an implementation of [Levenshtein distance](http://en.wikipedia.org/wiki/Levenshtein_distance). Every registered command that matches a minimum distance of 2 (ignoring case) will be displayed as a suggestion.
|
||||
|
||||
If you need to disable suggestions or tweak the string distance in your command, use:
|
||||
|
||||
```go
|
||||
command.DisableSuggestions = true
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```go
|
||||
command.SuggestionsMinimumDistance = 1
|
||||
```
|
||||
|
||||
You can also explicitly set names for which a given command will be suggested using the `SuggestFor` attribute. This allows suggestions for strings that are not close in terms of string distance, but makes sense in your set of commands and for some which you don't want aliases. Example:
|
||||
|
||||
```
|
||||
$ kubectl remove
|
||||
Error: unknown command "remove" for "kubectl"
|
||||
|
||||
Did you mean this?
|
||||
delete
|
||||
|
||||
Run 'kubectl help' for usage.
|
||||
```
|
||||
|
||||
## Generating Markdown-formatted documentation for your command
|
||||
|
||||
Cobra can generate a Markdown-formatted document based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Markdown Docs](doc/md_docs.md).
|
||||
|
||||
## Generating man pages for your command
|
||||
|
||||
Cobra can generate a man page based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Man Docs](doc/man_docs.md).
|
||||
|
||||
## Generating bash completions for your command
|
||||
|
||||
Cobra can generate a bash-completion file. If you add more information to your command, these completions can be amazingly powerful and flexible. Read more about it in [Bash Completions](bash_completions.md).
|
||||
|
||||
## Debugging
|
||||
|
||||
Cobra provides a ‘DebugFlags’ method on a command which, when called, will print
|
||||
out everything Cobra knows about the flags for each command.
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
command.DebugFlags()
|
||||
```
|
||||
|
||||
## Release Notes
|
||||
* **0.9.0** June 17, 2014
|
||||
* flags can appears anywhere in the args (provided they are unambiguous)
|
||||
* --help prints usage screen for app or command
|
||||
* Prefix matching for commands
|
||||
* Cleaner looking help and usage output
|
||||
* Extensive test suite
|
||||
* **0.8.0** Nov 5, 2013
|
||||
* Reworked interface to remove commander completely
|
||||
* Command now primary structure
|
||||
* No initialization needed
|
||||
* Usage & Help templates & functions definable at any level
|
||||
* Updated Readme
|
||||
* **0.7.0** Sept 24, 2013
|
||||
* Needs more eyes
|
||||
* Test suite
|
||||
* Support for automatic error messages
|
||||
* Support for help command
|
||||
* Support for printing to any io.Writer instead of os.Stderr
|
||||
* Support for persistent flags which cascade down tree
|
||||
* Ready for integration into Hugo
|
||||
* **0.1.0** Sept 3, 2013
|
||||
* Implement first draft
|
||||
|
||||
## Extensions
|
||||
|
||||
Libraries for extending Cobra:
|
||||
|
||||
* [cmdns](https://github.com/gosuri/cmdns): Enables name spacing a command's immediate children. It provides an alternative way to structure subcommands, similar to `heroku apps:create` and `ovrclk clusters:launch`.
|
||||
|
||||
## ToDo
|
||||
* Launch proper documentation site
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork it
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Commit your changes (`git commit -am 'Add some feature'`)
|
||||
4. Push to the branch (`git push origin my-new-feature`)
|
||||
5. Create new Pull Request
|
||||
|
||||
## Contributors
|
||||
|
||||
Names in no particular order:
|
||||
|
||||
* [spf13](https://github.com/spf13),
|
||||
[eparis](https://github.com/eparis),
|
||||
[bep](https://github.com/bep), and many more!
|
||||
|
||||
## License
|
||||
|
||||
Cobra is released under the Apache 2.0 license. See [LICENSE.txt](https://github.com/spf13/cobra/blob/master/LICENSE.txt)
|
||||
|
||||
|
||||
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/spf13/cobra/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
|
641
vendor/src/github.com/spf13/cobra/bash_completions.go
vendored
Normal file
641
vendor/src/github.com/spf13/cobra/bash_completions.go
vendored
Normal file
@ -0,0 +1,641 @@
|
||||
package cobra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const (
|
||||
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions"
|
||||
BashCompCustom = "cobra_annotation_bash_completion_custom"
|
||||
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
|
||||
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
|
||||
)
|
||||
|
||||
func preamble(out io.Writer, name string) error {
|
||||
_, err := fmt.Fprintf(out, "# bash completion for %-36s -*- shell-script -*-\n", name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprint(out, `
|
||||
__debug()
|
||||
{
|
||||
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
|
||||
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include
|
||||
# _init_completion. This is a very minimal version of that function.
|
||||
__my_init_completion()
|
||||
{
|
||||
COMPREPLY=()
|
||||
_get_comp_words_by_ref "$@" cur prev words cword
|
||||
}
|
||||
|
||||
__index_of_word()
|
||||
{
|
||||
local w word=$1
|
||||
shift
|
||||
index=0
|
||||
for w in "$@"; do
|
||||
[[ $w = "$word" ]] && return
|
||||
index=$((index+1))
|
||||
done
|
||||
index=-1
|
||||
}
|
||||
|
||||
__contains_word()
|
||||
{
|
||||
local w word=$1; shift
|
||||
for w in "$@"; do
|
||||
[[ $w = "$word" ]] && return
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
__handle_reply()
|
||||
{
|
||||
__debug "${FUNCNAME[0]}"
|
||||
case $cur in
|
||||
-*)
|
||||
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||
compopt -o nospace
|
||||
fi
|
||||
local allflags
|
||||
if [ ${#must_have_one_flag[@]} -ne 0 ]; then
|
||||
allflags=("${must_have_one_flag[@]}")
|
||||
else
|
||||
allflags=("${flags[*]} ${two_word_flags[*]}")
|
||||
fi
|
||||
COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") )
|
||||
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||
[[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace
|
||||
fi
|
||||
|
||||
# complete after --flag=abc
|
||||
if [[ $cur == *=* ]]; then
|
||||
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||
compopt +o nospace
|
||||
fi
|
||||
|
||||
local index flag
|
||||
flag="${cur%%=*}"
|
||||
__index_of_word "${flag}" "${flags_with_completion[@]}"
|
||||
if [[ ${index} -ge 0 ]]; then
|
||||
COMPREPLY=()
|
||||
PREFIX=""
|
||||
cur="${cur#*=}"
|
||||
${flags_completion[${index}]}
|
||||
if [ -n "${ZSH_VERSION}" ]; then
|
||||
# zfs completion needs --flag= prefix
|
||||
eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
return 0;
|
||||
;;
|
||||
esac
|
||||
|
||||
# check if we are handling a flag with special work handling
|
||||
local index
|
||||
__index_of_word "${prev}" "${flags_with_completion[@]}"
|
||||
if [[ ${index} -ge 0 ]]; then
|
||||
${flags_completion[${index}]}
|
||||
return
|
||||
fi
|
||||
|
||||
# we are parsing a flag and don't have a special handler, no completion
|
||||
if [[ ${cur} != "${words[cword]}" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
local completions
|
||||
completions=("${commands[@]}")
|
||||
if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
|
||||
completions=("${must_have_one_noun[@]}")
|
||||
fi
|
||||
if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
|
||||
completions+=("${must_have_one_flag[@]}")
|
||||
fi
|
||||
COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") )
|
||||
|
||||
if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then
|
||||
COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") )
|
||||
fi
|
||||
|
||||
if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
|
||||
declare -F __custom_func >/dev/null && __custom_func
|
||||
fi
|
||||
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
# The arguments should be in the form "ext1|ext2|extn"
|
||||
__handle_filename_extension_flag()
|
||||
{
|
||||
local ext="$1"
|
||||
_filedir "@(${ext})"
|
||||
}
|
||||
|
||||
__handle_subdirs_in_dir_flag()
|
||||
{
|
||||
local dir="$1"
|
||||
pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1
|
||||
}
|
||||
|
||||
__handle_flag()
|
||||
{
|
||||
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
|
||||
|
||||
# if a command required a flag, and we found it, unset must_have_one_flag()
|
||||
local flagname=${words[c]}
|
||||
local flagvalue
|
||||
# if the word contained an =
|
||||
if [[ ${words[c]} == *"="* ]]; then
|
||||
flagvalue=${flagname#*=} # take in as flagvalue after the =
|
||||
flagname=${flagname%%=*} # strip everything after the =
|
||||
flagname="${flagname}=" # but put the = back
|
||||
fi
|
||||
__debug "${FUNCNAME[0]}: looking for ${flagname}"
|
||||
if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then
|
||||
must_have_one_flag=()
|
||||
fi
|
||||
|
||||
# if you set a flag which only applies to this command, don't show subcommands
|
||||
if __contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then
|
||||
commands=()
|
||||
fi
|
||||
|
||||
# keep flag value with flagname as flaghash
|
||||
if [ -n "${flagvalue}" ] ; then
|
||||
flaghash[${flagname}]=${flagvalue}
|
||||
elif [ -n "${words[ $((c+1)) ]}" ] ; then
|
||||
flaghash[${flagname}]=${words[ $((c+1)) ]}
|
||||
else
|
||||
flaghash[${flagname}]="true" # pad "true" for bool flag
|
||||
fi
|
||||
|
||||
# skip the argument to a two word flag
|
||||
if __contains_word "${words[c]}" "${two_word_flags[@]}"; then
|
||||
c=$((c+1))
|
||||
# if we are looking for a flags value, don't show commands
|
||||
if [[ $c -eq $cword ]]; then
|
||||
commands=()
|
||||
fi
|
||||
fi
|
||||
|
||||
c=$((c+1))
|
||||
|
||||
}
|
||||
|
||||
__handle_noun()
|
||||
{
|
||||
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
|
||||
|
||||
if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
|
||||
must_have_one_noun=()
|
||||
elif __contains_word "${words[c]}" "${noun_aliases[@]}"; then
|
||||
must_have_one_noun=()
|
||||
fi
|
||||
|
||||
nouns+=("${words[c]}")
|
||||
c=$((c+1))
|
||||
}
|
||||
|
||||
__handle_command()
|
||||
{
|
||||
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
|
||||
|
||||
local next_command
|
||||
if [[ -n ${last_command} ]]; then
|
||||
next_command="_${last_command}_${words[c]//:/__}"
|
||||
else
|
||||
if [[ $c -eq 0 ]]; then
|
||||
next_command="_$(basename "${words[c]//:/__}")"
|
||||
else
|
||||
next_command="_${words[c]//:/__}"
|
||||
fi
|
||||
fi
|
||||
c=$((c+1))
|
||||
__debug "${FUNCNAME[0]}: looking for ${next_command}"
|
||||
declare -F $next_command >/dev/null && $next_command
|
||||
}
|
||||
|
||||
__handle_word()
|
||||
{
|
||||
if [[ $c -ge $cword ]]; then
|
||||
__handle_reply
|
||||
return
|
||||
fi
|
||||
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
|
||||
if [[ "${words[c]}" == -* ]]; then
|
||||
__handle_flag
|
||||
elif __contains_word "${words[c]}" "${commands[@]}"; then
|
||||
__handle_command
|
||||
elif [[ $c -eq 0 ]] && __contains_word "$(basename "${words[c]}")" "${commands[@]}"; then
|
||||
__handle_command
|
||||
else
|
||||
__handle_noun
|
||||
fi
|
||||
__handle_word
|
||||
}
|
||||
|
||||
`)
|
||||
return err
|
||||
}
|
||||
|
||||
func postscript(w io.Writer, name string) error {
|
||||
name = strings.Replace(name, ":", "__", -1)
|
||||
_, err := fmt.Fprintf(w, "__start_%s()\n", name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintf(w, `{
|
||||
local cur prev words cword
|
||||
declare -A flaghash 2>/dev/null || :
|
||||
if declare -F _init_completion >/dev/null 2>&1; then
|
||||
_init_completion -s || return
|
||||
else
|
||||
__my_init_completion -n "=" || return
|
||||
fi
|
||||
|
||||
local c=0
|
||||
local flags=()
|
||||
local two_word_flags=()
|
||||
local local_nonpersistent_flags=()
|
||||
local flags_with_completion=()
|
||||
local flags_completion=()
|
||||
local commands=("%s")
|
||||
local must_have_one_flag=()
|
||||
local must_have_one_noun=()
|
||||
local last_command
|
||||
local nouns=()
|
||||
|
||||
__handle_word
|
||||
}
|
||||
|
||||
`, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintf(w, `if [[ $(type -t compopt) = "builtin" ]]; then
|
||||
complete -o default -F __start_%s %s
|
||||
else
|
||||
complete -o default -o nospace -F __start_%s %s
|
||||
fi
|
||||
|
||||
`, name, name, name, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintf(w, "# ex: ts=4 sw=4 et filetype=sh\n")
|
||||
return err
|
||||
}
|
||||
|
||||
func writeCommands(cmd *Command, w io.Writer) error {
|
||||
if _, err := fmt.Fprintf(w, " commands=()\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range cmd.Commands() {
|
||||
if !c.IsAvailableCommand() || c == cmd.helpCommand {
|
||||
continue
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, " commands+=(%q)\n", c.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := fmt.Fprintf(w, "\n")
|
||||
return err
|
||||
}
|
||||
|
||||
func writeFlagHandler(name string, annotations map[string][]string, w io.Writer) error {
|
||||
for key, value := range annotations {
|
||||
switch key {
|
||||
case BashCompFilenameExt:
|
||||
_, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(value) > 0 {
|
||||
ext := "__handle_filename_extension_flag " + strings.Join(value, "|")
|
||||
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
|
||||
} else {
|
||||
ext := "_filedir"
|
||||
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case BashCompCustom:
|
||||
_, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(value) > 0 {
|
||||
handlers := strings.Join(value, "; ")
|
||||
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", handlers)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(w, " flags_completion+=(:)\n")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case BashCompSubdirsInDir:
|
||||
_, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name)
|
||||
|
||||
if len(value) == 1 {
|
||||
ext := "__handle_subdirs_in_dir_flag " + value[0]
|
||||
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
|
||||
} else {
|
||||
ext := "_filedir -d"
|
||||
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeShortFlag(flag *pflag.Flag, w io.Writer) error {
|
||||
b := (len(flag.NoOptDefVal) > 0)
|
||||
name := flag.Shorthand
|
||||
format := " "
|
||||
if !b {
|
||||
format += "two_word_"
|
||||
}
|
||||
format += "flags+=(\"-%s\")\n"
|
||||
if _, err := fmt.Fprintf(w, format, name); err != nil {
|
||||
return err
|
||||
}
|
||||
return writeFlagHandler("-"+name, flag.Annotations, w)
|
||||
}
|
||||
|
||||
func writeFlag(flag *pflag.Flag, w io.Writer) error {
|
||||
b := (len(flag.NoOptDefVal) > 0)
|
||||
name := flag.Name
|
||||
format := " flags+=(\"--%s"
|
||||
if !b {
|
||||
format += "="
|
||||
}
|
||||
format += "\")\n"
|
||||
if _, err := fmt.Fprintf(w, format, name); err != nil {
|
||||
return err
|
||||
}
|
||||
return writeFlagHandler("--"+name, flag.Annotations, w)
|
||||
}
|
||||
|
||||
func writeLocalNonPersistentFlag(flag *pflag.Flag, w io.Writer) error {
|
||||
b := (len(flag.NoOptDefVal) > 0)
|
||||
name := flag.Name
|
||||
format := " local_nonpersistent_flags+=(\"--%s"
|
||||
if !b {
|
||||
format += "="
|
||||
}
|
||||
format += "\")\n"
|
||||
_, err := fmt.Fprintf(w, format, name)
|
||||
return err
|
||||
}
|
||||
|
||||
func writeFlags(cmd *Command, w io.Writer) error {
|
||||
_, err := fmt.Fprintf(w, ` flags=()
|
||||
two_word_flags=()
|
||||
local_nonpersistent_flags=()
|
||||
flags_with_completion=()
|
||||
flags_completion=()
|
||||
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localNonPersistentFlags := cmd.LocalNonPersistentFlags()
|
||||
var visitErr error
|
||||
cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
||||
if nonCompletableFlag(flag) {
|
||||
return
|
||||
}
|
||||
if err := writeFlag(flag, w); err != nil {
|
||||
visitErr = err
|
||||
return
|
||||
}
|
||||
if len(flag.Shorthand) > 0 {
|
||||
if err := writeShortFlag(flag, w); err != nil {
|
||||
visitErr = err
|
||||
return
|
||||
}
|
||||
}
|
||||
if localNonPersistentFlags.Lookup(flag.Name) != nil {
|
||||
if err := writeLocalNonPersistentFlag(flag, w); err != nil {
|
||||
visitErr = err
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
if visitErr != nil {
|
||||
return visitErr
|
||||
}
|
||||
cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
||||
if nonCompletableFlag(flag) {
|
||||
return
|
||||
}
|
||||
if err := writeFlag(flag, w); err != nil {
|
||||
visitErr = err
|
||||
return
|
||||
}
|
||||
if len(flag.Shorthand) > 0 {
|
||||
if err := writeShortFlag(flag, w); err != nil {
|
||||
visitErr = err
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
if visitErr != nil {
|
||||
return visitErr
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintf(w, "\n")
|
||||
return err
|
||||
}
|
||||
|
||||
func writeRequiredFlag(cmd *Command, w io.Writer) error {
|
||||
if _, err := fmt.Fprintf(w, " must_have_one_flag=()\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
flags := cmd.NonInheritedFlags()
|
||||
var visitErr error
|
||||
flags.VisitAll(func(flag *pflag.Flag) {
|
||||
if nonCompletableFlag(flag) {
|
||||
return
|
||||
}
|
||||
for key := range flag.Annotations {
|
||||
switch key {
|
||||
case BashCompOneRequiredFlag:
|
||||
format := " must_have_one_flag+=(\"--%s"
|
||||
b := (flag.Value.Type() == "bool")
|
||||
if !b {
|
||||
format += "="
|
||||
}
|
||||
format += "\")\n"
|
||||
if _, err := fmt.Fprintf(w, format, flag.Name); err != nil {
|
||||
visitErr = err
|
||||
return
|
||||
}
|
||||
|
||||
if len(flag.Shorthand) > 0 {
|
||||
if _, err := fmt.Fprintf(w, " must_have_one_flag+=(\"-%s\")\n", flag.Shorthand); err != nil {
|
||||
visitErr = err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return visitErr
|
||||
}
|
||||
|
||||
func writeRequiredNouns(cmd *Command, w io.Writer) error {
|
||||
if _, err := fmt.Fprintf(w, " must_have_one_noun=()\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Sort(sort.StringSlice(cmd.ValidArgs))
|
||||
for _, value := range cmd.ValidArgs {
|
||||
if _, err := fmt.Fprintf(w, " must_have_one_noun+=(%q)\n", value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeArgAliases(cmd *Command, w io.Writer) error {
|
||||
if _, err := fmt.Fprintf(w, " noun_aliases=()\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Sort(sort.StringSlice(cmd.ArgAliases))
|
||||
for _, value := range cmd.ArgAliases {
|
||||
if _, err := fmt.Fprintf(w, " noun_aliases+=(%q)\n", value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func gen(cmd *Command, w io.Writer) error {
|
||||
for _, c := range cmd.Commands() {
|
||||
if !c.IsAvailableCommand() || c == cmd.helpCommand {
|
||||
continue
|
||||
}
|
||||
if err := gen(c, w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
commandName := cmd.CommandPath()
|
||||
commandName = strings.Replace(commandName, " ", "_", -1)
|
||||
commandName = strings.Replace(commandName, ":", "__", -1)
|
||||
if _, err := fmt.Fprintf(w, "_%s()\n{\n", commandName); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, " last_command=%q\n", commandName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeCommands(cmd, w); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFlags(cmd, w); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeRequiredFlag(cmd, w); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeRequiredNouns(cmd, w); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeArgAliases(cmd, w); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "}\n\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) GenBashCompletion(w io.Writer) error {
|
||||
if err := preamble(w, cmd.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cmd.BashCompletionFunction) > 0 {
|
||||
if _, err := fmt.Fprintf(w, "%s\n", cmd.BashCompletionFunction); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := gen(cmd, w); err != nil {
|
||||
return err
|
||||
}
|
||||
return postscript(w, cmd.Name())
|
||||
}
|
||||
|
||||
func nonCompletableFlag(flag *pflag.Flag) bool {
|
||||
return flag.Hidden || len(flag.Deprecated) > 0
|
||||
}
|
||||
|
||||
func (cmd *Command) GenBashCompletionFile(filename string) error {
|
||||
outFile, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
return cmd.GenBashCompletion(outFile)
|
||||
}
|
||||
|
||||
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists.
|
||||
func (cmd *Command) MarkFlagRequired(name string) error {
|
||||
return MarkFlagRequired(cmd.Flags(), name)
|
||||
}
|
||||
|
||||
// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag, if it exists.
|
||||
func (cmd *Command) MarkPersistentFlagRequired(name string) error {
|
||||
return MarkFlagRequired(cmd.PersistentFlags(), name)
|
||||
}
|
||||
|
||||
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag in the flag set, if it exists.
|
||||
func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
|
||||
return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
|
||||
}
|
||||
|
||||
// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
|
||||
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
|
||||
func (cmd *Command) MarkFlagFilename(name string, extensions ...string) error {
|
||||
return MarkFlagFilename(cmd.Flags(), name, extensions...)
|
||||
}
|
||||
|
||||
// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
|
||||
// Generated bash autocompletion will call the bash function f for the flag.
|
||||
func (cmd *Command) MarkFlagCustom(name string, f string) error {
|
||||
return MarkFlagCustom(cmd.Flags(), name, f)
|
||||
}
|
||||
|
||||
// MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists.
|
||||
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
|
||||
func (cmd *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
|
||||
return MarkFlagFilename(cmd.PersistentFlags(), name, extensions...)
|
||||
}
|
||||
|
||||
// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists.
|
||||
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
|
||||
func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
|
||||
return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
|
||||
}
|
||||
|
||||
// MarkFlagCustom adds the BashCompCustom annotation to the named flag in the flag set, if it exists.
|
||||
// Generated bash autocompletion will call the bash function f for the flag.
|
||||
func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error {
|
||||
return flags.SetAnnotation(name, BashCompCustom, []string{f})
|
||||
}
|
206
vendor/src/github.com/spf13/cobra/bash_completions.md
vendored
Normal file
206
vendor/src/github.com/spf13/cobra/bash_completions.md
vendored
Normal file
@ -0,0 +1,206 @@
|
||||
# Generating Bash Completions For Your Own cobra.Command
|
||||
|
||||
Generating bash completions from a cobra command is incredibly easy. An actual program which does so for the kubernetes kubectl binary is as follows:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
kubectl := cmd.NewFactory(nil).NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard)
|
||||
kubectl.GenBashCompletionFile("out.sh")
|
||||
}
|
||||
```
|
||||
|
||||
That will get you completions of subcommands and flags. If you make additional annotations to your code, you can get even more intelligent and flexible behavior.
|
||||
|
||||
## Creating your own custom functions
|
||||
|
||||
Some more actual code that works in kubernetes:
|
||||
|
||||
```bash
|
||||
const (
|
||||
bash_completion_func = `__kubectl_parse_get()
|
||||
{
|
||||
local kubectl_output out
|
||||
if kubectl_output=$(kubectl get --no-headers "$1" 2>/dev/null); then
|
||||
out=($(echo "${kubectl_output}" | awk '{print $1}'))
|
||||
COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
|
||||
fi
|
||||
}
|
||||
|
||||
__kubectl_get_resource()
|
||||
{
|
||||
if [[ ${#nouns[@]} -eq 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
__kubectl_parse_get ${nouns[${#nouns[@]} -1]}
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
__custom_func() {
|
||||
case ${last_command} in
|
||||
kubectl_get | kubectl_describe | kubectl_delete | kubectl_stop)
|
||||
__kubectl_get_resource
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
`)
|
||||
```
|
||||
|
||||
And then I set that in my command definition:
|
||||
|
||||
```go
|
||||
cmds := &cobra.Command{
|
||||
Use: "kubectl",
|
||||
Short: "kubectl controls the Kubernetes cluster manager",
|
||||
Long: `kubectl controls the Kubernetes cluster manager.
|
||||
|
||||
Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
|
||||
Run: runHelp,
|
||||
BashCompletionFunction: bash_completion_func,
|
||||
}
|
||||
```
|
||||
|
||||
The `BashCompletionFunction` option is really only valid/useful on the root command. Doing the above will cause `__custom_func()` to be called when the built in processor was unable to find a solution. In the case of kubernetes a valid command might look something like `kubectl get pod [mypod]`. If you type `kubectl get pod [tab][tab]` the `__customc_func()` will run because the cobra.Command only understood "kubectl" and "get." `__custom_func()` will see that the cobra.Command is "kubectl_get" and will thus call another helper `__kubectl_get_resource()`. `__kubectl_get_resource` will look at the 'nouns' collected. In our example the only noun will be `pod`. So it will call `__kubectl_parse_get pod`. `__kubectl_parse_get` will actually call out to kubernetes and get any pods. It will then set `COMPREPLY` to valid pods!
|
||||
|
||||
## Have the completions code complete your 'nouns'
|
||||
|
||||
In the above example "pod" was assumed to already be typed. But if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. Simplified code from `kubectl get` looks like:
|
||||
|
||||
```go
|
||||
validArgs []string = { "pod", "node", "service", "replicationcontroller" }
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)",
|
||||
Short: "Display one or many resources",
|
||||
Long: get_long,
|
||||
Example: get_example,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := RunGet(f, out, cmd, args)
|
||||
util.CheckErr(err)
|
||||
},
|
||||
ValidArgs: validArgs,
|
||||
}
|
||||
```
|
||||
|
||||
Notice we put the "ValidArgs" on the "get" subcommand. Doing so will give results like
|
||||
|
||||
```bash
|
||||
# kubectl get [tab][tab]
|
||||
node pod replicationcontroller service
|
||||
```
|
||||
|
||||
## Plural form and shortcuts for nouns
|
||||
|
||||
If your nouns have a number of aliases, you can define them alongside `ValidArgs` using `ArgAliases`:
|
||||
|
||||
```go`
|
||||
argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" }
|
||||
|
||||
cmd := &cobra.Command{
|
||||
...
|
||||
ValidArgs: validArgs,
|
||||
ArgAliases: argAliases
|
||||
}
|
||||
```
|
||||
|
||||
The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by
|
||||
the completion algorithm if entered manually, e.g. in:
|
||||
|
||||
```bash
|
||||
# kubectl get rc [tab][tab]
|
||||
backend frontend database
|
||||
```
|
||||
|
||||
Note that without declaring `rc` as an alias, the completion algorithm would show the list of nouns
|
||||
in this example again instead of the replication controllers.
|
||||
|
||||
## Mark flags as required
|
||||
|
||||
Most of the time completions will only show subcommands. But if a flag is required to make a subcommand work, you probably want it to show up when the user types [tab][tab]. Marking a flag as 'Required' is incredibly easy.
|
||||
|
||||
```go
|
||||
cmd.MarkFlagRequired("pod")
|
||||
cmd.MarkFlagRequired("container")
|
||||
```
|
||||
|
||||
and you'll get something like
|
||||
|
||||
```bash
|
||||
# kubectl exec [tab][tab][tab]
|
||||
-c --container= -p --pod=
|
||||
```
|
||||
|
||||
# Specify valid filename extensions for flags that take a filename
|
||||
|
||||
In this example we use --filename= and expect to get a json or yaml file as the argument. To make this easier we annotate the --filename flag with valid filename extensions.
|
||||
|
||||
```go
|
||||
annotations := []string{"json", "yaml", "yml"}
|
||||
annotation := make(map[string][]string)
|
||||
annotation[cobra.BashCompFilenameExt] = annotations
|
||||
|
||||
flag := &pflag.Flag{
|
||||
Name: "filename",
|
||||
Shorthand: "f",
|
||||
Usage: usage,
|
||||
Value: value,
|
||||
DefValue: value.String(),
|
||||
Annotations: annotation,
|
||||
}
|
||||
cmd.Flags().AddFlag(flag)
|
||||
```
|
||||
|
||||
Now when you run a command with this filename flag you'll get something like
|
||||
|
||||
```bash
|
||||
# kubectl create -f
|
||||
test/ example/ rpmbuild/
|
||||
hello.yml test.json
|
||||
```
|
||||
|
||||
So while there are many other files in the CWD it only shows me subdirs and those with valid extensions.
|
||||
|
||||
# Specifiy custom flag completion
|
||||
|
||||
Similar to the filename completion and filtering using cobra.BashCompFilenameExt, you can specifiy
|
||||
a custom flag completion function with cobra.BashCompCustom:
|
||||
|
||||
```go
|
||||
annotation := make(map[string][]string)
|
||||
annotation[cobra.BashCompFilenameExt] = []string{"__kubectl_get_namespaces"}
|
||||
|
||||
flag := &pflag.Flag{
|
||||
Name: "namespace",
|
||||
Usage: usage,
|
||||
Annotations: annotation,
|
||||
}
|
||||
cmd.Flags().AddFlag(flag)
|
||||
```
|
||||
|
||||
In addition add the `__handle_namespace_flag` implementation in the `BashCompletionFunction`
|
||||
value, e.g.:
|
||||
|
||||
```bash
|
||||
__kubectl_get_namespaces()
|
||||
{
|
||||
local template
|
||||
template="{{ range .items }}{{ .metadata.name }} {{ end }}"
|
||||
local kubectl_out
|
||||
if kubectl_out=$(kubectl get -o template --template="${template}" namespace 2>/dev/null); then
|
||||
COMPREPLY=( $( compgen -W "${kubectl_out}[*]" -- "$cur" ) )
|
||||
fi
|
||||
}
|
||||
```
|
180
vendor/src/github.com/spf13/cobra/bash_completions_test.go
vendored
Normal file
180
vendor/src/github.com/spf13/cobra/bash_completions_test.go
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
package cobra
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var _ = fmt.Println
|
||||
var _ = os.Stderr
|
||||
|
||||
func checkOmit(t *testing.T, found, unexpected string) {
|
||||
if strings.Contains(found, unexpected) {
|
||||
t.Errorf("Unexpected response.\nGot: %q\nBut should not have!\n", unexpected)
|
||||
}
|
||||
}
|
||||
|
||||
func check(t *testing.T, found, expected string) {
|
||||
if !strings.Contains(found, expected) {
|
||||
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
|
||||
}
|
||||
}
|
||||
|
||||
func runShellCheck(s string) error {
|
||||
excluded := []string{
|
||||
"SC2034", // PREFIX appears unused. Verify it or export it.
|
||||
}
|
||||
cmd := exec.Command("shellcheck", "-s", "bash", "-", "-e", strings.Join(excluded, ","))
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stdout
|
||||
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
defer stdin.Close()
|
||||
stdin.Write([]byte(s))
|
||||
}()
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// World worst custom function, just keep telling you to enter hello!
|
||||
const (
|
||||
bashCompletionFunc = `__custom_func() {
|
||||
COMPREPLY=( "hello" )
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
func TestBashCompletions(t *testing.T) {
|
||||
c := initializeWithRootCmd()
|
||||
cmdEcho.AddCommand(cmdTimes)
|
||||
c.AddCommand(cmdEcho, cmdPrint, cmdDeprecated, cmdColon)
|
||||
|
||||
// custom completion function
|
||||
c.BashCompletionFunction = bashCompletionFunc
|
||||
|
||||
// required flag
|
||||
c.MarkFlagRequired("introot")
|
||||
|
||||
// valid nouns
|
||||
validArgs := []string{"pod", "node", "service", "replicationcontroller"}
|
||||
c.ValidArgs = validArgs
|
||||
|
||||
// noun aliases
|
||||
argAliases := []string{"pods", "nodes", "services", "replicationcontrollers", "po", "no", "svc", "rc"}
|
||||
c.ArgAliases = argAliases
|
||||
|
||||
// filename
|
||||
var flagval string
|
||||
c.Flags().StringVar(&flagval, "filename", "", "Enter a filename")
|
||||
c.MarkFlagFilename("filename", "json", "yaml", "yml")
|
||||
|
||||
// persistent filename
|
||||
var flagvalPersistent string
|
||||
c.PersistentFlags().StringVar(&flagvalPersistent, "persistent-filename", "", "Enter a filename")
|
||||
c.MarkPersistentFlagFilename("persistent-filename")
|
||||
c.MarkPersistentFlagRequired("persistent-filename")
|
||||
|
||||
// filename extensions
|
||||
var flagvalExt string
|
||||
c.Flags().StringVar(&flagvalExt, "filename-ext", "", "Enter a filename (extension limited)")
|
||||
c.MarkFlagFilename("filename-ext")
|
||||
|
||||
// filename extensions
|
||||
var flagvalCustom string
|
||||
c.Flags().StringVar(&flagvalCustom, "custom", "", "Enter a filename (extension limited)")
|
||||
c.MarkFlagCustom("custom", "__complete_custom")
|
||||
|
||||
// subdirectories in a given directory
|
||||
var flagvalTheme string
|
||||
c.Flags().StringVar(&flagvalTheme, "theme", "", "theme to use (located in /themes/THEMENAME/)")
|
||||
c.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"})
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
c.GenBashCompletion(out)
|
||||
str := out.String()
|
||||
|
||||
check(t, str, "_cobra-test")
|
||||
check(t, str, "_cobra-test_echo")
|
||||
check(t, str, "_cobra-test_echo_times")
|
||||
check(t, str, "_cobra-test_print")
|
||||
check(t, str, "_cobra-test_cmd__colon")
|
||||
|
||||
// check for required flags
|
||||
check(t, str, `must_have_one_flag+=("--introot=")`)
|
||||
check(t, str, `must_have_one_flag+=("--persistent-filename=")`)
|
||||
// check for custom completion function
|
||||
check(t, str, `COMPREPLY=( "hello" )`)
|
||||
// check for required nouns
|
||||
check(t, str, `must_have_one_noun+=("pod")`)
|
||||
// check for noun aliases
|
||||
check(t, str, `noun_aliases+=("pods")`)
|
||||
check(t, str, `noun_aliases+=("rc")`)
|
||||
checkOmit(t, str, `must_have_one_noun+=("pods")`)
|
||||
// check for filename extension flags
|
||||
check(t, str, `flags_completion+=("_filedir")`)
|
||||
// check for filename extension flags
|
||||
check(t, str, `flags_completion+=("__handle_filename_extension_flag json|yaml|yml")`)
|
||||
// check for custom flags
|
||||
check(t, str, `flags_completion+=("__complete_custom")`)
|
||||
// check for subdirs_in_dir flags
|
||||
check(t, str, `flags_completion+=("__handle_subdirs_in_dir_flag themes")`)
|
||||
|
||||
checkOmit(t, str, cmdDeprecated.Name())
|
||||
|
||||
// if available, run shellcheck against the script
|
||||
if err := exec.Command("which", "shellcheck").Run(); err != nil {
|
||||
return
|
||||
}
|
||||
err := runShellCheck(str)
|
||||
if err != nil {
|
||||
t.Fatalf("shellcheck failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBashCompletionHiddenFlag(t *testing.T) {
|
||||
var cmdTrue = &Command{
|
||||
Use: "does nothing",
|
||||
Run: func(cmd *Command, args []string) {},
|
||||
}
|
||||
|
||||
const flagName = "hidden-foo-bar-baz"
|
||||
|
||||
var flagValue bool
|
||||
cmdTrue.Flags().BoolVar(&flagValue, flagName, false, "hidden flag")
|
||||
cmdTrue.Flags().MarkHidden(flagName)
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
cmdTrue.GenBashCompletion(out)
|
||||
bashCompletion := out.String()
|
||||
if strings.Contains(bashCompletion, flagName) {
|
||||
t.Errorf("expected completion to not include %q flag: Got %v", flagName, bashCompletion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBashCompletionDeprecatedFlag(t *testing.T) {
|
||||
var cmdTrue = &Command{
|
||||
Use: "does nothing",
|
||||
Run: func(cmd *Command, args []string) {},
|
||||
}
|
||||
|
||||
const flagName = "deprecated-foo-bar-baz"
|
||||
|
||||
var flagValue bool
|
||||
cmdTrue.Flags().BoolVar(&flagValue, flagName, false, "hidden flag")
|
||||
cmdTrue.Flags().MarkDeprecated(flagName, "use --does-not-exist instead")
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
cmdTrue.GenBashCompletion(out)
|
||||
bashCompletion := out.String()
|
||||
if strings.Contains(bashCompletion, flagName) {
|
||||
t.Errorf("expected completion to not include %q flag: Got %v", flagName, bashCompletion)
|
||||
}
|
||||
}
|
173
vendor/src/github.com/spf13/cobra/cobra.go
vendored
Normal file
173
vendor/src/github.com/spf13/cobra/cobra.go
vendored
Normal file
@ -0,0 +1,173 @@
|
||||
// Copyright © 2013 Steve Francia <spf@spf13.com>.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Commands similar to git, go tools and other modern CLI tools
|
||||
// inspired by go, go-Commander, gh and subcommand
|
||||
|
||||
package cobra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var templateFuncs = template.FuncMap{
|
||||
"trim": strings.TrimSpace,
|
||||
"trimRightSpace": trimRightSpace,
|
||||
"appendIfNotPresent": appendIfNotPresent,
|
||||
"rpad": rpad,
|
||||
"gt": Gt,
|
||||
"eq": Eq,
|
||||
}
|
||||
|
||||
var initializers []func()
|
||||
|
||||
// Automatic prefix matching can be a dangerous thing to automatically enable in CLI tools.
|
||||
// Set this to true to enable it.
|
||||
var EnablePrefixMatching = false
|
||||
|
||||
// EnableCommandSorting controls sorting of the slice of commands, which is turned on by default.
|
||||
// To disable sorting, set it to false.
|
||||
var EnableCommandSorting = true
|
||||
|
||||
// AddTemplateFunc adds a template function that's available to Usage and Help
|
||||
// template generation.
|
||||
func AddTemplateFunc(name string, tmplFunc interface{}) {
|
||||
templateFuncs[name] = tmplFunc
|
||||
}
|
||||
|
||||
// AddTemplateFuncs adds multiple template functions availalble to Usage and
|
||||
// Help template generation.
|
||||
func AddTemplateFuncs(tmplFuncs template.FuncMap) {
|
||||
for k, v := range tmplFuncs {
|
||||
templateFuncs[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// OnInitialize takes a series of func() arguments and appends them to a slice of func().
|
||||
func OnInitialize(y ...func()) {
|
||||
initializers = append(initializers, y...)
|
||||
}
|
||||
|
||||
// Gt takes two types and checks whether the first type is greater than the second. In case of types Arrays, Chans,
|
||||
// Maps and Slices, Gt will compare their lengths. Ints are compared directly while strings are first parsed as
|
||||
// ints and then compared.
|
||||
func Gt(a interface{}, b interface{}) bool {
|
||||
var left, right int64
|
||||
av := reflect.ValueOf(a)
|
||||
|
||||
switch av.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
||||
left = int64(av.Len())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
left = av.Int()
|
||||
case reflect.String:
|
||||
left, _ = strconv.ParseInt(av.String(), 10, 64)
|
||||
}
|
||||
|
||||
bv := reflect.ValueOf(b)
|
||||
|
||||
switch bv.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
||||
right = int64(bv.Len())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
right = bv.Int()
|
||||
case reflect.String:
|
||||
right, _ = strconv.ParseInt(bv.String(), 10, 64)
|
||||
}
|
||||
|
||||
return left > right
|
||||
}
|
||||
|
||||
// Eq takes two types and checks whether they are equal. Supported types are int and string. Unsupported types will panic.
|
||||
func Eq(a interface{}, b interface{}) bool {
|
||||
av := reflect.ValueOf(a)
|
||||
bv := reflect.ValueOf(b)
|
||||
|
||||
switch av.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
||||
panic("Eq called on unsupported type")
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return av.Int() == bv.Int()
|
||||
case reflect.String:
|
||||
return av.String() == bv.String()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func trimRightSpace(s string) string {
|
||||
return strings.TrimRightFunc(s, unicode.IsSpace)
|
||||
}
|
||||
|
||||
// appendIfNotPresent will append stringToAppend to the end of s, but only if it's not yet present in s.
|
||||
func appendIfNotPresent(s, stringToAppend string) string {
|
||||
if strings.Contains(s, stringToAppend) {
|
||||
return s
|
||||
}
|
||||
return s + " " + stringToAppend
|
||||
}
|
||||
|
||||
// rpad adds padding to the right of a string.
|
||||
func rpad(s string, padding int) string {
|
||||
template := fmt.Sprintf("%%-%ds", padding)
|
||||
return fmt.Sprintf(template, s)
|
||||
}
|
||||
|
||||
// tmpl executes the given template text on data, writing the result to w.
|
||||
func tmpl(w io.Writer, text string, data interface{}) error {
|
||||
t := template.New("top")
|
||||
t.Funcs(templateFuncs)
|
||||
template.Must(t.Parse(text))
|
||||
return t.Execute(w, data)
|
||||
}
|
||||
|
||||
// ld compares two strings and returns the levenshtein distance between them.
|
||||
func ld(s, t string, ignoreCase bool) int {
|
||||
if ignoreCase {
|
||||
s = strings.ToLower(s)
|
||||
t = strings.ToLower(t)
|
||||
}
|
||||
d := make([][]int, len(s)+1)
|
||||
for i := range d {
|
||||
d[i] = make([]int, len(t)+1)
|
||||
}
|
||||
for i := range d {
|
||||
d[i][0] = i
|
||||
}
|
||||
for j := range d[0] {
|
||||
d[0][j] = j
|
||||
}
|
||||
for j := 1; j <= len(t); j++ {
|
||||
for i := 1; i <= len(s); i++ {
|
||||
if s[i-1] == t[j-1] {
|
||||
d[i][j] = d[i-1][j-1]
|
||||
} else {
|
||||
min := d[i-1][j]
|
||||
if d[i][j-1] < min {
|
||||
min = d[i][j-1]
|
||||
}
|
||||
if d[i-1][j-1] < min {
|
||||
min = d[i-1][j-1]
|
||||
}
|
||||
d[i][j] = min + 1
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return d[len(s)][len(t)]
|
||||
}
|
128
vendor/src/github.com/spf13/cobra/cobra/cmd/add.go
vendored
Normal file
128
vendor/src/github.com/spf13/cobra/cobra/cmd/add.go
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
// Copyright © 2015 Steve Francia <spf@spf13.com>.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(addCmd)
|
||||
}
|
||||
|
||||
var pName string
|
||||
|
||||
// initialize Command
|
||||
var addCmd = &cobra.Command{
|
||||
Use: "add [command name]",
|
||||
Aliases: []string{"command"},
|
||||
Short: "Add a command to a Cobra Application",
|
||||
Long: `Add (cobra add) will create a new command, with a license and
|
||||
the appropriate structure for a Cobra-based CLI application,
|
||||
and register it to its parent (default RootCmd).
|
||||
|
||||
If you want your command to be public, pass in the command name
|
||||
with an initial uppercase letter.
|
||||
|
||||
Example: cobra add server -> resulting in a new cmd/server.go
|
||||
`,
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 1 {
|
||||
er("add needs a name for the command")
|
||||
}
|
||||
guessProjectPath()
|
||||
createCmdFile(args[0])
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
addCmd.Flags().StringVarP(&pName, "parent", "p", "RootCmd", "name of parent command for this command")
|
||||
}
|
||||
|
||||
func parentName() string {
|
||||
if !strings.HasSuffix(strings.ToLower(pName), "cmd") {
|
||||
return pName + "Cmd"
|
||||
}
|
||||
|
||||
return pName
|
||||
}
|
||||
|
||||
func createCmdFile(cmdName string) {
|
||||
lic := getLicense()
|
||||
|
||||
template := `{{ comment .copyright }}
|
||||
{{ comment .license }}
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// {{.cmdName}}Cmd represents the {{.cmdName}} command
|
||||
var {{ .cmdName }}Cmd = &cobra.Command{
|
||||
Use: "{{ .cmdName }}",
|
||||
Short: "A brief description of your command",
|
||||
Long: ` + "`" + `A longer description that spans multiple lines and likely contains examples
|
||||
and usage of using your command. For example:
|
||||
|
||||
Cobra is a CLI library for Go that empowers applications.
|
||||
This application is a tool to generate the needed files
|
||||
to quickly create a Cobra application.` + "`" + `,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// TODO: Work your own magic here
|
||||
fmt.Println("{{ .cmdName }} called")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
{{ .parentName }}.AddCommand({{ .cmdName }}Cmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// {{.cmdName}}Cmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// {{.cmdName}}Cmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
|
||||
}
|
||||
`
|
||||
|
||||
var data map[string]interface{}
|
||||
data = make(map[string]interface{})
|
||||
|
||||
data["copyright"] = copyrightLine()
|
||||
data["license"] = lic.Header
|
||||
data["appName"] = projectName()
|
||||
data["viper"] = viper.GetBool("useViper")
|
||||
data["parentName"] = parentName()
|
||||
data["cmdName"] = cmdName
|
||||
|
||||
err := writeTemplateToFile(filepath.Join(ProjectPath(), guessCmdDir()), cmdName+".go", template, data)
|
||||
if err != nil {
|
||||
er(err)
|
||||
}
|
||||
fmt.Println(cmdName, "created at", filepath.Join(ProjectPath(), guessCmdDir(), cmdName+".go"))
|
||||
}
|
356
vendor/src/github.com/spf13/cobra/cobra/cmd/helpers.go
vendored
Normal file
356
vendor/src/github.com/spf13/cobra/cobra/cmd/helpers.go
vendored
Normal file
@ -0,0 +1,356 @@
|
||||
// Copyright © 2015 Steve Francia <spf@spf13.com>.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// var BaseDir = ""
|
||||
// var AppName = ""
|
||||
// var CommandDir = ""
|
||||
|
||||
var funcMap template.FuncMap
|
||||
var projectPath = ""
|
||||
var inputPath = ""
|
||||
var projectBase = ""
|
||||
|
||||
// for testing only
|
||||
var testWd = ""
|
||||
|
||||
var cmdDirs = []string{"cmd", "cmds", "command", "commands"}
|
||||
|
||||
func init() {
|
||||
funcMap = template.FuncMap{
|
||||
"comment": commentifyString,
|
||||
}
|
||||
}
|
||||
|
||||
func er(msg interface{}) {
|
||||
fmt.Println("Error:", msg)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// Check if a file or directory exists.
|
||||
func exists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func ProjectPath() string {
|
||||
if projectPath == "" {
|
||||
guessProjectPath()
|
||||
}
|
||||
|
||||
return projectPath
|
||||
}
|
||||
|
||||
// wrapper of the os package so we can test better
|
||||
func getWd() (string, error) {
|
||||
if testWd == "" {
|
||||
return os.Getwd()
|
||||
}
|
||||
return testWd, nil
|
||||
}
|
||||
|
||||
func guessCmdDir() string {
|
||||
guessProjectPath()
|
||||
if b, _ := isEmpty(projectPath); b {
|
||||
return "cmd"
|
||||
}
|
||||
|
||||
files, _ := filepath.Glob(projectPath + string(os.PathSeparator) + "c*")
|
||||
for _, f := range files {
|
||||
for _, c := range cmdDirs {
|
||||
if f == c {
|
||||
return c
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "cmd"
|
||||
}
|
||||
|
||||
func guessImportPath() string {
|
||||
guessProjectPath()
|
||||
|
||||
if !strings.HasPrefix(projectPath, getSrcPath()) {
|
||||
er("Cobra only supports project within $GOPATH")
|
||||
}
|
||||
|
||||
return filepath.ToSlash(filepath.Clean(strings.TrimPrefix(projectPath, getSrcPath())))
|
||||
}
|
||||
|
||||
func getSrcPath() string {
|
||||
return filepath.Join(os.Getenv("GOPATH"), "src") + string(os.PathSeparator)
|
||||
}
|
||||
|
||||
func projectName() string {
|
||||
return filepath.Base(ProjectPath())
|
||||
}
|
||||
|
||||
func guessProjectPath() {
|
||||
// if no path is provided... assume CWD.
|
||||
if inputPath == "" {
|
||||
x, err := getWd()
|
||||
if err != nil {
|
||||
er(err)
|
||||
}
|
||||
|
||||
// inspect CWD
|
||||
base := filepath.Base(x)
|
||||
|
||||
// if we are in the cmd directory.. back up
|
||||
for _, c := range cmdDirs {
|
||||
if base == c {
|
||||
projectPath = filepath.Dir(x)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if projectPath == "" {
|
||||
projectPath = filepath.Clean(x)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
srcPath := getSrcPath()
|
||||
// if provided, inspect for logical locations
|
||||
if strings.ContainsRune(inputPath, os.PathSeparator) {
|
||||
if filepath.IsAbs(inputPath) || filepath.HasPrefix(inputPath, string(os.PathSeparator)) {
|
||||
// if Absolute, use it
|
||||
projectPath = filepath.Clean(inputPath)
|
||||
return
|
||||
}
|
||||
// If not absolute but contains slashes,
|
||||
// assuming it means create it from $GOPATH
|
||||
count := strings.Count(inputPath, string(os.PathSeparator))
|
||||
|
||||
switch count {
|
||||
// If only one directory deep, assume "github.com"
|
||||
case 1:
|
||||
projectPath = filepath.Join(srcPath, "github.com", inputPath)
|
||||
return
|
||||
case 2:
|
||||
projectPath = filepath.Join(srcPath, inputPath)
|
||||
return
|
||||
default:
|
||||
er("Unknown directory")
|
||||
}
|
||||
} else {
|
||||
// hardest case.. just a word.
|
||||
if projectBase == "" {
|
||||
x, err := getWd()
|
||||
if err == nil {
|
||||
projectPath = filepath.Join(x, inputPath)
|
||||
return
|
||||
}
|
||||
er(err)
|
||||
} else {
|
||||
projectPath = filepath.Join(srcPath, projectBase, inputPath)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isEmpty checks if a given path is empty.
|
||||
func isEmpty(path string) (bool, error) {
|
||||
if b, _ := exists(path); !b {
|
||||
return false, fmt.Errorf("%q path does not exist", path)
|
||||
}
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if fi.IsDir() {
|
||||
f, err := os.Open(path)
|
||||
// FIX: Resource leak - f.close() should be called here by defer or is missed
|
||||
// if the err != nil branch is taken.
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
list, _ := f.Readdir(-1)
|
||||
// f.Close() - see bug fix above
|
||||
return len(list) == 0, nil
|
||||
}
|
||||
return fi.Size() == 0, nil
|
||||
}
|
||||
|
||||
// isDir checks if a given path is a directory.
|
||||
func isDir(path string) (bool, error) {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return fi.IsDir(), nil
|
||||
}
|
||||
|
||||
// dirExists checks if a path exists and is a directory.
|
||||
func dirExists(path string) (bool, error) {
|
||||
fi, err := os.Stat(path)
|
||||
if err == nil && fi.IsDir() {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func writeTemplateToFile(path string, file string, template string, data interface{}) error {
|
||||
filename := filepath.Join(path, file)
|
||||
|
||||
r, err := templateToReader(template, data)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = safeWriteToDisk(filename, r)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeStringToFile(path, file, text string) error {
|
||||
filename := filepath.Join(path, file)
|
||||
|
||||
r := strings.NewReader(text)
|
||||
err := safeWriteToDisk(filename, r)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func templateToReader(tpl string, data interface{}) (io.Reader, error) {
|
||||
tmpl := template.New("")
|
||||
tmpl.Funcs(funcMap)
|
||||
tmpl, err := tmpl.Parse(tpl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
err = tmpl.Execute(buf, data)
|
||||
|
||||
return buf, err
|
||||
}
|
||||
|
||||
// Same as WriteToDisk but checks to see if file/directory already exists.
|
||||
func safeWriteToDisk(inpath string, r io.Reader) (err error) {
|
||||
dir, _ := filepath.Split(inpath)
|
||||
ospath := filepath.FromSlash(dir)
|
||||
|
||||
if ospath != "" {
|
||||
err = os.MkdirAll(ospath, 0777) // rwx, rw, r
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ex, err := exists(inpath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ex {
|
||||
return fmt.Errorf("%v already exists", inpath)
|
||||
}
|
||||
|
||||
file, err := os.Create(inpath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, r)
|
||||
return
|
||||
}
|
||||
|
||||
func getLicense() License {
|
||||
l := whichLicense()
|
||||
if l != "" {
|
||||
if x, ok := Licenses[l]; ok {
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
return Licenses["apache"]
|
||||
}
|
||||
|
||||
func whichLicense() string {
|
||||
// if explicitly flagged, use that
|
||||
if userLicense != "" {
|
||||
return matchLicense(userLicense)
|
||||
}
|
||||
|
||||
// if already present in the project, use that
|
||||
// TODO: Inspect project for existing license
|
||||
|
||||
// default to viper's setting
|
||||
|
||||
if viper.IsSet("license.header") || viper.IsSet("license.text") {
|
||||
if custom, ok := Licenses["custom"]; ok {
|
||||
custom.Header = viper.GetString("license.header")
|
||||
custom.Text = viper.GetString("license.text")
|
||||
Licenses["custom"] = custom
|
||||
return "custom"
|
||||
}
|
||||
}
|
||||
|
||||
return matchLicense(viper.GetString("license"))
|
||||
}
|
||||
|
||||
func copyrightLine() string {
|
||||
author := viper.GetString("author")
|
||||
year := time.Now().Format("2006")
|
||||
|
||||
return "Copyright © " + year + " " + author
|
||||
}
|
||||
|
||||
func commentifyString(in string) string {
|
||||
var newlines []string
|
||||
lines := strings.Split(in, "\n")
|
||||
for _, x := range lines {
|
||||
if !strings.HasPrefix(x, "//") {
|
||||
if x != "" {
|
||||
newlines = append(newlines, "// "+x)
|
||||
} else {
|
||||
newlines = append(newlines, "//")
|
||||
}
|
||||
} else {
|
||||
newlines = append(newlines, x)
|
||||
}
|
||||
}
|
||||
return strings.Join(newlines, "\n")
|
||||
}
|
40
vendor/src/github.com/spf13/cobra/cobra/cmd/helpers_test.go
vendored
Normal file
40
vendor/src/github.com/spf13/cobra/cobra/cmd/helpers_test.go
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var _ = fmt.Println
|
||||
var _ = os.Stderr
|
||||
|
||||
func checkGuess(t *testing.T, wd, input, expected string) {
|
||||
testWd = wd
|
||||
inputPath = input
|
||||
guessProjectPath()
|
||||
|
||||
if projectPath != expected {
|
||||
t.Errorf("Unexpected Project Path. \n Got: %q\nExpected: %q\n", projectPath, expected)
|
||||
}
|
||||
|
||||
reset()
|
||||
}
|
||||
|
||||
func reset() {
|
||||
testWd = ""
|
||||
inputPath = ""
|
||||
projectPath = ""
|
||||
}
|
||||
|
||||
func TestProjectPath(t *testing.T) {
|
||||
checkGuess(t, "", filepath.Join("github.com", "spf13", "hugo"), filepath.Join(getSrcPath(), "github.com", "spf13", "hugo"))
|
||||
checkGuess(t, "", filepath.Join("spf13", "hugo"), filepath.Join(getSrcPath(), "github.com", "spf13", "hugo"))
|
||||
checkGuess(t, "", filepath.Join("/", "bar", "foo"), filepath.Join("/", "bar", "foo"))
|
||||
checkGuess(t, "/bar/foo", "baz", filepath.Join("/", "bar", "foo", "baz"))
|
||||
checkGuess(t, "/bar/foo/cmd", "", filepath.Join("/", "bar", "foo"))
|
||||
checkGuess(t, "/bar/foo/command", "", filepath.Join("/", "bar", "foo"))
|
||||
checkGuess(t, "/bar/foo/commands", "", filepath.Join("/", "bar", "foo"))
|
||||
checkGuess(t, "github.com/spf13/hugo/../hugo", "", filepath.Join("github.com", "spf13", "hugo"))
|
||||
}
|
245
vendor/src/github.com/spf13/cobra/cobra/cmd/init.go
vendored
Normal file
245
vendor/src/github.com/spf13/cobra/cobra/cmd/init.go
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
// Copyright © 2015 Steve Francia <spf@spf13.com>.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(initCmd)
|
||||
}
|
||||
|
||||
// initialize Command
|
||||
var initCmd = &cobra.Command{
|
||||
Use: "init [name]",
|
||||
Aliases: []string{"initialize", "initialise", "create"},
|
||||
Short: "Initialize a Cobra Application",
|
||||
Long: `Initialize (cobra init) will create a new application, with a license
|
||||
and the appropriate structure for a Cobra-based CLI application.
|
||||
|
||||
* If a name is provided, it will be created in the current directory;
|
||||
* If no name is provided, the current directory will be assumed;
|
||||
* If a relative path is provided, it will be created inside $GOPATH
|
||||
(e.g. github.com/spf13/hugo);
|
||||
* If an absolute path is provided, it will be created;
|
||||
* If the directory already exists but is empty, it will be used.
|
||||
|
||||
Init will not use an existing directory with contents.`,
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
switch len(args) {
|
||||
case 0:
|
||||
inputPath = ""
|
||||
|
||||
case 1:
|
||||
inputPath = args[0]
|
||||
|
||||
default:
|
||||
er("init doesn't support more than 1 parameter")
|
||||
}
|
||||
guessProjectPath()
|
||||
initializePath(projectPath)
|
||||
},
|
||||
}
|
||||
|
||||
func initializePath(path string) {
|
||||
b, err := exists(path)
|
||||
if err != nil {
|
||||
er(err)
|
||||
}
|
||||
|
||||
if !b { // If path doesn't yet exist, create it
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
er(err)
|
||||
}
|
||||
} else { // If path exists and is not empty don't use it
|
||||
empty, err := exists(path)
|
||||
if err != nil {
|
||||
er(err)
|
||||
}
|
||||
if !empty {
|
||||
er("Cobra will not create a new project in a non empty directory")
|
||||
}
|
||||
}
|
||||
// We have a directory and it's empty.. Time to initialize it.
|
||||
|
||||
createLicenseFile()
|
||||
createMainFile()
|
||||
createRootCmdFile()
|
||||
}
|
||||
|
||||
func createLicenseFile() {
|
||||
lic := getLicense()
|
||||
|
||||
// Don't bother writing a LICENSE file if there is no text.
|
||||
if lic.Text != "" {
|
||||
data := make(map[string]interface{})
|
||||
|
||||
// Try to remove the email address, if any
|
||||
data["copyright"] = strings.Split(copyrightLine(), " <")[0]
|
||||
|
||||
data["appName"] = projectName()
|
||||
|
||||
// Generate license template from text and data.
|
||||
r, _ := templateToReader(lic.Text, data)
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(r)
|
||||
|
||||
err := writeTemplateToFile(ProjectPath(), "LICENSE", buf.String(), data)
|
||||
_ = err
|
||||
// if err != nil {
|
||||
// er(err)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
func createMainFile() {
|
||||
lic := getLicense()
|
||||
|
||||
template := `{{ comment .copyright }}
|
||||
{{if .license}}{{ comment .license }}
|
||||
{{end}}
|
||||
package main
|
||||
|
||||
import "{{ .importpath }}"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
`
|
||||
data := make(map[string]interface{})
|
||||
|
||||
data["copyright"] = copyrightLine()
|
||||
data["appName"] = projectName()
|
||||
|
||||
// Generate license template from header and data.
|
||||
r, _ := templateToReader(lic.Header, data)
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(r)
|
||||
data["license"] = buf.String()
|
||||
|
||||
data["importpath"] = guessImportPath() + "/" + guessCmdDir()
|
||||
|
||||
err := writeTemplateToFile(ProjectPath(), "main.go", template, data)
|
||||
_ = err
|
||||
// if err != nil {
|
||||
// er(err)
|
||||
// }
|
||||
}
|
||||
|
||||
func createRootCmdFile() {
|
||||
lic := getLicense()
|
||||
|
||||
template := `{{ comment .copyright }}
|
||||
{{if .license}}{{ comment .license }}
|
||||
{{end}}
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
{{ if .viper }} "github.com/spf13/viper"
|
||||
{{ end }})
|
||||
{{if .viper}}
|
||||
var cfgFile string
|
||||
{{ end }}
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "{{ .appName }}",
|
||||
Short: "A brief description of your application",
|
||||
Long: ` + "`" + `A longer description that spans multiple lines and likely contains
|
||||
examples and usage of using your application. For example:
|
||||
|
||||
Cobra is a CLI library for Go that empowers applications.
|
||||
This application is a tool to generate the needed files
|
||||
to quickly create a Cobra application.` + "`" + `,
|
||||
// Uncomment the following line if your bare application
|
||||
// has an action associated with it:
|
||||
// Run: func(cmd *cobra.Command, args []string) { },
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
if err := RootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
{{ if .viper }} cobra.OnInitialize(initConfig)
|
||||
|
||||
{{ end }} // Here you will define your flags and configuration settings.
|
||||
// Cobra supports Persistent Flags, which, if defined here,
|
||||
// will be global for your application.
|
||||
{{ if .viper }}
|
||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .appName }}.yaml)")
|
||||
{{ else }}
|
||||
// RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .appName }}.yaml)")
|
||||
{{ end }} // Cobra also supports local flags, which will only run
|
||||
// when this action is called directly.
|
||||
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
||||
{{ if .viper }}
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile != "" { // enable ability to specify config file via flag
|
||||
viper.SetConfigFile(cfgFile)
|
||||
}
|
||||
|
||||
viper.SetConfigName(".{{ .appName }}") // name of config file (without extension)
|
||||
viper.AddConfigPath("$HOME") // adding home directory as first search path
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
}
|
||||
{{ end }}`
|
||||
|
||||
data := make(map[string]interface{})
|
||||
|
||||
data["copyright"] = copyrightLine()
|
||||
data["appName"] = projectName()
|
||||
|
||||
// Generate license template from header and data.
|
||||
r, _ := templateToReader(lic.Header, data)
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(r)
|
||||
data["license"] = buf.String()
|
||||
|
||||
data["viper"] = viper.GetBool("useViper")
|
||||
|
||||
err := writeTemplateToFile(ProjectPath()+string(os.PathSeparator)+guessCmdDir(), "root.go", template, data)
|
||||
if err != nil {
|
||||
er(err)
|
||||
}
|
||||
|
||||
fmt.Println("Your Cobra application is ready at")
|
||||
fmt.Println(ProjectPath())
|
||||
fmt.Println("Give it a try by going there and running `go run main.go`")
|
||||
fmt.Println("Add commands to it by running `cobra add [cmdname]`")
|
||||
}
|
1384
vendor/src/github.com/spf13/cobra/cobra/cmd/licenses.go
vendored
Normal file
1384
vendor/src/github.com/spf13/cobra/cobra/cmd/licenses.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
72
vendor/src/github.com/spf13/cobra/cobra/cmd/root.go
vendored
Normal file
72
vendor/src/github.com/spf13/cobra/cobra/cmd/root.go
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright © 2015 Steve Francia <spf@spf13.com>.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
var userLicense string
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "cobra",
|
||||
Short: "A generator for Cobra based Applications",
|
||||
Long: `Cobra is a CLI library for Go that empowers applications.
|
||||
This application is a tool to generate the needed files
|
||||
to quickly create a Cobra application.`,
|
||||
}
|
||||
|
||||
//Execute adds all child commands to the root command sets flags appropriately.
|
||||
func Execute() {
|
||||
if err := RootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
|
||||
RootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory, e.g. github.com/spf13/")
|
||||
RootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution")
|
||||
RootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `license` in config)")
|
||||
RootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration")
|
||||
viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author"))
|
||||
viper.BindPFlag("projectbase", RootCmd.PersistentFlags().Lookup("projectbase"))
|
||||
viper.BindPFlag("useViper", RootCmd.PersistentFlags().Lookup("viper"))
|
||||
viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
|
||||
viper.SetDefault("license", "apache")
|
||||
}
|
||||
|
||||
// Read in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile != "" { // enable ability to specify config file via flag
|
||||
viper.SetConfigFile(cfgFile)
|
||||
}
|
||||
|
||||
viper.SetConfigName(".cobra") // name of config file (without extension)
|
||||
viper.AddConfigPath("$HOME") // adding home directory as first search path
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
}
|
20
vendor/src/github.com/spf13/cobra/cobra/main.go
vendored
Normal file
20
vendor/src/github.com/spf13/cobra/cobra/main.go
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright © 2015 Steve Francia <spf@spf13.com>.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import "github.com/spf13/cobra/cobra/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
1187
vendor/src/github.com/spf13/cobra/cobra_test.go
vendored
Normal file
1187
vendor/src/github.com/spf13/cobra/cobra_test.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1257
vendor/src/github.com/spf13/cobra/command.go
vendored
Normal file
1257
vendor/src/github.com/spf13/cobra/command.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5
vendor/src/github.com/spf13/cobra/command_notwin.go
vendored
Normal file
5
vendor/src/github.com/spf13/cobra/command_notwin.go
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// +build !windows
|
||||
|
||||
package cobra
|
||||
|
||||
var preExecHookFn func(*Command)
|
176
vendor/src/github.com/spf13/cobra/command_test.go
vendored
Normal file
176
vendor/src/github.com/spf13/cobra/command_test.go
vendored
Normal file
@ -0,0 +1,176 @@
|
||||
package cobra
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// test to ensure hidden commands run as intended
|
||||
func TestHiddenCommandExecutes(t *testing.T) {
|
||||
|
||||
// ensure that outs does not already equal what the command will be setting it
|
||||
// to, if it did this test would not actually be testing anything...
|
||||
if outs == "hidden" {
|
||||
t.Errorf("outs should NOT EQUAL hidden")
|
||||
}
|
||||
|
||||
cmdHidden.Execute()
|
||||
|
||||
// upon running the command, the value of outs should now be 'hidden'
|
||||
if outs != "hidden" {
|
||||
t.Errorf("Hidden command failed to run!")
|
||||
}
|
||||
}
|
||||
|
||||
// test to ensure hidden commands do not show up in usage/help text
|
||||
func TestHiddenCommandIsHidden(t *testing.T) {
|
||||
if cmdHidden.IsAvailableCommand() {
|
||||
t.Errorf("Hidden command found!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripFlags(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []string
|
||||
output []string
|
||||
}{
|
||||
{
|
||||
[]string{"foo", "bar"},
|
||||
[]string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
[]string{"foo", "--bar", "-b"},
|
||||
[]string{"foo"},
|
||||
},
|
||||
{
|
||||
[]string{"-b", "foo", "--bar", "bar"},
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
[]string{"-i10", "echo"},
|
||||
[]string{"echo"},
|
||||
},
|
||||
{
|
||||
[]string{"-i=10", "echo"},
|
||||
[]string{"echo"},
|
||||
},
|
||||
{
|
||||
[]string{"--int=100", "echo"},
|
||||
[]string{"echo"},
|
||||
},
|
||||
{
|
||||
[]string{"-ib", "echo", "-bfoo", "baz"},
|
||||
[]string{"echo", "baz"},
|
||||
},
|
||||
{
|
||||
[]string{"-i=baz", "bar", "-i", "foo", "blah"},
|
||||
[]string{"bar", "blah"},
|
||||
},
|
||||
{
|
||||
[]string{"--int=baz", "-bbar", "-i", "foo", "blah"},
|
||||
[]string{"blah"},
|
||||
},
|
||||
{
|
||||
[]string{"--cat", "bar", "-i", "foo", "blah"},
|
||||
[]string{"bar", "blah"},
|
||||
},
|
||||
{
|
||||
[]string{"-c", "bar", "-i", "foo", "blah"},
|
||||
[]string{"bar", "blah"},
|
||||
},
|
||||
{
|
||||
[]string{"--persist", "bar"},
|
||||
[]string{"bar"},
|
||||
},
|
||||
{
|
||||
[]string{"-p", "bar"},
|
||||
[]string{"bar"},
|
||||
},
|
||||
}
|
||||
|
||||
cmdPrint := &Command{
|
||||
Use: "print [string to print]",
|
||||
Short: "Print anything to the screen",
|
||||
Long: `an utterly useless command for testing.`,
|
||||
Run: func(cmd *Command, args []string) {
|
||||
tp = args
|
||||
},
|
||||
}
|
||||
|
||||
var flagi int
|
||||
var flagstr string
|
||||
var flagbool bool
|
||||
cmdPrint.PersistentFlags().BoolVarP(&flagbool, "persist", "p", false, "help for persistent one")
|
||||
cmdPrint.Flags().IntVarP(&flagi, "int", "i", 345, "help message for flag int")
|
||||
cmdPrint.Flags().StringVarP(&flagstr, "bar", "b", "bar", "help message for flag string")
|
||||
cmdPrint.Flags().BoolVarP(&flagbool, "cat", "c", false, "help message for flag bool")
|
||||
|
||||
for _, test := range tests {
|
||||
output := stripFlags(test.input, cmdPrint)
|
||||
if !reflect.DeepEqual(test.output, output) {
|
||||
t.Errorf("expected: %v, got: %v", test.output, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_DisableFlagParsing(t *testing.T) {
|
||||
as := []string{"-v", "-race", "-file", "foo.go"}
|
||||
targs := []string{}
|
||||
cmdPrint := &Command{
|
||||
DisableFlagParsing: true,
|
||||
Run: func(cmd *Command, args []string) {
|
||||
targs = args
|
||||
},
|
||||
}
|
||||
osargs := []string{"cmd"}
|
||||
os.Args = append(osargs, as...)
|
||||
err := cmdPrint.Execute()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !reflect.DeepEqual(as, targs) {
|
||||
t.Errorf("expected: %v, got: %v", as, targs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandsAreSorted(t *testing.T) {
|
||||
EnableCommandSorting = true
|
||||
|
||||
originalNames := []string{"middle", "zlast", "afirst"}
|
||||
expectedNames := []string{"afirst", "middle", "zlast"}
|
||||
|
||||
var tmpCommand = &Command{Use: "tmp"}
|
||||
|
||||
for _, name := range originalNames {
|
||||
tmpCommand.AddCommand(&Command{Use: name})
|
||||
}
|
||||
|
||||
for i, c := range tmpCommand.Commands() {
|
||||
if expectedNames[i] != c.Name() {
|
||||
t.Errorf("expected: %s, got: %s", expectedNames[i], c.Name())
|
||||
}
|
||||
}
|
||||
|
||||
EnableCommandSorting = true
|
||||
}
|
||||
|
||||
func TestEnableCommandSortingIsDisabled(t *testing.T) {
|
||||
EnableCommandSorting = false
|
||||
|
||||
originalNames := []string{"middle", "zlast", "afirst"}
|
||||
|
||||
var tmpCommand = &Command{Use: "tmp"}
|
||||
|
||||
for _, name := range originalNames {
|
||||
tmpCommand.AddCommand(&Command{Use: name})
|
||||
}
|
||||
|
||||
for i, c := range tmpCommand.Commands() {
|
||||
if originalNames[i] != c.Name() {
|
||||
t.Errorf("expected: %s, got: %s", originalNames[i], c.Name())
|
||||
}
|
||||
}
|
||||
|
||||
EnableCommandSorting = true
|
||||
}
|
26
vendor/src/github.com/spf13/cobra/command_win.go
vendored
Normal file
26
vendor/src/github.com/spf13/cobra/command_win.go
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
// +build windows
|
||||
|
||||
package cobra
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/inconshreveable/mousetrap"
|
||||
)
|
||||
|
||||
var preExecHookFn = preExecHook
|
||||
|
||||
// enables an information splash screen on Windows if the CLI is started from explorer.exe.
|
||||
var MousetrapHelpText string = `This is a command line tool
|
||||
|
||||
You need to open cmd.exe and run it from there.
|
||||
`
|
||||
|
||||
func preExecHook(c *Command) {
|
||||
if mousetrap.StartedByExplorer() {
|
||||
c.Print(MousetrapHelpText)
|
||||
time.Sleep(5 * time.Second)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
145
vendor/src/github.com/spf13/cobra/doc/cmd_test.go
vendored
Normal file
145
vendor/src/github.com/spf13/cobra/doc/cmd_test.go
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
package doc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var flagb1, flagb2, flagb3, flagbr, flagbp bool
|
||||
var flags1, flags2a, flags2b, flags3 string
|
||||
var flagi1, flagi2, flagi3, flagir int
|
||||
|
||||
const strtwoParentHelp = "help message for parent flag strtwo"
|
||||
const strtwoChildHelp = "help message for child flag strtwo"
|
||||
|
||||
var cmdEcho = &cobra.Command{
|
||||
Use: "echo [string to echo]",
|
||||
Aliases: []string{"say"},
|
||||
Short: "Echo anything to the screen",
|
||||
Long: `an utterly useless command for testing.`,
|
||||
Example: "Just run cobra-test echo",
|
||||
}
|
||||
|
||||
var cmdEchoSub = &cobra.Command{
|
||||
Use: "echosub [string to print]",
|
||||
Short: "second sub command for echo",
|
||||
Long: `an absolutely utterly useless command for testing gendocs!.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
|
||||
var cmdDeprecated = &cobra.Command{
|
||||
Use: "deprecated [can't do anything here]",
|
||||
Short: "A command which is deprecated",
|
||||
Long: `an absolutely utterly useless command for testing deprecation!.`,
|
||||
Deprecated: "Please use echo instead",
|
||||
}
|
||||
|
||||
var cmdTimes = &cobra.Command{
|
||||
Use: "times [# times] [string to echo]",
|
||||
SuggestFor: []string{"counts"},
|
||||
Short: "Echo anything to the screen more times",
|
||||
Long: `a slightly useless command for testing.`,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
||||
Run: func(cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
|
||||
var cmdPrint = &cobra.Command{
|
||||
Use: "print [string to print]",
|
||||
Short: "Print anything to the screen",
|
||||
Long: `an absolutely utterly useless command for testing.`,
|
||||
}
|
||||
|
||||
var cmdRootNoRun = &cobra.Command{
|
||||
Use: "cobra-test",
|
||||
Short: "The root can run its own function",
|
||||
Long: "The root description for help",
|
||||
}
|
||||
|
||||
var cmdRootSameName = &cobra.Command{
|
||||
Use: "print",
|
||||
Short: "Root with the same name as a subcommand",
|
||||
Long: "The root description for help",
|
||||
}
|
||||
|
||||
var cmdRootWithRun = &cobra.Command{
|
||||
Use: "cobra-test",
|
||||
Short: "The root can run its own function",
|
||||
Long: "The root description for help",
|
||||
}
|
||||
|
||||
var cmdSubNoRun = &cobra.Command{
|
||||
Use: "subnorun",
|
||||
Short: "A subcommand without a Run function",
|
||||
Long: "A long output about a subcommand without a Run function",
|
||||
}
|
||||
|
||||
var cmdVersion1 = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version number",
|
||||
Long: `First version of the version command`,
|
||||
}
|
||||
|
||||
var cmdVersion2 = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version number",
|
||||
Long: `Second version of the version command`,
|
||||
}
|
||||
|
||||
func flagInit() {
|
||||
cmdEcho.ResetFlags()
|
||||
cmdPrint.ResetFlags()
|
||||
cmdTimes.ResetFlags()
|
||||
cmdRootNoRun.ResetFlags()
|
||||
cmdRootSameName.ResetFlags()
|
||||
cmdRootWithRun.ResetFlags()
|
||||
cmdSubNoRun.ResetFlags()
|
||||
cmdRootNoRun.PersistentFlags().StringVarP(&flags2a, "strtwo", "t", "two", strtwoParentHelp)
|
||||
cmdEcho.Flags().IntVarP(&flagi1, "intone", "i", 123, "help message for flag intone")
|
||||
cmdTimes.Flags().IntVarP(&flagi2, "inttwo", "j", 234, "help message for flag inttwo")
|
||||
cmdPrint.Flags().IntVarP(&flagi3, "intthree", "i", 345, "help message for flag intthree")
|
||||
cmdEcho.PersistentFlags().StringVarP(&flags1, "strone", "s", "one", "help message for flag strone")
|
||||
cmdEcho.PersistentFlags().BoolVarP(&flagbp, "persistentbool", "p", false, "help message for flag persistentbool")
|
||||
cmdTimes.PersistentFlags().StringVarP(&flags2b, "strtwo", "t", "2", strtwoChildHelp)
|
||||
cmdPrint.PersistentFlags().StringVarP(&flags3, "strthree", "s", "three", "help message for flag strthree")
|
||||
cmdEcho.Flags().BoolVarP(&flagb1, "boolone", "b", true, "help message for flag boolone")
|
||||
cmdTimes.Flags().BoolVarP(&flagb2, "booltwo", "c", false, "help message for flag booltwo")
|
||||
cmdPrint.Flags().BoolVarP(&flagb3, "boolthree", "b", true, "help message for flag boolthree")
|
||||
cmdVersion1.ResetFlags()
|
||||
cmdVersion2.ResetFlags()
|
||||
}
|
||||
|
||||
func initializeWithRootCmd() *cobra.Command {
|
||||
cmdRootWithRun.ResetCommands()
|
||||
flagInit()
|
||||
cmdRootWithRun.Flags().BoolVarP(&flagbr, "boolroot", "b", false, "help message for flag boolroot")
|
||||
cmdRootWithRun.Flags().IntVarP(&flagir, "introot", "i", 321, "help message for flag introot")
|
||||
return cmdRootWithRun
|
||||
}
|
||||
|
||||
func checkStringContains(t *testing.T, found, expected string) {
|
||||
if !strings.Contains(found, expected) {
|
||||
logErr(t, found, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func checkStringOmits(t *testing.T, found, expected string) {
|
||||
if strings.Contains(found, expected) {
|
||||
logErr(t, found, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func logErr(t *testing.T, found, expected string) {
|
||||
out := new(bytes.Buffer)
|
||||
|
||||
_, _, line, ok := runtime.Caller(2)
|
||||
if ok {
|
||||
fmt.Fprintf(out, "Line: %d ", line)
|
||||
}
|
||||
fmt.Fprintf(out, "Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
|
||||
t.Errorf(out.String())
|
||||
}
|
231
vendor/src/github.com/spf13/cobra/doc/man_docs.go
vendored
Normal file
231
vendor/src/github.com/spf13/cobra/doc/man_docs.go
vendored
Normal file
@ -0,0 +1,231 @@
|
||||
// Copyright 2015 Red Hat Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package doc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
mangen "github.com/cpuguy83/go-md2man/md2man"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// GenManTree will generate a man page for this command and all descendants
|
||||
// in the directory given. The header may be nil. This function may not work
|
||||
// correctly if your command names have - in them. If you have `cmd` with two
|
||||
// subcmds, `sub` and `sub-third`. And `sub` has a subcommand called `third`
|
||||
// it is undefined which help output will be in the file `cmd-sub-third.1`.
|
||||
func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) error {
|
||||
return GenManTreeFromOpts(cmd, GenManTreeOptions{
|
||||
Header: header,
|
||||
Path: dir,
|
||||
CommandSeparator: "_",
|
||||
})
|
||||
}
|
||||
|
||||
// GenManTreeFromOpts generates a man page for the command and all descendants.
|
||||
// The pages are written to the opts.Path directory.
|
||||
func GenManTreeFromOpts(cmd *cobra.Command, opts GenManTreeOptions) error {
|
||||
header := opts.Header
|
||||
if header == nil {
|
||||
header = &GenManHeader{}
|
||||
}
|
||||
for _, c := range cmd.Commands() {
|
||||
if !c.IsAvailableCommand() || c.IsHelpCommand() {
|
||||
continue
|
||||
}
|
||||
if err := GenManTreeFromOpts(c, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
section := "1"
|
||||
if header.Section != "" {
|
||||
section = header.Section
|
||||
}
|
||||
|
||||
separator := "_"
|
||||
if opts.CommandSeparator != "" {
|
||||
separator = opts.CommandSeparator
|
||||
}
|
||||
basename := strings.Replace(cmd.CommandPath(), " ", separator, -1)
|
||||
filename := filepath.Join(opts.Path, basename+"."+section)
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
headerCopy := *header
|
||||
return GenMan(cmd, &headerCopy, f)
|
||||
}
|
||||
|
||||
type GenManTreeOptions struct {
|
||||
Header *GenManHeader
|
||||
Path string
|
||||
CommandSeparator string
|
||||
}
|
||||
|
||||
// GenManHeader is a lot like the .TH header at the start of man pages. These
|
||||
// include the title, section, date, source, and manual. We will use the
|
||||
// current time if Date if unset and will use "Auto generated by spf13/cobra"
|
||||
// if the Source is unset.
|
||||
type GenManHeader struct {
|
||||
Title string
|
||||
Section string
|
||||
Date *time.Time
|
||||
date string
|
||||
Source string
|
||||
Manual string
|
||||
}
|
||||
|
||||
// GenMan will generate a man page for the given command and write it to
|
||||
// w. The header argument may be nil, however obviously w may not.
|
||||
func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error {
|
||||
if header == nil {
|
||||
header = &GenManHeader{}
|
||||
}
|
||||
fillHeader(header, cmd.CommandPath())
|
||||
|
||||
b := genMan(cmd, header)
|
||||
_, err := w.Write(mangen.Render(b))
|
||||
return err
|
||||
}
|
||||
|
||||
func fillHeader(header *GenManHeader, name string) {
|
||||
if header.Title == "" {
|
||||
header.Title = strings.ToUpper(strings.Replace(name, " ", "\\-", -1))
|
||||
}
|
||||
if header.Section == "" {
|
||||
header.Section = "1"
|
||||
}
|
||||
if header.Date == nil {
|
||||
now := time.Now()
|
||||
header.Date = &now
|
||||
}
|
||||
header.date = (*header.Date).Format("Jan 2006")
|
||||
if header.Source == "" {
|
||||
header.Source = "Auto generated by spf13/cobra"
|
||||
}
|
||||
}
|
||||
|
||||
func manPreamble(out io.Writer, header *GenManHeader, cmd *cobra.Command, dashedName string) {
|
||||
description := cmd.Long
|
||||
if len(description) == 0 {
|
||||
description = cmd.Short
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, `%% %s(%s)%s
|
||||
%% %s
|
||||
%% %s
|
||||
# NAME
|
||||
`, header.Title, header.Section, header.date, header.Source, header.Manual)
|
||||
fmt.Fprintf(out, "%s \\- %s\n\n", dashedName, cmd.Short)
|
||||
fmt.Fprintf(out, "# SYNOPSIS\n")
|
||||
fmt.Fprintf(out, "**%s**\n\n", cmd.UseLine())
|
||||
fmt.Fprintf(out, "# DESCRIPTION\n")
|
||||
fmt.Fprintf(out, "%s\n\n", description)
|
||||
}
|
||||
|
||||
func manPrintFlags(out io.Writer, flags *pflag.FlagSet) {
|
||||
flags.VisitAll(func(flag *pflag.Flag) {
|
||||
if len(flag.Deprecated) > 0 || flag.Hidden {
|
||||
return
|
||||
}
|
||||
format := ""
|
||||
if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 {
|
||||
format = fmt.Sprintf("**-%s**, **--%s**", flag.Shorthand, flag.Name)
|
||||
} else {
|
||||
format = fmt.Sprintf("**--%s**", flag.Name)
|
||||
}
|
||||
if len(flag.NoOptDefVal) > 0 {
|
||||
format = format + "["
|
||||
}
|
||||
if flag.Value.Type() == "string" {
|
||||
// put quotes on the value
|
||||
format = format + "=%q"
|
||||
} else {
|
||||
format = format + "=%s"
|
||||
}
|
||||
if len(flag.NoOptDefVal) > 0 {
|
||||
format = format + "]"
|
||||
}
|
||||
format = format + "\n\t%s\n\n"
|
||||
fmt.Fprintf(out, format, flag.DefValue, flag.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func manPrintOptions(out io.Writer, command *cobra.Command) {
|
||||
flags := command.NonInheritedFlags()
|
||||
if flags.HasFlags() {
|
||||
fmt.Fprintf(out, "# OPTIONS\n")
|
||||
manPrintFlags(out, flags)
|
||||
fmt.Fprintf(out, "\n")
|
||||
}
|
||||
flags = command.InheritedFlags()
|
||||
if flags.HasFlags() {
|
||||
fmt.Fprintf(out, "# OPTIONS INHERITED FROM PARENT COMMANDS\n")
|
||||
manPrintFlags(out, flags)
|
||||
fmt.Fprintf(out, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
|
||||
// something like `rootcmd-subcmd1-subcmd2`
|
||||
dashCommandName := strings.Replace(cmd.CommandPath(), " ", "-", -1)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
manPreamble(buf, header, cmd, dashCommandName)
|
||||
manPrintOptions(buf, cmd)
|
||||
if len(cmd.Example) > 0 {
|
||||
fmt.Fprintf(buf, "# EXAMPLE\n")
|
||||
fmt.Fprintf(buf, "```\n%s\n```\n", cmd.Example)
|
||||
}
|
||||
if hasSeeAlso(cmd) {
|
||||
fmt.Fprintf(buf, "# SEE ALSO\n")
|
||||
seealsos := make([]string, 0)
|
||||
if cmd.HasParent() {
|
||||
parentPath := cmd.Parent().CommandPath()
|
||||
dashParentPath := strings.Replace(parentPath, " ", "-", -1)
|
||||
seealso := fmt.Sprintf("**%s(%s)**", dashParentPath, header.Section)
|
||||
seealsos = append(seealsos, seealso)
|
||||
cmd.VisitParents(func(c *cobra.Command) {
|
||||
if c.DisableAutoGenTag {
|
||||
cmd.DisableAutoGenTag = c.DisableAutoGenTag
|
||||
}
|
||||
})
|
||||
}
|
||||
children := cmd.Commands()
|
||||
sort.Sort(byName(children))
|
||||
for _, c := range children {
|
||||
if !c.IsAvailableCommand() || c.IsHelpCommand() {
|
||||
continue
|
||||
}
|
||||
seealso := fmt.Sprintf("**%s-%s(%s)**", dashCommandName, c.Name(), header.Section)
|
||||
seealsos = append(seealsos, seealso)
|
||||
}
|
||||
fmt.Fprintf(buf, "%s\n", strings.Join(seealsos, ", "))
|
||||
}
|
||||
if !cmd.DisableAutoGenTag {
|
||||
fmt.Fprintf(buf, "# HISTORY\n%s Auto generated by spf13/cobra\n", header.Date.Format("2-Jan-2006"))
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
26
vendor/src/github.com/spf13/cobra/doc/man_docs.md
vendored
Normal file
26
vendor/src/github.com/spf13/cobra/doc/man_docs.md
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
# Generating Man Pages For Your Own cobra.Command
|
||||
|
||||
Generating man pages from a cobra command is incredibly easy. An example is as follows:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/cobra/doc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd := &cobra.Command{
|
||||
Use: "test",
|
||||
Short: "my test program",
|
||||
}
|
||||
header := &cobra.GenManHeader{
|
||||
Title: "MINE",
|
||||
Section: "3",
|
||||
}
|
||||
doc.GenManTree(cmd, header, "/tmp")
|
||||
}
|
||||
```
|
||||
|
||||
That will get you a man page `/tmp/test.1`
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user