x/build/blob: add Parts for streaming ref parts

Also, make ParseRef use the new Parts method to parse the ref parts.
This commit is contained in:
Blake Mizerany 2024-04-03 22:27:55 -07:00
parent def4d902bf
commit 6d2da77ce2

View File

@ -3,17 +3,20 @@ package blob
import ( import (
"cmp" "cmp"
"fmt" "fmt"
"iter"
"slices" "slices"
"strings" "strings"
) )
type Kind int
// Levels of concreteness // Levels of concreteness
const ( const (
domain = iota Domain Kind = iota
namespace Namespace
name Name
tag Tag
build Build
) )
// Ref is an opaque reference to a blob. // Ref is an opaque reference to a blob.
@ -32,42 +35,42 @@ type Ref struct {
// WithDomain returns a copy of r with the provided domain. If the provided // WithDomain returns a copy of r with the provided domain. If the provided
// domain is empty, it returns the short, unqualified copy of r. // domain is empty, it returns the short, unqualified copy of r.
func (r Ref) WithDomain(s string) Ref { func (r Ref) WithDomain(s string) Ref {
return with(r, domain, s) return with(r, Domain, s)
} }
// WithNamespace returns a copy of r with the provided namespace. If the // WithNamespace returns a copy of r with the provided namespace. If the
// provided namespace is empty, it returns the short, unqualified copy of r. // provided namespace is empty, it returns the short, unqualified copy of r.
func (r Ref) WithNamespace(s string) Ref { func (r Ref) WithNamespace(s string) Ref {
return with(r, namespace, s) return with(r, Namespace, s)
} }
func (r Ref) WithTag(s string) Ref { func (r Ref) WithTag(s string) Ref {
return with(r, tag, s) return with(r, Tag, s)
} }
// WithBuild returns a copy of r with the provided build. If the provided // WithBuild returns a copy of r with the provided build. If the provided
// build is empty, it returns the short, unqualified copy of r. // build is empty, it returns the short, unqualified copy of r.
func (r Ref) WithBuild(s string) Ref { func (r Ref) WithBuild(s string) Ref {
return with(r, build, s) return with(r, Build, s)
} }
func with(r Ref, part int, value string) Ref { func with(r Ref, kind Kind, value string) Ref {
if value != "" && !isValidPart(value) { if value != "" && !isValidPart(value) {
return Ref{} return Ref{}
} }
switch part { switch kind {
case domain: case Domain:
r.domain = value r.domain = value
case namespace: case Namespace:
r.namespace = value r.namespace = value
case name: case Name:
r.name = value r.name = value
case tag: case Tag:
r.tag = value r.tag = value
case build: case Build:
r.build = value r.build = value
default: default:
panic(fmt.Sprintf("invalid completeness: %d", part)) panic(fmt.Sprintf("invalid completeness: %d", kind))
} }
return r return r
} }
@ -126,7 +129,7 @@ func (r Ref) Complete() bool {
} }
func (r Ref) CompleteWithoutBuild() bool { func (r Ref) CompleteWithoutBuild() bool {
return r.Valid() && !slices.Contains(r.Parts()[:tag], "") return r.Valid() && !slices.Contains(r.Parts()[:Tag], "")
} }
// Less returns true if r is less concrete than o; false otherwise. // Less returns true if r is less concrete than o; false otherwise.
@ -146,11 +149,11 @@ func (r Ref) Less(o Ref) bool {
// The length of the returned slice is always 5. // The length of the returned slice is always 5.
func (r Ref) Parts() []string { func (r Ref) Parts() []string {
return []string{ return []string{
domain: r.domain, Domain: r.domain,
namespace: r.namespace, Namespace: r.namespace,
name: r.name, Name: r.name,
tag: r.tag, Tag: r.tag,
build: r.build, Build: r.build,
} }
} }
@ -183,9 +186,32 @@ func (r Ref) Build() string { return r.build }
// ParseRef("m stral") returns ("", "", "") // zero // ParseRef("m stral") returns ("", "", "") // zero
// ParseRef("... 129 chars ...") returns ("", "", "") // zero // ParseRef("... 129 chars ...") returns ("", "", "") // zero
func ParseRef(s string) Ref { func ParseRef(s string) Ref {
if len(s) > 128 { var r Ref
for kind, part := range Parts(s) {
switch kind {
case Domain:
r.domain = part
case Namespace:
r.namespace = part
case Name:
r.name = part
case Tag:
r.tag = part
case Build:
r.build = part
}
}
if !r.Valid() {
return Ref{} return Ref{}
} }
return r
}
func Parts(s string) iter.Seq2[Kind, string] {
return func(yield func(Kind, string) bool) {
if len(s) > 128 {
return
}
if strings.HasPrefix(s, "http://") { if strings.HasPrefix(s, "http://") {
s = s[len("http://"):] s = s[len("http://"):]
@ -194,63 +220,67 @@ func ParseRef(s string) Ref {
s = s[len("https://"):] s = s[len("https://"):]
} }
var r Ref state, j := Build, len(s)
state, j := build, len(s)
for i := len(s) - 1; i >= 0; i-- { for i := len(s) - 1; i >= 0; i-- {
c := s[i] c := s[i]
switch c { switch c {
case '+': case '+':
switch state { switch state {
case build: case Build:
r.build = s[i+1 : j] v := s[i+1 : j]
if r.build == "" { if v == "" {
return Ref{} return
} }
r.build = strings.ToUpper(r.build) v = strings.ToUpper(v)
state, j = tag, i if !yield(Build, v) {
return
}
state, j = Tag, i
default: default:
return Ref{} return
} }
case ':': case ':':
switch state { switch state {
case build, tag: case Build, Tag:
r.tag = s[i+1 : j] v := s[i+1 : j]
if r.tag == "" { if v == "" {
return Ref{} return
} }
state, j = name, i if !yield(Tag, v) {
return
}
state, j = Name, i
default: default:
return Ref{} return
} }
case '/': case '/':
switch state { switch state {
case name, tag, build: case Name, Tag, Build:
r.name = s[i+1 : j] if !yield(Name, s[i+1:j]) {
state, j = namespace, i return
case namespace: }
r.namespace = s[i+1 : j] state, j = Namespace, i
state, j = domain, i case Namespace:
if !yield(Namespace, s[i+1:j]) {
return
}
state, j = Domain, i
default: default:
return Ref{} return
} }
} }
} }
// handle the first part based on final state // handle the first part based on final state
switch state { switch state {
case domain: case Domain:
r.domain = s[:j] yield(Domain, s[:j])
case namespace: case Namespace:
r.namespace = s[:j] yield(Namespace, s[:j])
default: default:
r.name = s[:j] yield(Name, s[:j])
} }
if !r.Valid() {
return Ref{}
} }
return r
} }
// Complete is the same as ParseRef(s).Complete(). // Complete is the same as ParseRef(s).Complete().