github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/command/flag/path.go (about) 1 package flag 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 11 flags "github.com/jessevdk/go-flags" 12 ) 13 14 type Path string 15 16 func (p Path) String() string { 17 return string(p) 18 } 19 20 func (Path) Complete(prefix string) []flags.Completion { 21 return completeWithTilde(prefix) 22 } 23 24 type PathWithExistenceCheck string 25 26 func (PathWithExistenceCheck) Complete(prefix string) []flags.Completion { 27 return completeWithTilde(prefix) 28 } 29 30 func (p *PathWithExistenceCheck) UnmarshalFlag(path string) error { 31 _, err := os.Stat(path) 32 if err != nil { 33 if os.IsNotExist(err) { 34 return &flags.Error{ 35 Type: flags.ErrRequired, 36 Message: fmt.Sprintf("The specified path '%s' does not exist.", path), 37 } 38 } 39 return err 40 } 41 42 *p = PathWithExistenceCheck(path) 43 return nil 44 } 45 46 type JSONOrFileWithValidation map[string]interface{} 47 48 func (JSONOrFileWithValidation) Complete(prefix string) []flags.Completion { 49 return completeWithTilde(prefix) 50 } 51 52 func (p *JSONOrFileWithValidation) UnmarshalFlag(pathOrJSON string) error { 53 var jsonBytes []byte 54 55 errorToReturn := &flags.Error{ 56 Type: flags.ErrRequired, 57 Message: "Invalid configuration provided for -c flag. Please provide a valid JSON object or path to a file containing a valid JSON object.", 58 } 59 60 _, err := os.Stat(pathOrJSON) 61 if err == nil { 62 jsonBytes, err = ioutil.ReadFile(pathOrJSON) 63 if err != nil { 64 return errorToReturn 65 } 66 } else { 67 jsonBytes = []byte(pathOrJSON) 68 } 69 70 var jsonMap map[string]interface{} 71 72 if jsonIsInvalid := json.Unmarshal(jsonBytes, &jsonMap); jsonIsInvalid != nil { 73 return errorToReturn 74 } 75 76 *p = JSONOrFileWithValidation(jsonMap) 77 return nil 78 } 79 80 type PathWithExistenceCheckOrURL string 81 82 func (PathWithExistenceCheckOrURL) Complete(prefix string) []flags.Completion { 83 return completeWithTilde(prefix) 84 } 85 86 func (p *PathWithExistenceCheckOrURL) UnmarshalFlag(path string) error { 87 if !strings.HasPrefix(path, "http://") && !strings.HasPrefix(path, "https://") { 88 _, err := os.Stat(path) 89 if err != nil { 90 if os.IsNotExist(err) { 91 return &flags.Error{ 92 Type: flags.ErrRequired, 93 Message: fmt.Sprintf("The specified path '%s' does not exist.", path), 94 } 95 } 96 return err 97 } 98 } 99 100 *p = PathWithExistenceCheckOrURL(path) 101 return nil 102 } 103 104 type PathWithAt string 105 106 func (PathWithAt) Complete(prefix string) []flags.Completion { 107 if prefix == "" || prefix[0] != '@' { 108 return nil 109 } 110 111 prefix = prefix[1:] 112 113 var homeDir string 114 if strings.HasPrefix(prefix, fmt.Sprintf("~%c", os.PathSeparator)) { 115 // when $HOME is empty this will complete on /, however this is not tested 116 homeDir = os.Getenv("HOME") 117 prefix = fmt.Sprintf("%s%s", homeDir, prefix[1:]) 118 } 119 120 return findMatches( 121 fmt.Sprintf("%s*", prefix), 122 func(path string) string { 123 if homeDir != "" { 124 newPath, err := filepath.Rel(homeDir, path) 125 if err == nil { 126 path = filepath.Join("~", newPath) 127 } 128 } 129 return fmt.Sprintf("@%s", path) 130 }) 131 } 132 133 type PathWithBool string 134 135 func (PathWithBool) Complete(prefix string) []flags.Completion { 136 return append( 137 completions([]string{"true", "false"}, prefix, false), 138 completeWithTilde(prefix)..., 139 ) 140 } 141 142 func findMatches(pattern string, formatMatch func(string) string) []flags.Completion { 143 paths, _ := filepath.Glob(pattern) 144 if paths == nil { 145 return nil 146 } 147 148 matches := []flags.Completion{} 149 for _, path := range paths { 150 info, err := os.Stat(path) 151 if err != nil { 152 continue 153 } 154 155 formattedMatch := formatMatch(path) 156 if info.IsDir() { 157 formattedMatch = fmt.Sprintf("%s%c", formattedMatch, os.PathSeparator) 158 } 159 matches = append(matches, flags.Completion{Item: formattedMatch}) 160 } 161 162 return matches 163 } 164 165 func completeWithTilde(prefix string) []flags.Completion { 166 var homeDir string 167 if strings.HasPrefix(prefix, fmt.Sprintf("~%c", os.PathSeparator)) { 168 // when $HOME is empty this will complete on /, however this is not tested 169 homeDir = os.Getenv("HOME") 170 prefix = fmt.Sprintf("%s%s", homeDir, prefix[1:]) 171 } 172 173 return findMatches( 174 fmt.Sprintf("%s*", prefix), 175 func(path string) string { 176 if homeDir != "" { 177 newPath, err := filepath.Rel(homeDir, path) 178 if err == nil { 179 path = filepath.Join("~", newPath) 180 } 181 } 182 return path 183 }) 184 }