func Sort(infos []Info, newer func(a, b *Info) bool) {
	if newer == nil {
		newer = NewerByCreated
	}
	sort.Sort(&infoSort{infos: infos, newer: newer})
}
Sort orders the given image infos according to `newer` func.
package image
import (
	"encoding/json"
	"fmt"
	"regexp"
	"sort"
	"strings"
	"time"
	"github.com/Masterminds/semver/v3"
	"github.com/pkg/errors"
)
const (
	dockerHubHost = "index.docker.io"
	oldDockerHubHost = "docker.io"
)
var (
	ErrInvalidImageID   = errors.New("invalid image ID")
	ErrBlankImageID     = errors.Wrap(ErrInvalidImageID, "blank image name")
	ErrMalformedImageID = errors.Wrap(ErrInvalidImageID, `expected image name as either <image>:<tag> or just <image>`)
)
type Name struct {
	Domain, Image string
}
type CanonicalName struct {
	Name
}
func (i Name) String() string {
	if i.Image == "" {
		return ""  
	}
	var host string
	if i.Domain != "" {
		host = i.Domain + "/"
	}
	return fmt.Sprintf("%s%s", host, i.Image)
}
func (i Name) Repository() string {
	switch i.Domain {
	case "", oldDockerHubHost, dockerHubHost:
		path := strings.Split(i.Image, "/")
		if len(path) == 1 {
			return "library/" + i.Image
		}
		return i.Image
	default:
		return i.Image
	}
}
func (i Name) Registry() string {
	switch i.Domain {
	case "", oldDockerHubHost:
		return dockerHubHost
	default:
		return i.Domain
	}
}
func (i Name) CanonicalName() CanonicalName {
	return CanonicalName{
		Name: Name{
			Domain: i.Registry(),
			Image:  i.Repository(),
		},
	}
}
func (i Name) ToRef(tag string) Ref {
	return Ref{
		Name: i,
		Tag:  tag,
	}
}
type Ref struct {
	Name
	Tag string
	SHA string
}
type CanonicalRef struct {
	Ref
}
func (i Ref) String() string {
	var suffix string
	if i.SHA != "" {
		suffix = "@sha256:" + i.SHA
	} else if i.Tag != "" {
		suffix = ":" + i.Tag
	}
	return fmt.Sprintf("%s%s", i.Name.String(), suffix)
}
func ParseRef(s string) (Ref, error) {
	var id Ref
	if s == "" {
		return id, errors.Wrapf(ErrBlankImageID, "parsing %q", s)
	}
	if strings.HasPrefix(s, "/") || strings.HasSuffix(s, "/") {
		return id, errors.Wrapf(ErrMalformedImageID, "parsing %q", s)
	}
	elements := strings.Split(s, "/")
	switch len(elements) {
	case 0:  
		return id, errors.Wrapf(ErrMalformedImageID, "parsing %q", s)
	case 1:  
		id.Image = s
	case 2:  
		if domainRegexp.MatchString(elements[0]) {
			id.Domain = elements[0]
			id.Image = elements[1]
		} else {
			id.Image = s
		}
	default:  
		id.Domain = elements[0]
		id.Image = strings.Join(elements[1:], "/")
	}
	imageParts := strings.SplitN(id.Image, "@sha256:", 2)
	switch len(imageParts) {
	case 1:
		break
	case 2:
		if imageParts[0] == "" || imageParts[1] == "" {
			return id, errors.Wrapf(ErrMalformedImageID, "parsing %q", s)
		}
		id.Image = imageParts[0]
		id.SHA = imageParts[1]
	}
	imageParts = strings.Split(id.Image, ":")
	switch len(imageParts) {
	case 1:
		break
	case 2:
		if imageParts[0] == "" || imageParts[1] == "" {
			return id, errors.Wrapf(ErrMalformedImageID, "parsing %q", s)
		}
		id.Image = imageParts[0]
		id.Tag = imageParts[1]
	default:
		return id, ErrMalformedImageID
	}
	return id, nil
}
var (
	domainComponent = `([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`
	domain          = fmt.Sprintf(`localhost|(%s([.]%s)+)(:[0-9]+)?`, domainComponent, domainComponent)
	domainRegexp    = regexp.MustCompile(domain)
)
func (i Ref) MarshalJSON() ([]byte, error) {
	return json.Marshal(i.String())
}
func (i *Ref) UnmarshalJSON(data []byte) (err error) {
	var str string
	if err := json.Unmarshal(data, &str); err != nil {
		return err
	}
	*i, err = ParseRef(string(str))
	return err
}
func (i Ref) CanonicalRef() CanonicalRef {
	name := i.CanonicalName()
	return CanonicalRef{
		Ref: Ref{
			Name: name.Name,
			Tag:  i.Tag,
		},
	}
}
func (i Ref) Components() (domain, repo, tag string) {
	return i.Domain, i.Image, i.Tag
}
func (i Ref) WithNewTag(t string) Ref {
	var img Ref
	img = i
	img.Tag = t
	return img
}
type LabelTimestampFormatError struct {
	Labels []string
}
func (e *LabelTimestampFormatError) Error() string {
	return fmt.Sprintf(
		"failed to parse %d timestamp label(s) as RFC3339 (%s); ask the repository administrator to correct this as it conflicts with the spec",
		len(e.Labels),
		strings.Join(e.Labels, ", "))
}
type Labels struct {
	BuildDate time.Time `json:"org.label-schema.build-date,omitempty"`
	Created time.Time `json:"org.opencontainers.image.created,omitempty"`
}
func (l Labels) MarshalJSON() ([]byte, error) {
	var bd, c string
	if !l.BuildDate.IsZero() {
		bd = l.BuildDate.UTC().Format(time.RFC3339Nano)
	}
	if !l.Created.IsZero() {
		c = l.Created.UTC().Format(time.RFC3339Nano)
	}
	encode := struct {
		BuildDate string `json:"org.label-schema.build-date,omitempty"`
		Created   string `json:"org.opencontainers.image.created,omitempty"`
	}{BuildDate: bd, Created: c}
	return json.Marshal(encode)
}
func (l *Labels) UnmarshalJSON(b []byte) error {
	unencode := struct {
		BuildDate string `json:"org.label-schema.build-date,omitempty"`
		Created   string `json:"org.opencontainers.image.created,omitempty"`
	}{}
	json.Unmarshal(b, &unencode)
	labelErr := LabelTimestampFormatError{}
	if err := decodeTime(unencode.BuildDate, &l.BuildDate); err != nil {
		if _, ok := err.(*time.ParseError); !ok {
			return err
		}
		labelErr.Labels = append(labelErr.Labels, "org.label-schema.build-date")
	}
	if err := decodeTime(unencode.Created, &l.Created); err != nil {
		if _, ok := err.(*time.ParseError); !ok {
			return err
		}
		labelErr.Labels = append(labelErr.Labels, "org.opencontainers.image.created")
	}
	if len(labelErr.Labels) >= 1 {
		return &labelErr
	}
	return nil
}
type Info struct {
	ID Ref `json:",omitempty"`
	Digest string `json:",omitempty"`
	ImageID string `json:",omitempty"`
	Labels Labels `json:",omitempty"`
	CreatedAt time.Time `json:",omitempty"`
	LastFetched time.Time `json:",omitempty"`
}
func (im Info) MarshalJSON() ([]byte, error) {
	type InfoAlias Info  
	var ca, lf string
	if !im.CreatedAt.IsZero() {
		ca = im.CreatedAt.UTC().Format(time.RFC3339Nano)
	}
	if !im.LastFetched.IsZero() {
		lf = im.LastFetched.UTC().Format(time.RFC3339Nano)
	}
	encode := struct {
		InfoAlias
		CreatedAt   string `json:",omitempty"`
		LastFetched string `json:",omitempty"`
	}{InfoAlias(im), ca, lf}
	return json.Marshal(encode)
}
func (im *Info) UnmarshalJSON(b []byte) error {
	type InfoAlias Info
	unencode := struct {
		InfoAlias
		CreatedAt   string `json:",omitempty"`
		LastFetched string `json:",omitempty"`
	}{}
	json.Unmarshal(b, &unencode)
	*im = Info(unencode.InfoAlias)
	var err error
	if err = decodeTime(unencode.CreatedAt, &im.CreatedAt); err == nil {
		err = decodeTime(unencode.LastFetched, &im.LastFetched)
	}
	return err
}
type RepositoryMetadata struct {
	Tags   []string         
	Images map[string]Info  
}
func (rm RepositoryMetadata) FindImageWithRef(ref Ref) Info {
	for _, img := range rm.Images {
		if img.ID == ref {
			return img
		}
	}
	return Info{ID: ref}
}
func (rm RepositoryMetadata) GetImageTagInfo() ([]Info, error) {
	result := make([]Info, len(rm.Tags), len(rm.Tags))
	for i, tag := range rm.Tags {
		info, ok := rm.Images[tag]
		if !ok {
			return nil, fmt.Errorf("missing metadata for image tag %q", tag)
		}
		result[i] = info
	}
	return result, nil
}
func decodeTime(s string, t *time.Time) error {
	if s == "" {
		*t = time.Time{}
	} else {
		var err error
		*t, err = time.Parse(time.RFC3339, s)
		if err != nil {
			return err
		}
	}
	return nil
}
func NewerByCreated(lhs, rhs *Info) bool {
	if lhs.CreatedAt.Equal(rhs.CreatedAt) {
		return lhs.ID.String() < rhs.ID.String()
	}
	return lhs.CreatedAt.After(rhs.CreatedAt)
}
func NewerBySemver(lhs, rhs *Info) bool {
	lv, lerr := semver.NewVersion(lhs.ID.Tag)
	rv, rerr := semver.NewVersion(rhs.ID.Tag)
	if (lerr != nil && rerr != nil) || (lv == rv) {
		return lhs.ID.String() < rhs.ID.String()
	}
	if lerr != nil {
		return false
	}
	if rerr != nil {
		return true
	}
	cmp := lv.Compare(rv)
	if cmp == 0 {
		return lhs.ID.String() > rhs.ID.String()
	}
	return cmp > 0
}