mirror of
https://github.com/golang/go
synced 2024-11-19 07:24:42 -07:00
9c309ee22f
go/packages needs to call `go list` multiple times, which causes redundant work and slows down goimports. If we reimplement `go list` in memory, we can reuse state, saving time. `go list` also does work we don't really need, like adding stuff to go.mod, and skipping that saves more time. We start with `go list -m`, which does MVS and such. The remaining work is mostly mapping import paths and directories through the in-scope modules to make sure we're giving the right answers. Unfortunately this is quite subtle, and I don't know where all the traps are. I did my best. cmd/go already has tests for `go list`, of course, and packagestest is not well suited to tests of this complexity. So I ripped off the script tests in cmd/go that seemed relevant and made sure that our logic returns the right stuff in each case. I'm sure that there are more cases to cover, but this hit all the stuff I knew about and quite a bit I didn't. Since we may want to use the go/packages code path in the future, e.g. for Bazel, I left that in place. It won't be used unless the magic env var is set. Files in internal and imports/testdata/mod were copied verbatim from cmd/go. Change-Id: I1248d99c400c1a0c7ef180d4460b9b8a3db0246b Reviewed-on: https://go-review.googlesource.com/c/158097 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
141 lines
4.1 KiB
Go
141 lines
4.1 KiB
Go
// Copyright 2018 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 txtar implements a trivial text-based file archive format.
|
|
//
|
|
// The goals for the format are:
|
|
//
|
|
// - be trivial enough to create and edit by hand.
|
|
// - be able to store trees of text files describing go command test cases.
|
|
// - diff nicely in git history and code reviews.
|
|
//
|
|
// Non-goals include being a completely general archive format,
|
|
// storing binary data, storing file modes, storing special files like
|
|
// symbolic links, and so on.
|
|
//
|
|
// Txtar format
|
|
//
|
|
// A txtar archive is zero or more comment lines and then a sequence of file entries.
|
|
// Each file entry begins with a file marker line of the form "-- FILENAME --"
|
|
// and is followed by zero or more file content lines making up the file data.
|
|
// The comment or file content ends at the next file marker line.
|
|
// The file marker line must begin with the three-byte sequence "-- "
|
|
// and end with the three-byte sequence " --", but the enclosed
|
|
// file name can be surrounding by additional white space,
|
|
// all of which is stripped.
|
|
//
|
|
// If the txtar file is missing a trailing newline on the final line,
|
|
// parsers should consider a final newline to be present anyway.
|
|
//
|
|
// There are no possible syntax errors in a txtar archive.
|
|
package txtar
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"strings"
|
|
)
|
|
|
|
// An Archive is a collection of files.
|
|
type Archive struct {
|
|
Comment []byte
|
|
Files []File
|
|
}
|
|
|
|
// A File is a single file in an archive.
|
|
type File struct {
|
|
Name string // name of file ("foo/bar.txt")
|
|
Data []byte // text content of file
|
|
}
|
|
|
|
// Format returns the serialized form of an Archive.
|
|
// It is assumed that the Archive data structure is well-formed:
|
|
// a.Comment and all a.File[i].Data contain no file marker lines,
|
|
// and all a.File[i].Name is non-empty.
|
|
func Format(a *Archive) []byte {
|
|
var buf bytes.Buffer
|
|
buf.Write(fixNL(a.Comment))
|
|
for _, f := range a.Files {
|
|
fmt.Fprintf(&buf, "-- %s --\n", f.Name)
|
|
buf.Write(fixNL(f.Data))
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// ParseFile parses the named file as an archive.
|
|
func ParseFile(file string) (*Archive, error) {
|
|
data, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Parse(data), nil
|
|
}
|
|
|
|
// Parse parses the serialized form of an Archive.
|
|
// The returned Archive holds slices of data.
|
|
func Parse(data []byte) *Archive {
|
|
a := new(Archive)
|
|
var name string
|
|
a.Comment, name, data = findFileMarker(data)
|
|
for name != "" {
|
|
f := File{name, nil}
|
|
f.Data, name, data = findFileMarker(data)
|
|
a.Files = append(a.Files, f)
|
|
}
|
|
return a
|
|
}
|
|
|
|
var (
|
|
newlineMarker = []byte("\n-- ")
|
|
marker = []byte("-- ")
|
|
markerEnd = []byte(" --")
|
|
)
|
|
|
|
// findFileMarker finds the next file marker in data,
|
|
// extracts the file name, and returns the data before the marker,
|
|
// the file name, and the data after the marker.
|
|
// If there is no next marker, findFileMarker returns before = fixNL(data), name = "", after = nil.
|
|
func findFileMarker(data []byte) (before []byte, name string, after []byte) {
|
|
var i int
|
|
for {
|
|
if name, after = isMarker(data[i:]); name != "" {
|
|
return data[:i], name, after
|
|
}
|
|
j := bytes.Index(data[i:], newlineMarker)
|
|
if j < 0 {
|
|
return fixNL(data), "", nil
|
|
}
|
|
i += j + 1 // positioned at start of new possible marker
|
|
}
|
|
}
|
|
|
|
// isMarker checks whether data begins with a file marker line.
|
|
// If so, it returns the name from the line and the data after the line.
|
|
// Otherwise it returns name == "" with an unspecified after.
|
|
func isMarker(data []byte) (name string, after []byte) {
|
|
if !bytes.HasPrefix(data, marker) {
|
|
return "", nil
|
|
}
|
|
if i := bytes.IndexByte(data, '\n'); i >= 0 {
|
|
data, after = data[:i], data[i+1:]
|
|
}
|
|
if !bytes.HasSuffix(data, markerEnd) {
|
|
return "", nil
|
|
}
|
|
return strings.TrimSpace(string(data[len(marker) : len(data)-len(markerEnd)])), after
|
|
}
|
|
|
|
// If data is empty or ends in \n, fixNL returns data.
|
|
// Otherwise fixNL returns a new slice consisting of data with a final \n added.
|
|
func fixNL(data []byte) []byte {
|
|
if len(data) == 0 || data[len(data)-1] == '\n' {
|
|
return data
|
|
}
|
|
d := make([]byte, len(data)+1)
|
|
copy(d, data)
|
|
d[len(data)] = '\n'
|
|
return d
|
|
}
|