-
Notifications
You must be signed in to change notification settings - Fork 1
/
path.go
220 lines (199 loc) · 7.04 KB
/
path.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// Package path defines functions that manipulate directories, it's inspired by pathlib module from Mojo.
package path
import (
"fmt"
"os"
"path/filepath"
"sync"
tps "github.com/1set/starlet/dataconv/types"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
)
// ModuleName defines the expected name for this Module when used in starlark's load() function, eg: load('path', 'join')
const ModuleName = "path"
var (
once sync.Once
pathModule starlark.StringDict
)
// LoadModule loads the path module. It is concurrency-safe and idempotent.
func LoadModule() (starlark.StringDict, error) {
once.Do(func() {
pathModule = starlark.StringDict{
ModuleName: &starlarkstruct.Module{
Name: ModuleName,
Members: starlark.StringDict{
"abs": starlark.NewBuiltin(ModuleName+".abs", absPath),
"join": starlark.NewBuiltin(ModuleName+".join", joinPaths),
"exists": wrapExistPath("exists", checkExistPath),
"is_file": wrapExistPath("is_file", checkFileExist),
"is_dir": wrapExistPath("is_dir", checkDirExist),
"is_link": wrapExistPath("is_link", checkSymlinkExist),
"listdir": starlark.NewBuiltin(ModuleName+".listdir", listDirContents),
"getcwd": starlark.NewBuiltin(ModuleName+".getcwd", getCWD),
"chdir": starlark.NewBuiltin(ModuleName+".chdir", changeCWD),
"mkdir": starlark.NewBuiltin(ModuleName+".mkdir", makeDir),
},
},
}
})
return pathModule, nil
}
// absPath returns the absolute representation of path.
func absPath(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var path string
if err := starlark.UnpackArgs(b.Name(), args, kwargs, "path", &path); err != nil {
return nil, err
}
// get absolute path
abs, err := filepath.Abs(path)
if err != nil {
return nil, err
}
return starlark.String(abs), nil
}
// joinPaths joins any number of path elements into a single path.
func joinPaths(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
// check arguments
if len(args) < 1 {
return nil, fmt.Errorf("%s: got %d arguments, want at least 1", b.Name(), len(args))
}
// unpack arguments
paths := make([]string, len(args))
for i, arg := range args {
s, ok := starlark.AsString(arg)
if !ok {
return nil, fmt.Errorf("%s: for parameter path: got %s, want string", b.Name(), arg.Type())
}
paths[i] = s
}
// join paths
joined := filepath.Join(paths...)
return starlark.String(joined), nil
}
// wrapExistPath wraps the existPath function to be used in Starlark with a given function to check if the path exists.
func wrapExistPath(funcName string, workLoad func(path string) bool) starlark.Callable {
return starlark.NewBuiltin(ModuleName+"."+funcName, func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var path string
if err := starlark.UnpackArgs(b.Name(), args, kwargs, "path", &path); err != nil {
return starlark.None, err
}
return starlark.Bool(workLoad(path)), nil
})
}
// checkExistPath returns true if the path exists, if it's a symbolic link, the symbolic link is followed.
func checkExistPath(path string) bool {
_, err := os.Stat(path)
return err == nil
}
// checkFileExist returns true if the file exists, if it's a symbolic link, the symbolic link is followed.
func checkFileExist(path string) bool {
info, err := os.Stat(path)
return err == nil && info != nil && info.Mode().IsRegular()
}
// checkDirExist returns true if the directory exists, if it's a symbolic link, the symbolic link is followed.
func checkDirExist(path string) bool {
info, err := os.Stat(path)
return err == nil && info != nil && info.IsDir()
}
// checkSymlinkExist returns true if the symbolic link exists.
func checkSymlinkExist(path string) bool {
info, err := os.Lstat(path)
return err == nil && info != nil && info.Mode()&os.ModeSymlink == os.ModeSymlink
}
// listDirContents returns a list of directory contents.
func listDirContents(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var (
path string
recursive bool
filterFunc = tps.NullableCallable{}
)
if err := starlark.UnpackArgs(b.Name(), args, kwargs, "path", &path, "recursive?", &recursive, "filter?", &filterFunc); err != nil {
return nil, err
}
// get filter func
var ff starlark.Callable
if !filterFunc.IsNull() {
ff = filterFunc.Value()
}
// check root stat
rootInfo, err := os.Lstat(path)
if err != nil {
return nil, fmt.Errorf("%s: %w", b.Name(), err)
}
// check if path is a directory, if not return empty list
var sl []starlark.Value
if !rootInfo.IsDir() {
return starlark.NewList(sl), nil
}
// scan directory contents
if err := filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// skip same path to avoid infinite loop in case of symbolic links
if os.SameFile(rootInfo, info) {
return nil
}
// filter path
sp := starlark.String(p)
if ff != nil {
filtered, err := starlark.Call(thread, ff, starlark.Tuple{sp}, nil)
if err != nil {
return fmt.Errorf("filter %q: %w", p, err)
}
if fb, ok := filtered.(starlark.Bool); !ok {
return fmt.Errorf("filter %q: got %s, want bool", p, filtered.Type())
} else if fb == false {
return nil // skip path
}
}
// add path to list
sl = append(sl, sp)
// check if we should list recursively
if !recursive && p != path && info.IsDir() {
return filepath.SkipDir
}
return nil
}); err != nil {
return nil, fmt.Errorf("%s: %w", b.Name(), err)
}
return starlark.NewList(sl), nil
}
// getCWD returns the current working directory.
func getCWD(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
// check the arguments: no arguments
if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 0); err != nil {
return nil, err
}
// get current working directory
cwd, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("%s: %w", b.Name(), err)
}
return starlark.String(cwd), nil
}
// changeCWD changes the current working directory.
func changeCWD(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var path string
if err := starlark.UnpackArgs(b.Name(), args, kwargs, "path", &path); err != nil {
return nil, err
}
// change working directory
if err := os.Chdir(path); err != nil {
return nil, fmt.Errorf("%s: %w", b.Name(), err)
}
return starlark.None, nil
}
// makeDir creates a directory with the given name.
func makeDir(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var (
pathVal tps.StringOrBytes
modeVal = uint32(0755)
)
if err := starlark.UnpackArgs(b.Name(), args, kwargs, "path", &pathVal, "mode?", &modeVal); err != nil {
return starlark.None, err
}
// do the work
mode := os.FileMode(modeVal)
return starlark.None, os.MkdirAll(pathVal.GoString(), mode)
}