зеркало из https://github.com/golang/pkgsite.git
internal/complete: add a package for shared completion types
We're going to store completion entries in redis sorted sets, which means they need to be string-encodable and string-decodable, in such a way that they preserve their lexical sorting. This CL adds a 'complete' package with a 'Completion' type that implements such an encoding, so that it can be shared by both ETL and frontend. There are also some constants added that will be used in later CLs. Updates b/143370178 Change-Id: I94700d8f4e5deab4d74e9ef47cc8b124087f7275 Reviewed-on: https://team-review.git.corp.google.com/c/golang/discovery/+/602061 Reviewed-by: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
Родитель
857e664937
Коммит
707980d183
|
@ -0,0 +1,97 @@
|
|||
// Copyright 2019 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 complete defines a Completion type that is used in auto-completion,
|
||||
// along with Encode and Decode methods that can be used for storing this type
|
||||
// in redis.
|
||||
package complete
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/discovery/internal/derrors"
|
||||
)
|
||||
|
||||
const keySep = "|"
|
||||
|
||||
// Redis keys for completion sorted sets ("indexes"). They are in this package
|
||||
// so that they can be accessed by both ETL and frontend.
|
||||
const (
|
||||
KeyPrefix = "completions"
|
||||
PopularKey = KeyPrefix + "Popular"
|
||||
RemainingKey = KeyPrefix + "Rest"
|
||||
)
|
||||
|
||||
// Completion holds package data from an auto-completion match.
|
||||
type Completion struct {
|
||||
// Suffix is the path suffix that matched the compltion input, e.g. a query
|
||||
// for "error" would match the suffix "errors" of "github.com/pkg/errors".
|
||||
Suffix string
|
||||
// ModulePath is the module path of the completion match. We may support
|
||||
// matches of the same path in different modules.
|
||||
ModulePath string
|
||||
// Version is the module version of the completion entry.
|
||||
Version string
|
||||
// PackagePath is the full import path.
|
||||
PackagePath string
|
||||
// Importers is the number of importers of this package. It is used for
|
||||
// sorting completion results.
|
||||
Importers int
|
||||
}
|
||||
|
||||
// Encode string-encodes a completion for storing in the completion index.
|
||||
func (c Completion) Encode() string {
|
||||
return strings.Join(c.keyData(), keySep)
|
||||
}
|
||||
|
||||
func (c Completion) keyData() []string {
|
||||
var suffix string
|
||||
if strings.HasPrefix(c.PackagePath, c.ModulePath) {
|
||||
suffix = strings.TrimPrefix(c.PackagePath, c.ModulePath)
|
||||
suffix = "/" + strings.Trim(suffix, "/")
|
||||
} else {
|
||||
// In the case of the standard library, ModulePath will not be a prefix of
|
||||
// PackagePath.
|
||||
suffix = c.PackagePath
|
||||
}
|
||||
return []string{
|
||||
c.Suffix,
|
||||
strings.TrimRight(c.ModulePath, "/"),
|
||||
c.Version,
|
||||
suffix,
|
||||
// It's important that importers is last in this key, since it is the only
|
||||
// datum that changes. By having it last, we reserve the ability to
|
||||
// selectively update this entry by deleting the prefix corresponding to
|
||||
// the values above.
|
||||
strconv.Itoa(c.Importers),
|
||||
}
|
||||
}
|
||||
|
||||
// Decode parses a completion entry from the completions index.
|
||||
func Decode(entry string) (_ *Completion, err error) {
|
||||
defer derrors.Wrap(&err, "complete.Decode(%q)", entry)
|
||||
parts := strings.Split(entry, "|")
|
||||
if len(parts) != 5 {
|
||||
return nil, fmt.Errorf("got %d parts, want 5", len(parts))
|
||||
}
|
||||
c := &Completion{
|
||||
Suffix: parts[0],
|
||||
ModulePath: parts[1],
|
||||
Version: parts[2],
|
||||
}
|
||||
suffix := parts[3]
|
||||
if strings.HasPrefix(suffix, "/") {
|
||||
c.PackagePath = strings.Trim(c.ModulePath+suffix, "/")
|
||||
} else {
|
||||
c.PackagePath = suffix
|
||||
}
|
||||
importers, err := strconv.Atoi(parts[4])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing importers: %v", err)
|
||||
}
|
||||
c.Importers = importers
|
||||
return c, nil
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2019 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 complete
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestEncodeDecode(t *testing.T) {
|
||||
completions := []Completion{
|
||||
{},
|
||||
{Version: "foo"},
|
||||
{Importers: 42},
|
||||
{Suffix: "foo"},
|
||||
{ModulePath: "foo"},
|
||||
{PackagePath: "github.com/foo/bar/baz"},
|
||||
{
|
||||
Suffix: "github.com/foo",
|
||||
ModulePath: "github.com/foo/bar",
|
||||
Version: "v1.2.3",
|
||||
PackagePath: "github.com/foo/bar/baz",
|
||||
Importers: 101,
|
||||
},
|
||||
{
|
||||
Suffix: "fmt",
|
||||
ModulePath: "std",
|
||||
Version: "go1.13",
|
||||
PackagePath: "fmt",
|
||||
Importers: 1234,
|
||||
},
|
||||
}
|
||||
for _, c := range completions {
|
||||
encoded := c.Encode()
|
||||
decoded, err := Decode(encoded)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(c, *decoded); diff != "" {
|
||||
t.Errorf("[%#v] decoded mismatch (-initial +decoded):\n%s\nencoded: %q", c, diff, encoded)
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче