1
2
3
4
5
6 package list
7
8 import (
9 "bufio"
10 "bytes"
11 "context"
12 "encoding/json"
13 "fmt"
14 "io"
15 "os"
16 "sort"
17 "strings"
18 "text/template"
19
20 "cmd/go/internal/base"
21 "cmd/go/internal/cache"
22 "cmd/go/internal/cfg"
23 "cmd/go/internal/load"
24 "cmd/go/internal/modinfo"
25 "cmd/go/internal/modload"
26 "cmd/go/internal/str"
27 "cmd/go/internal/work"
28 )
29
30 var CmdList = &base.Command{
31
32
33 UsageLine: "go list [-f format] [-json] [-m] [list flags] [build flags] [packages]",
34 Short: "list packages or modules",
35 Long: `
36 List lists the named packages, one per line.
37 The most commonly-used flags are -f and -json, which control the form
38 of the output printed for each package. Other list flags, documented below,
39 control more specific details.
40
41 The default output shows the package import path:
42
43 bytes
44 encoding/json
45 github.com/gorilla/mux
46 golang.org/x/net/html
47
48 The -f flag specifies an alternate format for the list, using the
49 syntax of package template. The default output is equivalent
50 to -f '{{.ImportPath}}'. The struct being passed to the template is:
51
52 type Package struct {
53 Dir string // directory containing package sources
54 ImportPath string // import path of package in dir
55 ImportComment string // path in import comment on package statement
56 Name string // package name
57 Doc string // package documentation string
58 Target string // install path
59 Shlib string // the shared library that contains this package (only set when -linkshared)
60 Goroot bool // is this package in the Go root?
61 Standard bool // is this package part of the standard Go library?
62 Stale bool // would 'go install' do anything for this package?
63 StaleReason string // explanation for Stale==true
64 Root string // Go root or Go path dir containing this package
65 ConflictDir string // this directory shadows Dir in $GOPATH
66 BinaryOnly bool // binary-only package (no longer supported)
67 ForTest string // package is only for use in named test
68 Export string // file containing export data (when using -export)
69 BuildID string // build ID of the compiled package (when using -export)
70 Module *Module // info about package's containing module, if any (can be nil)
71 Match []string // command-line patterns matching this package
72 DepOnly bool // package is only a dependency, not explicitly listed
73
74 // Source files
75 GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
76 CgoFiles []string // .go source files that import "C"
77 CompiledGoFiles []string // .go files presented to compiler (when using -compiled)
78 IgnoredGoFiles []string // .go source files ignored due to build constraints
79 IgnoredOtherFiles []string // non-.go source files ignored due to build constraints
80 CFiles []string // .c source files
81 CXXFiles []string // .cc, .cxx and .cpp source files
82 MFiles []string // .m source files
83 HFiles []string // .h, .hh, .hpp and .hxx source files
84 FFiles []string // .f, .F, .for and .f90 Fortran source files
85 SFiles []string // .s source files
86 SwigFiles []string // .swig files
87 SwigCXXFiles []string // .swigcxx files
88 SysoFiles []string // .syso object files to add to archive
89 TestGoFiles []string // _test.go files in package
90 XTestGoFiles []string // _test.go files outside package
91
92 // Embedded files
93 EmbedPatterns []string // //go:embed patterns
94 EmbedFiles []string // files matched by EmbedPatterns
95 TestEmbedPatterns []string // //go:embed patterns in TestGoFiles
96 TestEmbedFiles []string // files matched by TestEmbedPatterns
97 XTestEmbedPatterns []string // //go:embed patterns in XTestGoFiles
98 XTestEmbedFiles []string // files matched by XTestEmbedPatterns
99
100 // Cgo directives
101 CgoCFLAGS []string // cgo: flags for C compiler
102 CgoCPPFLAGS []string // cgo: flags for C preprocessor
103 CgoCXXFLAGS []string // cgo: flags for C++ compiler
104 CgoFFLAGS []string // cgo: flags for Fortran compiler
105 CgoLDFLAGS []string // cgo: flags for linker
106 CgoPkgConfig []string // cgo: pkg-config names
107
108 // Dependency information
109 Imports []string // import paths used by this package
110 ImportMap map[string]string // map from source import to ImportPath (identity entries omitted)
111 Deps []string // all (recursively) imported dependencies
112 TestImports []string // imports from TestGoFiles
113 XTestImports []string // imports from XTestGoFiles
114
115 // Error information
116 Incomplete bool // this package or a dependency has an error
117 Error *PackageError // error loading package
118 DepsErrors []*PackageError // errors loading dependencies
119 }
120
121 Packages stored in vendor directories report an ImportPath that includes the
122 path to the vendor directory (for example, "d/vendor/p" instead of "p"),
123 so that the ImportPath uniquely identifies a given copy of a package.
124 The Imports, Deps, TestImports, and XTestImports lists also contain these
125 expanded import paths. See golang.org/s/go15vendor for more about vendoring.
126
127 The error information, if any, is
128
129 type PackageError struct {
130 ImportStack []string // shortest path from package named on command line to this one
131 Pos string // position of error (if present, file:line:col)
132 Err string // the error itself
133 }
134
135 The module information is a Module struct, defined in the discussion
136 of list -m below.
137
138 The template function "join" calls strings.Join.
139
140 The template function "context" returns the build context, defined as:
141
142 type Context struct {
143 GOARCH string // target architecture
144 GOOS string // target operating system
145 GOROOT string // Go root
146 GOPATH string // Go path
147 CgoEnabled bool // whether cgo can be used
148 UseAllFiles bool // use files regardless of +build lines, file names
149 Compiler string // compiler to assume when computing target paths
150 BuildTags []string // build constraints to match in +build lines
151 ToolTags []string // toolchain-specific build constraints
152 ReleaseTags []string // releases the current release is compatible with
153 InstallSuffix string // suffix to use in the name of the install dir
154 }
155
156 For more information about the meaning of these fields see the documentation
157 for the go/build package's Context type.
158
159 The -json flag causes the package data to be printed in JSON format
160 instead of using the template format.
161
162 The -compiled flag causes list to set CompiledGoFiles to the Go source
163 files presented to the compiler. Typically this means that it repeats
164 the files listed in GoFiles and then also adds the Go code generated
165 by processing CgoFiles and SwigFiles. The Imports list contains the
166 union of all imports from both GoFiles and CompiledGoFiles.
167
168 The -deps flag causes list to iterate over not just the named packages
169 but also all their dependencies. It visits them in a depth-first post-order
170 traversal, so that a package is listed only after all its dependencies.
171 Packages not explicitly listed on the command line will have the DepOnly
172 field set to true.
173
174 The -e flag changes the handling of erroneous packages, those that
175 cannot be found or are malformed. By default, the list command
176 prints an error to standard error for each erroneous package and
177 omits the packages from consideration during the usual printing.
178 With the -e flag, the list command never prints errors to standard
179 error and instead processes the erroneous packages with the usual
180 printing. Erroneous packages will have a non-empty ImportPath and
181 a non-nil Error field; other information may or may not be missing
182 (zeroed).
183
184 The -export flag causes list to set the Export field to the name of a
185 file containing up-to-date export information for the given package.
186
187 The -find flag causes list to identify the named packages but not
188 resolve their dependencies: the Imports and Deps lists will be empty.
189
190 The -test flag causes list to report not only the named packages
191 but also their test binaries (for packages with tests), to convey to
192 source code analysis tools exactly how test binaries are constructed.
193 The reported import path for a test binary is the import path of
194 the package followed by a ".test" suffix, as in "math/rand.test".
195 When building a test, it is sometimes necessary to rebuild certain
196 dependencies specially for that test (most commonly the tested
197 package itself). The reported import path of a package recompiled
198 for a particular test binary is followed by a space and the name of
199 the test binary in brackets, as in "math/rand [math/rand.test]"
200 or "regexp [sort.test]". The ForTest field is also set to the name
201 of the package being tested ("math/rand" or "sort" in the previous
202 examples).
203
204 The Dir, Target, Shlib, Root, ConflictDir, and Export file paths
205 are all absolute paths.
206
207 By default, the lists GoFiles, CgoFiles, and so on hold names of files in Dir
208 (that is, paths relative to Dir, not absolute paths).
209 The generated files added when using the -compiled and -test flags
210 are absolute paths referring to cached copies of generated Go source files.
211 Although they are Go source files, the paths may not end in ".go".
212
213 The -m flag causes list to list modules instead of packages.
214
215 When listing modules, the -f flag still specifies a format template
216 applied to a Go struct, but now a Module struct:
217
218 type Module struct {
219 Path string // module path
220 Version string // module version
221 Versions []string // available module versions (with -versions)
222 Replace *Module // replaced by this module
223 Time *time.Time // time version was created
224 Update *Module // available update, if any (with -u)
225 Main bool // is this the main module?
226 Indirect bool // is this module only an indirect dependency of main module?
227 Dir string // directory holding files for this module, if any
228 GoMod string // path to go.mod file used when loading this module, if any
229 GoVersion string // go version used in module
230 Retracted string // retraction information, if any (with -retracted or -u)
231 Error *ModuleError // error loading module
232 }
233
234 type ModuleError struct {
235 Err string // the error itself
236 }
237
238 The file GoMod refers to may be outside the module directory if the
239 module is in the module cache or if the -modfile flag is used.
240
241 The default output is to print the module path and then
242 information about the version and replacement if any.
243 For example, 'go list -m all' might print:
244
245 my/main/module
246 golang.org/x/text v0.3.0 => /tmp/text
247 rsc.io/pdf v0.1.1
248
249 The Module struct has a String method that formats this
250 line of output, so that the default format is equivalent
251 to -f '{{.String}}'.
252
253 Note that when a module has been replaced, its Replace field
254 describes the replacement module, and its Dir field is set to
255 the replacement's source code, if present. (That is, if Replace
256 is non-nil, then Dir is set to Replace.Dir, with no access to
257 the replaced source code.)
258
259 The -u flag adds information about available upgrades.
260 When the latest version of a given module is newer than
261 the current one, list -u sets the Module's Update field
262 to information about the newer module. list -u will also set
263 the module's Retracted field if the current version is retracted.
264 The Module's String method indicates an available upgrade by
265 formatting the newer version in brackets after the current version.
266 If a version is retracted, the string "(retracted)" will follow it.
267 For example, 'go list -m -u all' might print:
268
269 my/main/module
270 golang.org/x/text v0.3.0 [v0.4.0] => /tmp/text
271 rsc.io/pdf v0.1.1 (retracted) [v0.1.2]
272
273 (For tools, 'go list -m -u -json all' may be more convenient to parse.)
274
275 The -versions flag causes list to set the Module's Versions field
276 to a list of all known versions of that module, ordered according
277 to semantic versioning, earliest to latest. The flag also changes
278 the default output format to display the module path followed by the
279 space-separated version list.
280
281 The -retracted flag causes list to report information about retracted
282 module versions. When -retracted is used with -f or -json, the Retracted
283 field will be set to a string explaining why the version was retracted.
284 The string is taken from comments on the retract directive in the
285 module's go.mod file. When -retracted is used with -versions, retracted
286 versions are listed together with unretracted versions. The -retracted
287 flag may be used with or without -m.
288
289 The arguments to list -m are interpreted as a list of modules, not packages.
290 The main module is the module containing the current directory.
291 The active modules are the main module and its dependencies.
292 With no arguments, list -m shows the main module.
293 With arguments, list -m shows the modules specified by the arguments.
294 Any of the active modules can be specified by its module path.
295 The special pattern "all" specifies all the active modules, first the main
296 module and then dependencies sorted by module path.
297 A pattern containing "..." specifies the active modules whose
298 module paths match the pattern.
299 A query of the form path@version specifies the result of that query,
300 which is not limited to active modules.
301 See 'go help modules' for more about module queries.
302
303 The template function "module" takes a single string argument
304 that must be a module path or query and returns the specified
305 module as a Module struct. If an error occurs, the result will
306 be a Module struct with a non-nil Error field.
307
308 For more about build flags, see 'go help build'.
309
310 For more about specifying packages, see 'go help packages'.
311
312 For more about modules, see https://golang.org/ref/mod.
313 `,
314 }
315
316 func init() {
317 CmdList.Run = runList
318 work.AddBuildFlags(CmdList, work.DefaultBuildFlags)
319 }
320
321 var (
322 listCompiled = CmdList.Flag.Bool("compiled", false, "")
323 listDeps = CmdList.Flag.Bool("deps", false, "")
324 listE = CmdList.Flag.Bool("e", false, "")
325 listExport = CmdList.Flag.Bool("export", false, "")
326 listFmt = CmdList.Flag.String("f", "", "")
327 listFind = CmdList.Flag.Bool("find", false, "")
328 listJson = CmdList.Flag.Bool("json", false, "")
329 listM = CmdList.Flag.Bool("m", false, "")
330 listRetracted = CmdList.Flag.Bool("retracted", false, "")
331 listTest = CmdList.Flag.Bool("test", false, "")
332 listU = CmdList.Flag.Bool("u", false, "")
333 listVersions = CmdList.Flag.Bool("versions", false, "")
334 )
335
336 var nl = []byte{'\n'}
337
338 func runList(ctx context.Context, cmd *base.Command, args []string) {
339 modload.InitWorkfile()
340
341 if *listFmt != "" && *listJson == true {
342 base.Fatalf("go list -f cannot be used with -json")
343 }
344
345 work.BuildInit()
346 out := newTrackingWriter(os.Stdout)
347 defer out.w.Flush()
348
349 if *listFmt == "" {
350 if *listM {
351 *listFmt = "{{.String}}"
352 if *listVersions {
353 *listFmt = `{{.Path}}{{range .Versions}} {{.}}{{end}}{{if .Deprecated}} (deprecated){{end}}`
354 }
355 } else {
356 *listFmt = "{{.ImportPath}}"
357 }
358 }
359
360 var do func(any)
361 if *listJson {
362 do = func(x any) {
363 b, err := json.MarshalIndent(x, "", "\t")
364 if err != nil {
365 out.Flush()
366 base.Fatalf("%s", err)
367 }
368 out.Write(b)
369 out.Write(nl)
370 }
371 } else {
372 var cachedCtxt *Context
373 context := func() *Context {
374 if cachedCtxt == nil {
375 cachedCtxt = newContext(&cfg.BuildContext)
376 }
377 return cachedCtxt
378 }
379 fm := template.FuncMap{
380 "join": strings.Join,
381 "context": context,
382 "module": func(path string) *modinfo.ModulePublic { return modload.ModuleInfo(ctx, path) },
383 }
384 tmpl, err := template.New("main").Funcs(fm).Parse(*listFmt)
385 if err != nil {
386 base.Fatalf("%s", err)
387 }
388 do = func(x any) {
389 if err := tmpl.Execute(out, x); err != nil {
390 out.Flush()
391 base.Fatalf("%s", err)
392 }
393 if out.NeedNL() {
394 out.Write(nl)
395 }
396 }
397 }
398
399 modload.Init()
400 if *listRetracted {
401 if cfg.BuildMod == "vendor" {
402 base.Fatalf("go list -retracted cannot be used when vendoring is enabled")
403 }
404 if !modload.Enabled() {
405 base.Fatalf("go list -retracted can only be used in module-aware mode")
406 }
407 }
408
409 if *listM {
410
411 if *listCompiled {
412 base.Fatalf("go list -compiled cannot be used with -m")
413 }
414 if *listDeps {
415
416 base.Fatalf("go list -deps cannot be used with -m")
417 }
418 if *listExport {
419 base.Fatalf("go list -export cannot be used with -m")
420 }
421 if *listFind {
422 base.Fatalf("go list -find cannot be used with -m")
423 }
424 if *listTest {
425 base.Fatalf("go list -test cannot be used with -m")
426 }
427
428 if modload.Init(); !modload.Enabled() {
429 base.Fatalf("go: list -m cannot be used with GO111MODULE=off")
430 }
431
432 modload.LoadModFile(ctx)
433 if cfg.BuildMod == "vendor" {
434 const actionDisabledFormat = "go: can't %s using the vendor directory\n\t(Use -mod=mod or -mod=readonly to bypass.)"
435
436 if *listVersions {
437 base.Fatalf(actionDisabledFormat, "determine available versions")
438 }
439 if *listU {
440 base.Fatalf(actionDisabledFormat, "determine available upgrades")
441 }
442
443 for _, arg := range args {
444
445
446
447 if arg == "all" {
448 base.Fatalf(actionDisabledFormat, "compute 'all'")
449 }
450 if strings.Contains(arg, "...") {
451 base.Fatalf(actionDisabledFormat, "match module patterns")
452 }
453 }
454 }
455
456 var mode modload.ListMode
457 if *listU {
458 mode |= modload.ListU | modload.ListRetracted | modload.ListDeprecated
459 }
460 if *listRetracted {
461 mode |= modload.ListRetracted
462 }
463 if *listVersions {
464 mode |= modload.ListVersions
465 if *listRetracted {
466 mode |= modload.ListRetractedVersions
467 }
468 }
469 mods, err := modload.ListModules(ctx, args, mode)
470 if !*listE {
471 for _, m := range mods {
472 if m.Error != nil {
473 base.Errorf("go: %v", m.Error.Err)
474 }
475 }
476 if err != nil {
477 base.Errorf("go: %v", err)
478 }
479 base.ExitIfErrors()
480 }
481 for _, m := range mods {
482 do(m)
483 }
484 return
485 }
486
487
488 if *listU {
489 base.Fatalf("go list -u can only be used with -m")
490 }
491 if *listVersions {
492 base.Fatalf("go list -versions can only be used with -m")
493 }
494
495
496 if *listFind && *listDeps {
497 base.Fatalf("go list -deps cannot be used with -find")
498 }
499 if *listFind && *listTest {
500 base.Fatalf("go list -test cannot be used with -find")
501 }
502
503 pkgOpts := load.PackageOpts{
504 IgnoreImports: *listFind,
505 ModResolveTests: *listTest,
506 LoadVCS: true,
507 }
508 pkgs := load.PackagesAndErrors(ctx, pkgOpts, args)
509 if !*listE {
510 w := 0
511 for _, pkg := range pkgs {
512 if pkg.Error != nil {
513 base.Errorf("%v", pkg.Error)
514 continue
515 }
516 pkgs[w] = pkg
517 w++
518 }
519 pkgs = pkgs[:w]
520 base.ExitIfErrors()
521 }
522
523 if cache.Default() == nil {
524
525
526 if *listCompiled {
527 base.Fatalf("go list -compiled requires build cache")
528 }
529 if *listExport {
530 base.Fatalf("go list -export requires build cache")
531 }
532 if *listTest {
533 base.Fatalf("go list -test requires build cache")
534 }
535 }
536
537 if *listTest {
538 c := cache.Default()
539
540 for _, p := range pkgs {
541 if len(p.TestGoFiles)+len(p.XTestGoFiles) > 0 {
542 var pmain, ptest, pxtest *load.Package
543 var err error
544 if *listE {
545 pmain, ptest, pxtest = load.TestPackagesAndErrors(ctx, pkgOpts, p, nil)
546 } else {
547 pmain, ptest, pxtest, err = load.TestPackagesFor(ctx, pkgOpts, p, nil)
548 if err != nil {
549 base.Errorf("can't load test package: %s", err)
550 }
551 }
552 if pmain != nil {
553 pkgs = append(pkgs, pmain)
554 data := *pmain.Internal.TestmainGo
555 h := cache.NewHash("testmain")
556 h.Write([]byte("testmain\n"))
557 h.Write(data)
558 out, _, err := c.Put(h.Sum(), bytes.NewReader(data))
559 if err != nil {
560 base.Fatalf("%s", err)
561 }
562 pmain.GoFiles[0] = c.OutputFile(out)
563 }
564 if ptest != nil && ptest != p {
565 pkgs = append(pkgs, ptest)
566 }
567 if pxtest != nil {
568 pkgs = append(pkgs, pxtest)
569 }
570 }
571 }
572 }
573
574
575 cmdline := make(map[*load.Package]bool)
576 for _, p := range pkgs {
577 cmdline[p] = true
578 }
579
580 if *listDeps {
581
582
583
584
585
586
587
588
589 pkgs = loadPackageList(pkgs)
590 }
591
592
593 needStale := *listJson || strings.Contains(*listFmt, ".Stale")
594 if needStale || *listExport || *listCompiled {
595 var b work.Builder
596 b.Init()
597 b.IsCmdList = true
598 b.NeedExport = *listExport
599 b.NeedCompiledGoFiles = *listCompiled
600 a := &work.Action{}
601
602 for _, p := range pkgs {
603 if len(p.GoFiles)+len(p.CgoFiles) > 0 {
604 a.Deps = append(a.Deps, b.AutoAction(work.ModeInstall, work.ModeInstall, p))
605 }
606 }
607 b.Do(ctx, a)
608 }
609
610 for _, p := range pkgs {
611
612 p.TestImports = p.Resolve(p.TestImports)
613 p.XTestImports = p.Resolve(p.XTestImports)
614 p.DepOnly = !cmdline[p]
615
616 if *listCompiled {
617 p.Imports = str.StringList(p.Imports, p.Internal.CompiledImports)
618 }
619 }
620
621 if *listTest {
622 all := pkgs
623 if !*listDeps {
624 all = loadPackageList(pkgs)
625 }
626
627
628
629
630
631 old := make(map[string]string)
632 for _, p := range all {
633 if p.ForTest != "" {
634 new := p.Desc()
635 old[new] = p.ImportPath
636 p.ImportPath = new
637 }
638 p.DepOnly = !cmdline[p]
639 }
640
641 m := make(map[string]string)
642 for _, p := range all {
643 for _, p1 := range p.Internal.Imports {
644 if p1.ForTest != "" {
645 m[old[p1.ImportPath]] = p1.ImportPath
646 }
647 }
648 for i, old := range p.Imports {
649 if new := m[old]; new != "" {
650 p.Imports[i] = new
651 }
652 }
653 for old := range m {
654 delete(m, old)
655 }
656 }
657
658 for _, p := range all {
659 deps := make(map[string]bool)
660 for _, p1 := range p.Internal.Imports {
661 deps[p1.ImportPath] = true
662 for _, d := range p1.Deps {
663 deps[d] = true
664 }
665 }
666 p.Deps = make([]string, 0, len(deps))
667 for d := range deps {
668 p.Deps = append(p.Deps, d)
669 }
670 sort.Strings(p.Deps)
671 }
672 }
673
674
675
676 if *listRetracted {
677
678
679
680
681
682
683 modToArg := make(map[*modinfo.ModulePublic]string)
684 argToMods := make(map[string][]*modinfo.ModulePublic)
685 var args []string
686 addModule := func(mod *modinfo.ModulePublic) {
687 if mod.Version == "" {
688 return
689 }
690 arg := fmt.Sprintf("%s@%s", mod.Path, mod.Version)
691 if argToMods[arg] == nil {
692 args = append(args, arg)
693 }
694 argToMods[arg] = append(argToMods[arg], mod)
695 modToArg[mod] = arg
696 }
697 for _, p := range pkgs {
698 if p.Module == nil {
699 continue
700 }
701 addModule(p.Module)
702 if p.Module.Replace != nil {
703 addModule(p.Module.Replace)
704 }
705 }
706
707 if len(args) > 0 {
708 var mode modload.ListMode
709 if *listRetracted {
710 mode |= modload.ListRetracted
711 }
712 rmods, err := modload.ListModules(ctx, args, mode)
713 if err != nil && !*listE {
714 base.Errorf("go: %v", err)
715 }
716 for i, arg := range args {
717 rmod := rmods[i]
718 for _, mod := range argToMods[arg] {
719 mod.Retracted = rmod.Retracted
720 if rmod.Error != nil && mod.Error == nil {
721 mod.Error = rmod.Error
722 }
723 }
724 }
725 }
726 }
727
728
729 for _, p := range pkgs {
730 nRaw := len(p.Internal.RawImports)
731 for i, path := range p.Imports {
732 var srcPath string
733 if i < nRaw {
734 srcPath = p.Internal.RawImports[i]
735 } else {
736
737
738
739 srcPath = p.Internal.CompiledImports[i-nRaw]
740 }
741
742 if path != srcPath {
743 if p.ImportMap == nil {
744 p.ImportMap = make(map[string]string)
745 }
746 p.ImportMap[srcPath] = path
747 }
748 }
749 }
750
751 for _, p := range pkgs {
752 do(&p.PackagePublic)
753 }
754 }
755
756
757
758
759 func loadPackageList(roots []*load.Package) []*load.Package {
760 pkgs := load.PackageList(roots)
761
762 if !*listE {
763 for _, pkg := range pkgs {
764 if pkg.Error != nil {
765 base.Errorf("%v", pkg.Error)
766 }
767 }
768 }
769
770 return pkgs
771 }
772
773
774
775
776 type TrackingWriter struct {
777 w *bufio.Writer
778 last byte
779 }
780
781 func newTrackingWriter(w io.Writer) *TrackingWriter {
782 return &TrackingWriter{
783 w: bufio.NewWriter(w),
784 last: '\n',
785 }
786 }
787
788 func (t *TrackingWriter) Write(p []byte) (n int, err error) {
789 n, err = t.w.Write(p)
790 if n > 0 {
791 t.last = p[n-1]
792 }
793 return
794 }
795
796 func (t *TrackingWriter) Flush() {
797 t.w.Flush()
798 }
799
800 func (t *TrackingWriter) NeedNL() bool {
801 return t.last != '\n'
802 }
803
View as plain text