github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/auto-complete.go (about)

     1  // Copyright (c) 2015-2022 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"os"
    22  	"path/filepath"
    23  	"sort"
    24  	"strings"
    25  
    26  	"github.com/minio/cli"
    27  	"github.com/posener/complete"
    28  )
    29  
    30  // fsComplete knows how to complete file/dir names by the given path
    31  type fsComplete struct{}
    32  
    33  // predictPathWithTilde completes an FS path which starts with a `~/`
    34  func (fs fsComplete) predictPathWithTilde(a complete.Args) []string {
    35  	homeDir, e := os.UserHomeDir()
    36  	if e != nil || homeDir == "" {
    37  		return nil
    38  	}
    39  	// Clean the home directory path
    40  	homeDir = strings.TrimRight(homeDir, "/")
    41  
    42  	// Replace the first occurrence of ~ with the real path and complete
    43  	a.Last = strings.Replace(a.Last, "~", homeDir, 1)
    44  	predictions := complete.PredictFiles("*").Predict(a)
    45  
    46  	// Restore ~ to avoid disturbing the completion user experience
    47  	for i := range predictions {
    48  		predictions[i] = strings.Replace(predictions[i], homeDir, "~", 1)
    49  	}
    50  
    51  	return predictions
    52  }
    53  
    54  func (fs fsComplete) Predict(a complete.Args) []string {
    55  	if strings.HasPrefix(a.Last, "~/") {
    56  		return fs.predictPathWithTilde(a)
    57  	}
    58  	return complete.PredictFiles("*").Predict(a)
    59  }
    60  
    61  func completeAdminConfigKeys(aliasPath, keyPrefix string) (prediction []string) {
    62  	// Convert alias/bucket/incompl to alias/bucket/ to list its contents
    63  	parentDirPath := filepath.Dir(aliasPath) + "/"
    64  	clnt, err := newAdminClient(parentDirPath)
    65  	if err != nil {
    66  		return nil
    67  	}
    68  
    69  	h, e := clnt.HelpConfigKV(globalContext, "", "", false)
    70  	if e != nil {
    71  		return nil
    72  	}
    73  
    74  	for _, hkv := range h.KeysHelp {
    75  		if strings.HasPrefix(hkv.Key, keyPrefix) {
    76  			prediction = append(prediction, hkv.Key)
    77  		}
    78  	}
    79  
    80  	return prediction
    81  }
    82  
    83  // Complete S3 path. If the prediction result is only one directory,
    84  // then recursively scans it. This is needed to satisfy posener/complete
    85  // (look at posener/complete.PredictFiles)
    86  func completeS3Path(s3Path string) (prediction []string) {
    87  	// Convert alias/bucket/incompl to alias/bucket/ to list its contents
    88  	parentDirPath := filepath.Dir(s3Path) + "/"
    89  	clnt, err := newClient(parentDirPath)
    90  	if err != nil {
    91  		return nil
    92  	}
    93  
    94  	// Calculate alias from the path
    95  	alias := splitStr(s3Path, "/", 3)[0]
    96  
    97  	// List dirPath content and only pick elements that corresponds
    98  	// to the path that we want to complete
    99  	for content := range clnt.List(globalContext, ListOptions{Recursive: false, ShowDir: DirFirst}) {
   100  		cmplS3Path := alias + getKey(content)
   101  		if content.Type.IsDir() {
   102  			if !strings.HasSuffix(cmplS3Path, "/") {
   103  				cmplS3Path += "/"
   104  			}
   105  		}
   106  		if strings.HasPrefix(cmplS3Path, s3Path) {
   107  			prediction = append(prediction, cmplS3Path)
   108  		}
   109  	}
   110  
   111  	// If completion found only one directory, recursively scan it.
   112  	if len(prediction) == 1 && strings.HasSuffix(prediction[0], "/") {
   113  		prediction = append(prediction, completeS3Path(prediction[0])...)
   114  	}
   115  
   116  	return
   117  }
   118  
   119  type adminConfigComplete struct{}
   120  
   121  func (adm adminConfigComplete) Predict(a complete.Args) (prediction []string) {
   122  	defer func() {
   123  		sort.Strings(prediction)
   124  	}()
   125  
   126  	loadMcConfig = loadMcConfigFactory()
   127  	conf, err := loadMcConfig()
   128  	if err != nil {
   129  		return
   130  	}
   131  
   132  	// We have already predicted the keys, we are done.
   133  	if len(a.Completed) == 3 {
   134  		return
   135  	}
   136  
   137  	arg := a.Last
   138  	lastArg := a.LastCompleted
   139  	if _, ok := conf.Aliases[filepath.Clean(a.LastCompleted)]; !ok {
   140  		if strings.IndexByte(arg, '/') == -1 {
   141  			// Only predict alias since '/' is not found
   142  			for alias := range conf.Aliases {
   143  				if strings.HasPrefix(alias, arg) {
   144  					prediction = append(prediction, alias+"/")
   145  				}
   146  			}
   147  		} else {
   148  			prediction = completeAdminConfigKeys(arg, "")
   149  		}
   150  	} else {
   151  		prediction = completeAdminConfigKeys(lastArg, arg)
   152  	}
   153  	return
   154  }
   155  
   156  // s3Complete knows how to complete an mc s3 path
   157  type s3Complete struct {
   158  	deepLevel int
   159  }
   160  
   161  func (s3 s3Complete) Predict(a complete.Args) (prediction []string) {
   162  	defer func() {
   163  		sort.Strings(prediction)
   164  	}()
   165  
   166  	loadMcConfig = loadMcConfigFactory()
   167  	conf, err := loadMcConfig()
   168  	if err != nil {
   169  		return nil
   170  	}
   171  
   172  	arg := a.Last
   173  
   174  	if strings.IndexByte(arg, '/') == -1 {
   175  		// Only predict alias since '/' is not found
   176  		for alias := range conf.Aliases {
   177  			if strings.HasPrefix(alias, arg) {
   178  				prediction = append(prediction, alias+"/")
   179  			}
   180  		}
   181  		if len(prediction) == 1 && strings.HasSuffix(prediction[0], "/") {
   182  			prediction = append(prediction, completeS3Path(prediction[0])...)
   183  		}
   184  	} else {
   185  		// Complete S3 path until the specified path deep level
   186  		if s3.deepLevel > 0 {
   187  			if strings.Count(arg, "/") >= s3.deepLevel {
   188  				return []string{arg}
   189  			}
   190  		}
   191  		// Predict S3 path
   192  		prediction = completeS3Path(arg)
   193  	}
   194  
   195  	return
   196  }
   197  
   198  // aliasComplete only completes aliases
   199  type aliasComplete struct{}
   200  
   201  func (al aliasComplete) Predict(a complete.Args) (prediction []string) {
   202  	defer func() {
   203  		sort.Strings(prediction)
   204  	}()
   205  
   206  	loadMcConfig = loadMcConfigFactory()
   207  	conf, err := loadMcConfig()
   208  	if err != nil {
   209  		return nil
   210  	}
   211  
   212  	arg := a.Last
   213  	for alias := range conf.Aliases {
   214  		if strings.HasPrefix(alias, arg) {
   215  			prediction = append(prediction, alias+"/")
   216  		}
   217  	}
   218  
   219  	return
   220  }
   221  
   222  var (
   223  	adminConfigCompleter = adminConfigComplete{}
   224  	s3Completer          = s3Complete{}
   225  	aliasCompleter       = aliasComplete{}
   226  	fsCompleter          = fsComplete{}
   227  )
   228  
   229  // The list of all commands supported by mc with their mapping
   230  // with their bash completer function
   231  var completeCmds = map[string]complete.Predictor{
   232  	// S3 API level commands
   233  	"/ls":        complete.PredictOr(s3Completer, fsCompleter),
   234  	"/cp":        complete.PredictOr(s3Completer, fsCompleter),
   235  	"/mv":        complete.PredictOr(s3Completer, fsCompleter),
   236  	"/rm":        complete.PredictOr(s3Completer, fsCompleter),
   237  	"/rb":        complete.PredictOr(s3Complete{deepLevel: 2}, fsCompleter),
   238  	"/cat":       complete.PredictOr(s3Completer, fsCompleter),
   239  	"/head":      complete.PredictOr(s3Completer, fsCompleter),
   240  	"/diff":      complete.PredictOr(s3Completer, fsCompleter),
   241  	"/find":      complete.PredictOr(s3Completer, fsCompleter),
   242  	"/mirror":    complete.PredictOr(s3Completer, fsCompleter),
   243  	"/pipe":      complete.PredictOr(s3Completer, fsCompleter),
   244  	"/stat":      complete.PredictOr(s3Completer, fsCompleter),
   245  	"/watch":     complete.PredictOr(s3Completer, fsCompleter),
   246  	"/anonymous": complete.PredictOr(s3Completer, fsCompleter),
   247  	"/tree":      complete.PredictOr(s3Complete{deepLevel: 2}, fsCompleter),
   248  	"/du":        complete.PredictOr(s3Complete{deepLevel: 2}, fsCompleter),
   249  
   250  	"/retention/set":   s3Completer,
   251  	"/retention/clear": s3Completer,
   252  	"/retention/info":  s3Completer,
   253  
   254  	"/legalhold/set":   s3Completer,
   255  	"/legalhold/clear": s3Completer,
   256  	"/legalhold/info":  s3Completer,
   257  
   258  	"/sql": s3Completer,
   259  	"/mb":  aliasCompleter,
   260  
   261  	"/event/add":    s3Complete{deepLevel: 2},
   262  	"/event/list":   s3Complete{deepLevel: 2},
   263  	"/event/remove": s3Complete{deepLevel: 2},
   264  
   265  	"/encrypt/set":   s3Complete{deepLevel: 2},
   266  	"/encrypt/info":  s3Complete{deepLevel: 2},
   267  	"/encrypt/clear": s3Complete{deepLevel: 2},
   268  
   269  	"/replicate/add":     s3Complete{deepLevel: 2},
   270  	"/replicate/edit":    s3Complete{deepLevel: 2},
   271  	"/replicate/update":  s3Complete{deepLevel: 2},
   272  	"/replicate/list":    s3Complete{deepLevel: 2},
   273  	"/replicate/remove":  s3Complete{deepLevel: 2},
   274  	"/replicate/backlog": s3Complete{deepLevel: 2},
   275  
   276  	"/replicate/export":        s3Complete{deepLevel: 2},
   277  	"/replicate/import":        s3Complete{deepLevel: 2},
   278  	"/replicate/status":        s3Complete{deepLevel: 2},
   279  	"/replicate/resync/start":  s3Complete{deepLevel: 3},
   280  	"/replicate/resync/status": s3Complete{deepLevel: 3},
   281  
   282  	"/tag/list":   s3Completer,
   283  	"/tag/remove": s3Completer,
   284  	"/tag/set":    s3Completer,
   285  
   286  	"/version/info":    s3Complete{deepLevel: 2},
   287  	"/version/enable":  s3Complete{deepLevel: 2},
   288  	"/version/suspend": s3Complete{deepLevel: 2},
   289  
   290  	"/lock/compliance": s3Completer,
   291  	"/lock/governance": s3Completer,
   292  	"/lock/clear":      s3Completer,
   293  	"/lock/info":       s3Completer,
   294  
   295  	"/share/download": s3Completer,
   296  	"/share/list":     nil,
   297  	"/share/upload":   s3Completer,
   298  
   299  	"/ilm/list":    s3Complete{deepLevel: 2},
   300  	"/ilm/add":     s3Complete{deepLevel: 2},
   301  	"/ilm/edit":    s3Complete{deepLevel: 2},
   302  	"/ilm/remove":  s3Complete{deepLevel: 2},
   303  	"/ilm/export":  s3Complete{deepLevel: 2},
   304  	"/ilm/import":  s3Complete{deepLevel: 2},
   305  	"/ilm/restore": s3Completer,
   306  
   307  	"/ilm/rule/list":    s3Complete{deepLevel: 2},
   308  	"/ilm/rule/add":     s3Complete{deepLevel: 2},
   309  	"/ilm/rule/edit":    s3Complete{deepLevel: 2},
   310  	"/ilm/rule/remove":  s3Complete{deepLevel: 2},
   311  	"/ilm/rule/export":  s3Complete{deepLevel: 2},
   312  	"/ilm/rule/import":  s3Complete{deepLevel: 2},
   313  	"/ilm/rule/restore": s3Completer,
   314  
   315  	"/undo": s3Completer,
   316  
   317  	// Admin API commands MinIO only.
   318  	"/admin/heal": s3Completer,
   319  
   320  	"/admin/info": aliasCompleter,
   321  	"/admin/logs": aliasCompleter,
   322  
   323  	"/admin/config/get":     adminConfigCompleter,
   324  	"/admin/config/set":     adminConfigCompleter,
   325  	"/admin/config/reset":   adminConfigCompleter,
   326  	"/admin/config/import":  aliasCompleter,
   327  	"/admin/config/export":  aliasCompleter,
   328  	"/admin/config/history": aliasCompleter,
   329  	"/admin/config/restore": aliasCompleter,
   330  
   331  	"/admin/decom/start":         aliasCompleter,
   332  	"/admin/decom/status":        aliasCompleter,
   333  	"/admin/decom/cancel":        aliasCompleter,
   334  	"/admin/decommission/start":  aliasCompleter,
   335  	"/admin/decommission/status": aliasCompleter,
   336  	"/admin/decommission/cancel": aliasCompleter,
   337  
   338  	"/admin/rebalance/start":  aliasCompleter,
   339  	"/admin/rebalance/status": aliasCompleter,
   340  	"/admin/rebalance/stop":   aliasCompleter,
   341  
   342  	"/admin/trace":     aliasCompleter,
   343  	"/admin/speedtest": aliasCompleter,
   344  	"/admin/console":   aliasCompleter,
   345  	"/admin/update":    aliasCompleter,
   346  	"/admin/inspect":   s3Completer,
   347  	"/admin/top/locks": aliasCompleter,
   348  	"/admin/top/api":   aliasCompleter,
   349  
   350  	"/admin/scanner/status": aliasCompleter,
   351  	"/admin/scanner/trace":  aliasCompleter,
   352  
   353  	"/admin/service/stop":     aliasCompleter,
   354  	"/admin/service/restart":  aliasCompleter,
   355  	"/admin/service/freeze":   aliasCompleter,
   356  	"/admin/service/unfreeze": aliasCompleter,
   357  
   358  	"/admin/prometheus/generate": aliasCompleter,
   359  	"/admin/prometheus/metrics":  aliasCompleter,
   360  
   361  	"/admin/profile/start": aliasCompleter,
   362  	"/admin/profile/stop":  aliasCompleter,
   363  
   364  	"/idp/openid/add":     aliasCompleter,
   365  	"/idp/openid/update":  aliasCompleter,
   366  	"/idp/openid/remove":  aliasCompleter,
   367  	"/idp/openid/list":    aliasCompleter,
   368  	"/idp/openid/info":    aliasCompleter,
   369  	"/idp/openid/enable":  aliasCompleter,
   370  	"/idp/openid/disable": aliasCompleter,
   371  
   372  	"/idp/ldap/add":     aliasCompleter,
   373  	"/idp/ldap/update":  aliasCompleter,
   374  	"/idp/ldap/remove":  aliasCompleter,
   375  	"/idp/ldap/list":    aliasCompleter,
   376  	"/idp/ldap/info":    aliasCompleter,
   377  	"/idp/ldap/enable":  aliasCompleter,
   378  	"/idp/ldap/disable": aliasCompleter,
   379  
   380  	"/idp/ldap/policy/entities": aliasCompleter,
   381  	"/idp/ldap/policy/attach":   aliasCompleter,
   382  	"/idp/ldap/policy/detach":   aliasCompleter,
   383  
   384  	"/idp/ldap/accesskey/create":            aliasCompleter,
   385  	"/idp/ldap/accesskey/create-with-login": aliasCompleter,
   386  	"/idp/ldap/accesskey/list":              aliasCompleter,
   387  	"/idp/ldap/accesskey/ls":                aliasCompleter,
   388  	"/idp/ldap/accesskey/remove":            aliasCompleter,
   389  	"/idp/ldap/accesskey/rm":                aliasCompleter,
   390  	"/idp/ldap/accesskey/info":              aliasCompleter,
   391  
   392  	"/admin/policy/info":     aliasCompleter,
   393  	"/admin/policy/update":   aliasCompleter,
   394  	"/admin/policy/add":      aliasCompleter,
   395  	"/admin/policy/remove":   aliasCompleter,
   396  	"/admin/policy/create":   aliasCompleter,
   397  	"/admin/policy/list":     aliasCompleter,
   398  	"/admin/policy/attach":   aliasCompleter,
   399  	"/admin/policy/detach":   aliasCompleter,
   400  	"/admin/policy/entities": aliasCompleter,
   401  
   402  	"/admin/user/add":     aliasCompleter,
   403  	"/admin/user/disable": aliasCompleter,
   404  	"/admin/user/enable":  aliasCompleter,
   405  	"/admin/user/list":    aliasCompleter,
   406  	"/admin/user/remove":  aliasCompleter,
   407  	"/admin/user/info":    aliasCompleter,
   408  	"/admin/user/policy":  aliasCompleter,
   409  
   410  	"/admin/user/svcacct/add":     aliasCompleter,
   411  	"/admin/user/svcacct/list":    aliasCompleter,
   412  	"/admin/user/svcacct/remove":  aliasCompleter,
   413  	"/admin/user/svcacct/info":    aliasCompleter,
   414  	"/admin/user/svcacct/edit":    aliasCompleter,
   415  	"/admin/user/svcacct/set":     aliasCompleter,
   416  	"/admin/user/svcacct/enable":  aliasCompleter,
   417  	"/admin/user/svcacct/disable": aliasCompleter,
   418  
   419  	"/admin/user/sts/info": aliasCompleter,
   420  
   421  	"/admin/group/add":     aliasCompleter,
   422  	"/admin/group/disable": aliasCompleter,
   423  	"/admin/group/enable":  aliasCompleter,
   424  	"/admin/group/list":    aliasCompleter,
   425  	"/admin/group/remove":  aliasCompleter,
   426  	"/admin/group/info":    aliasCompleter,
   427  
   428  	"/admin/bucket/remote/add":    aliasCompleter,
   429  	"/admin/bucket/remote/edit":   aliasCompleter,
   430  	"/admin/bucket/remote/remove": aliasCompleter,
   431  	"/admin/bucket/quota":         aliasCompleter,
   432  	"/admin/bucket/info":          s3Complete{deepLevel: 2},
   433  
   434  	"/admin/kms/key/create": aliasCompleter,
   435  	"/admin/kms/key/status": aliasCompleter,
   436  	"/admin/kms/key/list":   aliasCompleter,
   437  
   438  	"/admin/subnet/health":   aliasCompleter,
   439  	"/admin/subnet/register": aliasCompleter,
   440  
   441  	"/admin/tier/add":    nil,
   442  	"/admin/tier/edit":   nil,
   443  	"/admin/tier/list":   nil,
   444  	"/admin/tier/info":   nil,
   445  	"/admin/tier/remove": nil,
   446  	"/admin/tier/verify": nil,
   447  
   448  	"/ilm/tier/info":   nil,
   449  	"/ilm/tier/list":   nil,
   450  	"/ilm/tier/add":    nil,
   451  	"/ilm/tier/update": nil,
   452  	"/ilm/tier/check":  nil,
   453  	"/ilm/tier/remove": nil,
   454  
   455  	"/admin/replicate/add":           aliasCompleter,
   456  	"/admin/replicate/update":        aliasCompleter,
   457  	"/admin/replicate/edit":          aliasCompleter,
   458  	"/admin/replicate/info":          aliasCompleter,
   459  	"/admin/replicate/status":        aliasCompleter,
   460  	"/admin/replicate/remove":        aliasCompleter,
   461  	"/admin/replicate/resync/start":  aliasCompleter,
   462  	"/admin/replicate/resync/cancel": aliasCompleter,
   463  	"/admin/replicate/resync/status": aliasCompleter,
   464  
   465  	"/admin/cluster/bucket/export": aliasCompleter,
   466  	"/admin/cluster/bucket/import": aliasCompleter,
   467  	"/admin/cluster/iam/export":    aliasCompleter,
   468  	"/admin/cluster/iam/import":    aliasCompleter,
   469  
   470  	"/alias/set":    nil,
   471  	"/alias/list":   aliasCompleter,
   472  	"/alias/remove": aliasCompleter,
   473  	"/alias/import": nil,
   474  	"/alias/export": aliasCompleter,
   475  
   476  	"/support/callhome":     aliasCompleter,
   477  	"/support/register":     aliasCompleter,
   478  	"/support/diag":         aliasCompleter,
   479  	"/support/profile":      aliasCompleter,
   480  	"/support/proxy/set":    aliasCompleter,
   481  	"/support/proxy/show":   aliasCompleter,
   482  	"/support/proxy/remove": aliasCompleter,
   483  	"/support/inspect":      aliasCompleter,
   484  	"/support/perf":         aliasCompleter,
   485  	"/support/metrics":      aliasCompleter,
   486  	"/support/status":       aliasCompleter,
   487  	"/support/top/locks":    aliasCompleter,
   488  	"/support/top/api":      aliasCompleter,
   489  	"/support/top/drive":    aliasCompleter,
   490  	"/support/top/disk":     aliasCompleter,
   491  	"/support/top/net":      aliasCompleter,
   492  	"/support/upload":       aliasCompleter,
   493  
   494  	"/license/register": aliasCompleter,
   495  	"/license/info":     aliasCompleter,
   496  	"/license/update":   aliasCompleter,
   497  
   498  	"/update":         nil,
   499  	"/ready":          aliasCompleter,
   500  	"/ping":           aliasCompleter,
   501  	"/od":             nil,
   502  	"/batch/generate": aliasCompleter,
   503  	"/batch/start":    aliasCompleter,
   504  	"/batch/list":     aliasCompleter,
   505  	"/batch/status":   aliasCompleter,
   506  	"/batch/describe": aliasCompleter,
   507  	"/batch/cancel":   aliasCompleter,
   508  
   509  	"/quota/set":   aliasCompleter,
   510  	"/quota/info":  aliasCompleter,
   511  	"/quota/clear": aliasCompleter,
   512  	"/put":         complete.PredictOr(s3Completer, fsCompleter),
   513  	"/get":         complete.PredictOr(s3Completer, fsCompleter),
   514  }
   515  
   516  // flagsToCompleteFlags transforms a cli.Flag to complete.Flags
   517  // understood by posener/complete library.
   518  func flagsToCompleteFlags(flags []cli.Flag) complete.Flags {
   519  	complFlags := make(complete.Flags)
   520  	for _, f := range flags {
   521  		for _, s := range strings.Split(f.GetName(), ",") {
   522  			var flagName string
   523  			s = strings.TrimSpace(s)
   524  			if len(s) == 1 {
   525  				flagName = "-" + s
   526  			} else {
   527  				flagName = "--" + s
   528  			}
   529  			complFlags[flagName] = complete.PredictNothing
   530  		}
   531  	}
   532  	return complFlags
   533  }
   534  
   535  // This function recursively transforms cli.Command to complete.Command
   536  // understood by posener/complete library.
   537  func cmdToCompleteCmd(cmd cli.Command, parentPath string) complete.Command {
   538  	var complCmd complete.Command
   539  	complCmd.Sub = make(complete.Commands)
   540  
   541  	for _, subCmd := range cmd.Subcommands {
   542  		if subCmd.Hidden {
   543  			continue
   544  		}
   545  		complCmd.Sub[subCmd.Name] = cmdToCompleteCmd(subCmd, parentPath+"/"+cmd.Name)
   546  		for _, alias := range subCmd.Aliases {
   547  			complCmd.Sub[alias] = cmdToCompleteCmd(subCmd, parentPath+"/"+cmd.Name)
   548  		}
   549  	}
   550  
   551  	complCmd.Flags = flagsToCompleteFlags(cmd.Flags)
   552  	complCmd.Args = completeCmds[parentPath+"/"+cmd.Name]
   553  	return complCmd
   554  }
   555  
   556  // Main function to answer to bash completion calls
   557  func mainComplete() error {
   558  	// Recursively register all commands and subcommands
   559  	// along with global and local flags
   560  	complCmds := make(complete.Commands)
   561  	for _, cmd := range appCmds {
   562  		if cmd.Hidden {
   563  			continue
   564  		}
   565  		complCmds[cmd.Name] = cmdToCompleteCmd(cmd, "")
   566  		for _, alias := range cmd.Aliases {
   567  			complCmds[alias] = cmdToCompleteCmd(cmd, "")
   568  		}
   569  	}
   570  	complFlags := flagsToCompleteFlags(globalFlags)
   571  	mcComplete := complete.Command{
   572  		Sub:         complCmds,
   573  		GlobalFlags: complFlags,
   574  	}
   575  	// Answer to bash completion call
   576  	complete.New(filepath.Base(os.Args[0]), mcComplete).Run()
   577  	return nil
   578  }