mirror of
https://github.com/minio/mc.git
synced 2025-04-18 10:04:03 +03:00
599 lines
18 KiB
Go
599 lines
18 KiB
Go
// Copyright (c) 2015-2022 MinIO, Inc.
|
|
//
|
|
// This file is part of MinIO Object Storage stack
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/minio/cli"
|
|
"github.com/posener/complete"
|
|
)
|
|
|
|
// fsComplete knows how to complete file/dir names by the given path
|
|
type fsComplete struct{}
|
|
|
|
// predictPathWithTilde completes an FS path which starts with a `~/`
|
|
func (fs fsComplete) predictPathWithTilde(a complete.Args) []string {
|
|
homeDir, e := os.UserHomeDir()
|
|
if e != nil || homeDir == "" {
|
|
return nil
|
|
}
|
|
// Clean the home directory path
|
|
homeDir = strings.TrimRight(homeDir, "/")
|
|
|
|
// Replace the first occurrence of ~ with the real path and complete
|
|
a.Last = strings.Replace(a.Last, "~", homeDir, 1)
|
|
predictions := complete.PredictFiles("*").Predict(a)
|
|
|
|
// Restore ~ to avoid disturbing the completion user experience
|
|
for i := range predictions {
|
|
predictions[i] = strings.Replace(predictions[i], homeDir, "~", 1)
|
|
}
|
|
|
|
return predictions
|
|
}
|
|
|
|
func (fs fsComplete) Predict(a complete.Args) []string {
|
|
if strings.HasPrefix(a.Last, "~/") {
|
|
return fs.predictPathWithTilde(a)
|
|
}
|
|
return complete.PredictFiles("*").Predict(a)
|
|
}
|
|
|
|
func completeAdminConfigKeys(aliasPath, keyPrefix string) (prediction []string) {
|
|
// Convert alias/bucket/incompl to alias/bucket/ to list its contents
|
|
parentDirPath := filepath.Dir(aliasPath) + "/"
|
|
clnt, err := newAdminClient(parentDirPath)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
h, e := clnt.HelpConfigKV(globalContext, "", "", false)
|
|
if e != nil {
|
|
return nil
|
|
}
|
|
|
|
for _, hkv := range h.KeysHelp {
|
|
if strings.HasPrefix(hkv.Key, keyPrefix) {
|
|
prediction = append(prediction, hkv.Key)
|
|
}
|
|
}
|
|
|
|
return prediction
|
|
}
|
|
|
|
// Complete S3 path. If the prediction result is only one directory,
|
|
// then recursively scans it. This is needed to satisfy posener/complete
|
|
// (look at posener/complete.PredictFiles)
|
|
func completeS3Path(s3Path string) (prediction []string) {
|
|
// Convert alias/bucket/incompl to alias/bucket/ to list its contents
|
|
parentDirPath := filepath.Dir(s3Path) + "/"
|
|
clnt, err := newClient(parentDirPath)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Calculate alias from the path
|
|
alias := splitStr(s3Path, "/", 3)[0]
|
|
|
|
// List dirPath content and only pick elements that corresponds
|
|
// to the path that we want to complete
|
|
for content := range clnt.List(globalContext, ListOptions{Recursive: false, ShowDir: DirFirst}) {
|
|
cmplS3Path := alias + getKey(content)
|
|
if content.Type.IsDir() {
|
|
if !strings.HasSuffix(cmplS3Path, "/") {
|
|
cmplS3Path += "/"
|
|
}
|
|
}
|
|
if strings.HasPrefix(cmplS3Path, s3Path) {
|
|
prediction = append(prediction, cmplS3Path)
|
|
}
|
|
}
|
|
|
|
// If completion found only one directory, recursively scan it.
|
|
if len(prediction) == 1 && strings.HasSuffix(prediction[0], "/") {
|
|
prediction = append(prediction, completeS3Path(prediction[0])...)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
type adminConfigComplete struct{}
|
|
|
|
func (adm adminConfigComplete) Predict(a complete.Args) (prediction []string) {
|
|
defer func() {
|
|
sort.Strings(prediction)
|
|
}()
|
|
|
|
loadMcConfig = loadMcConfigFactory()
|
|
conf, err := loadMcConfig()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// We have already predicted the keys, we are done.
|
|
if len(a.Completed) == 3 {
|
|
return
|
|
}
|
|
|
|
arg := a.Last
|
|
lastArg := a.LastCompleted
|
|
if _, ok := conf.Aliases[filepath.Clean(a.LastCompleted)]; !ok {
|
|
if strings.IndexByte(arg, '/') == -1 {
|
|
// Only predict alias since '/' is not found
|
|
for alias := range conf.Aliases {
|
|
if strings.HasPrefix(alias, arg) {
|
|
prediction = append(prediction, alias+"/")
|
|
}
|
|
}
|
|
} else {
|
|
prediction = completeAdminConfigKeys(arg, "")
|
|
}
|
|
} else {
|
|
prediction = completeAdminConfigKeys(lastArg, arg)
|
|
}
|
|
return
|
|
}
|
|
|
|
// s3Complete knows how to complete an mc s3 path
|
|
type s3Complete struct {
|
|
deepLevel int
|
|
}
|
|
|
|
func (s3 s3Complete) Predict(a complete.Args) (prediction []string) {
|
|
defer func() {
|
|
sort.Strings(prediction)
|
|
}()
|
|
|
|
loadMcConfig = loadMcConfigFactory()
|
|
conf, err := loadMcConfig()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
arg := a.Last
|
|
|
|
if strings.IndexByte(arg, '/') == -1 {
|
|
// Only predict alias since '/' is not found
|
|
for alias := range conf.Aliases {
|
|
if strings.HasPrefix(alias, arg) {
|
|
prediction = append(prediction, alias+"/")
|
|
}
|
|
}
|
|
if len(prediction) == 1 && strings.HasSuffix(prediction[0], "/") {
|
|
prediction = append(prediction, completeS3Path(prediction[0])...)
|
|
}
|
|
} else {
|
|
// Complete S3 path until the specified path deep level
|
|
if s3.deepLevel > 0 {
|
|
if strings.Count(arg, "/") >= s3.deepLevel {
|
|
return []string{arg}
|
|
}
|
|
}
|
|
// Predict S3 path
|
|
prediction = completeS3Path(arg)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// aliasComplete only completes aliases
|
|
type aliasComplete struct{}
|
|
|
|
func (al aliasComplete) Predict(a complete.Args) (prediction []string) {
|
|
defer func() {
|
|
sort.Strings(prediction)
|
|
}()
|
|
|
|
loadMcConfig = loadMcConfigFactory()
|
|
conf, err := loadMcConfig()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
arg := a.Last
|
|
for alias := range conf.Aliases {
|
|
if strings.HasPrefix(alias, arg) {
|
|
prediction = append(prediction, alias+"/")
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
var (
|
|
adminConfigCompleter = adminConfigComplete{}
|
|
s3Completer = s3Complete{}
|
|
aliasCompleter = aliasComplete{}
|
|
fsCompleter = fsComplete{}
|
|
)
|
|
|
|
// The list of all commands supported by mc with their mapping
|
|
// with their bash completer function
|
|
var completeCmds = map[string]complete.Predictor{
|
|
// S3 API level commands
|
|
"/ls": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/cp": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/mv": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/rm": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/rb": complete.PredictOr(s3Complete{deepLevel: 2}, fsCompleter),
|
|
"/cat": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/head": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/diff": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/find": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/mirror": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/pipe": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/stat": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/watch": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/anonymous": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/tree": complete.PredictOr(s3Complete{deepLevel: 2}, fsCompleter),
|
|
"/du": complete.PredictOr(s3Complete{deepLevel: 2}, fsCompleter),
|
|
|
|
"/retention/set": s3Completer,
|
|
"/retention/clear": s3Completer,
|
|
"/retention/info": s3Completer,
|
|
|
|
"/legalhold/set": s3Completer,
|
|
"/legalhold/clear": s3Completer,
|
|
"/legalhold/info": s3Completer,
|
|
|
|
"/sql": s3Completer,
|
|
"/mb": aliasCompleter,
|
|
|
|
"/event/add": s3Complete{deepLevel: 2},
|
|
"/event/list": s3Complete{deepLevel: 2},
|
|
"/event/remove": s3Complete{deepLevel: 2},
|
|
|
|
"/encrypt/set": s3Complete{deepLevel: 2},
|
|
"/encrypt/info": s3Complete{deepLevel: 2},
|
|
"/encrypt/clear": s3Complete{deepLevel: 2},
|
|
|
|
"/replicate/add": s3Complete{deepLevel: 2},
|
|
"/replicate/edit": s3Complete{deepLevel: 2},
|
|
"/replicate/update": s3Complete{deepLevel: 2},
|
|
"/replicate/list": s3Complete{deepLevel: 2},
|
|
"/replicate/remove": s3Complete{deepLevel: 2},
|
|
"/replicate/backlog": s3Complete{deepLevel: 2},
|
|
|
|
"/replicate/export": s3Complete{deepLevel: 2},
|
|
"/replicate/import": s3Complete{deepLevel: 2},
|
|
"/replicate/status": s3Complete{deepLevel: 2},
|
|
"/replicate/resync/start": s3Complete{deepLevel: 3},
|
|
"/replicate/resync/status": s3Complete{deepLevel: 3},
|
|
|
|
"/tag/list": s3Completer,
|
|
"/tag/remove": s3Completer,
|
|
"/tag/set": s3Completer,
|
|
|
|
"/version/info": s3Complete{deepLevel: 2},
|
|
"/version/enable": s3Complete{deepLevel: 2},
|
|
"/version/suspend": s3Complete{deepLevel: 2},
|
|
|
|
"/lock/compliance": s3Completer,
|
|
"/lock/governance": s3Completer,
|
|
"/lock/clear": s3Completer,
|
|
"/lock/info": s3Completer,
|
|
|
|
"/share/download": s3Completer,
|
|
"/share/list": nil,
|
|
"/share/upload": s3Completer,
|
|
|
|
"/ilm/list": s3Complete{deepLevel: 2},
|
|
"/ilm/add": s3Complete{deepLevel: 2},
|
|
"/ilm/edit": s3Complete{deepLevel: 2},
|
|
"/ilm/remove": s3Complete{deepLevel: 2},
|
|
"/ilm/export": s3Complete{deepLevel: 2},
|
|
"/ilm/import": s3Complete{deepLevel: 2},
|
|
"/ilm/restore": s3Completer,
|
|
|
|
"/ilm/rule/list": s3Complete{deepLevel: 2},
|
|
"/ilm/rule/add": s3Complete{deepLevel: 2},
|
|
"/ilm/rule/edit": s3Complete{deepLevel: 2},
|
|
"/ilm/rule/remove": s3Complete{deepLevel: 2},
|
|
"/ilm/rule/export": s3Complete{deepLevel: 2},
|
|
"/ilm/rule/import": s3Complete{deepLevel: 2},
|
|
"/ilm/rule/restore": s3Completer,
|
|
|
|
"/undo": s3Completer,
|
|
|
|
// Admin API commands MinIO only.
|
|
"/admin/heal": s3Completer,
|
|
|
|
"/admin/info": aliasCompleter,
|
|
"/admin/logs": aliasCompleter,
|
|
|
|
"/admin/config/get": adminConfigCompleter,
|
|
"/admin/config/set": adminConfigCompleter,
|
|
"/admin/config/reset": adminConfigCompleter,
|
|
"/admin/config/import": aliasCompleter,
|
|
"/admin/config/export": aliasCompleter,
|
|
"/admin/config/history": aliasCompleter,
|
|
"/admin/config/restore": aliasCompleter,
|
|
|
|
"/admin/decom/start": aliasCompleter,
|
|
"/admin/decom/status": aliasCompleter,
|
|
"/admin/decom/cancel": aliasCompleter,
|
|
"/admin/decommission/start": aliasCompleter,
|
|
"/admin/decommission/status": aliasCompleter,
|
|
"/admin/decommission/cancel": aliasCompleter,
|
|
|
|
"/admin/rebalance/start": aliasCompleter,
|
|
"/admin/rebalance/status": aliasCompleter,
|
|
"/admin/rebalance/stop": aliasCompleter,
|
|
|
|
"/admin/trace": aliasCompleter,
|
|
"/admin/speedtest": aliasCompleter,
|
|
"/admin/console": aliasCompleter,
|
|
"/admin/update": aliasCompleter,
|
|
"/admin/inspect": s3Completer,
|
|
"/admin/top/locks": aliasCompleter,
|
|
"/admin/top/api": aliasCompleter,
|
|
|
|
"/admin/scanner/status": aliasCompleter,
|
|
"/admin/scanner/trace": aliasCompleter,
|
|
|
|
"/admin/service/stop": aliasCompleter,
|
|
"/admin/service/restart": aliasCompleter,
|
|
"/admin/service/freeze": aliasCompleter,
|
|
"/admin/service/unfreeze": aliasCompleter,
|
|
|
|
"/admin/prometheus/generate": aliasCompleter,
|
|
"/admin/prometheus/metrics": aliasCompleter,
|
|
|
|
"/admin/profile/start": aliasCompleter,
|
|
"/admin/profile/stop": aliasCompleter,
|
|
|
|
"/idp/openid/add": aliasCompleter,
|
|
"/idp/openid/update": aliasCompleter,
|
|
"/idp/openid/remove": aliasCompleter,
|
|
"/idp/openid/list": aliasCompleter,
|
|
"/idp/openid/info": aliasCompleter,
|
|
"/idp/openid/enable": aliasCompleter,
|
|
"/idp/openid/disable": aliasCompleter,
|
|
|
|
"/idp/ldap/add": aliasCompleter,
|
|
"/idp/ldap/update": aliasCompleter,
|
|
"/idp/ldap/remove": aliasCompleter,
|
|
"/idp/ldap/list": aliasCompleter,
|
|
"/idp/ldap/info": aliasCompleter,
|
|
"/idp/ldap/enable": aliasCompleter,
|
|
"/idp/ldap/disable": aliasCompleter,
|
|
|
|
"/idp/ldap/policy/entities": aliasCompleter,
|
|
"/idp/ldap/policy/attach": aliasCompleter,
|
|
"/idp/ldap/policy/detach": aliasCompleter,
|
|
|
|
"/idp/ldap/accesskey/create": aliasCompleter,
|
|
"/idp/ldap/accesskey/create-with-login": aliasCompleter,
|
|
"/idp/ldap/accesskey/list": aliasCompleter,
|
|
"/idp/ldap/accesskey/ls": aliasCompleter,
|
|
"/idp/ldap/accesskey/remove": aliasCompleter,
|
|
"/idp/ldap/accesskey/rm": aliasCompleter,
|
|
"/idp/ldap/accesskey/info": aliasCompleter,
|
|
"/idp/ldap/accesskey/edit": aliasCompleter,
|
|
"/idp/ldap/accesskey/enable": aliasCompleter,
|
|
"/idp/ldap/accesskey/disable": aliasCompleter,
|
|
"/idp/ldap/accesskey/sts-revoke": aliasCompleter,
|
|
|
|
"/admin/accesskey/create": aliasCompleter,
|
|
"/admin/accesskey/list": aliasCompleter,
|
|
"/admin/accesskey/ls": aliasCompleter,
|
|
"/admin/accesskey/remove": aliasCompleter,
|
|
"/admin/accesskey/rm": aliasCompleter,
|
|
"/admin/accesskey/info": aliasCompleter,
|
|
"/admin/accesskey/edit": aliasCompleter,
|
|
"/admin/accesskey/enable": aliasCompleter,
|
|
"/admin/accesskey/disable": aliasCompleter,
|
|
"/admin/accesskey/sts-revoke": aliasCompleter,
|
|
|
|
"/admin/policy/info": aliasCompleter,
|
|
"/admin/policy/update": aliasCompleter,
|
|
"/admin/policy/add": aliasCompleter,
|
|
"/admin/policy/remove": aliasCompleter,
|
|
"/admin/policy/create": aliasCompleter,
|
|
"/admin/policy/list": aliasCompleter,
|
|
"/admin/policy/attach": aliasCompleter,
|
|
"/admin/policy/detach": aliasCompleter,
|
|
"/admin/policy/entities": aliasCompleter,
|
|
|
|
"/admin/user/add": aliasCompleter,
|
|
"/admin/user/disable": aliasCompleter,
|
|
"/admin/user/enable": aliasCompleter,
|
|
"/admin/user/list": aliasCompleter,
|
|
"/admin/user/remove": aliasCompleter,
|
|
"/admin/user/info": aliasCompleter,
|
|
"/admin/user/policy": aliasCompleter,
|
|
|
|
"/admin/user/svcacct/add": aliasCompleter,
|
|
"/admin/user/svcacct/list": aliasCompleter,
|
|
"/admin/user/svcacct/remove": aliasCompleter,
|
|
"/admin/user/svcacct/info": aliasCompleter,
|
|
"/admin/user/svcacct/edit": aliasCompleter,
|
|
"/admin/user/svcacct/set": aliasCompleter,
|
|
"/admin/user/svcacct/enable": aliasCompleter,
|
|
"/admin/user/svcacct/disable": aliasCompleter,
|
|
|
|
"/admin/user/sts/info": aliasCompleter,
|
|
|
|
"/admin/group/add": aliasCompleter,
|
|
"/admin/group/disable": aliasCompleter,
|
|
"/admin/group/enable": aliasCompleter,
|
|
"/admin/group/list": aliasCompleter,
|
|
"/admin/group/remove": aliasCompleter,
|
|
"/admin/group/info": aliasCompleter,
|
|
|
|
"/admin/bucket/remote/add": aliasCompleter,
|
|
"/admin/bucket/remote/edit": aliasCompleter,
|
|
"/admin/bucket/remote/remove": aliasCompleter,
|
|
"/admin/bucket/quota": aliasCompleter,
|
|
"/admin/bucket/info": s3Complete{deepLevel: 2},
|
|
|
|
"/admin/kms/key/create": aliasCompleter,
|
|
"/admin/kms/key/status": aliasCompleter,
|
|
"/admin/kms/key/list": aliasCompleter,
|
|
|
|
"/admin/subnet/health": aliasCompleter,
|
|
"/admin/subnet/register": aliasCompleter,
|
|
|
|
"/admin/tier/add": nil,
|
|
"/admin/tier/edit": nil,
|
|
"/admin/tier/list": nil,
|
|
"/admin/tier/info": nil,
|
|
"/admin/tier/remove": nil,
|
|
"/admin/tier/verify": nil,
|
|
|
|
"/ilm/tier/info": nil,
|
|
"/ilm/tier/list": nil,
|
|
"/ilm/tier/add": nil,
|
|
"/ilm/tier/update": nil,
|
|
"/ilm/tier/check": nil,
|
|
"/ilm/tier/remove": nil,
|
|
|
|
"/admin/replicate/add": aliasCompleter,
|
|
"/admin/replicate/update": aliasCompleter,
|
|
"/admin/replicate/edit": aliasCompleter,
|
|
"/admin/replicate/info": aliasCompleter,
|
|
"/admin/replicate/status": aliasCompleter,
|
|
"/admin/replicate/remove": aliasCompleter,
|
|
"/admin/replicate/resync/start": aliasCompleter,
|
|
"/admin/replicate/resync/cancel": aliasCompleter,
|
|
"/admin/replicate/resync/status": aliasCompleter,
|
|
|
|
"/admin/cluster/bucket/export": aliasCompleter,
|
|
"/admin/cluster/bucket/import": aliasCompleter,
|
|
"/admin/cluster/iam/export": aliasCompleter,
|
|
"/admin/cluster/iam/import": aliasCompleter,
|
|
|
|
"/alias/set": nil,
|
|
"/alias/list": aliasCompleter,
|
|
"/alias/remove": aliasCompleter,
|
|
"/alias/import": nil,
|
|
"/alias/export": aliasCompleter,
|
|
|
|
"/support/callhome": aliasCompleter,
|
|
"/support/register": aliasCompleter,
|
|
"/support/diag": aliasCompleter,
|
|
"/support/profile": aliasCompleter,
|
|
"/support/proxy/set": aliasCompleter,
|
|
"/support/proxy/show": aliasCompleter,
|
|
"/support/proxy/remove": aliasCompleter,
|
|
"/support/inspect": aliasCompleter,
|
|
"/support/perf": aliasCompleter,
|
|
"/support/metrics": aliasCompleter,
|
|
"/support/status": aliasCompleter,
|
|
"/support/top/locks": aliasCompleter,
|
|
"/support/top/api": aliasCompleter,
|
|
"/support/top/drive": aliasCompleter,
|
|
"/support/top/disk": aliasCompleter,
|
|
"/support/top/net": aliasCompleter,
|
|
"/support/top/rpc": aliasCompleter,
|
|
"/support/upload": aliasCompleter,
|
|
|
|
"/license/register": aliasCompleter,
|
|
"/license/info": aliasCompleter,
|
|
"/license/update": aliasCompleter,
|
|
|
|
"/update": nil,
|
|
"/ready": aliasCompleter,
|
|
"/ping": aliasCompleter,
|
|
"/od": nil,
|
|
"/batch/generate": aliasCompleter,
|
|
"/batch/start": aliasCompleter,
|
|
"/batch/list": aliasCompleter,
|
|
"/batch/status": aliasCompleter,
|
|
"/batch/describe": aliasCompleter,
|
|
"/batch/cancel": aliasCompleter,
|
|
|
|
"/quota/set": aliasCompleter,
|
|
"/quota/info": aliasCompleter,
|
|
"/quota/clear": aliasCompleter,
|
|
"/put": complete.PredictOr(s3Completer, fsCompleter),
|
|
"/get": complete.PredictOr(s3Completer, fsCompleter),
|
|
|
|
"/cors/set": s3Complete{deepLevel: 2},
|
|
"/cors/get": s3Complete{deepLevel: 2},
|
|
"/cors/remove": s3Complete{deepLevel: 2},
|
|
}
|
|
|
|
// flagsToCompleteFlags transforms a cli.Flag to complete.Flags
|
|
// understood by posener/complete library.
|
|
func flagsToCompleteFlags(flags []cli.Flag) complete.Flags {
|
|
complFlags := make(complete.Flags)
|
|
for _, f := range flags {
|
|
for _, s := range strings.Split(f.GetName(), ",") {
|
|
var flagName string
|
|
s = strings.TrimSpace(s)
|
|
if len(s) == 1 {
|
|
flagName = "-" + s
|
|
} else {
|
|
flagName = "--" + s
|
|
}
|
|
complFlags[flagName] = complete.PredictNothing
|
|
}
|
|
}
|
|
return complFlags
|
|
}
|
|
|
|
// This function recursively transforms cli.Command to complete.Command
|
|
// understood by posener/complete library.
|
|
func cmdToCompleteCmd(cmd cli.Command, parentPath string) complete.Command {
|
|
var complCmd complete.Command
|
|
complCmd.Sub = make(complete.Commands)
|
|
|
|
for _, subCmd := range cmd.Subcommands {
|
|
if subCmd.Hidden {
|
|
continue
|
|
}
|
|
complCmd.Sub[subCmd.Name] = cmdToCompleteCmd(subCmd, parentPath+"/"+cmd.Name)
|
|
for _, alias := range subCmd.Aliases {
|
|
complCmd.Sub[alias] = cmdToCompleteCmd(subCmd, parentPath+"/"+cmd.Name)
|
|
}
|
|
}
|
|
|
|
complCmd.Flags = flagsToCompleteFlags(cmd.Flags)
|
|
complCmd.Args = completeCmds[parentPath+"/"+cmd.Name]
|
|
return complCmd
|
|
}
|
|
|
|
// Main function to answer to bash completion calls
|
|
func mainComplete() error {
|
|
// Recursively register all commands and subcommands
|
|
// along with global and local flags
|
|
complCmds := make(complete.Commands)
|
|
for _, cmd := range appCmds {
|
|
if cmd.Hidden {
|
|
continue
|
|
}
|
|
complCmds[cmd.Name] = cmdToCompleteCmd(cmd, "")
|
|
for _, alias := range cmd.Aliases {
|
|
complCmds[alias] = cmdToCompleteCmd(cmd, "")
|
|
}
|
|
}
|
|
complFlags := flagsToCompleteFlags(globalFlags)
|
|
mcComplete := complete.Command{
|
|
Sub: complCmds,
|
|
GlobalFlags: complFlags,
|
|
}
|
|
// Answer to bash completion call
|
|
complete.New(filepath.Base(os.Args[0]), mcComplete).Run()
|
|
return nil
|
|
}
|