Repository navigation
Expand file tree
/
Copy pathscan.go
More file actions
77 lines (73 loc) · 2.03 KB
/
Copy pathscan.go
File metadata and controls
77 lines (73 loc) · 2.03 KB
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
package languages
import (
"context"
"errors"
"fmt"
"io/fs"
"path"
)
type ScanOptions struct {
Bytes int64 // Zero reads each entire file; positive values limit bytes per file.
// Exclude receives root-relative paths. Excluding a directory skips its descendants.
Exclude func(string, fs.DirEntry) bool
}
// Scan summarizes regular files in fsys. Git metadata and symlinks are skipped.
// Use fs.Sub to select a subtree. On an error no partial tree is returned.
//
// Confinement depends on fsys: a file replaced after enumeration is opened
// as-is, and os.DirFS and fs.Sub follow the replacement. For a mutable or
// untrusted directory, pass os.Root.FS, subject to its documented platform
// guarantees.
func Scan(ctx context.Context, fsys fs.FS, options ScanOptions) (*Tree, error) {
if options.Bytes < 0 {
return nil, errors.New("bytes must be zero or greater")
}
var tree Tree
var analysis Analysis
err := fs.WalkDir(fsys, ".", func(name string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if err := ctx.Err(); err != nil {
return err
}
if name != "." && (path.Base(name) == ".git" || options.Exclude != nil && options.Exclude(name, entry)) {
if entry.IsDir() {
return fs.SkipDir
}
return nil
}
if !entry.Type().IsRegular() {
return nil
}
size, err := analyzeFile(ctx, fsys, name, options.Bytes, &analysis)
if err != nil {
return fmt.Errorf("%s: %w", name, err)
}
return tree.Add(name, size, &analysis)
})
if err != nil {
return nil, err
}
return &tree, nil
}
func analyzeFile(ctx context.Context, fsys fs.FS, name string, limit int64, analysis *Analysis) (int64, error) {
file, err := fsys.Open(name)
if err != nil {
return 0, err
}
info, statErr := file.Stat()
if statErr != nil {
_ = file.Close()
return 0, statErr
}
readErr := analyzeReader(ctx, file, ReadOptions{Bytes: limit, Filename: name}, analysis, info.Size())
closeErr := file.Close()
if readErr != nil {
return 0, readErr
}
if closeErr != nil {
return 0, closeErr
}
return info.Size(), nil
}