github.com/pachyderm/pachyderm@v1.13.4/src/server/cmd/pachctl/shell/completions.go (about)

     1  package shell
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"log"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/pachyderm/pachyderm/src/client"
    13  	"github.com/pachyderm/pachyderm/src/client/pfs"
    14  	"github.com/pachyderm/pachyderm/src/client/pps"
    15  	"github.com/pachyderm/pachyderm/src/server/pkg/cmdutil"
    16  	"github.com/pachyderm/pachyderm/src/server/pkg/errutil"
    17  	"github.com/pachyderm/pachyderm/src/server/pkg/pretty"
    18  	pps_pretty "github.com/pachyderm/pachyderm/src/server/pps/pretty"
    19  
    20  	prompt "github.com/c-bata/go-prompt"
    21  	units "github.com/docker/go-units"
    22  )
    23  
    24  type partEnum int
    25  
    26  const (
    27  	repoPart partEnum = iota
    28  	commitOrBranchPart
    29  	filePart
    30  )
    31  
    32  func parsePart(text string) partEnum {
    33  	switch {
    34  	case !strings.ContainsRune(text, '@'):
    35  		return repoPart
    36  	case !strings.ContainsRune(text, ':'):
    37  		return commitOrBranchPart
    38  	default:
    39  		return filePart
    40  	}
    41  }
    42  
    43  func samePart(p partEnum) CacheFunc {
    44  	return func(_, text string) bool {
    45  		return parsePart(text) == p
    46  	}
    47  }
    48  
    49  var (
    50  	pachClient     *client.APIClient
    51  	pachClientOnce sync.Once
    52  )
    53  
    54  func getPachClient() *client.APIClient {
    55  	pachClientOnce.Do(func() {
    56  		c, err := client.NewOnUserMachine("user-completion")
    57  		if err != nil {
    58  			log.Fatal(err)
    59  		}
    60  		pachClient = c
    61  	})
    62  	return pachClient
    63  }
    64  
    65  func closePachClient() error {
    66  	if pachClient == nil {
    67  		return nil
    68  	}
    69  	return pachClient.Close()
    70  }
    71  
    72  // RepoCompletion completes repo parameters of the form <repo>
    73  func RepoCompletion(_, text string, maxCompletions int64) ([]prompt.Suggest, CacheFunc) {
    74  	c := getPachClient()
    75  	ris, err := c.ListRepo()
    76  	if err != nil {
    77  		return nil, CacheNone
    78  	}
    79  	var result []prompt.Suggest
    80  	for _, ri := range ris {
    81  		result = append(result, prompt.Suggest{
    82  			Text:        ri.Repo.Name,
    83  			Description: fmt.Sprintf("%s (%s)", ri.Description, units.BytesSize(float64(ri.SizeBytes))),
    84  		})
    85  	}
    86  	return result, samePart(parsePart(text))
    87  }
    88  
    89  // BranchCompletion completes branch parameters of the form <repo>@<branch>
    90  func BranchCompletion(flag, text string, maxCompletions int64) ([]prompt.Suggest, CacheFunc) {
    91  	c := getPachClient()
    92  	partialFile := cmdutil.ParsePartialFile(text)
    93  	part := parsePart(text)
    94  	var result []prompt.Suggest
    95  	switch part {
    96  	case repoPart:
    97  		return RepoCompletion(flag, text, maxCompletions)
    98  	case commitOrBranchPart:
    99  		bis, err := c.ListBranch(partialFile.Commit.Repo.Name)
   100  		if err != nil {
   101  			return nil, CacheNone
   102  		}
   103  		for _, bi := range bis {
   104  			head := "-"
   105  			if bi.Head != nil {
   106  				head = bi.Head.ID
   107  			}
   108  			result = append(result, prompt.Suggest{
   109  				Text:        fmt.Sprintf("%s@%s:", partialFile.Commit.Repo.Name, bi.Branch.Name),
   110  				Description: fmt.Sprintf("(%s)", head),
   111  			})
   112  		}
   113  		if len(result) == 0 {
   114  			// Master should show up even if it doesn't exist yet
   115  			result = append(result, prompt.Suggest{
   116  				Text:        fmt.Sprintf("%s@master", partialFile.Commit.Repo.Name),
   117  				Description: "(nil)",
   118  			})
   119  		}
   120  	}
   121  	return result, samePart(part)
   122  }
   123  
   124  const (
   125  	// filePathCacheLength is how many new characters must be typed in a file
   126  	// path before we go to the server for new results.
   127  	filePathCacheLength = 4
   128  )
   129  
   130  func abs(i int) int {
   131  	if i < 0 {
   132  		return -i
   133  	}
   134  	return i
   135  }
   136  
   137  // FileCompletion completes file parameters of the form <repo>@<branch>:/file
   138  func FileCompletion(flag, text string, maxCompletions int64) ([]prompt.Suggest, CacheFunc) {
   139  	c := getPachClient()
   140  	partialFile := cmdutil.ParsePartialFile(text)
   141  	part := parsePart(text)
   142  	var result []prompt.Suggest
   143  	switch part {
   144  	case repoPart:
   145  		return RepoCompletion(flag, text, maxCompletions)
   146  	case commitOrBranchPart:
   147  		return BranchCompletion(flag, text, maxCompletions)
   148  	case filePart:
   149  		if err := c.GlobFileF(partialFile.Commit.Repo.Name, partialFile.Commit.ID, partialFile.Path+"*", func(fi *pfs.FileInfo) error {
   150  			if maxCompletions > 0 {
   151  				maxCompletions--
   152  			} else {
   153  				return errutil.ErrBreak
   154  			}
   155  			result = append(result, prompt.Suggest{
   156  				Text: fmt.Sprintf("%s@%s:%s", partialFile.Commit.Repo.Name, partialFile.Commit.ID, fi.File.Path),
   157  			})
   158  			return nil
   159  		}); err != nil {
   160  			return nil, CacheNone
   161  		}
   162  	}
   163  	return result, AndCacheFunc(samePart(part), func(_, text string) (result bool) {
   164  		_partialFile := cmdutil.ParsePartialFile(text)
   165  		return path.Dir(_partialFile.Path) == path.Dir(partialFile.Path) &&
   166  			abs(len(_partialFile.Path)-len(partialFile.Path)) < filePathCacheLength
   167  
   168  	})
   169  }
   170  
   171  // FilesystemCompletion completes file parameters from the local filesystem (not from pfs).
   172  func FilesystemCompletion(_, text string, maxCompletions int64) ([]prompt.Suggest, CacheFunc) {
   173  	dir := filepath.Dir(text)
   174  	fis, err := ioutil.ReadDir(dir)
   175  	if err != nil {
   176  		return nil, CacheNone
   177  	}
   178  	var result []prompt.Suggest
   179  	for _, fi := range fis {
   180  		result = append(result, prompt.Suggest{
   181  			Text: filepath.Join(dir, fi.Name()),
   182  		})
   183  	}
   184  	return result, func(_, text string) bool {
   185  		return filepath.Dir(text) == dir
   186  	}
   187  }
   188  
   189  // PipelineCompletion completes pipeline parameters of the form <pipeline>
   190  func PipelineCompletion(_, _ string, maxCompletions int64) ([]prompt.Suggest, CacheFunc) {
   191  	c := getPachClient()
   192  	resp, err := c.PpsAPIClient.ListPipeline(c.Ctx(), &pps.ListPipelineRequest{AllowIncomplete: true})
   193  	if err != nil {
   194  		return nil, CacheNone
   195  	}
   196  	var result []prompt.Suggest
   197  	for _, pi := range resp.PipelineInfo {
   198  		result = append(result, prompt.Suggest{
   199  			Text:        pi.Pipeline.Name,
   200  			Description: pi.Description,
   201  		})
   202  	}
   203  	return result, CacheAll
   204  }
   205  
   206  func jobDesc(ji *pps.JobInfo) string {
   207  	statusString := ""
   208  	if ji.Finished == nil {
   209  		statusString = fmt.Sprintf("%s for %s", pps_pretty.JobState(ji.State), pretty.Since(ji.Started))
   210  	} else {
   211  		statusString = fmt.Sprintf("%s %s", pps_pretty.JobState(ji.State), pretty.Ago(ji.Finished))
   212  	}
   213  	return fmt.Sprintf("%s: %s - %s", ji.Pipeline.Name, pps_pretty.Progress(ji), statusString)
   214  }
   215  
   216  // JobCompletion completes job parameters of the form <job>
   217  func JobCompletion(_, text string, maxCompletions int64) ([]prompt.Suggest, CacheFunc) {
   218  	c := getPachClient()
   219  	var result []prompt.Suggest
   220  	if err := c.ListJobF("", nil, nil, 0, false, func(ji *pps.JobInfo) error {
   221  		if maxCompletions > 0 {
   222  			maxCompletions--
   223  		} else {
   224  			return errutil.ErrBreak
   225  		}
   226  		result = append(result, prompt.Suggest{
   227  			Text:        ji.Job.ID,
   228  			Description: jobDesc(ji),
   229  		})
   230  		return nil
   231  	}); err != nil {
   232  		return nil, CacheNone
   233  	}
   234  	return result, CacheAll
   235  }