Repository navigation
Expand file tree
/
Copy pathtree.go
More file actions
158 lines (147 loc) · 4.51 KB
/
Copy pathtree.go
File metadata and controls
158 lines (147 loc) · 4.51 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
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
package languages
import (
"fmt"
"io/fs"
"path"
"sort"
"strings"
)
type FileTotals struct {
Files int64 `json:"files"`
Bytes int64 `json:"bytes"`
}
type LanguageTotals struct {
Language string `json:"language"`
FileTotals
}
// Summary counts each file once, including files with no selected language.
type Summary struct {
FileTotals
Languages []LanguageTotals `json:"languages"`
Unknown FileTotals `json:"unknown"`
Ambiguous FileTotals `json:"ambiguous"`
Conflicts FileTotals `json:"conflicts"`
Binary FileTotals `json:"binary"`
Incomplete int64 `json:"incomplete"`
}
// Directory includes totals for all descendants. Path is relative to the tree root.
type Directory struct {
Path string `json:"path"`
Summary Summary `json:"summary"`
Children []Directory `json:"children,omitempty"`
}
// Tree accumulates file analyses without retaining their content or analysis buffers.
// Its zero value is ready to use. Add must not run concurrently with other operations.
type Tree struct {
dirs map[string]*directoryTotals
files map[string]struct{}
}
type directoryTotals struct {
summary Summary
languages map[Language]FileTotals
children map[string]struct{}
}
// Add records one file using its full size and filename-contextual detection.
// Names must be unique, slash-separated relative file paths without dot components.
func (t *Tree) Add(name string, size int64, analysis *Analysis) error {
if !fs.ValidPath(name) || name == "." {
return fmt.Errorf("invalid file path %q", name)
}
if analysis == nil || size < 0 || analysis.Bytes > size {
return fmt.Errorf("invalid size or analysis for %q", name)
}
if _, exists := t.files[name]; exists || t.dirs[name] != nil {
return fmt.Errorf("path already added: %q", name)
}
for parent := path.Dir(name); parent != "."; parent = path.Dir(parent) {
if _, exists := t.files[parent]; exists {
return fmt.Errorf("parent is a file: %q", parent)
}
}
if t.files == nil {
t.files = make(map[string]struct{})
t.dirs = make(map[string]*directoryTotals)
}
t.files[name] = struct{}{}
result := analysis.Detect(name)
child := ""
for parent := path.Dir(name); ; parent = path.Dir(parent) {
dir := t.dirs[parent]
if dir == nil {
dir = &directoryTotals{languages: make(map[Language]FileTotals), children: make(map[string]struct{})}
t.dirs[parent] = dir
}
dir.add(size, analysis, result)
if child != "" {
dir.children[child] = struct{}{}
}
if parent == "." {
break
}
child = parent
}
return nil
}
func (d *directoryTotals) add(size int64, analysis *Analysis, result Result) {
d.summary.Files++
d.summary.Bytes += size
if analysis.Prefix {
d.summary.Incomplete++
}
var category *FileTotals
switch {
case analysis.Binary:
category = &d.summary.Binary
case result.Conflict:
category = &d.summary.Conflicts
case result.Language != Unknown:
count := d.languages[result.Language]
count.Files++
count.Bytes += size
d.languages[result.Language] = count
return
case result.Candidates.Empty():
category = &d.summary.Unknown
default:
category = &d.summary.Ambiguous
}
category.Files++
category.Bytes += size
}
// Root returns an independent snapshot, with children sorted by path.
func (t *Tree) Root() Directory {
root, _ := t.Subtree(".")
return root
}
// Subtree returns a snapshot of an existing directory, retaining root-relative paths.
// Language totals are ordered by bytes, then file count, then language name.
func (t *Tree) Subtree(name string) (Directory, bool) {
if !fs.ValidPath(name) {
return Directory{}, false
}
dir := t.dirs[name]
if dir == nil {
return Directory{Path: name, Summary: Summary{Languages: []LanguageTotals{}}}, name == "."
}
result := Directory{Path: name, Summary: dir.summary}
result.Summary.Languages = make([]LanguageTotals, 0, len(dir.languages))
for language, count := range dir.languages {
result.Summary.Languages = append(result.Summary.Languages, LanguageTotals{language.String(), count})
}
sort.Slice(result.Summary.Languages, func(i, j int) bool {
a, b := result.Summary.Languages[i], result.Summary.Languages[j]
if a.Bytes != b.Bytes {
return a.Bytes > b.Bytes
}
if a.Files != b.Files {
return a.Files > b.Files
}
return strings.Compare(a.Language, b.Language) < 0
})
for child := range dir.children {
snapshot, _ := t.Subtree(child)
result.Children = append(result.Children, snapshot)
}
sort.Slice(result.Children, func(i, j int) bool { return result.Children[i].Path < result.Children[j].Path })
return result, true
}