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 if *listFmt != "" && *listJson == true {
340 base.Fatalf("go list -f cannot be used with -json")
341 }
342
343 work.BuildInit()
344 out := newTrackingWriter(os.Stdout)
345 defer out.w.Flush()
346
347 if *listFmt == "" {
348 if *listM {
349 *listFmt = "{{.String}}"
350 if *listVersions {
351 *listFmt = `{{.Path}}{{range .Versions}} {{.}}{{end}}{{if .Deprecated}} (deprecated){{end}}`
352 }
353 } else {
354 *listFmt = "{{.ImportPath}}"
355 }
356 }
357
358 var do func(interface{})
359 if *listJson {
360 do = func(x interface{}) {
361 b, err := json.MarshalIndent(x, "", "\t")
362 if err != nil {
363 out.Flush()
364 base.Fatalf("%s", err)
365 }
366 out.Write(b)
367 out.Write(nl)
368 }
369 } else {
370 var cachedCtxt *Context
371 context := func() *Context {
372 if cachedCtxt == nil {
373 cachedCtxt = newContext(&cfg.BuildContext)
374 }
375 return cachedCtxt
376 }
377 fm := template.FuncMap{
378 "join": strings.Join,
379 "context": context,
380 "module": func(path string) *modinfo.ModulePublic { return modload.ModuleInfo(ctx, path) },
381 }
382 tmpl, err := template.New("main").Funcs(fm).Parse(*listFmt)
383 if err != nil {
384 base.Fatalf("%s", err)
385 }
386 do = func(x interface{}) {
387 if err := tmpl.Execute(out, x); err != nil {
388 out.Flush()
389 base.Fatalf("%s", err)
390 }
391 if out.NeedNL() {
392 out.Write(nl)
393 }
394 }
395 }
396
397 modload.Init()
398 if *listRetracted {
399 if cfg.BuildMod == "vendor" {
400 base.Fatalf("go list -retracted cannot be used when vendoring is enabled")
401 }
402 if !modload.Enabled() {
403 base.Fatalf("go list -retracted can only be used in module-aware mode")
404 }
405 }
406
407 if *listM {
408
409 if *listCompiled {
410 base.Fatalf("go list -compiled cannot be used with -m")
411 }
412 if *listDeps {
413
414 base.Fatalf("go list -deps cannot be used with -m")
415 }
416 if *listExport {
417 base.Fatalf("go list -export cannot be used with -m")
418 }
419 if *listFind {
420 base.Fatalf("go list -find cannot be used with -m")
421 }
422 if *listTest {
423 base.Fatalf("go list -test cannot be used with -m")
424 }
425
426 if modload.Init(); !modload.Enabled() {
427 base.Fatalf("go list -m: not using modules")
428 }
429
430 modload.LoadModFile(ctx)
431 if cfg.BuildMod == "vendor" {
432 const actionDisabledFormat = "go list -m: can't %s using the vendor directory\n\t(Use -mod=mod or -mod=readonly to bypass.)"
433
434 if *listVersions {
435 base.Fatalf(actionDisabledFormat, "determine available versions")
436 }
437 if *listU {
438 base.Fatalf(actionDisabledFormat, "determine available upgrades")
439 }
440
441 for _, arg := range args {
442
443
444
445 if arg == "all" {
446 base.Fatalf(actionDisabledFormat, "compute 'all'")
447 }
448 if strings.Contains(arg, "...") {
449 base.Fatalf(actionDisabledFormat, "match module patterns")
450 }
451 }
452 }
453
454 var mode modload.ListMode
455 if *listU {
456 mode |= modload.ListU | modload.ListRetracted | modload.ListDeprecated
457 }
458 if *listRetracted {
459 mode |= modload.ListRetracted
460 }
461 if *listVersions {
462 mode |= modload.ListVersions
463 if *listRetracted {
464 mode |= modload.ListRetractedVersions
465 }
466 }
467 mods, err := modload.ListModules(ctx, args, mode)
468 if !*listE {
469 for _, m := range mods {
470 if m.Error != nil {
471 base.Errorf("go list -m: %v", m.Error.Err)
472 }
473 }
474 if err != nil {
475 base.Errorf("go list -m: %v", err)
476 }
477 base.ExitIfErrors()
478 }
479 for _, m := range mods {
480 do(m)
481 }
482 return
483 }
484
485
486 if *listU {
487 base.Fatalf("go list -u can only be used with -m")
488 }
489 if *listVersions {
490 base.Fatalf("go list -versions can only be used with -m")
491 }
492
493
494 if *listFind && *listDeps {
495 base.Fatalf("go list -deps cannot be used with -find")
496 }
497 if *listFind && *listTest {
498 base.Fatalf("go list -test cannot be used with -find")
499 }
500
501 pkgOpts := load.PackageOpts{
502 IgnoreImports: *listFind,
503 ModResolveTests: *listTest,
504 }
505 pkgs := load.PackagesAndErrors(ctx, pkgOpts, args)
506 if !*listE {
507 w := 0
508 for _, pkg := range pkgs {
509 if pkg.Error != nil {
510 base.Errorf("%v", pkg.Error)
511 continue
512 }
513 pkgs[w] = pkg
514 w++
515 }
516 pkgs = pkgs[:w]
517 base.ExitIfErrors()
518 }
519
520 if cache.Default() == nil {
521
522
523 if *listCompiled {
524 base.Fatalf("go list -compiled requires build cache")
525 }
526 if *listExport {
527 base.Fatalf("go list -export requires build cache")
528 }
529 if *listTest {
530 base.Fatalf("go list -test requires build cache")
531 }
532 }
533
534 if *listTest {
535 c := cache.Default()
536
537 for _, p := range pkgs {
538 if len(p.TestGoFiles)+len(p.XTestGoFiles) > 0 {
539 var pmain, ptest, pxtest *load.Package
540 var err error
541 if *listE {
542 pmain, ptest, pxtest = load.TestPackagesAndErrors(ctx, pkgOpts, p, nil)
543 } else {
544 pmain, ptest, pxtest, err = load.TestPackagesFor(ctx, pkgOpts, p, nil)
545 if err != nil {
546 base.Errorf("can't load test package: %s", err)
547 }
548 }
549 if pmain != nil {
550 pkgs = append(pkgs, pmain)
551 data := *pmain.Internal.TestmainGo
552 h := cache.NewHash("testmain")
553 h.Write([]byte("testmain\n"))
554 h.Write(data)
555 out, _, err := c.Put(h.Sum(), bytes.NewReader(data))
556 if err != nil {
557 base.Fatalf("%s", err)
558 }
559 pmain.GoFiles[0] = c.OutputFile(out)
560 }
561 if ptest != nil && ptest != p {
562 pkgs = append(pkgs, ptest)
563 }
564 if pxtest != nil {
565 pkgs = append(pkgs, pxtest)
566 }
567 }
568 }
569 }
570
571
572 cmdline := make(map[*load.Package]bool)
573 for _, p := range pkgs {
574 cmdline[p] = true
575 }
576
577 if *listDeps {
578
579
580
581
582
583
584
585
586 pkgs = loadPackageList(pkgs)
587 }
588
589
590 needStale := *listJson || strings.Contains(*listFmt, ".Stale")
591 if needStale || *listExport || *listCompiled {
592 var b work.Builder
593 b.Init()
594 b.IsCmdList = true
595 b.NeedExport = *listExport
596 b.NeedCompiledGoFiles = *listCompiled
597 a := &work.Action{}
598
599 for _, p := range pkgs {
600 if len(p.GoFiles)+len(p.CgoFiles) > 0 {
601 a.Deps = append(a.Deps, b.AutoAction(work.ModeInstall, work.ModeInstall, p))
602 }
603 }
604 b.Do(ctx, a)
605 }
606
607 for _, p := range pkgs {
608
609 p.TestImports = p.Resolve(p.TestImports)
610 p.XTestImports = p.Resolve(p.XTestImports)
611 p.DepOnly = !cmdline[p]
612
613 if *listCompiled {
614 p.Imports = str.StringList(p.Imports, p.Internal.CompiledImports)
615 }
616 }
617
618 if *listTest {
619 all := pkgs
620 if !*listDeps {
621 all = loadPackageList(pkgs)
622 }
623
624
625
626
627
628 old := make(map[string]string)
629 for _, p := range all {
630 if p.ForTest != "" {
631 new := p.Desc()
632 old[new] = p.ImportPath
633 p.ImportPath = new
634 }
635 p.DepOnly = !cmdline[p]
636 }
637
638 m := make(map[string]string)
639 for _, p := range all {
640 for _, p1 := range p.Internal.Imports {
641 if p1.ForTest != "" {
642 m[old[p1.ImportPath]] = p1.ImportPath
643 }
644 }
645 for i, old := range p.Imports {
646 if new := m[old]; new != "" {
647 p.Imports[i] = new
648 }
649 }
650 for old := range m {
651 delete(m, old)
652 }
653 }
654
655 for _, p := range all {
656 deps := make(map[string]bool)
657 for _, p1 := range p.Internal.Imports {
658 deps[p1.ImportPath] = true
659 for _, d := range p1.Deps {
660 deps[d] = true
661 }
662 }
663 p.Deps = make([]string, 0, len(deps))
664 for d := range deps {
665 p.Deps = append(p.Deps, d)
666 }
667 sort.Strings(p.Deps)
668 }
669 }
670
671
672
673 if *listRetracted {
674
675
676
677
678
679
680 modToArg := make(map[*modinfo.ModulePublic]string)
681 argToMods := make(map[string][]*modinfo.ModulePublic)
682 var args []string
683 addModule := func(mod *modinfo.ModulePublic) {
684 if mod.Version == "" {
685 return
686 }
687 arg := fmt.Sprintf("%s@%s", mod.Path, mod.Version)
688 if argToMods[arg] == nil {
689 args = append(args, arg)
690 }
691 argToMods[arg] = append(argToMods[arg], mod)
692 modToArg[mod] = arg
693 }
694 for _, p := range pkgs {
695 if p.Module == nil {
696 continue
697 }
698 addModule(p.Module)
699 if p.Module.Replace != nil {
700 addModule(p.Module.Replace)
701 }
702 }
703
704 if len(args) > 0 {
705 var mode modload.ListMode
706 if *listRetracted {
707 mode |= modload.ListRetracted
708 }
709 rmods, err := modload.ListModules(ctx, args, mode)
710 if err != nil && !*listE {
711 base.Errorf("go list -retracted: %v", err)
712 }
713 for i, arg := range args {
714 rmod := rmods[i]
715 for _, mod := range argToMods[arg] {
716 mod.Retracted = rmod.Retracted
717 if rmod.Error != nil && mod.Error == nil {
718 mod.Error = rmod.Error
719 }
720 }
721 }
722 }
723 }
724
725
726 for _, p := range pkgs {
727 nRaw := len(p.Internal.RawImports)
728 for i, path := range p.Imports {
729 var srcPath string
730 if i < nRaw {
731 srcPath = p.Internal.RawImports[i]
732 } else {
733
734
735
736 srcPath = p.Internal.CompiledImports[i-nRaw]
737 }
738
739 if path != srcPath {
740 if p.ImportMap == nil {
741 p.ImportMap = make(map[string]string)
742 }
743 p.ImportMap[srcPath] = path
744 }
745 }
746 }
747
748 for _, p := range pkgs {
749 do(&p.PackagePublic)
750 }
751 }
752
753
754
755
756 func loadPackageList(roots []*load.Package) []*load.Package {
757 pkgs := load.PackageList(roots)
758
759 if !*listE {
760 for _, pkg := range pkgs {
761 if pkg.Error != nil {
762 base.Errorf("%v", pkg.Error)
763 }
764 }
765 }
766
767 return pkgs
768 }
769
770
771
772
773 type TrackingWriter struct {
774 w *bufio.Writer
775 last byte
776 }
777
778 func newTrackingWriter(w io.Writer) *TrackingWriter {
779 return &TrackingWriter{
780 w: bufio.NewWriter(w),
781 last: '\n',
782 }
783 }
784
785 func (t *TrackingWriter) Write(p []byte) (n int, err error) {
786 n, err = t.w.Write(p)
787 if n > 0 {
788 t.last = p[n-1]
789 }
790 return
791 }
792
793 func (t *TrackingWriter) Flush() {
794 t.w.Flush()
795 }
796
797 func (t *TrackingWriter) NeedNL() bool {
798 return t.last != '\n'
799 }
800
View as plain text