NameParts -> Parts
This commit is contained in:
parent
bd446a72cc
commit
6ba495d4a3
@ -43,7 +43,7 @@ const (
|
|||||||
// It should be kept as the last part in the list.
|
// It should be kept as the last part in the list.
|
||||||
Invalid
|
Invalid
|
||||||
|
|
||||||
NumParts = Invalid - 1
|
NumParts = Invalid
|
||||||
)
|
)
|
||||||
|
|
||||||
var kindNames = map[NamePart]string{
|
var kindNames = map[NamePart]string{
|
||||||
@ -134,7 +134,7 @@ type Name struct {
|
|||||||
// [Name.String] will not print a "+" if the build is empty.
|
// [Name.String] will not print a "+" if the build is empty.
|
||||||
func ParseName(s string) Name {
|
func ParseName(s string) Name {
|
||||||
var r Name
|
var r Name
|
||||||
for kind, part := range NameParts(s) {
|
for kind, part := range Parts(s) {
|
||||||
if kind == Invalid {
|
if kind == Invalid {
|
||||||
return Name{}
|
return Name{}
|
||||||
}
|
}
|
||||||
@ -230,7 +230,8 @@ var seps = [...]string{
|
|||||||
Namespace: "/",
|
Namespace: "/",
|
||||||
Model: ":",
|
Model: ":",
|
||||||
Tag: "+",
|
Tag: "+",
|
||||||
Build: "",
|
Build: "@",
|
||||||
|
Digest: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo implements io.WriterTo. It writes the fullest possible display
|
// 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
|
// Complete reports whether the Name is fully qualified. That is it has a
|
||||||
// domain, namespace, name, tag, and build.
|
// domain, namespace, name, tag, and build.
|
||||||
func (r Name) Complete() bool {
|
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
|
// CompleteNoBuild is like [Name.Complete] but it does not require the
|
||||||
// build part to be present.
|
// build part to be present.
|
||||||
func (r Name) CompleteNoBuild() bool {
|
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
|
// 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.
|
// It normalizes the input string by removing "http://" and "https://" only.
|
||||||
// No other normalization is done.
|
// 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) {
|
return func(yield func(NamePart, string) bool) {
|
||||||
if strings.HasPrefix(s, "http://") {
|
if strings.HasPrefix(s, "http://") {
|
||||||
s = s[len("http://"):]
|
s = s[len("http://"):]
|
||||||
@ -418,16 +419,27 @@ func NameParts(s string) iter.Seq2[NamePart, string] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
partLen := 0
|
partLen := 0
|
||||||
state, j := Build, len(s)
|
state, j := Digest, len(s)
|
||||||
for i := len(s) - 1; i >= 0; i-- {
|
for i := len(s) - 1; i >= 0; i-- {
|
||||||
if partLen++; partLen > MaxNamePartLen {
|
if partLen++; partLen > MaxNamePartLen {
|
||||||
yield(Invalid, "")
|
yield(Invalid, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch s[i] {
|
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 '+':
|
case '+':
|
||||||
switch state {
|
switch state {
|
||||||
case Build:
|
case Build, Digest:
|
||||||
if !yieldValid(Build, s[i+1:j]) {
|
if !yieldValid(Build, s[i+1:j]) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -438,7 +450,7 @@ func NameParts(s string) iter.Seq2[NamePart, string] {
|
|||||||
}
|
}
|
||||||
case ':':
|
case ':':
|
||||||
switch state {
|
switch state {
|
||||||
case Build, Tag:
|
case Tag, Build, Digest:
|
||||||
if !yieldValid(Tag, s[i+1:j]) {
|
if !yieldValid(Tag, s[i+1:j]) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -449,7 +461,7 @@ func NameParts(s string) iter.Seq2[NamePart, string] {
|
|||||||
}
|
}
|
||||||
case '/':
|
case '/':
|
||||||
switch state {
|
switch state {
|
||||||
case Model, Tag, Build:
|
case Model, Tag, Build, Digest:
|
||||||
if !yieldValid(Model, s[i+1:j]) {
|
if !yieldValid(Model, s[i+1:j]) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
host, namespace, model, tag, build string
|
host, namespace, model, tag, build string
|
||||||
|
digest string
|
||||||
}
|
}
|
||||||
|
|
||||||
func fieldsFromName(p Name) fields {
|
func fieldsFromName(p Name) fields {
|
||||||
@ -22,6 +23,7 @@ func fieldsFromName(p Name) fields {
|
|||||||
model: p.parts[Model],
|
model: p.parts[Model],
|
||||||
tag: p.parts[Tag],
|
tag: p.parts[Tag],
|
||||||
build: p.parts[Build],
|
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+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"},
|
"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
|
// preserves case for build
|
||||||
"x+b": {model: "x", build: "b"},
|
"x+b": {model: "x", build: "b"},
|
||||||
|
|
||||||
@ -87,10 +92,9 @@ var testNames = map[string]fields{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNameParts(t *testing.T) {
|
func TestNameParts(t *testing.T) {
|
||||||
const wantNumParts = 5
|
|
||||||
var p Name
|
var p Name
|
||||||
if len(p.Parts()) != wantNumParts {
|
if len(p.Parts()) != int(NumParts) {
|
||||||
t.Errorf("Parts() = %d; want %d", len(p.Parts()), wantNumParts)
|
t.Errorf("Parts() = %d; want %d", len(p.Parts()), NumParts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,6 +215,7 @@ func TestNameDisplay(t *testing.T) {
|
|||||||
wantLong: "library/mistral:latest",
|
wantLong: "library/mistral:latest",
|
||||||
wantComplete: "example.com/library/mistral:latest",
|
wantComplete: "example.com/library/mistral:latest",
|
||||||
wantModel: "mistral",
|
wantModel: "mistral",
|
||||||
|
wantGoString: "example.com/library/mistral:latest+Q4_0@?",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Short Name",
|
name: "Short Name",
|
||||||
@ -219,7 +224,7 @@ func TestNameDisplay(t *testing.T) {
|
|||||||
wantLong: "mistral:latest",
|
wantLong: "mistral:latest",
|
||||||
wantComplete: "mistral:latest",
|
wantComplete: "mistral:latest",
|
||||||
wantModel: "mistral",
|
wantModel: "mistral",
|
||||||
wantGoString: "?/?/mistral:latest+?",
|
wantGoString: "?/?/mistral:latest+?@?",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Long Name",
|
name: "Long Name",
|
||||||
@ -228,7 +233,7 @@ func TestNameDisplay(t *testing.T) {
|
|||||||
wantLong: "library/mistral:latest",
|
wantLong: "library/mistral:latest",
|
||||||
wantComplete: "library/mistral:latest",
|
wantComplete: "library/mistral:latest",
|
||||||
wantModel: "mistral",
|
wantModel: "mistral",
|
||||||
wantGoString: "?/library/mistral:latest+?",
|
wantGoString: "?/library/mistral:latest+?@?",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Case Preserved",
|
name: "Case Preserved",
|
||||||
@ -237,7 +242,16 @@ func TestNameDisplay(t *testing.T) {
|
|||||||
wantLong: "Library/Mistral:Latest",
|
wantLong: "Library/Mistral:Latest",
|
||||||
wantComplete: "Library/Mistral:Latest",
|
wantComplete: "Library/Mistral:Latest",
|
||||||
wantModel: "Mistral",
|
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",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user