소스 검색

NameParts -> Parts

Blake Mizerany 1 년 전
부모
커밋
6ba495d4a3
2개의 변경된 파일42개의 추가작업 그리고 16개의 파일을 삭제
  1. 22 10
      x/model/name.go
  2. 20 6
      x/model/name_test.go

+ 22 - 10
x/model/name.go

@@ -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
 					}

+ 20 - 6
x/model/name_test.go

@@ -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",
 		},
 	}