-
Notifications
You must be signed in to change notification settings - Fork 53
/
Copy pathfiles.go
185 lines (164 loc) · 4.81 KB
/
files.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
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package benchfmt
import (
"fmt"
"os"
"strings"
)
// A Files reads benchmark results from a sequence of input files.
//
// This reader adds a ".file" configuration key to the output Results
// corresponding to each path read in. By default, this will be the
// file name directly from Paths, except that duplicate strings will
// be disambiguated by appending "#N". If AllowLabels is true, then
// entries in Path may be of the form label=path, and the label part
// will be used for .file (without any disambiguation).
type Files struct {
// Paths is the list of file names to read in.
//
// If AllowLabels is set, these strings may be of the form
// label=path, and the label part will be used for the
// ".file" key in the results.
Paths []string
// AllowStdin indicates that the path "-" should be treated as
// stdin and if the file list is empty, it should be treated
// as consisting of stdin.
//
// This is generally the desired behavior when the file list
// comes from command-line flags.
AllowStdin bool
// AllowLabels indicates that custom labels are allowed in
// Paths.
//
// This is generally the desired behavior when the file list
// comes from command-line flags, as it allows users to
// override .file.
AllowLabels bool
// inputs is the sequence of remaining inputs, or nil if this
// Files has not started yet. Note that this distinguishes nil
// from length 0.
inputs []input
reader Reader
file *os.File
isStdin bool
err error
}
type input struct {
path string
label string
isStdin bool
isLabeled bool
}
// init does first-use initialization of f.
func (f *Files) init() {
// Set f.inputs to a non-nil slice to indicate initialization
// has happened.
f.inputs = []input{}
// Parse the paths. Doing this first simplifies iteration and
// disambiguation.
pathCount := make(map[string]int)
if f.AllowStdin && len(f.Paths) == 0 {
f.inputs = append(f.inputs, input{"-", "-", true, false})
}
for _, path := range f.Paths {
// Parse the label.
label := path
isLabeled := false
if i := strings.Index(path, "="); f.AllowLabels && i >= 0 {
label, path = path[:i], path[i+1:]
isLabeled = true
} else {
pathCount[path]++
}
isStdin := f.AllowStdin && path == "-"
f.inputs = append(f.inputs, input{path, label, isStdin, isLabeled})
}
// If the same path is given multiple times, disambiguate its
// .file. Otherwise, the results have indistinguishable
// configurations, which just doubles up samples, which is
// generally not what users are expecting. For overridden
// labels, we do exactly what the user says.
pathI := make(map[string]int)
for i := range f.inputs {
inp := &f.inputs[i]
if inp.isLabeled || pathCount[inp.path] == 1 {
continue
}
// Disambiguate.
inp.label = fmt.Sprintf("%s#%d", inp.path, pathI[inp.path])
pathI[inp.path]++
}
}
// Scan advances the reader to the next result in the sequence of
// files and reports whether a result was read. The caller should use
// the Result method to get the result. If Scan reaches the end of the
// file sequence, or if an I/O error occurs, it returns false. In this
// case, the caller should use the Err method to check for errors.
func (f *Files) Scan() bool {
if f.err != nil {
return false
}
if f.inputs == nil {
f.init()
}
for {
if f.file == nil {
// Open the next file.
if len(f.inputs) == 0 {
// We're out of inputs.
return false
}
inp := f.inputs[0]
f.inputs = f.inputs[1:]
if inp.isStdin {
f.isStdin, f.file = true, os.Stdin
} else {
file, err := os.Open(inp.path)
if err != nil {
f.err = err
return false
}
f.isStdin, f.file = false, file
}
// Prepare the reader. Because ".file" is not
// valid syntax for file configuration keys in
// the file itself, there's no danger of it
// being overwritten.
f.reader.Reset(f.file, inp.path, ".file", inp.label)
}
// Try to get the next result.
if f.reader.Scan() {
return true
}
err := f.reader.Err()
if err != nil {
f.err = err
break
}
// Just an EOF. Close this file and open the next.
if !f.isStdin {
f.file.Close()
}
f.file = nil
}
// We're out of files.
return false
}
// Result returns the record that was just read by Scan.
// See Reader.Result.
func (f *Files) Result() Record {
return f.reader.Result()
}
// Err returns the I/O error that stopped Scan, if any.
// If Scan stopped because it read each file to completion,
// or if Scan has not yet returned false, Err returns nil.
func (f *Files) Err() error {
return f.err
}
// Units returns the accumulated unit metadata.
// See Reader.Units.
func (f *Files) Units() map[UnitMetadataKey]*UnitMetadata {
return f.reader.Units()
}