This repository has been archived by the owner on Aug 1, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
filepath.go
171 lines (142 loc) · 4.74 KB
/
filepath.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// Copyright (C) 2019 The CodeActual Go Environment Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package filepath
import (
"os"
"regexp"
"strings"
"github.com/pkg/errors"
std_filepath "path/filepath"
)
var unsafePathPartRe *regexp.Regexp
func init() {
unsafePathPartRe = regexp.MustCompile(`[\s\\\/]`)
}
// WalkFunc matches std_filepath.WalkFunc except it returns an error slice.
type WalkFunc func(string, os.FileInfo, error) []error
// Append behaves like Join except that as each element must add at least one
// level to the path.
//
// For example, an element with "../../some_path" would effectively replace
// a level instead of adding one.
//
// An error is returned after the first illegal element is reached.
func Append(paths ...string) (joined string, err error) {
var prev string
for _, p := range paths {
joined = std_filepath.Join(joined, p)
if prev != "" && !strings.HasPrefix(joined, prev) {
return "", errors.Errorf("path [%s] cannot be appended, escapes prefix [%s]", p, prev)
}
prev = joined
}
return joined, nil
}
func Abs(p *string) error {
abs, err := std_filepath.Abs(*p)
if err != nil {
return errors.Wrapf(err, "failed to resolve absolute path [%s]", *p)
}
*p = abs
return nil
}
func IsGoFile(name string) bool {
return std_filepath.Ext(name) == ".go"
}
// AncestorDirs returns all absolute paths between a starting descendant absolute path and
// an ancestor absolute path.
//
// The end path is included in the results (if it is encountered).
//
// If the end path is not encountered, all ancestors will be returned up to the root.
func AncestorDirs(start, end string) (dirs []string) {
start = std_filepath.Clean(start)
end = std_filepath.Clean(end)
current := start
for {
if current == end {
break
}
prev := current
current = std_filepath.Dir(current)
if prev == current { // cannot ascend any farther
break
}
dirs = append(dirs, current)
}
return dirs
}
// WalkAbs wraps the standard Walk to make several adjustments: an absolute path is
// passed to the standard Walk, absolute paths are passed to the input WalkFunc,
// and the WalkFunc is a new type defined by this package.
func WalkAbs(root string, walkFn WalkFunc) (errs []error) {
root, rootErr := std_filepath.Abs(root)
if rootErr != nil {
return []error{errors.Wrapf(rootErr, "failed to walk file tree: unable to get absolute path [%s]", root)}
}
_ = std_filepath.Walk(root, func(path string, info os.FileInfo, walkErr error) error {
pathAbs, pathErr := std_filepath.Abs(path)
if pathErr != nil {
errs = append(errs, errors.Wrapf(pathErr, "failed to walk file tree: unable to get absolute path [%s]", path))
return errors.New("") // just cancel the walk
}
if walkFnErrs := walkFn(pathAbs, info, walkErr); len(walkFnErrs) > 0 {
if walkFnErrs[0] == std_filepath.SkipDir {
return walkFnErrs[0]
}
for _, err := range walkFnErrs {
errs = append(errs, errors.WithStack(err))
}
return errors.New("") // just cancel the walk
}
return nil
})
return errs
}
// FileAncestor returns the intermediate directories between a root directory and a descendant
// file/directory.
//
// For example, consider the behavior of os.MkdirAll. Just as it creates all intermediate
// directories as necessary to fulfill the request, this function returns the names of those
// directories instead of creating them. And instead of using / or the working directory as a
// root or starting point based on the name, this function requires the root to be chosen
// and applies it globally.
//
// It will return an error if the file/directory is not a descendant of the root.
//
// Every input and output path is resolved with filepath.Abs.
func FileAncestor(descendant, root string) (a []string, err error) {
root, err = std_filepath.Abs(root)
if err != nil {
return a, errors.Wrapf(err, "failed to get absolute path of root [%s]", root)
}
if descendant == root {
return a, nil
}
descendant, err = std_filepath.Abs(descendant)
if err != nil {
return a, errors.Wrapf(err, "failed to get absolute path of descendant [%s]", descendant)
}
if !strings.HasPrefix(descendant, root) {
return a, errors.Errorf("[%s] is not a descendant of [%s]", descendant, root)
}
for {
base := descendant
descendant = std_filepath.Dir(descendant)
if descendant == base { // reached filesystem root
break
}
a = append(a, descendant)
if descendant == root {
break
}
}
return a, nil
}
// PathToSafeFilename converts a filepath to a string usable as a filename.
func PathToSafeFilename(p string) string {
return strings.Trim(unsafePathPartRe.ReplaceAllString(p, "-"), "-")
}