diff options
| author | Eyal Posener <[email protected]> | 2019-11-14 06:51:44 +0200 |
|---|---|---|
| committer | Eyal Posener <[email protected]> | 2019-11-18 01:05:47 +0200 |
| commit | 8724aaf18312e54750540a9578e00d61b1c545d8 (patch) | |
| tree | d3e736b4fb279975bbcc017ae1bad53e454c5773 /predict/files.go | |
| parent | 05b68ffc813dd10c420993cb1cf927b346c057b8 (diff) | |
V2
Diffstat (limited to 'predict/files.go')
| -rw-r--r-- | predict/files.go | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/predict/files.go b/predict/files.go new file mode 100644 index 0000000..4654ec4 --- /dev/null +++ b/predict/files.go @@ -0,0 +1,175 @@ +package predict + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +// Dirs returns a predictor that predict directory paths. If a non-empty pattern is given, the +// predicted paths will match that pattern. +func Dirs(pattern string) FilesPredictor { + return FilesPredictor{pattern: pattern, includeFiles: false} +} + +// Dirs returns a predictor that predict file or directory paths. If a non-empty pattern is given, +// the predicted paths will match that pattern. +func Files(pattern string) FilesPredictor { + return FilesPredictor{pattern: pattern, includeFiles: true} +} + +type FilesPredictor struct { + pattern string + includeFiles bool +} + +// Predict searches for files according to the given prefix. +// If the only predicted path is a single directory, the search will continue another recursive +// layer into that directory. +func (f FilesPredictor) Predict(prefix string) (options []string) { + options = f.predictFiles(prefix) + + // If the number of prediction is not 1, we either have many results or have no results, so we + // return it. + if len(options) != 1 { + return + } + + // Only try deeper, if the one item is a directory. + if stat, err := os.Stat(options[0]); err != nil || !stat.IsDir() { + return + } + + return f.predictFiles(options[0]) +} + +func (f FilesPredictor) predictFiles(prefix string) []string { + if strings.HasSuffix(prefix, "/..") { + return nil + } + + dir := directory(prefix) + files := f.listFiles(dir) + + // Add dir if match. + files = append(files, dir) + + return FilesSet(files).Predict(prefix) +} + +func (f FilesPredictor) listFiles(dir string) []string { + // Set of all file names. + m := map[string]bool{} + + // List files. + if files, err := filepath.Glob(filepath.Join(dir, f.pattern)); err == nil { + for _, file := range files { + if stat, err := os.Stat(file); err != nil || stat.IsDir() || f.includeFiles { + m[file] = true + } + } + } + + // List directories. + if dirs, err := ioutil.ReadDir(dir); err == nil { + for _, d := range dirs { + if d.IsDir() { + m[filepath.Join(dir, d.Name())] = true + } + } + } + + list := make([]string, 0, len(m)) + for k := range m { + list = append(list, k) + } + return list +} + +// directory gives the directory of the given partial path in case that it is not, we fall back to +// the current directory. +func directory(path string) string { + if info, err := os.Stat(path); err == nil && info.IsDir() { + return fixPathForm(path, path) + } + dir := filepath.Dir(path) + if info, err := os.Stat(dir); err == nil && info.IsDir() { + return fixPathForm(path, dir) + } + return "./" +} + +// FilesSet predict according to file rules to a given fixed set of file names. +type FilesSet []string + +func (s FilesSet) Predict(prefix string) (prediction []string) { + // add all matching files to prediction + for _, f := range s { + f = fixPathForm(prefix, f) + + // test matching of file to the argument + if matchFile(f, prefix) { + prediction = append(prediction, f) + } + } + if len(prediction) == 0 { + return s + } + return +} + +// MatchFile returns true if prefix can match the file +func matchFile(file, prefix string) bool { + // special case for current directory completion + if file == "./" && (prefix == "." || prefix == "") { + return true + } + if prefix == "." && strings.HasPrefix(file, ".") { + return true + } + + file = strings.TrimPrefix(file, "./") + prefix = strings.TrimPrefix(prefix, "./") + + return strings.HasPrefix(file, prefix) +} + +// fixPathForm changes a file name to a relative name +func fixPathForm(last string, file string) string { + // Get wording directory for relative name. + workDir, err := os.Getwd() + if err != nil { + return file + } + + abs, err := filepath.Abs(file) + if err != nil { + return file + } + + // If last is absolute, return path as absolute. + if filepath.IsAbs(last) { + return fixDirPath(abs) + } + + rel, err := filepath.Rel(workDir, abs) + if err != nil { + return file + } + + // Fix ./ prefix of path. + if rel != "." && strings.HasPrefix(last, ".") { + rel = "./" + rel + } + + return fixDirPath(rel) +} + +func fixDirPath(path string) string { + info, err := os.Stat(path) + if err == nil && info.IsDir() && !strings.HasSuffix(path, "/") { + path += "/" + } + return path +} |
