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 }