NameParts -> Parts

This commit is contained in:
Blake Mizerany 2024-04-06 23:19:55 -07:00
parent bd446a72cc
commit 6ba495d4a3
2 changed files with 42 additions and 16 deletions

View File

@ -43,7 +43,7 @@ const (
// It should be kept as the last part in the list.
Invalid
NumParts = Invalid - 1
NumParts = Invalid
)
var kindNames = map[NamePart]string{
@ -134,7 +134,7 @@ type Name struct {
// [Name.String] will not print a "+" if the build is empty.
func ParseName(s string) Name {
var r Name
for kind, part := range NameParts(s) {
for kind, part := range Parts(s) {
if kind == Invalid {
return Name{}
}
@ -230,7 +230,8 @@ var seps = [...]string{
Namespace: "/",
Model: ":",
Tag: "+",
Build: "",
Build: "@",
Digest: "",
}
// WriteTo implements io.WriterTo. It writes the fullest possible display
@ -345,13 +346,13 @@ func unsafeString(b []byte) string {
// Complete reports whether the Name is fully qualified. That is it has a
// domain, namespace, name, tag, and build.
func (r Name) Complete() bool {
return !slices.Contains(r.parts[:], "")
return !slices.Contains(r.parts[:Digest], "")
}
// CompleteNoBuild is like [Name.Complete] but it does not require the
// build part to be present.
func (r Name) CompleteNoBuild() bool {
return !slices.Contains(r.parts[:Build-1], "")
return !slices.Contains(r.parts[:Build], "")
}
// EqualFold reports whether r and o are equivalent model names, ignoring
@ -396,7 +397,7 @@ func (r Name) Parts() []string {
//
// It normalizes the input string by removing "http://" and "https://" only.
// No other normalization is done.
func NameParts(s string) iter.Seq2[NamePart, string] {
func Parts(s string) iter.Seq2[NamePart, string] {
return func(yield func(NamePart, string) bool) {
if strings.HasPrefix(s, "http://") {
s = s[len("http://"):]
@ -418,16 +419,27 @@ func NameParts(s string) iter.Seq2[NamePart, string] {
}
partLen := 0
state, j := Build, len(s)
state, j := Digest, len(s)
for i := len(s) - 1; i >= 0; i-- {
if partLen++; partLen > MaxNamePartLen {
yield(Invalid, "")
return
}
switch s[i] {
case '@':
switch state {
case Digest:
if !yieldValid(Digest, s[i+1:j]) {
return
}
state, j, partLen = Build, i, 0
default:
yield(Invalid, "")
return
}
case '+':
switch state {
case Build:
case Build, Digest:
if !yieldValid(Build, s[i+1:j]) {
return
}
@ -438,7 +450,7 @@ func NameParts(s string) iter.Seq2[NamePart, string] {
}
case ':':
switch state {
case Build, Tag:
case Tag, Build, Digest:
if !yieldValid(Tag, s[i+1:j]) {
return
}
@ -449,7 +461,7 @@ func NameParts(s string) iter.Seq2[NamePart, string] {
}
case '/':
switch state {
case Model, Tag, Build:
case Model, Tag, Build, Digest:
if !yieldValid(Model, s[i+1:j]) {
return
}

View File

@ -13,6 +13,7 @@ import (
type fields struct {
host, namespace, model, tag, build string
digest string
}
func fieldsFromName(p Name) fields {
@ -22,6 +23,7 @@ func fieldsFromName(p Name) fields {
model: p.parts[Model],
tag: p.parts[Tag],
build: p.parts[Build],
digest: p.parts[Digest],
}
}
@ -47,6 +49,9 @@ var testNames = map[string]fields{
"example.com/ns/mistral:7b+Q4_0": {host: "example.com", namespace: "ns", model: "mistral", tag: "7b", build: "Q4_0"},
"example.com/ns/mistral:7b+X": {host: "example.com", namespace: "ns", model: "mistral", tag: "7b", build: "X"},
// resolved
"x@123": {model: "x", digest: "123"},
// preserves case for build
"x+b": {model: "x", build: "b"},
@ -87,10 +92,9 @@ var testNames = map[string]fields{
}
func TestNameParts(t *testing.T) {
const wantNumParts = 5
var p Name
if len(p.Parts()) != wantNumParts {
t.Errorf("Parts() = %d; want %d", len(p.Parts()), wantNumParts)
if len(p.Parts()) != int(NumParts) {
t.Errorf("Parts() = %d; want %d", len(p.Parts()), NumParts)
}
}
@ -211,6 +215,7 @@ func TestNameDisplay(t *testing.T) {
wantLong: "library/mistral:latest",
wantComplete: "example.com/library/mistral:latest",
wantModel: "mistral",
wantGoString: "example.com/library/mistral:latest+Q4_0@?",
},
{
name: "Short Name",
@ -219,7 +224,7 @@ func TestNameDisplay(t *testing.T) {
wantLong: "mistral:latest",
wantComplete: "mistral:latest",
wantModel: "mistral",
wantGoString: "?/?/mistral:latest+?",
wantGoString: "?/?/mistral:latest+?@?",
},
{
name: "Long Name",
@ -228,7 +233,7 @@ func TestNameDisplay(t *testing.T) {
wantLong: "library/mistral:latest",
wantComplete: "library/mistral:latest",
wantModel: "mistral",
wantGoString: "?/library/mistral:latest+?",
wantGoString: "?/library/mistral:latest+?@?",
},
{
name: "Case Preserved",
@ -237,7 +242,16 @@ func TestNameDisplay(t *testing.T) {
wantLong: "Library/Mistral:Latest",
wantComplete: "Library/Mistral:Latest",
wantModel: "Mistral",
wantGoString: "?/Library/Mistral:Latest+?",
wantGoString: "?/Library/Mistral:Latest+?@?",
},
{
name: "With digest",
in: "Library/Mistral:Latest@sha256-123456",
wantShort: "Mistral:Latest",
wantLong: "Library/Mistral:Latest",
wantComplete: "Library/Mistral:Latest",
wantModel: "Mistral",
wantGoString: "?/Library/Mistral:Latest+?@sha256-123456",
},
}